diff --git a/app/instance/instance.go b/app/instance/instance.go new file mode 100644 index 0000000..65e154c --- /dev/null +++ b/app/instance/instance.go @@ -0,0 +1,74 @@ +package instance + +import log "github.com/sirupsen/logrus" + +var Ready = "ready" +var NotReady = "not-ready" +var Error = "error" + +type State struct { + Name string + CurrentReplicas int + Status string + Error string +} + +func (instance State) IsReady() bool { + return instance.Status == Ready +} + +func (instance State) HasError() bool { + return instance.Status == Error +} + +func ErrorInstanceState(name string, err error) (State, error) { + log.Error(err.Error()) + return State{ + Name: name, + CurrentReplicas: 0, + Status: Error, + Error: err.Error(), + }, err +} + +func UnrecoverableInstanceState(name string, err string) (State, error) { + log.Warn(err) + return State{ + Name: name, + CurrentReplicas: 0, + Status: Error, + Error: err, + }, nil +} + +func ReadyInstanceState(name string) (State, error) { + return State{ + Name: name, + CurrentReplicas: 1, + Status: Ready, + }, nil +} + +func ReadyInstanceStateOfReplicas(name string, replicas int) (State, error) { + return State{ + Name: name, + CurrentReplicas: replicas, + Status: Ready, + }, nil +} + +func NotReadyInstanceState(name string) (State, error) { + return State{ + Name: name, + CurrentReplicas: 0, + Status: NotReady, + }, nil +} + +func NotReadyInstanceStateOfReplicas(name string, replicas int) (State, error) { + return State{ + Name: name, + CurrentReplicas: replicas, + Status: NotReady, + }, nil +} diff --git a/app/providers/docker_classic.go b/app/providers/docker_classic.go index 62ab92e..3a34406 100644 --- a/app/providers/docker_classic.go +++ b/app/providers/docker_classic.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/acouvreur/sablier/app/instance" "github.com/docker/docker/api/types" "github.com/docker/docker/client" log "github.com/sirupsen/logrus" @@ -24,77 +25,77 @@ func NewDockerClassicProvider() (*DockerClassicProvider, error) { }, nil } -func (provider *DockerClassicProvider) Start(name string) (InstanceState, error) { +func (provider *DockerClassicProvider) Start(name string) (instance.State, error) { ctx := context.Background() err := provider.Client.ContainerStart(ctx, name, types.ContainerStartOptions{}) if err != nil { - return errorInstanceState(name, err) + return instance.ErrorInstanceState(name, err) } - return InstanceState{ + return instance.State{ Name: name, CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, err } -func (provider *DockerClassicProvider) Stop(name string) (InstanceState, error) { +func (provider *DockerClassicProvider) Stop(name string) (instance.State, error) { ctx := context.Background() // TODO: Allow to specify a termination timeout err := provider.Client.ContainerStop(ctx, name, nil) if err != nil { - return errorInstanceState(name, err) + return instance.ErrorInstanceState(name, err) } - return InstanceState{ + return instance.State{ Name: name, CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, nil } -func (provider *DockerClassicProvider) GetState(name string) (InstanceState, error) { +func (provider *DockerClassicProvider) GetState(name string) (instance.State, error) { ctx := context.Background() spec, err := provider.Client.ContainerInspect(ctx, name) if err != nil { - return errorInstanceState(name, err) + return instance.ErrorInstanceState(name, err) } // "created", "running", "paused", "restarting", "removing", "exited", or "dead" switch spec.State.Status { case "created", "paused", "restarting", "removing": - return notReadyInstanceState(name) + return instance.NotReadyInstanceState(name) case "running": if spec.State.Health != nil { // // "starting", "healthy" or "unhealthy" if spec.State.Health.Status == "healthy" { - return readyInstanceState(name) + return instance.ReadyInstanceState(name) } else if spec.State.Health.Status == "unhealthy" { if len(spec.State.Health.Log) >= 1 { lastLog := spec.State.Health.Log[len(spec.State.Health.Log)-1] - return unrecoverableInstanceState(name, fmt.Sprintf("container is unhealthy: %s (%d)", lastLog.Output, lastLog.ExitCode)) + return instance.UnrecoverableInstanceState(name, fmt.Sprintf("container is unhealthy: %s (%d)", lastLog.Output, lastLog.ExitCode)) } else { - return unrecoverableInstanceState(name, "container is unhealthy: no log available") + return instance.UnrecoverableInstanceState(name, "container is unhealthy: no log available") } } else { - return notReadyInstanceState(name) + return instance.NotReadyInstanceState(name) } } - return readyInstanceState(name) + return instance.ReadyInstanceState(name) case "exited": if spec.State.ExitCode != 0 { - return unrecoverableInstanceState(name, fmt.Sprintf("container exited with code \"%d\"", spec.State.ExitCode)) + return instance.UnrecoverableInstanceState(name, fmt.Sprintf("container exited with code \"%d\"", spec.State.ExitCode)) } - return notReadyInstanceState(name) + return instance.NotReadyInstanceState(name) case "dead": - return unrecoverableInstanceState(name, "container in \"dead\" state cannot be restarted") + return instance.UnrecoverableInstanceState(name, "container in \"dead\" state cannot be restarted") default: - return unrecoverableInstanceState(name, fmt.Sprintf("container status \"%s\" not handled", spec.State.Status)) + return instance.UnrecoverableInstanceState(name, fmt.Sprintf("container status \"%s\" not handled", spec.State.Status)) } } diff --git a/app/providers/docker_classic_test.go b/app/providers/docker_classic_test.go index 1ae8798..e4041ff 100644 --- a/app/providers/docker_classic_test.go +++ b/app/providers/docker_classic_test.go @@ -5,6 +5,7 @@ import ( "reflect" "testing" + "github.com/acouvreur/sablier/app/instance" "github.com/acouvreur/sablier/app/providers/mocks" "github.com/docker/docker/api/types" "github.com/stretchr/testify/mock" @@ -21,7 +22,7 @@ func TestDockerClassicProvider_GetState(t *testing.T) { name string fields fields args args - want InstanceState + want instance.State wantErr bool containerSpec types.ContainerJSON err error @@ -34,10 +35,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, containerSpec: mocks.CreatedContainerSpec("nginx"), @@ -50,10 +51,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 1, - Status: Ready, + Status: instance.Ready, }, wantErr: false, containerSpec: mocks.RunningWithoutHealthcheckContainerSpec("nginx"), @@ -66,10 +67,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, containerSpec: mocks.RunningWithHealthcheckContainerSpec("nginx", "starting"), @@ -82,10 +83,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "container is unhealthy: curl http://localhost failed (1)", }, wantErr: false, @@ -99,10 +100,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 1, - Status: Ready, + Status: instance.Ready, }, wantErr: false, containerSpec: mocks.RunningWithHealthcheckContainerSpec("nginx", "healthy"), @@ -115,10 +116,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, containerSpec: mocks.PausedContainerSpec("nginx"), @@ -131,10 +132,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, containerSpec: mocks.RestartingContainerSpec("nginx"), @@ -147,10 +148,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, containerSpec: mocks.RemovingContainerSpec("nginx"), @@ -163,10 +164,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, containerSpec: mocks.ExitedContainerSpec("nginx", 0), @@ -179,10 +180,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "container exited with code \"137\"", }, wantErr: false, @@ -196,10 +197,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "container in \"dead\" state cannot be restarted", }, wantErr: false, @@ -213,10 +214,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "container with name \"nginx\" was not found", }, wantErr: true, @@ -255,7 +256,7 @@ func TestDockerClassicProvider_Stop(t *testing.T) { name string fields fields args args - want InstanceState + want instance.State wantErr bool err error }{ @@ -267,10 +268,10 @@ func TestDockerClassicProvider_Stop(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "container with name \"nginx\" was not found", }, wantErr: true, @@ -284,10 +285,10 @@ func TestDockerClassicProvider_Stop(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, err: nil, @@ -324,7 +325,7 @@ func TestDockerClassicProvider_Start(t *testing.T) { name string fields fields args args - want InstanceState + want instance.State wantErr bool err error }{ @@ -336,10 +337,10 @@ func TestDockerClassicProvider_Start(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "container with name \"nginx\" was not found", }, wantErr: true, @@ -353,10 +354,10 @@ func TestDockerClassicProvider_Start(t *testing.T) { args: args{ name: "nginx", }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, err: nil, diff --git a/app/providers/docker_swarm.go b/app/providers/docker_swarm.go index 5347e48..71e48a3 100644 --- a/app/providers/docker_swarm.go +++ b/app/providers/docker_swarm.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/acouvreur/sablier/app/instance" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" @@ -27,26 +28,26 @@ func NewDockerSwarmProvider() (*DockerSwarmProvider, error) { }, nil } -func (provider *DockerSwarmProvider) Start(name string) (InstanceState, error) { +func (provider *DockerSwarmProvider) Start(name string) (instance.State, error) { return provider.scale(name, 1) } -func (provider *DockerSwarmProvider) Stop(name string) (InstanceState, error) { +func (provider *DockerSwarmProvider) Stop(name string) (instance.State, error) { return provider.scale(name, 0) } -func (provider *DockerSwarmProvider) scale(name string, replicas uint64) (InstanceState, error) { +func (provider *DockerSwarmProvider) scale(name string, replicas uint64) (instance.State, error) { ctx := context.Background() service, err := provider.getServiceByName(name, ctx) if err != nil { - return errorInstanceState(name, err) + return instance.ErrorInstanceState(name, err) } foundName := provider.getInstanceName(name, *service) if service.Spec.Mode.Replicated == nil { - return unrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode") + return instance.UnrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode") } service.Spec.Mode.Replicated.Replicas = &replicas @@ -54,35 +55,35 @@ func (provider *DockerSwarmProvider) scale(name string, replicas uint64) (Instan response, err := provider.Client.ServiceUpdate(ctx, service.ID, service.Meta.Version, service.Spec, types.ServiceUpdateOptions{}) if err != nil { - return errorInstanceState(foundName, err) + return instance.ErrorInstanceState(foundName, err) } if len(response.Warnings) > 0 { - return unrecoverableInstanceState(foundName, strings.Join(response.Warnings, ", ")) + return instance.UnrecoverableInstanceState(foundName, strings.Join(response.Warnings, ", ")) } - return notReadyInstanceState(foundName) + return instance.NotReadyInstanceState(foundName) } -func (provider *DockerSwarmProvider) GetState(name string) (InstanceState, error) { +func (provider *DockerSwarmProvider) GetState(name string) (instance.State, error) { ctx := context.Background() service, err := provider.getServiceByName(name, ctx) if err != nil { - return errorInstanceState(name, err) + return instance.ErrorInstanceState(name, err) } foundName := provider.getInstanceName(name, *service) if service.Spec.Mode.Replicated == nil { - return unrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode") + return instance.UnrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode") } if service.ServiceStatus.DesiredTasks != service.ServiceStatus.RunningTasks || service.ServiceStatus.DesiredTasks == 0 { - return notReadyInstanceState(foundName) + return instance.NotReadyInstanceState(foundName) } - return readyInstanceState(foundName) + return instance.ReadyInstanceState(foundName) } func (provider *DockerSwarmProvider) getServiceByName(name string, ctx context.Context) (*swarm.Service, error) { diff --git a/app/providers/docker_swarm_test.go b/app/providers/docker_swarm_test.go index 49390b8..c8f4667 100644 --- a/app/providers/docker_swarm_test.go +++ b/app/providers/docker_swarm_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/acouvreur/sablier/app/instance" "github.com/acouvreur/sablier/app/providers/mocks" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" @@ -21,7 +22,7 @@ func TestDockerSwarmProvider_Start(t *testing.T) { name string fields fields args args - want InstanceState + want instance.State serviceList []swarm.Service response types.ServiceUpdateResponse wantService swarm.Service @@ -41,10 +42,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantService: mocks.ServiceReplicated("nginx", 1), wantErr: false, @@ -64,10 +65,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "ambiguous service names found for \"nginx\" (STACK1_nginx, STACK2_nginx)", }, wantService: mocks.ServiceReplicated("nginx", 1), @@ -89,10 +90,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantService: mocks.ServiceReplicated("nginx", 1), wantErr: false, @@ -112,10 +113,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx (STACK1_nginx)", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantService: mocks.ServiceReplicated("STACK1_nginx", 1), wantErr: false, @@ -134,10 +135,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "swarm service is not in \"replicated\" mode", }, wantService: mocks.ServiceReplicated("nginx", 1), @@ -176,7 +177,7 @@ func TestDockerSwarmProvider_Stop(t *testing.T) { name string fields fields args args - want InstanceState + want instance.State serviceList []swarm.Service response types.ServiceUpdateResponse wantService swarm.Service @@ -196,10 +197,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantService: mocks.ServiceReplicated("nginx", 0), wantErr: false, @@ -219,10 +220,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "ambiguous service names found for \"nginx\" (STACK1_nginx, STACK2_nginx)", }, wantService: mocks.ServiceReplicated("nginx", 1), @@ -244,10 +245,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantService: mocks.ServiceReplicated("nginx", 0), wantErr: false, @@ -267,10 +268,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx (STACK1_nginx)", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantService: mocks.ServiceReplicated("STACK1_nginx", 0), wantErr: false, @@ -289,10 +290,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) { response: types.ServiceUpdateResponse{ Warnings: []string{}, }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "swarm service is not in \"replicated\" mode", }, wantService: mocks.ServiceReplicated("nginx", 1), @@ -331,7 +332,7 @@ func TestDockerSwarmProvider_GetState(t *testing.T) { name string fields fields args args - want InstanceState + want instance.State serviceList []swarm.Service wantErr bool }{ @@ -346,10 +347,10 @@ func TestDockerSwarmProvider_GetState(t *testing.T) { serviceList: []swarm.Service{ mocks.ServiceReplicated("nginx", 1), }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 1, - Status: Ready, + Status: instance.Ready, }, wantErr: false, }, @@ -364,10 +365,10 @@ func TestDockerSwarmProvider_GetState(t *testing.T) { serviceList: []swarm.Service{ mocks.ServiceNotReadyReplicated("nginx", 1, 0), }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, }, @@ -382,10 +383,10 @@ func TestDockerSwarmProvider_GetState(t *testing.T) { serviceList: []swarm.Service{ mocks.ServiceNotReadyReplicated("STACK_nginx", 1, 0), }, - want: InstanceState{ + want: instance.State{ Name: "nginx (STACK_nginx)", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, wantErr: false, }, @@ -400,10 +401,10 @@ func TestDockerSwarmProvider_GetState(t *testing.T) { serviceList: []swarm.Service{ mocks.ServiceGlobal("nginx"), }, - want: InstanceState{ + want: instance.State{ Name: "nginx", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "swarm service is not in \"replicated\" mode", }, wantErr: false, diff --git a/app/providers/kubernetes.go b/app/providers/kubernetes.go index 9617501..406ed09 100644 --- a/app/providers/kubernetes.go +++ b/app/providers/kubernetes.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/acouvreur/sablier/app/instance" log "github.com/sirupsen/logrus" autoscalingv1 "k8s.io/api/autoscaling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -68,19 +69,19 @@ func NewKubernetesProvider() *KubernetesProvider { } } -func (provider *KubernetesProvider) Start(name string) (InstanceState, error) { +func (provider *KubernetesProvider) Start(name string) (instance.State, error) { config, err := convertName(name) if err != nil { - return unrecoverableInstanceState(name, err.Error()) + return instance.UnrecoverableInstanceState(name, err.Error()) } return provider.scale(config) } -func (provider *KubernetesProvider) Stop(name string) (InstanceState, error) { +func (provider *KubernetesProvider) Stop(name string) (instance.State, error) { config, err := convertName(name) if err != nil { - return unrecoverableInstanceState(name, err.Error()) + return instance.UnrecoverableInstanceState(name, err.Error()) } config.Replicas = 0 @@ -89,7 +90,7 @@ func (provider *KubernetesProvider) Stop(name string) (InstanceState, error) { } -func (provider *KubernetesProvider) scale(config *Config) (InstanceState, error) { +func (provider *KubernetesProvider) scale(config *Config) (instance.State, error) { ctx := context.Background() var workload Workload @@ -100,28 +101,28 @@ func (provider *KubernetesProvider) scale(config *Config) (InstanceState, error) case "statefulset": workload = provider.Client.AppsV1().StatefulSets(config.Namespace) default: - return unrecoverableInstanceState(config.OriginalName, fmt.Sprintf("unsupported kind \"%s\" must be one of \"deployment\", \"statefulset\"", config.Kind)) + return instance.UnrecoverableInstanceState(config.OriginalName, fmt.Sprintf("unsupported kind \"%s\" must be one of \"deployment\", \"statefulset\"", config.Kind)) } s, err := workload.GetScale(ctx, config.Name, metav1.GetOptions{}) if err != nil { - return errorInstanceState(config.OriginalName, err) + return instance.ErrorInstanceState(config.OriginalName, err) } s.Spec.Replicas = config.Replicas _, err = workload.UpdateScale(ctx, config.Name, s, metav1.UpdateOptions{}) if err != nil { - return errorInstanceState(config.OriginalName, err) + return instance.ErrorInstanceState(config.OriginalName, err) } - return notReadyInstanceState(config.OriginalName) + return instance.NotReadyInstanceState(config.OriginalName) } -func (provider *KubernetesProvider) GetState(name string) (InstanceState, error) { +func (provider *KubernetesProvider) GetState(name string) (instance.State, error) { config, err := convertName(name) if err != nil { - return unrecoverableInstanceState(name, err.Error()) + return instance.UnrecoverableInstanceState(name, err.Error()) } switch config.Kind { @@ -130,40 +131,40 @@ func (provider *KubernetesProvider) GetState(name string) (InstanceState, error) case "statefulset": return provider.getStatefulsetState(config) default: - return unrecoverableInstanceState(config.OriginalName, fmt.Sprintf("unsupported kind \"%s\" must be one of \"deployment\", \"statefulset\"", config.Kind)) + return instance.UnrecoverableInstanceState(config.OriginalName, fmt.Sprintf("unsupported kind \"%s\" must be one of \"deployment\", \"statefulset\"", config.Kind)) } } -func (provider *KubernetesProvider) getDeploymentState(config *Config) (InstanceState, error) { +func (provider *KubernetesProvider) getDeploymentState(config *Config) (instance.State, error) { ctx := context.Background() d, err := provider.Client.AppsV1().Deployments(config.Namespace). Get(ctx, config.Name, metav1.GetOptions{}) if err != nil { - return errorInstanceState(config.OriginalName, err) + return instance.ErrorInstanceState(config.OriginalName, err) } if *d.Spec.Replicas == d.Status.ReadyReplicas { - return readyInstanceStateOfReplicas(config.OriginalName, int(d.Status.ReadyReplicas)) + return instance.ReadyInstanceStateOfReplicas(config.OriginalName, int(d.Status.ReadyReplicas)) } - return notReadyInstanceStateOfReplicas(config.OriginalName, int(d.Status.ReadyReplicas)) + return instance.NotReadyInstanceStateOfReplicas(config.OriginalName, int(d.Status.ReadyReplicas)) } -func (provider *KubernetesProvider) getStatefulsetState(config *Config) (InstanceState, error) { +func (provider *KubernetesProvider) getStatefulsetState(config *Config) (instance.State, error) { ctx := context.Background() ss, err := provider.Client.AppsV1().StatefulSets(config.Namespace). Get(ctx, config.Name, metav1.GetOptions{}) if err != nil { - return errorInstanceState(config.OriginalName, err) + return instance.ErrorInstanceState(config.OriginalName, err) } if *ss.Spec.Replicas == ss.Status.ReadyReplicas { - return readyInstanceStateOfReplicas(config.OriginalName, int(ss.Status.ReadyReplicas)) + return instance.ReadyInstanceStateOfReplicas(config.OriginalName, int(ss.Status.ReadyReplicas)) } - return notReadyInstanceStateOfReplicas(config.OriginalName, int(ss.Status.ReadyReplicas)) + return instance.NotReadyInstanceStateOfReplicas(config.OriginalName, int(ss.Status.ReadyReplicas)) } diff --git a/app/providers/kubernetes_test.go b/app/providers/kubernetes_test.go index c735bf9..f126015 100644 --- a/app/providers/kubernetes_test.go +++ b/app/providers/kubernetes_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/acouvreur/sablier/app/instance" "github.com/acouvreur/sablier/app/providers/mocks" "github.com/stretchr/testify/mock" v1 "k8s.io/api/apps/v1" @@ -23,7 +24,7 @@ func TestKubernetesProvider_Start(t *testing.T) { tests := []struct { name string args args - want InstanceState + want instance.State data data wantErr bool }{ @@ -32,10 +33,10 @@ func TestKubernetesProvider_Start(t *testing.T) { args: args{ name: "deployment_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "deployment_default_nginx_2", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, data: data{ name: "nginx", @@ -49,10 +50,10 @@ func TestKubernetesProvider_Start(t *testing.T) { args: args{ name: "statefulset_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "statefulset_default_nginx_2", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, data: data{ name: "nginx", @@ -66,10 +67,10 @@ func TestKubernetesProvider_Start(t *testing.T) { args: args{ name: "gateway_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "gateway_default_nginx_2", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "unsupported kind \"gateway\" must be one of \"deployment\", \"statefulset\"", }, data: data{ @@ -118,7 +119,7 @@ func TestKubernetesProvider_Stop(t *testing.T) { tests := []struct { name string args args - want InstanceState + want instance.State data data wantErr bool }{ @@ -127,10 +128,10 @@ func TestKubernetesProvider_Stop(t *testing.T) { args: args{ name: "deployment_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "deployment_default_nginx_2", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, data: data{ name: "nginx", @@ -144,10 +145,10 @@ func TestKubernetesProvider_Stop(t *testing.T) { args: args{ name: "statefulset_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "statefulset_default_nginx_2", CurrentReplicas: 0, - Status: NotReady, + Status: instance.NotReady, }, data: data{ name: "nginx", @@ -161,10 +162,10 @@ func TestKubernetesProvider_Stop(t *testing.T) { args: args{ name: "gateway_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "gateway_default_nginx_2", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "unsupported kind \"gateway\" must be one of \"deployment\", \"statefulset\"", }, data: data{ @@ -213,7 +214,7 @@ func TestKubernetesProvider_GetState(t *testing.T) { tests := []struct { name string args args - want InstanceState + want instance.State data data wantErr bool }{ @@ -222,10 +223,10 @@ func TestKubernetesProvider_GetState(t *testing.T) { args: args{ name: "deployment_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "deployment_default_nginx_2", CurrentReplicas: 2, - Status: Ready, + Status: instance.Ready, }, data: data{ name: "nginx", @@ -238,10 +239,10 @@ func TestKubernetesProvider_GetState(t *testing.T) { args: args{ name: "deployment_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "deployment_default_nginx_2", CurrentReplicas: 1, - Status: NotReady, + Status: instance.NotReady, }, data: data{ name: "nginx", @@ -254,10 +255,10 @@ func TestKubernetesProvider_GetState(t *testing.T) { args: args{ name: "statefulset_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "statefulset_default_nginx_2", CurrentReplicas: 2, - Status: Ready, + Status: instance.Ready, }, data: data{ name: "nginx", @@ -270,10 +271,10 @@ func TestKubernetesProvider_GetState(t *testing.T) { args: args{ name: "statefulset_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "statefulset_default_nginx_2", CurrentReplicas: 1, - Status: NotReady, + Status: instance.NotReady, }, data: data{ name: "nginx", @@ -286,10 +287,10 @@ func TestKubernetesProvider_GetState(t *testing.T) { args: args{ name: "gateway_default_nginx_2", }, - want: InstanceState{ + want: instance.State{ Name: "gateway_default_nginx_2", CurrentReplicas: 0, - Status: Error, + Status: instance.Error, Error: "unsupported kind \"gateway\" must be one of \"deployment\", \"statefulset\"", }, data: data{ diff --git a/app/providers/provider.go b/app/providers/provider.go index 271fe59..8a5cdb5 100644 --- a/app/providers/provider.go +++ b/app/providers/provider.go @@ -1,80 +1,9 @@ package providers -import log "github.com/sirupsen/logrus" - -type InstanceState struct { - Name string - CurrentReplicas int - Status string - Error string -} - -var Ready = "ready" -var NotReady = "not-ready" -var Error = "error" +import "github.com/acouvreur/sablier/app/instance" type Provider interface { - Start(name string) (InstanceState, error) - Stop(name string) (InstanceState, error) - GetState(name string) (InstanceState, error) -} - -func (instance InstanceState) IsReady() bool { - return instance.Status == Ready -} - -func (instance InstanceState) HasError() bool { - return instance.Status == Error -} - -func errorInstanceState(name string, err error) (InstanceState, error) { - log.Error(err.Error()) - return InstanceState{ - Name: name, - CurrentReplicas: 0, - Status: Error, - Error: err.Error(), - }, err -} - -func unrecoverableInstanceState(name string, err string) (InstanceState, error) { - log.Warn(err) - return InstanceState{ - Name: name, - CurrentReplicas: 0, - Status: Error, - Error: err, - }, nil -} - -func readyInstanceState(name string) (InstanceState, error) { - return InstanceState{ - Name: name, - CurrentReplicas: 1, - Status: Ready, - }, nil -} - -func readyInstanceStateOfReplicas(name string, replicas int) (InstanceState, error) { - return InstanceState{ - Name: name, - CurrentReplicas: replicas, - Status: Ready, - }, nil -} - -func notReadyInstanceState(name string) (InstanceState, error) { - return InstanceState{ - Name: name, - CurrentReplicas: 0, - Status: NotReady, - }, nil -} - -func notReadyInstanceStateOfReplicas(name string, replicas int) (InstanceState, error) { - return InstanceState{ - Name: name, - CurrentReplicas: replicas, - Status: NotReady, - }, nil + Start(name string) (instance.State, error) + Stop(name string) (instance.State, error) + GetState(name string) (instance.State, error) }