diff --git a/aci/backend.go b/aci/backend.go index 982a1faf..3c6fd17b 100644 --- a/aci/backend.go +++ b/aci/backend.go @@ -132,7 +132,6 @@ func (a *aciAPIService) SecretsService() secrets.Service { return nil } - type aciContainerService struct { ctx store.AciContext } diff --git a/backend/backend.go b/backend/backend.go index b5d5f9d5..e44b218b 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -20,12 +20,13 @@ import ( "context" "errors" "fmt" - "github.com/docker/api/secrets" + "github.com/sirupsen/logrus" "github.com/docker/api/compose" "github.com/docker/api/containers" "github.com/docker/api/context/cloud" + "github.com/docker/api/secrets" ) var ( diff --git a/cli/cmd/compose/compose.go b/cli/cmd/compose/compose.go index b37a4ba6..1e2e52e4 100644 --- a/cli/cmd/compose/compose.go +++ b/cli/cmd/compose/compose.go @@ -18,8 +18,8 @@ package compose import ( "context" - "github.com/compose-spec/compose-go/cli" + "github.com/compose-spec/compose-go/cli" "github.com/pkg/errors" "github.com/spf13/cobra" diff --git a/client/client.go b/client/client.go index 86230505..64b98e89 100644 --- a/client/client.go +++ b/client/client.go @@ -18,14 +18,14 @@ package client import ( "context" - "github.com/docker/api/secrets" - "github.com/docker/api/context/cloud" + "github.com/docker/api/secrets" "github.com/docker/api/backend" "github.com/docker/api/compose" "github.com/docker/api/containers" apicontext "github.com/docker/api/context" + "github.com/docker/api/context/cloud" "github.com/docker/api/context/store" ) @@ -71,7 +71,7 @@ func (c *Client) ComposeService() compose.Service { return c.bs.ComposeService() } -// ComposeService returns the backend service for the current context +// SecretsService returns the backend service for the current context func (c *Client) SecretsService() secrets.Service { return c.bs.SecretsService() } diff --git a/compose/api.go b/compose/api.go index 2cd4c3a1..5a917037 100644 --- a/compose/api.go +++ b/compose/api.go @@ -37,6 +37,7 @@ type Service interface { Convert(ctx context.Context, opts *cli.ProjectOptions) ([]byte, error) } +// PortPublisher hold status about published port type PortPublisher struct { URL string TargetPort int @@ -44,11 +45,12 @@ type PortPublisher struct { Protocol string } +// ServiceStatus hold status about a service type ServiceStatus struct { - ID string - Name string - Replicas int - Desired int - Ports []string - LoadBalancers []PortPublisher + ID string + Name string + Replicas int + Desired int + Ports []string + Publishers []PortPublisher } diff --git a/compose/tags.go b/compose/tags.go index cd9ecbe3..300e55bb 100644 --- a/compose/tags.go +++ b/compose/tags.go @@ -17,7 +17,10 @@ package compose const ( + // ProjectTag allow to track resource related to a compose project ProjectTag = "com.docker.compose.project" + // NetworkTag allow to track resource related to a compose network NetworkTag = "com.docker.compose.network" + // ServiceTag allow to track resource related to a compose service ServiceTag = "com.docker.compose.service" ) diff --git a/ecs/backend.go b/ecs/backend.go index be9c68f9..107902f3 100644 --- a/ecs/backend.go +++ b/ecs/backend.go @@ -76,7 +76,7 @@ func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) { return &ecsAPIService{ ctx: ecsCtx, Region: ecsCtx.Region, - SDK: NewSDK(sess), + SDK: newSDK(sess), }, nil } diff --git a/ecs/cloudformation.go b/ecs/cloudformation.go index 0e9685d3..9481a350 100644 --- a/ecs/cloudformation.go +++ b/ecs/cloudformation.go @@ -46,11 +46,11 @@ import ( ) const ( - ParameterClusterName = "ParameterClusterName" - ParameterVPCId = "ParameterVPCId" - ParameterSubnet1Id = "ParameterSubnet1Id" - ParameterSubnet2Id = "ParameterSubnet2Id" - ParameterLoadBalancerARN = "ParameterLoadBalancerARN" + parameterClusterName = "ParameterClusterName" + parameterVPCId = "ParameterVPCId" + parameterSubnet1Id = "ParameterSubnet1Id" + parameterSubnet2Id = "ParameterSubnet2Id" + parameterLoadBalancerARN = "ParameterLoadBalancerARN" ) func (b *ecsAPIService) Convert(ctx context.Context, opts *cli.ProjectOptions) ([]byte, error) { @@ -62,12 +62,12 @@ func (b *ecsAPIService) Convert(ctx context.Context, opts *cli.ProjectOptions) ( if err != nil { return nil, err } - return Marshall(template) + return marshall(template) } // Convert a compose project into a CloudFormation template -func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, error) { - var checker compatibility.Checker = &FargateCompatibilityChecker{ +func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, error) { //nolint:gocyclo + var checker compatibility.Checker = &fargateCompatibilityChecker{ compatibility.AllowList{ Supported: compatibleComposeAttributes, }, @@ -86,12 +86,12 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat template := cloudformation.NewTemplate() template.Description = "CloudFormation template created by Docker for deploying applications on Amazon ECS" - template.Parameters[ParameterClusterName] = cloudformation.Parameter{ + template.Parameters[parameterClusterName] = cloudformation.Parameter{ Type: "String", Description: "Name of the ECS cluster to deploy to (optional)", } - template.Parameters[ParameterVPCId] = cloudformation.Parameter{ + template.Parameters[parameterVPCId] = cloudformation.Parameter{ Type: "AWS::EC2::VPC::Id", Description: "ID of the VPC", } @@ -103,28 +103,28 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat Description: "The list of SubnetIds, for at least two Availability Zones in the region in your VPC", } */ - template.Parameters[ParameterSubnet1Id] = cloudformation.Parameter{ + template.Parameters[parameterSubnet1Id] = cloudformation.Parameter{ Type: "AWS::EC2::Subnet::Id", Description: "SubnetId, for Availability Zone 1 in the region in your VPC", } - template.Parameters[ParameterSubnet2Id] = cloudformation.Parameter{ + template.Parameters[parameterSubnet2Id] = cloudformation.Parameter{ Type: "AWS::EC2::Subnet::Id", Description: "SubnetId, for Availability Zone 2 in the region in your VPC", } - template.Parameters[ParameterLoadBalancerARN] = cloudformation.Parameter{ + template.Parameters[parameterLoadBalancerARN] = cloudformation.Parameter{ Type: "String", Description: "Name of the LoadBalancer to connect to (optional)", } // Create Cluster is `ParameterClusterName` parameter is not set - template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(ParameterClusterName)) + template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(parameterClusterName)) cluster := createCluster(project, template) networks := map[string]string{} for _, net := range project.Networks { - networks[net.Name] = convertNetwork(project, net, cloudformation.Ref(ParameterVPCId), template) + networks[net.Name] = convertNetwork(project, net, cloudformation.Ref(parameterVPCId), template) } for i, s := range project.Secrets { @@ -175,9 +175,6 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat template.Resources[taskDefinition] = definition var healthCheck *cloudmap.Service_HealthCheckConfig - if service.HealthCheck != nil && !service.HealthCheck.Disable { - // FIXME ECS only support HTTP(s) health checks, while Docker only support CMD - } serviceRegistry := createServiceRegistry(service, template, healthCheck) @@ -242,8 +239,8 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat AssignPublicIp: ecsapi.AssignPublicIpEnabled, SecurityGroups: serviceSecurityGroups, Subnets: []string{ - cloudformation.Ref(ParameterSubnet1Id), - cloudformation.Ref(ParameterSubnet2Id), + cloudformation.Ref(parameterSubnet1Id), + cloudformation.Ref(parameterSubnet2Id), }, }, }, @@ -268,7 +265,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat func createLogGroup(project *types.Project, template *cloudformation.Template) { retention := 0 - if v, ok := project.Extensions[ExtensionRetention]; ok { + if v, ok := project.Extensions[extensionRetention]; ok { retention = v.(int) } logGroup := fmt.Sprintf("/docker-compose/%s", project.Name) @@ -285,11 +282,11 @@ func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) { return minPercent, maxPercent, nil } updateConfig := service.Deploy.UpdateConfig - min, okMin := updateConfig.Extensions[ExtensionMinPercent] + min, okMin := updateConfig.Extensions[extensionMinPercent] if okMin { minPercent = min.(int) } - max, okMax := updateConfig.Extensions[ExtensionMaxPercent] + max, okMax := updateConfig.Extensions[extensionMaxPercent] if okMax { maxPercent = max.(int) } @@ -333,7 +330,7 @@ func getLoadBalancerSecurityGroups(project *types.Project, template *cloudformat securityGroups := []string{} for _, network := range project.Networks { if !network.Internal { - net := convertNetwork(project, network, cloudformation.Ref(ParameterVPCId), template) + net := convertNetwork(project, network, cloudformation.Ref(parameterVPCId), template) securityGroups = append(securityGroups, net) } } @@ -354,7 +351,7 @@ func createLoadBalancer(project *types.Project, template *cloudformation.Templat // load balancer names are limited to 32 characters total loadBalancerName := fmt.Sprintf("%.32s", fmt.Sprintf("%sLoadBalancer", strings.Title(project.Name))) // Create PortPublisher if `ParameterLoadBalancerName` is not set - template.Conditions["CreateLoadBalancer"] = cloudformation.Equals("", cloudformation.Ref(ParameterLoadBalancerARN)) + template.Conditions["CreateLoadBalancer"] = cloudformation.Equals("", cloudformation.Ref(parameterLoadBalancerARN)) loadBalancerType := getLoadBalancerType(project) securityGroups := []string{} @@ -367,8 +364,8 @@ func createLoadBalancer(project *types.Project, template *cloudformation.Templat Scheme: elbv2.LoadBalancerSchemeEnumInternetFacing, SecurityGroups: securityGroups, Subnets: []string{ - cloudformation.Ref(ParameterSubnet1Id), - cloudformation.Ref(ParameterSubnet2Id), + cloudformation.Ref(parameterSubnet1Id), + cloudformation.Ref(parameterSubnet2Id), }, Tags: []tags.Tag{ { @@ -379,7 +376,7 @@ func createLoadBalancer(project *types.Project, template *cloudformation.Templat Type: loadBalancerType, AWSCloudFormationCondition: "CreateLoadBalancer", } - return cloudformation.If("CreateLoadBalancer", cloudformation.Ref(loadBalancerName), cloudformation.Ref(ParameterLoadBalancerARN)) + return cloudformation.If("CreateLoadBalancer", cloudformation.Ref(loadBalancerName), cloudformation.Ref(parameterLoadBalancerARN)) } func createListener(service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, targetGroupName string, loadBalancerARN string, protocol string) string { @@ -427,7 +424,7 @@ func createTargetGroup(project *types.Project, service types.ServiceConfig, port Value: project.Name, }, }, - VpcId: cloudformation.Ref(ParameterVPCId), + VpcId: cloudformation.Ref(parameterVPCId), TargetType: elbv2.TargetTypeEnumIp, } return targetGroupName @@ -462,7 +459,7 @@ func createServiceRegistry(service types.ServiceConfig, template *cloudformation func createTaskExecutionRole(service types.ServiceConfig, err error, definition *ecs.TaskDefinition, template *cloudformation.Template) (string, error) { taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name)) - policy, err := getPolicy(definition) + policy := getPolicy(definition) if err != nil { return taskExecutionRole, err } @@ -474,16 +471,16 @@ func createTaskExecutionRole(service types.ServiceConfig, err error, definition }) } - if roles, ok := service.Extensions[ExtensionRole]; ok { + if roles, ok := service.Extensions[extensionRole]; ok { rolePolicies = append(rolePolicies, iam.Role_Policy{ PolicyDocument: roles, }) } managedPolicies := []string{ - ECSTaskExecutionPolicy, - ECRReadOnlyPolicy, + ecsTaskExecutionPolicy, + ecrReadOnlyPolicy, } - if v, ok := service.Extensions[ExtensionManagedPolicies]; ok { + if v, ok := service.Extensions[extensionManagedPolicies]; ok { for _, s := range v.([]interface{}) { managedPolicies = append(managedPolicies, s.(string)) } @@ -507,7 +504,7 @@ func createCluster(project *types.Project, template *cloudformation.Template) st }, AWSCloudFormationCondition: "CreateCluster", } - cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(ParameterClusterName)) + cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(parameterClusterName)) return cluster } @@ -515,12 +512,12 @@ func createCloudMap(project *types.Project, template *cloudformation.Template) { template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{ Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name), Name: fmt.Sprintf("%s.local", project.Name), - Vpc: cloudformation.Ref(ParameterVPCId), + Vpc: cloudformation.Ref(parameterVPCId), } } func convertNetwork(project *types.Project, net types.NetworkConfig, vpc string, template *cloudformation.Template) string { - if sg, ok := net.Extensions[ExtensionSecurityGroup]; ok { + if sg, ok := net.Extensions[extensionSecurityGroup]; ok { logrus.Debugf("Security Group for network %q set by user to %q", net.Name, sg) return sg.(string) } @@ -583,7 +580,7 @@ func normalizeResourceName(s string) string { return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, "")) } -func getPolicy(taskDef *ecs.TaskDefinition) (*PolicyDocument, error) { +func getPolicy(taskDef *ecs.TaskDefinition) *PolicyDocument { arns := []string{} for _, container := range taskDef.ContainerDefinitions { if container.RepositoryCredentials != nil { @@ -601,12 +598,12 @@ func getPolicy(taskDef *ecs.TaskDefinition) (*PolicyDocument, error) { Statement: []PolicyStatement{ { Effect: "Allow", - Action: []string{ActionGetSecretValue, ActionGetParameters, ActionDecrypt}, + Action: []string{actionGetSecretValue, actionGetParameters, actionDecrypt}, Resource: arns, }}, - }, nil + } } - return nil, nil + return nil } func uniqueStrings(items []string) []string { diff --git a/ecs/cloudformation_test.go b/ecs/cloudformation_test.go index 5f1d6ba4..02959120 100644 --- a/ecs/cloudformation_test.go +++ b/ecs/cloudformation_test.go @@ -45,7 +45,7 @@ func TestSimpleConvert(t *testing.T) { } func TestLogging(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: foo: image: hello_world @@ -64,7 +64,7 @@ x-aws-logs_retention: 10 } func TestEnvFile(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: foo: image: hello_world @@ -84,7 +84,7 @@ services: } func TestEnvFileAndEnv(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: foo: image: hello_world @@ -106,7 +106,7 @@ services: } func TestRollingUpdateLimits(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: foo: image: hello_world @@ -121,7 +121,7 @@ services: } func TestRollingUpdateExtension(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: foo: image: hello_world @@ -136,16 +136,17 @@ services: } func TestRolePolicy(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: foo: image: hello_world x-aws-pull_credentials: "secret" `) - role := template.Resources["FooTaskExecutionRole"].(*iam.Role) - assert.Check(t, role != nil) - assert.Check(t, role.ManagedPolicyArns[0] == ECSTaskExecutionPolicy) - assert.Check(t, role.ManagedPolicyArns[1] == ECRReadOnlyPolicy) + x := template.Resources["FooTaskExecutionRole"] + assert.Check(t, x != nil) + role := *(x.(*iam.Role)) + assert.Check(t, role.ManagedPolicyArns[0] == ecsTaskExecutionPolicy) + assert.Check(t, role.ManagedPolicyArns[1] == ecrReadOnlyPolicy) // We expect an extra policy has been created for x-aws-pull_credentials assert.Check(t, len(role.Policies) == 1) policy := role.Policies[0].PolicyDocument.(*PolicyDocument) @@ -155,7 +156,7 @@ services: } func TestMapNetworksToSecurityGroups(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: test: image: hello_world @@ -172,29 +173,31 @@ networks: assert.Check(t, template.Resources["TestPublicNetwork"] != nil) assert.Check(t, template.Resources["TestBacktierNetwork"] != nil) assert.Check(t, template.Resources["TestBacktierNetworkIngress"] != nil) - ingress := template.Resources["TestPublicNetworkIngress"].(*ec2.SecurityGroupIngress) - assert.Check(t, ingress != nil) + i := template.Resources["TestPublicNetworkIngress"] + assert.Check(t, i != nil) + ingress := *i.(*ec2.SecurityGroupIngress) assert.Check(t, ingress.SourceSecurityGroupId == cloudformation.Ref("TestPublicNetwork")) } func TestLoadBalancerTypeApplication(t *testing.T) { - template := convertYaml(t, "test123456789009876543211234567890", ` + template := convertYaml(t, ` services: test: image: nginx ports: - 80:80 `) - lb := template.Resources["TestLoadBalancer"].(*elasticloadbalancingv2.LoadBalancer) + lb := template.Resources["TestLoadBalancer"] assert.Check(t, lb != nil) - assert.Check(t, len(lb.Name) <= 32) - assert.Check(t, lb.Type == elbv2.LoadBalancerTypeEnumApplication) - assert.Check(t, len(lb.SecurityGroups) > 0) + loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer) + assert.Check(t, len(loadBalancer.Name) <= 32) + assert.Check(t, loadBalancer.Type == elbv2.LoadBalancerTypeEnumApplication) + assert.Check(t, len(loadBalancer.SecurityGroups) > 0) } func TestNoLoadBalancerIfNoPortExposed(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: test: image: nginx @@ -209,20 +212,21 @@ services: } func TestServiceReplicas(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: test: image: nginx deploy: replicas: 10 `) - s := template.Resources["TestService"].(*ecs.Service) + s := template.Resources["TestService"] assert.Check(t, s != nil) - assert.Check(t, s.DesiredCount == 10) + service := *s.(*ecs.Service) + assert.Check(t, service.DesiredCount == 10) } func TestTaskSizeConvert(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: test: image: nginx @@ -239,7 +243,7 @@ services: assert.Equal(t, def.Cpu, "512") assert.Equal(t, def.Memory, "2048") - template = convertYaml(t, "test", ` + template = convertYaml(t, ` services: test: image: nginx @@ -257,7 +261,7 @@ services: assert.Equal(t, def.Memory, "8192") } func TestTaskSizeConvertFailure(t *testing.T) { - model := loadConfig(t, "test", ` + model := loadConfig(t, ` services: test: image: nginx @@ -273,7 +277,7 @@ services: } func TestLoadBalancerTypeNetwork(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: test: image: nginx @@ -281,13 +285,14 @@ services: - 80:80 - 88:88 `) - lb := template.Resources["TestLoadBalancer"].(*elasticloadbalancingv2.LoadBalancer) + lb := template.Resources["TestLoadBalancer"] assert.Check(t, lb != nil) - assert.Check(t, lb.Type == elbv2.LoadBalancerTypeEnumNetwork) + loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer) + assert.Check(t, loadBalancer.Type == elbv2.LoadBalancerTypeEnumNetwork) } func TestServiceMapping(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: test: image: "image" @@ -326,7 +331,7 @@ func get(l []ecs.TaskDefinition_KeyValuePair, name string) string { } func TestResourcesHaveProjectTagSet(t *testing.T) { - template := convertYaml(t, "test", ` + template := convertYaml(t, ` services: test: image: nginx @@ -353,7 +358,7 @@ func convertResultAsString(t *testing.T, project *types.Project) string { backend := &ecsAPIService{} template, err := backend.convert(project) assert.NilError(t, err) - resultAsJSON, err := Marshall(template) + resultAsJSON, err := marshall(template) assert.NilError(t, err) return fmt.Sprintf("%s\n", string(resultAsJSON)) } @@ -368,15 +373,15 @@ func load(t *testing.T, paths ...string) *types.Project { return project } -func convertYaml(t *testing.T, name string, yaml string) *cloudformation.Template { - project := loadConfig(t, name, yaml) +func convertYaml(t *testing.T, yaml string) *cloudformation.Template { + project := loadConfig(t, yaml) backend := &ecsAPIService{} template, err := backend.convert(project) assert.NilError(t, err) return template } -func loadConfig(t *testing.T, name string, yaml string) *types.Project { +func loadConfig(t *testing.T, yaml string) *types.Project { dict, err := loader.ParseYAML([]byte(yaml)) assert.NilError(t, err) model, err := loader.Load(types.ConfigDetails{ diff --git a/ecs/colors.go b/ecs/colors.go index 22d609c8..1486e138 100644 --- a/ecs/colors.go +++ b/ecs/colors.go @@ -21,7 +21,7 @@ import ( "strconv" ) -var NAMES = []string{ +var names = []string{ "grey", "red", "green", @@ -32,14 +32,8 @@ var NAMES = []string{ "white", } -var COLORS map[string]ColorFunc - -// ColorFunc use ANSI codes to render colored text on console -type ColorFunc func(s string) string - -var Monochrome = func(s string) string { - return s -} +// colorFunc use ANSI codes to render colored text on console +type colorFunc func(s string) string func ansiColor(code, s string) string { return fmt.Sprintf("%s%s%s", ansi(code), s, ansi("0")) @@ -49,38 +43,38 @@ func ansi(code string) string { return fmt.Sprintf("\033[%sm", code) } -func makeColorFunc(code string) ColorFunc { +func makeColorFunc(code string) colorFunc { return func(s string) string { return ansiColor(code, s) } } -var Rainbow = make(chan ColorFunc) +var loop = make(chan colorFunc) func init() { - COLORS = map[string]ColorFunc{} - for i, name := range NAMES { - COLORS[name] = makeColorFunc(strconv.Itoa(30 + i)) - COLORS["intense_"+name] = makeColorFunc(strconv.Itoa(30+i) + ";1") + colors := map[string]colorFunc{} + for i, name := range names { + colors[name] = makeColorFunc(strconv.Itoa(30 + i)) + colors["intense_"+name] = makeColorFunc(strconv.Itoa(30+i) + ";1") } go func() { i := 0 - rainbow := []ColorFunc{ - COLORS["cyan"], - COLORS["yellow"], - COLORS["green"], - COLORS["magenta"], - COLORS["blue"], - COLORS["intense_cyan"], - COLORS["intense_yellow"], - COLORS["intense_green"], - COLORS["intense_magenta"], - COLORS["intense_blue"], + rainbow := []colorFunc{ + colors["cyan"], + colors["yellow"], + colors["green"], + colors["magenta"], + colors["blue"], + colors["intense_cyan"], + colors["intense_yellow"], + colors["intense_green"], + colors["intense_magenta"], + colors["intense_blue"], } for { - Rainbow <- rainbow[i] + loop <- rainbow[i] i = (i + 1) % len(rainbow) } }() diff --git a/ecs/compatibility.go b/ecs/compatibility.go index 316c6b88..803943d1 100644 --- a/ecs/compatibility.go +++ b/ecs/compatibility.go @@ -21,7 +21,7 @@ import ( "github.com/compose-spec/compose-go/types" ) -type FargateCompatibilityChecker struct { +type fargateCompatibilityChecker struct { compatibility.AllowList } @@ -68,13 +68,13 @@ var compatibleComposeAttributes = []string{ "secrets.file", } -func (c *FargateCompatibilityChecker) CheckImage(service *types.ServiceConfig) { +func (c *fargateCompatibilityChecker) CheckImage(service *types.ServiceConfig) { if service.Image == "" { c.Incompatible("service %s doesn't define a Docker image to run", service.Name) } } -func (c *FargateCompatibilityChecker) CheckPortsPublished(p *types.ServicePortConfig) { +func (c *fargateCompatibilityChecker) CheckPortsPublished(p *types.ServicePortConfig) { if p.Published == 0 { p.Published = p.Target } @@ -83,7 +83,7 @@ func (c *FargateCompatibilityChecker) CheckPortsPublished(p *types.ServicePortCo } } -func (c *FargateCompatibilityChecker) CheckCapAdd(service *types.ServiceConfig) { +func (c *fargateCompatibilityChecker) CheckCapAdd(service *types.ServiceConfig) { add := []string{} for _, cap := range service.CapAdd { switch cap { @@ -96,7 +96,7 @@ func (c *FargateCompatibilityChecker) CheckCapAdd(service *types.ServiceConfig) service.CapAdd = add } -func (c *FargateCompatibilityChecker) CheckLoggingDriver(config *types.LoggingConfig) { +func (c *fargateCompatibilityChecker) CheckLoggingDriver(config *types.LoggingConfig) { if config.Driver != "" && config.Driver != "awslogs" { c.Unsupported("services.logging.driver %s is not supported", config.Driver) } diff --git a/ecs/context.go b/ecs/context.go index e40202cb..c3b91023 100644 --- a/ecs/context.go +++ b/ecs/context.go @@ -103,7 +103,7 @@ func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID stri p := credentials.SharedCredentialsProvider{Profile: profile} _, err := p.Retrieve() if err == nil { - return fmt.Errorf("credentials already exists!") + return fmt.Errorf("credentials already exists") } if err.(awserr.Error).Code() == "SharedCredsLoad" && err.(awserr.Error).Message() == "failed to load shared credentials file" { diff --git a/ecs/convert.go b/ecs/convert.go index 06557210..0f1f503b 100644 --- a/ecs/convert.go +++ b/ecs/convert.go @@ -44,10 +44,7 @@ func convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi if err != nil { return nil, err } - _, memReservation, err := toContainerReservation(service) - if err != nil { - return nil, err - } + _, memReservation := toContainerReservation(service) credential := getRepoCredentials(service) // override resolve.conf search directive to also search .local @@ -141,7 +138,11 @@ func convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi }, nil } -func createSecretsSideCar(project *types.Project, service types.ServiceConfig, logConfiguration *ecs.TaskDefinition_LogConfiguration) (ecs.TaskDefinition_Volume, ecs.TaskDefinition_MountPoint, ecs.TaskDefinition_ContainerDefinition, error) { +func createSecretsSideCar(project *types.Project, service types.ServiceConfig, logConfiguration *ecs.TaskDefinition_LogConfiguration) ( + ecs.TaskDefinition_Volume, + ecs.TaskDefinition_MountPoint, + ecs.TaskDefinition_ContainerDefinition, + error) { initContainerName := fmt.Sprintf("%s_Secrets_InitContainer", normalizeResourceName(service.Name)) secretsVolume := ecs.TaskDefinition_Volume{ Name: "secrets", @@ -166,7 +167,7 @@ func createSecretsSideCar(project *types.Project, service types.ServiceConfig, l ValueFrom: secretConfig.Name, }) var keys []string - if ext, ok := secretConfig.Extensions[ExtensionKeys]; ok { + if ext, ok := secretConfig.Extensions[extensionKeys]; ok { if key, ok := ext.(string); ok { keys = append(keys, key) } else { @@ -275,7 +276,7 @@ func toSystemControls(sysctls types.Mapping) []ecs.TaskDefinition_SystemControl return sys } -const MiB = 1024 * 1024 +const miB = 1024 * 1024 func toLimits(service types.ServiceConfig) (string, string, error) { // All possible cpu/mem values for Fargate @@ -315,9 +316,9 @@ func toLimits(service types.ServiceConfig) (string, string, error) { for _, cpu := range cpus { mem := cpuToMem[cpu] - if v <= cpu*MiB { + if v <= cpu*miB { for _, m := range mem { - if limits.MemoryBytes <= m*MiB { + if limits.MemoryBytes <= m*miB { cpuLimit = strconv.FormatInt(cpu, 10) memLimit = strconv.FormatInt(int64(m), 10) return cpuLimit, memLimit, nil @@ -328,19 +329,19 @@ func toLimits(service types.ServiceConfig) (string, string, error) { return "", "", fmt.Errorf("the resources requested are not supported by ECS/Fargate") } -func toContainerReservation(service types.ServiceConfig) (string, int, error) { +func toContainerReservation(service types.ServiceConfig) (string, int) { cpuReservation := ".0" memReservation := 0 if service.Deploy == nil { - return cpuReservation, memReservation, nil + return cpuReservation, memReservation } reservations := service.Deploy.Resources.Reservations if reservations == nil { - return cpuReservation, memReservation, nil + return cpuReservation, memReservation } - return reservations.NanoCPUs, int(reservations.MemoryBytes / MiB), nil + return reservations.NanoCPUs, int(reservations.MemoryBytes / miB) } func toPlacementConstraints(deploy *types.DeployConfig) []ecs.TaskDefinition_TaskDefinitionPlacementConstraint { @@ -467,7 +468,7 @@ func toHostEntryPtr(hosts types.HostsList) []ecs.TaskDefinition_HostEntry { func getRepoCredentials(service types.ServiceConfig) *ecs.TaskDefinition_RepositoryCredentials { // extract registry and namespace string from image name for key, value := range service.Extensions { - if key == ExtensionPullCredentials { + if key == extensionPullCredentials { return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: value.(string)} } } diff --git a/ecs/down.go b/ecs/down.go index 55e0dec9..4852f815 100644 --- a/ecs/down.go +++ b/ecs/down.go @@ -32,7 +32,7 @@ func (b *ecsAPIService) Down(ctx context.Context, options *cli.ProjectOptions) e if err != nil { return err } - return b.WaitStackCompletion(ctx, name, StackDelete) + return b.WaitStackCompletion(ctx, name, stackDelete) } func (b *ecsAPIService) projectName(options *cli.ProjectOptions) (string, error) { diff --git a/ecs/iam.go b/ecs/iam.go index 30670b0f..7a4510ea 100644 --- a/ecs/iam.go +++ b/ecs/iam.go @@ -17,12 +17,12 @@ package ecs const ( - ECSTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" - ECRReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ecsTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" + ecrReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" - ActionGetSecretValue = "secretsmanager:GetSecretValue" - ActionGetParameters = "ssm:GetParameters" - ActionDecrypt = "kms:Decrypt" + actionGetSecretValue = "secretsmanager:GetSecretValue" + actionGetParameters = "ssm:GetParameters" + actionDecrypt = "kms:Decrypt" ) var assumeRolePolicyDocument = PolicyDocument{ @@ -38,12 +38,14 @@ var assumeRolePolicyDocument = PolicyDocument{ }, } +// PolicyDocument describes an IAM policy document // could alternatively depend on https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/master/cmd/clusterawsadm/api/iam/v1alpha1/types.go type PolicyDocument struct { Version string `json:",omitempty"` Statement []PolicyStatement `json:",omitempty"` } +// PolicyStatement describes an IAM policy statement type PolicyStatement struct { Effect string `json:",omitempty"` Action []string `json:",omitempty"` @@ -51,6 +53,7 @@ type PolicyStatement struct { Resource []string `json:",omitempty"` } +// PolicyPrincipal describes an IAM policy principal type PolicyPrincipal struct { Service string `json:",omitempty"` } diff --git a/ecs/list.go b/ecs/list.go index 02be946e..3c9ea5ad 100644 --- a/ecs/list.go +++ b/ecs/list.go @@ -35,7 +35,7 @@ func (b *ecsAPIService) Ps(ctx context.Context, options *cli.ProjectOptions) ([] if err != nil { return nil, err } - cluster := parameters[ParameterClusterName] + cluster := parameters[parameterClusterName] resources, err := b.SDK.ListStackResources(ctx, projectName) if err != nil { @@ -61,7 +61,7 @@ func (b *ecsAPIService) Ps(ctx context.Context, options *cli.ProjectOptions) ([] for i, state := range status { ports := []string{} - for _, lb := range state.LoadBalancers { + for _, lb := range state.Publishers { ports = append(ports, fmt.Sprintf( "%s:%d->%d/%s", lb.URL, diff --git a/ecs/logs.go b/ecs/logs.go index 58010fc1..84e913ea 100644 --- a/ecs/logs.go +++ b/ecs/logs.go @@ -40,7 +40,7 @@ func (b *ecsAPIService) Logs(ctx context.Context, options *cli.ProjectOptions, w } consumer := logConsumer{ - colors: map[string]ColorFunc{}, + colors: map[string]colorFunc{}, width: 0, writer: writer, } @@ -58,7 +58,7 @@ func (b *ecsAPIService) Logs(ctx context.Context, options *cli.ProjectOptions, w func (l *logConsumer) Log(service, container, message string) { cf, ok := l.colors[service] if !ok { - cf = <-Rainbow + cf = <-loop l.colors[service] = cf l.computeWidth() } @@ -81,7 +81,7 @@ func (l *logConsumer) computeWidth() { } type logConsumer struct { - colors map[string]ColorFunc + colors map[string]colorFunc width int writer io.Writer } diff --git a/ecs/marshall.go b/ecs/marshall.go index 8ef38a9d..d595e7c5 100644 --- a/ecs/marshall.go +++ b/ecs/marshall.go @@ -24,7 +24,7 @@ import ( "github.com/awslabs/goformation/v4/cloudformation" ) -func Marshall(template *cloudformation.Template) ([]byte, error) { +func marshall(template *cloudformation.Template) ([]byte, error) { raw, err := template.JSON() if err != nil { return nil, err diff --git a/ecs/sdk.go b/ecs/sdk.go index 3d499b1b..3f442976 100644 --- a/ecs/sdk.go +++ b/ecs/sdk.go @@ -22,11 +22,12 @@ import ( "strings" "time" + "github.com/aws/aws-sdk-go/aws/client" + "github.com/docker/api/compose" "github.com/docker/api/secrets" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" @@ -46,17 +47,16 @@ import ( ) type sdk struct { - sess *session.Session - ECS ecsiface.ECSAPI - EC2 ec2iface.EC2API - ELB elbv2iface.ELBV2API - CW cloudwatchlogsiface.CloudWatchLogsAPI - IAM iamiface.IAMAPI - CF cloudformationiface.CloudFormationAPI - SM secretsmanageriface.SecretsManagerAPI + ECS ecsiface.ECSAPI + EC2 ec2iface.EC2API + ELB elbv2iface.ELBV2API + CW cloudwatchlogsiface.CloudWatchLogsAPI + IAM iamiface.IAMAPI + CF cloudformationiface.CloudFormationAPI + SM secretsmanageriface.SecretsManagerAPI } -func NewSDK(sess *session.Session) sdk { +func newSDK(sess client.ConfigProvider) sdk { return sdk{ ECS: ecs.New(sess), EC2: ec2.New(sess), @@ -177,7 +177,7 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) { func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template, parameters map[string]string) error { logrus.Debug("Create CloudFormation stack") - json, err := Marshall(template) + json, err := marshall(template) if err != nil { return err } @@ -205,7 +205,7 @@ func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template func (s sdk) CreateChangeSet(ctx context.Context, name string, template *cf.Template, parameters map[string]string) (string, error) { logrus.Debug("Create CloudFormation Changeset") - json, err := Marshall(template) + json, err := marshall(template) if err != nil { return "", err } @@ -258,9 +258,9 @@ func (s sdk) UpdateStack(ctx context.Context, changeset string) error { } const ( - StackCreate = iota - StackUpdate - StackDelete + stackCreate = iota + stackUpdate + stackDelete ) func (s sdk) WaitStackComplete(ctx context.Context, name string, operation int) error { @@ -268,9 +268,9 @@ func (s sdk) WaitStackComplete(ctx context.Context, name string, operation int) StackName: aws.String(name), } switch operation { - case StackCreate: + case stackCreate: return s.CF.WaitUntilStackCreateCompleteWithContext(ctx, input) - case StackDelete: + case stackDelete: return s.CF.WaitUntilStackDeleteCompleteWithContext(ctx, input) default: return fmt.Errorf("internal error: unexpected stack operation %d", operation) @@ -322,14 +322,14 @@ func (s sdk) ListStackParameters(ctx context.Context, name string) (map[string]s return parameters, nil } -type StackResource struct { +type stackResource struct { LogicalID string Type string ARN string Status string } -func (s sdk) ListStackResources(ctx context.Context, name string) ([]StackResource, error) { +func (s sdk) ListStackResources(ctx context.Context, name string) ([]stackResource, error) { // FIXME handle pagination res, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{ StackName: aws.String(name), @@ -338,9 +338,9 @@ func (s sdk) ListStackResources(ctx context.Context, name string) ([]StackResour return nil, err } - resources := []StackResource{} + resources := []stackResource{} for _, r := range res.StackResourceSummaries { - resources = append(resources, StackResource{ + resources = append(resources, stackResource{ LogicalID: aws.StringValue(r.LogicalResourceId), Type: aws.StringValue(r.ResourceType), ARN: aws.StringValue(r.PhysicalResourceId), @@ -495,11 +495,11 @@ func (s sdk) DescribeServices(ctx context.Context, cluster string, arns []string return nil, err } status = append(status, compose.ServiceStatus{ - ID: aws.StringValue(service.ServiceName), - Name: name, - Replicas: int(aws.Int64Value(service.RunningCount)), - Desired: int(aws.Int64Value(service.DesiredCount)), - LoadBalancers: loadBalancers, + ID: aws.StringValue(service.ServiceName), + Name: name, + Replicas: int(aws.Int64Value(service.RunningCount)), + Desired: int(aws.Int64Value(service.DesiredCount)), + Publishers: loadBalancers, }) } return status, nil diff --git a/ecs/secrets/init.go b/ecs/secrets/init.go index 8ee28f29..729afe10 100644 --- a/ecs/secrets/init.go +++ b/ecs/secrets/init.go @@ -24,11 +24,13 @@ import ( "path/filepath" ) +// Secret define sensitive data to be bound as file type Secret struct { Name string Keys []string } +// CreateSecretFiles retrieve sensitive data from env and store as plain text a a file in path func CreateSecretFiles(secret Secret, path string) error { value, ok := os.LookupEnv(secret.Name) if !ok { diff --git a/ecs/secrets/main/main.go b/ecs/secrets/main/main.go index b70baf0d..d4b23876 100644 --- a/ecs/secrets/main/main.go +++ b/ecs/secrets/main/main.go @@ -28,21 +28,21 @@ const secretsFolder = "/run/secrets" func main() { if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "usage: secrets ") + fmt.Fprint(os.Stderr, "usage: secrets ") os.Exit(1) } var input []secrets.Secret err := json.Unmarshal([]byte(os.Args[1]), &input) if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) + fmt.Fprint(os.Stderr, err.Error()) os.Exit(1) } for _, secret := range input { err := secrets.CreateSecretFiles(secret, secretsFolder) if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) + fmt.Fprint(os.Stderr, err.Error()) os.Exit(1) } } diff --git a/ecs/up.go b/ecs/up.go index a3e0b4dc..140f804f 100644 --- a/ecs/up.go +++ b/ecs/up.go @@ -67,20 +67,20 @@ func (b *ecsAPIService) Up(ctx context.Context, options *cli.ProjectOptions) err } parameters := map[string]string{ - ParameterClusterName: cluster, - ParameterVPCId: vpc, - ParameterSubnet1Id: subNets[0], - ParameterSubnet2Id: subNets[1], - ParameterLoadBalancerARN: lb, + parameterClusterName: cluster, + parameterVPCId: vpc, + parameterSubnet1Id: subNets[0], + parameterSubnet2Id: subNets[1], + parameterLoadBalancerARN: lb, } update, err := b.SDK.StackExists(ctx, project.Name) if err != nil { return err } - operation := StackCreate + operation := stackCreate if update { - operation = StackUpdate + operation = stackUpdate changeset, err := b.SDK.CreateChangeSet(ctx, project.Name, template, parameters) if err != nil { return err @@ -110,7 +110,7 @@ func (b *ecsAPIService) Up(ctx context.Context, options *cli.ProjectOptions) err func (b ecsAPIService) GetVPC(ctx context.Context, project *types.Project) (string, error) { //check compose file for custom VPC selected - if vpc, ok := project.Extensions[ExtensionVPC]; ok { + if vpc, ok := project.Extensions[extensionVPC]; ok { vpcID := vpc.(string) ok, err := b.SDK.VpcExists(ctx, vpcID) if err != nil { @@ -130,7 +130,7 @@ func (b ecsAPIService) GetVPC(ctx context.Context, project *types.Project) (stri func (b ecsAPIService) GetLoadBalancer(ctx context.Context, project *types.Project) (string, error) { //check compose file for custom VPC selected - if ext, ok := project.Extensions[ExtensionLB]; ok { + if ext, ok := project.Extensions[extensionLB]; ok { lb := ext.(string) ok, err := b.SDK.LoadBalancerExists(ctx, lb) if err != nil { @@ -146,7 +146,7 @@ func (b ecsAPIService) GetLoadBalancer(ctx context.Context, project *types.Proje func (b ecsAPIService) GetCluster(ctx context.Context, project *types.Project) (string, error) { //check compose file for custom VPC selected - if ext, ok := project.Extensions[ExtensionCluster]; ok { + if ext, ok := project.Extensions[extensionCluster]; ok { cluster := ext.(string) ok, err := b.SDK.ClusterExists(ctx, cluster) if err != nil { diff --git a/ecs/wait.go b/ecs/wait.go index 04632ee6..a6933dde 100644 --- a/ecs/wait.go +++ b/ecs/wait.go @@ -28,7 +28,7 @@ import ( "github.com/aws/aws-sdk-go/aws" ) -func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, operation int) error { +func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, operation int) error { //nolint:gocyclo knownEvents := map[string]struct{}{} // progress writer w := progress.ContextWriter(ctx) @@ -76,23 +76,23 @@ func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, op switch status { case "CREATE_COMPLETE": - if operation == StackCreate { + if operation == stackCreate { progressStatus = progress.Done } case "UPDATE_COMPLETE": - if operation == StackUpdate { + if operation == stackUpdate { progressStatus = progress.Done } case "DELETE_COMPLETE": - if operation == StackDelete { + if operation == stackDelete { progressStatus = progress.Done } default: if strings.HasSuffix(status, "_FAILED") { progressStatus = progress.Error if stackErr == nil { - operation = StackDelete + operation = stackDelete stackErr = fmt.Errorf(reason) } } diff --git a/ecs/x.go b/ecs/x.go index 1f2ba5ce..63af9c2e 100644 --- a/ecs/x.go +++ b/ecs/x.go @@ -17,15 +17,15 @@ package ecs const ( - ExtensionSecurityGroup = "x-aws-securitygroup" - ExtensionVPC = "x-aws-vpc" - ExtensionPullCredentials = "x-aws-pull_credentials" - ExtensionLB = "x-aws-loadbalancer" - ExtensionCluster = "x-aws-cluster" - ExtensionKeys = "x-aws-keys" - ExtensionMinPercent = "x-aws-min_percent" - ExtensionMaxPercent = "x-aws-max_percent" - ExtensionRetention = "x-aws-logs_retention" - ExtensionRole = "x-aws-role" - ExtensionManagedPolicies = "x-aws-policies" + extensionSecurityGroup = "x-aws-securitygroup" + extensionVPC = "x-aws-vpc" + extensionPullCredentials = "x-aws-pull_credentials" + extensionLB = "x-aws-loadbalancer" + extensionCluster = "x-aws-cluster" + extensionKeys = "x-aws-keys" + extensionMinPercent = "x-aws-min_percent" + extensionMaxPercent = "x-aws-max_percent" + extensionRetention = "x-aws-logs_retention" + extensionRole = "x-aws-role" + extensionManagedPolicies = "x-aws-policies" ) diff --git a/secrets/api.go b/secrets/api.go index f1231b7d..53fafe96 100644 --- a/secrets/api.go +++ b/secrets/api.go @@ -29,6 +29,7 @@ type Service interface { DeleteSecret(ctx context.Context, id string, recover bool) error } +// Secret hold sensitive data type Secret struct { ID string `json:"ID"` Name string `json:"Name"` @@ -38,6 +39,7 @@ type Secret struct { password string } +// NewSecret builds a secret func NewSecret(name, username, password, description string) Secret { return Secret{ Name: name, @@ -47,6 +49,7 @@ func NewSecret(name, username, password, description string) Secret { } } +// ToJSON marshall a Secret into JSON string func (s Secret) ToJSON() (string, error) { b, err := json.MarshalIndent(&s, "", "\t") if err != nil { @@ -55,6 +58,7 @@ func (s Secret) ToJSON() (string, error) { return string(b), nil } +// GetCredString marshall a Secret's sensitive data into JSON string func (s Secret) GetCredString() (string, error) { creds := map[string]string{ "username": s.username,