Apply linter recommendations

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-08-18 16:56:42 +02:00
parent 0a6d7b1c6d
commit ec4615ae57
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
25 changed files with 213 additions and 202 deletions

View File

@ -132,7 +132,6 @@ func (a *aciAPIService) SecretsService() secrets.Service {
return nil return nil
} }
type aciContainerService struct { type aciContainerService struct {
ctx store.AciContext ctx store.AciContext
} }

View File

@ -20,12 +20,13 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/docker/api/secrets"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/docker/api/compose" "github.com/docker/api/compose"
"github.com/docker/api/containers" "github.com/docker/api/containers"
"github.com/docker/api/context/cloud" "github.com/docker/api/context/cloud"
"github.com/docker/api/secrets"
) )
var ( var (

View File

@ -18,8 +18,8 @@ package compose
import ( import (
"context" "context"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/cli"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"

View File

@ -18,14 +18,14 @@ package client
import ( import (
"context" "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/backend"
"github.com/docker/api/compose" "github.com/docker/api/compose"
"github.com/docker/api/containers" "github.com/docker/api/containers"
apicontext "github.com/docker/api/context" apicontext "github.com/docker/api/context"
"github.com/docker/api/context/cloud"
"github.com/docker/api/context/store" "github.com/docker/api/context/store"
) )
@ -71,7 +71,7 @@ func (c *Client) ComposeService() compose.Service {
return c.bs.ComposeService() 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 { func (c *Client) SecretsService() secrets.Service {
return c.bs.SecretsService() return c.bs.SecretsService()
} }

View File

@ -37,6 +37,7 @@ type Service interface {
Convert(ctx context.Context, opts *cli.ProjectOptions) ([]byte, error) Convert(ctx context.Context, opts *cli.ProjectOptions) ([]byte, error)
} }
// PortPublisher hold status about published port
type PortPublisher struct { type PortPublisher struct {
URL string URL string
TargetPort int TargetPort int
@ -44,11 +45,12 @@ type PortPublisher struct {
Protocol string Protocol string
} }
// ServiceStatus hold status about a service
type ServiceStatus struct { type ServiceStatus struct {
ID string ID string
Name string Name string
Replicas int Replicas int
Desired int Desired int
Ports []string Ports []string
LoadBalancers []PortPublisher Publishers []PortPublisher
} }

View File

@ -17,7 +17,10 @@
package compose package compose
const ( const (
// ProjectTag allow to track resource related to a compose project
ProjectTag = "com.docker.compose.project" ProjectTag = "com.docker.compose.project"
// NetworkTag allow to track resource related to a compose network
NetworkTag = "com.docker.compose.network" NetworkTag = "com.docker.compose.network"
// ServiceTag allow to track resource related to a compose service
ServiceTag = "com.docker.compose.service" ServiceTag = "com.docker.compose.service"
) )

View File

@ -76,7 +76,7 @@ func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) {
return &ecsAPIService{ return &ecsAPIService{
ctx: ecsCtx, ctx: ecsCtx,
Region: ecsCtx.Region, Region: ecsCtx.Region,
SDK: NewSDK(sess), SDK: newSDK(sess),
}, nil }, nil
} }

View File

@ -46,11 +46,11 @@ import (
) )
const ( const (
ParameterClusterName = "ParameterClusterName" parameterClusterName = "ParameterClusterName"
ParameterVPCId = "ParameterVPCId" parameterVPCId = "ParameterVPCId"
ParameterSubnet1Id = "ParameterSubnet1Id" parameterSubnet1Id = "ParameterSubnet1Id"
ParameterSubnet2Id = "ParameterSubnet2Id" parameterSubnet2Id = "ParameterSubnet2Id"
ParameterLoadBalancerARN = "ParameterLoadBalancerARN" parameterLoadBalancerARN = "ParameterLoadBalancerARN"
) )
func (b *ecsAPIService) Convert(ctx context.Context, opts *cli.ProjectOptions) ([]byte, error) { 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 { if err != nil {
return nil, err return nil, err
} }
return Marshall(template) return marshall(template)
} }
// Convert a compose project into a CloudFormation template // Convert a compose project into a CloudFormation template
func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, error) { func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, error) { //nolint:gocyclo
var checker compatibility.Checker = &FargateCompatibilityChecker{ var checker compatibility.Checker = &fargateCompatibilityChecker{
compatibility.AllowList{ compatibility.AllowList{
Supported: compatibleComposeAttributes, Supported: compatibleComposeAttributes,
}, },
@ -86,12 +86,12 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
template := cloudformation.NewTemplate() template := cloudformation.NewTemplate()
template.Description = "CloudFormation template created by Docker for deploying applications on Amazon ECS" 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", Type: "String",
Description: "Name of the ECS cluster to deploy to (optional)", 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", Type: "AWS::EC2::VPC::Id",
Description: "ID of the VPC", 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", 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", Type: "AWS::EC2::Subnet::Id",
Description: "SubnetId, for Availability Zone 1 in the region in your VPC", 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", Type: "AWS::EC2::Subnet::Id",
Description: "SubnetId, for Availability Zone 2 in the region in your VPC", Description: "SubnetId, for Availability Zone 2 in the region in your VPC",
} }
template.Parameters[ParameterLoadBalancerARN] = cloudformation.Parameter{ template.Parameters[parameterLoadBalancerARN] = cloudformation.Parameter{
Type: "String", Type: "String",
Description: "Name of the LoadBalancer to connect to (optional)", Description: "Name of the LoadBalancer to connect to (optional)",
} }
// Create Cluster is `ParameterClusterName` parameter is not set // 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) cluster := createCluster(project, template)
networks := map[string]string{} networks := map[string]string{}
for _, net := range project.Networks { 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 { for i, s := range project.Secrets {
@ -175,9 +175,6 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
template.Resources[taskDefinition] = definition template.Resources[taskDefinition] = definition
var healthCheck *cloudmap.Service_HealthCheckConfig 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) serviceRegistry := createServiceRegistry(service, template, healthCheck)
@ -242,8 +239,8 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
AssignPublicIp: ecsapi.AssignPublicIpEnabled, AssignPublicIp: ecsapi.AssignPublicIpEnabled,
SecurityGroups: serviceSecurityGroups, SecurityGroups: serviceSecurityGroups,
Subnets: []string{ Subnets: []string{
cloudformation.Ref(ParameterSubnet1Id), cloudformation.Ref(parameterSubnet1Id),
cloudformation.Ref(ParameterSubnet2Id), cloudformation.Ref(parameterSubnet2Id),
}, },
}, },
}, },
@ -268,7 +265,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
func createLogGroup(project *types.Project, template *cloudformation.Template) { func createLogGroup(project *types.Project, template *cloudformation.Template) {
retention := 0 retention := 0
if v, ok := project.Extensions[ExtensionRetention]; ok { if v, ok := project.Extensions[extensionRetention]; ok {
retention = v.(int) retention = v.(int)
} }
logGroup := fmt.Sprintf("/docker-compose/%s", project.Name) logGroup := fmt.Sprintf("/docker-compose/%s", project.Name)
@ -285,11 +282,11 @@ func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) {
return minPercent, maxPercent, nil return minPercent, maxPercent, nil
} }
updateConfig := service.Deploy.UpdateConfig updateConfig := service.Deploy.UpdateConfig
min, okMin := updateConfig.Extensions[ExtensionMinPercent] min, okMin := updateConfig.Extensions[extensionMinPercent]
if okMin { if okMin {
minPercent = min.(int) minPercent = min.(int)
} }
max, okMax := updateConfig.Extensions[ExtensionMaxPercent] max, okMax := updateConfig.Extensions[extensionMaxPercent]
if okMax { if okMax {
maxPercent = max.(int) maxPercent = max.(int)
} }
@ -333,7 +330,7 @@ func getLoadBalancerSecurityGroups(project *types.Project, template *cloudformat
securityGroups := []string{} securityGroups := []string{}
for _, network := range project.Networks { for _, network := range project.Networks {
if !network.Internal { if !network.Internal {
net := convertNetwork(project, network, cloudformation.Ref(ParameterVPCId), template) net := convertNetwork(project, network, cloudformation.Ref(parameterVPCId), template)
securityGroups = append(securityGroups, net) 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 // load balancer names are limited to 32 characters total
loadBalancerName := fmt.Sprintf("%.32s", fmt.Sprintf("%sLoadBalancer", strings.Title(project.Name))) loadBalancerName := fmt.Sprintf("%.32s", fmt.Sprintf("%sLoadBalancer", strings.Title(project.Name)))
// Create PortPublisher if `ParameterLoadBalancerName` is not set // 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) loadBalancerType := getLoadBalancerType(project)
securityGroups := []string{} securityGroups := []string{}
@ -367,8 +364,8 @@ func createLoadBalancer(project *types.Project, template *cloudformation.Templat
Scheme: elbv2.LoadBalancerSchemeEnumInternetFacing, Scheme: elbv2.LoadBalancerSchemeEnumInternetFacing,
SecurityGroups: securityGroups, SecurityGroups: securityGroups,
Subnets: []string{ Subnets: []string{
cloudformation.Ref(ParameterSubnet1Id), cloudformation.Ref(parameterSubnet1Id),
cloudformation.Ref(ParameterSubnet2Id), cloudformation.Ref(parameterSubnet2Id),
}, },
Tags: []tags.Tag{ Tags: []tags.Tag{
{ {
@ -379,7 +376,7 @@ func createLoadBalancer(project *types.Project, template *cloudformation.Templat
Type: loadBalancerType, Type: loadBalancerType,
AWSCloudFormationCondition: "CreateLoadBalancer", 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 { 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, Value: project.Name,
}, },
}, },
VpcId: cloudformation.Ref(ParameterVPCId), VpcId: cloudformation.Ref(parameterVPCId),
TargetType: elbv2.TargetTypeEnumIp, TargetType: elbv2.TargetTypeEnumIp,
} }
return targetGroupName 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) { func createTaskExecutionRole(service types.ServiceConfig, err error, definition *ecs.TaskDefinition, template *cloudformation.Template) (string, error) {
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name)) taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
policy, err := getPolicy(definition) policy := getPolicy(definition)
if err != nil { if err != nil {
return taskExecutionRole, err 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{ rolePolicies = append(rolePolicies, iam.Role_Policy{
PolicyDocument: roles, PolicyDocument: roles,
}) })
} }
managedPolicies := []string{ managedPolicies := []string{
ECSTaskExecutionPolicy, ecsTaskExecutionPolicy,
ECRReadOnlyPolicy, ecrReadOnlyPolicy,
} }
if v, ok := service.Extensions[ExtensionManagedPolicies]; ok { if v, ok := service.Extensions[extensionManagedPolicies]; ok {
for _, s := range v.([]interface{}) { for _, s := range v.([]interface{}) {
managedPolicies = append(managedPolicies, s.(string)) managedPolicies = append(managedPolicies, s.(string))
} }
@ -507,7 +504,7 @@ func createCluster(project *types.Project, template *cloudformation.Template) st
}, },
AWSCloudFormationCondition: "CreateCluster", AWSCloudFormationCondition: "CreateCluster",
} }
cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(ParameterClusterName)) cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(parameterClusterName))
return cluster return cluster
} }
@ -515,12 +512,12 @@ func createCloudMap(project *types.Project, template *cloudformation.Template) {
template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{ template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name), Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
Name: fmt.Sprintf("%s.local", 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 { 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) logrus.Debugf("Security Group for network %q set by user to %q", net.Name, sg)
return sg.(string) return sg.(string)
} }
@ -583,7 +580,7 @@ func normalizeResourceName(s string) string {
return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, "")) 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{} arns := []string{}
for _, container := range taskDef.ContainerDefinitions { for _, container := range taskDef.ContainerDefinitions {
if container.RepositoryCredentials != nil { if container.RepositoryCredentials != nil {
@ -601,12 +598,12 @@ func getPolicy(taskDef *ecs.TaskDefinition) (*PolicyDocument, error) {
Statement: []PolicyStatement{ Statement: []PolicyStatement{
{ {
Effect: "Allow", Effect: "Allow",
Action: []string{ActionGetSecretValue, ActionGetParameters, ActionDecrypt}, Action: []string{actionGetSecretValue, actionGetParameters, actionDecrypt},
Resource: arns, Resource: arns,
}}, }},
}, nil }
} }
return nil, nil return nil
} }
func uniqueStrings(items []string) []string { func uniqueStrings(items []string) []string {

View File

@ -45,7 +45,7 @@ func TestSimpleConvert(t *testing.T) {
} }
func TestLogging(t *testing.T) { func TestLogging(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
foo: foo:
image: hello_world image: hello_world
@ -64,7 +64,7 @@ x-aws-logs_retention: 10
} }
func TestEnvFile(t *testing.T) { func TestEnvFile(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
foo: foo:
image: hello_world image: hello_world
@ -84,7 +84,7 @@ services:
} }
func TestEnvFileAndEnv(t *testing.T) { func TestEnvFileAndEnv(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
foo: foo:
image: hello_world image: hello_world
@ -106,7 +106,7 @@ services:
} }
func TestRollingUpdateLimits(t *testing.T) { func TestRollingUpdateLimits(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
foo: foo:
image: hello_world image: hello_world
@ -121,7 +121,7 @@ services:
} }
func TestRollingUpdateExtension(t *testing.T) { func TestRollingUpdateExtension(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
foo: foo:
image: hello_world image: hello_world
@ -136,16 +136,17 @@ services:
} }
func TestRolePolicy(t *testing.T) { func TestRolePolicy(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
foo: foo:
image: hello_world image: hello_world
x-aws-pull_credentials: "secret" x-aws-pull_credentials: "secret"
`) `)
role := template.Resources["FooTaskExecutionRole"].(*iam.Role) x := template.Resources["FooTaskExecutionRole"]
assert.Check(t, role != nil) assert.Check(t, x != nil)
assert.Check(t, role.ManagedPolicyArns[0] == ECSTaskExecutionPolicy) role := *(x.(*iam.Role))
assert.Check(t, role.ManagedPolicyArns[1] == ECRReadOnlyPolicy) 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 // We expect an extra policy has been created for x-aws-pull_credentials
assert.Check(t, len(role.Policies) == 1) assert.Check(t, len(role.Policies) == 1)
policy := role.Policies[0].PolicyDocument.(*PolicyDocument) policy := role.Policies[0].PolicyDocument.(*PolicyDocument)
@ -155,7 +156,7 @@ services:
} }
func TestMapNetworksToSecurityGroups(t *testing.T) { func TestMapNetworksToSecurityGroups(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
test: test:
image: hello_world image: hello_world
@ -172,29 +173,31 @@ networks:
assert.Check(t, template.Resources["TestPublicNetwork"] != nil) assert.Check(t, template.Resources["TestPublicNetwork"] != nil)
assert.Check(t, template.Resources["TestBacktierNetwork"] != nil) assert.Check(t, template.Resources["TestBacktierNetwork"] != nil)
assert.Check(t, template.Resources["TestBacktierNetworkIngress"] != nil) assert.Check(t, template.Resources["TestBacktierNetworkIngress"] != nil)
ingress := template.Resources["TestPublicNetworkIngress"].(*ec2.SecurityGroupIngress) i := template.Resources["TestPublicNetworkIngress"]
assert.Check(t, ingress != nil) assert.Check(t, i != nil)
ingress := *i.(*ec2.SecurityGroupIngress)
assert.Check(t, ingress.SourceSecurityGroupId == cloudformation.Ref("TestPublicNetwork")) assert.Check(t, ingress.SourceSecurityGroupId == cloudformation.Ref("TestPublicNetwork"))
} }
func TestLoadBalancerTypeApplication(t *testing.T) { func TestLoadBalancerTypeApplication(t *testing.T) {
template := convertYaml(t, "test123456789009876543211234567890", ` template := convertYaml(t, `
services: services:
test: test:
image: nginx image: nginx
ports: ports:
- 80:80 - 80:80
`) `)
lb := template.Resources["TestLoadBalancer"].(*elasticloadbalancingv2.LoadBalancer) lb := template.Resources["TestLoadBalancer"]
assert.Check(t, lb != nil) assert.Check(t, lb != nil)
assert.Check(t, len(lb.Name) <= 32) loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer)
assert.Check(t, lb.Type == elbv2.LoadBalancerTypeEnumApplication) assert.Check(t, len(loadBalancer.Name) <= 32)
assert.Check(t, len(lb.SecurityGroups) > 0) assert.Check(t, loadBalancer.Type == elbv2.LoadBalancerTypeEnumApplication)
assert.Check(t, len(loadBalancer.SecurityGroups) > 0)
} }
func TestNoLoadBalancerIfNoPortExposed(t *testing.T) { func TestNoLoadBalancerIfNoPortExposed(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
test: test:
image: nginx image: nginx
@ -209,20 +212,21 @@ services:
} }
func TestServiceReplicas(t *testing.T) { func TestServiceReplicas(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
test: test:
image: nginx image: nginx
deploy: deploy:
replicas: 10 replicas: 10
`) `)
s := template.Resources["TestService"].(*ecs.Service) s := template.Resources["TestService"]
assert.Check(t, s != nil) 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) { func TestTaskSizeConvert(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
test: test:
image: nginx image: nginx
@ -239,7 +243,7 @@ services:
assert.Equal(t, def.Cpu, "512") assert.Equal(t, def.Cpu, "512")
assert.Equal(t, def.Memory, "2048") assert.Equal(t, def.Memory, "2048")
template = convertYaml(t, "test", ` template = convertYaml(t, `
services: services:
test: test:
image: nginx image: nginx
@ -257,7 +261,7 @@ services:
assert.Equal(t, def.Memory, "8192") assert.Equal(t, def.Memory, "8192")
} }
func TestTaskSizeConvertFailure(t *testing.T) { func TestTaskSizeConvertFailure(t *testing.T) {
model := loadConfig(t, "test", ` model := loadConfig(t, `
services: services:
test: test:
image: nginx image: nginx
@ -273,7 +277,7 @@ services:
} }
func TestLoadBalancerTypeNetwork(t *testing.T) { func TestLoadBalancerTypeNetwork(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
test: test:
image: nginx image: nginx
@ -281,13 +285,14 @@ services:
- 80:80 - 80:80
- 88:88 - 88:88
`) `)
lb := template.Resources["TestLoadBalancer"].(*elasticloadbalancingv2.LoadBalancer) lb := template.Resources["TestLoadBalancer"]
assert.Check(t, lb != nil) 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) { func TestServiceMapping(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
test: test:
image: "image" image: "image"
@ -326,7 +331,7 @@ func get(l []ecs.TaskDefinition_KeyValuePair, name string) string {
} }
func TestResourcesHaveProjectTagSet(t *testing.T) { func TestResourcesHaveProjectTagSet(t *testing.T) {
template := convertYaml(t, "test", ` template := convertYaml(t, `
services: services:
test: test:
image: nginx image: nginx
@ -353,7 +358,7 @@ func convertResultAsString(t *testing.T, project *types.Project) string {
backend := &ecsAPIService{} backend := &ecsAPIService{}
template, err := backend.convert(project) template, err := backend.convert(project)
assert.NilError(t, err) assert.NilError(t, err)
resultAsJSON, err := Marshall(template) resultAsJSON, err := marshall(template)
assert.NilError(t, err) assert.NilError(t, err)
return fmt.Sprintf("%s\n", string(resultAsJSON)) return fmt.Sprintf("%s\n", string(resultAsJSON))
} }
@ -368,15 +373,15 @@ func load(t *testing.T, paths ...string) *types.Project {
return project return project
} }
func convertYaml(t *testing.T, name string, yaml string) *cloudformation.Template { func convertYaml(t *testing.T, yaml string) *cloudformation.Template {
project := loadConfig(t, name, yaml) project := loadConfig(t, yaml)
backend := &ecsAPIService{} backend := &ecsAPIService{}
template, err := backend.convert(project) template, err := backend.convert(project)
assert.NilError(t, err) assert.NilError(t, err)
return template 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)) dict, err := loader.ParseYAML([]byte(yaml))
assert.NilError(t, err) assert.NilError(t, err)
model, err := loader.Load(types.ConfigDetails{ model, err := loader.Load(types.ConfigDetails{

View File

@ -21,7 +21,7 @@ import (
"strconv" "strconv"
) )
var NAMES = []string{ var names = []string{
"grey", "grey",
"red", "red",
"green", "green",
@ -32,14 +32,8 @@ var NAMES = []string{
"white", "white",
} }
var COLORS map[string]ColorFunc // colorFunc use ANSI codes to render colored text on console
type colorFunc func(s string) string
// ColorFunc use ANSI codes to render colored text on console
type ColorFunc func(s string) string
var Monochrome = func(s string) string {
return s
}
func ansiColor(code, s string) string { func ansiColor(code, s string) string {
return fmt.Sprintf("%s%s%s", ansi(code), s, ansi("0")) 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) return fmt.Sprintf("\033[%sm", code)
} }
func makeColorFunc(code string) ColorFunc { func makeColorFunc(code string) colorFunc {
return func(s string) string { return func(s string) string {
return ansiColor(code, s) return ansiColor(code, s)
} }
} }
var Rainbow = make(chan ColorFunc) var loop = make(chan colorFunc)
func init() { func init() {
COLORS = map[string]ColorFunc{} colors := map[string]colorFunc{}
for i, name := range NAMES { for i, name := range names {
COLORS[name] = makeColorFunc(strconv.Itoa(30 + i)) colors[name] = makeColorFunc(strconv.Itoa(30 + i))
COLORS["intense_"+name] = makeColorFunc(strconv.Itoa(30+i) + ";1") colors["intense_"+name] = makeColorFunc(strconv.Itoa(30+i) + ";1")
} }
go func() { go func() {
i := 0 i := 0
rainbow := []ColorFunc{ rainbow := []colorFunc{
COLORS["cyan"], colors["cyan"],
COLORS["yellow"], colors["yellow"],
COLORS["green"], colors["green"],
COLORS["magenta"], colors["magenta"],
COLORS["blue"], colors["blue"],
COLORS["intense_cyan"], colors["intense_cyan"],
COLORS["intense_yellow"], colors["intense_yellow"],
COLORS["intense_green"], colors["intense_green"],
COLORS["intense_magenta"], colors["intense_magenta"],
COLORS["intense_blue"], colors["intense_blue"],
} }
for { for {
Rainbow <- rainbow[i] loop <- rainbow[i]
i = (i + 1) % len(rainbow) i = (i + 1) % len(rainbow)
} }
}() }()

View File

@ -21,7 +21,7 @@ import (
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
) )
type FargateCompatibilityChecker struct { type fargateCompatibilityChecker struct {
compatibility.AllowList compatibility.AllowList
} }
@ -68,13 +68,13 @@ var compatibleComposeAttributes = []string{
"secrets.file", "secrets.file",
} }
func (c *FargateCompatibilityChecker) CheckImage(service *types.ServiceConfig) { func (c *fargateCompatibilityChecker) CheckImage(service *types.ServiceConfig) {
if service.Image == "" { if service.Image == "" {
c.Incompatible("service %s doesn't define a Docker image to run", service.Name) 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 { if p.Published == 0 {
p.Published = p.Target 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{} add := []string{}
for _, cap := range service.CapAdd { for _, cap := range service.CapAdd {
switch cap { switch cap {
@ -96,7 +96,7 @@ func (c *FargateCompatibilityChecker) CheckCapAdd(service *types.ServiceConfig)
service.CapAdd = add service.CapAdd = add
} }
func (c *FargateCompatibilityChecker) CheckLoggingDriver(config *types.LoggingConfig) { func (c *fargateCompatibilityChecker) CheckLoggingDriver(config *types.LoggingConfig) {
if config.Driver != "" && config.Driver != "awslogs" { if config.Driver != "" && config.Driver != "awslogs" {
c.Unsupported("services.logging.driver %s is not supported", config.Driver) c.Unsupported("services.logging.driver %s is not supported", config.Driver)
} }

View File

@ -103,7 +103,7 @@ func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID stri
p := credentials.SharedCredentialsProvider{Profile: profile} p := credentials.SharedCredentialsProvider{Profile: profile}
_, err := p.Retrieve() _, err := p.Retrieve()
if err == nil { 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" { if err.(awserr.Error).Code() == "SharedCredsLoad" && err.(awserr.Error).Message() == "failed to load shared credentials file" {

View File

@ -44,10 +44,7 @@ func convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, memReservation, err := toContainerReservation(service) _, memReservation := toContainerReservation(service)
if err != nil {
return nil, err
}
credential := getRepoCredentials(service) credential := getRepoCredentials(service)
// override resolve.conf search directive to also search <project>.local // override resolve.conf search directive to also search <project>.local
@ -141,7 +138,11 @@ func convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi
}, nil }, 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)) initContainerName := fmt.Sprintf("%s_Secrets_InitContainer", normalizeResourceName(service.Name))
secretsVolume := ecs.TaskDefinition_Volume{ secretsVolume := ecs.TaskDefinition_Volume{
Name: "secrets", Name: "secrets",
@ -166,7 +167,7 @@ func createSecretsSideCar(project *types.Project, service types.ServiceConfig, l
ValueFrom: secretConfig.Name, ValueFrom: secretConfig.Name,
}) })
var keys []string var keys []string
if ext, ok := secretConfig.Extensions[ExtensionKeys]; ok { if ext, ok := secretConfig.Extensions[extensionKeys]; ok {
if key, ok := ext.(string); ok { if key, ok := ext.(string); ok {
keys = append(keys, key) keys = append(keys, key)
} else { } else {
@ -275,7 +276,7 @@ func toSystemControls(sysctls types.Mapping) []ecs.TaskDefinition_SystemControl
return sys return sys
} }
const MiB = 1024 * 1024 const miB = 1024 * 1024
func toLimits(service types.ServiceConfig) (string, string, error) { func toLimits(service types.ServiceConfig) (string, string, error) {
// All possible cpu/mem values for Fargate // All possible cpu/mem values for Fargate
@ -315,9 +316,9 @@ func toLimits(service types.ServiceConfig) (string, string, error) {
for _, cpu := range cpus { for _, cpu := range cpus {
mem := cpuToMem[cpu] mem := cpuToMem[cpu]
if v <= cpu*MiB { if v <= cpu*miB {
for _, m := range mem { for _, m := range mem {
if limits.MemoryBytes <= m*MiB { if limits.MemoryBytes <= m*miB {
cpuLimit = strconv.FormatInt(cpu, 10) cpuLimit = strconv.FormatInt(cpu, 10)
memLimit = strconv.FormatInt(int64(m), 10) memLimit = strconv.FormatInt(int64(m), 10)
return cpuLimit, memLimit, nil 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") 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" cpuReservation := ".0"
memReservation := 0 memReservation := 0
if service.Deploy == nil { if service.Deploy == nil {
return cpuReservation, memReservation, nil return cpuReservation, memReservation
} }
reservations := service.Deploy.Resources.Reservations reservations := service.Deploy.Resources.Reservations
if reservations == nil { 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 { 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 { func getRepoCredentials(service types.ServiceConfig) *ecs.TaskDefinition_RepositoryCredentials {
// extract registry and namespace string from image name // extract registry and namespace string from image name
for key, value := range service.Extensions { for key, value := range service.Extensions {
if key == ExtensionPullCredentials { if key == extensionPullCredentials {
return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: value.(string)} return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: value.(string)}
} }
} }

View File

@ -32,7 +32,7 @@ func (b *ecsAPIService) Down(ctx context.Context, options *cli.ProjectOptions) e
if err != nil { if err != nil {
return err return err
} }
return b.WaitStackCompletion(ctx, name, StackDelete) return b.WaitStackCompletion(ctx, name, stackDelete)
} }
func (b *ecsAPIService) projectName(options *cli.ProjectOptions) (string, error) { func (b *ecsAPIService) projectName(options *cli.ProjectOptions) (string, error) {

View File

@ -17,12 +17,12 @@
package ecs package ecs
const ( const (
ECSTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" ecsTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
ECRReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" ecrReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
ActionGetSecretValue = "secretsmanager:GetSecretValue" actionGetSecretValue = "secretsmanager:GetSecretValue"
ActionGetParameters = "ssm:GetParameters" actionGetParameters = "ssm:GetParameters"
ActionDecrypt = "kms:Decrypt" actionDecrypt = "kms:Decrypt"
) )
var assumeRolePolicyDocument = PolicyDocument{ 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 // 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 { type PolicyDocument struct {
Version string `json:",omitempty"` Version string `json:",omitempty"`
Statement []PolicyStatement `json:",omitempty"` Statement []PolicyStatement `json:",omitempty"`
} }
// PolicyStatement describes an IAM policy statement
type PolicyStatement struct { type PolicyStatement struct {
Effect string `json:",omitempty"` Effect string `json:",omitempty"`
Action []string `json:",omitempty"` Action []string `json:",omitempty"`
@ -51,6 +53,7 @@ type PolicyStatement struct {
Resource []string `json:",omitempty"` Resource []string `json:",omitempty"`
} }
// PolicyPrincipal describes an IAM policy principal
type PolicyPrincipal struct { type PolicyPrincipal struct {
Service string `json:",omitempty"` Service string `json:",omitempty"`
} }

View File

@ -35,7 +35,7 @@ func (b *ecsAPIService) Ps(ctx context.Context, options *cli.ProjectOptions) ([]
if err != nil { if err != nil {
return nil, err return nil, err
} }
cluster := parameters[ParameterClusterName] cluster := parameters[parameterClusterName]
resources, err := b.SDK.ListStackResources(ctx, projectName) resources, err := b.SDK.ListStackResources(ctx, projectName)
if err != nil { if err != nil {
@ -61,7 +61,7 @@ func (b *ecsAPIService) Ps(ctx context.Context, options *cli.ProjectOptions) ([]
for i, state := range status { for i, state := range status {
ports := []string{} ports := []string{}
for _, lb := range state.LoadBalancers { for _, lb := range state.Publishers {
ports = append(ports, fmt.Sprintf( ports = append(ports, fmt.Sprintf(
"%s:%d->%d/%s", "%s:%d->%d/%s",
lb.URL, lb.URL,

View File

@ -40,7 +40,7 @@ func (b *ecsAPIService) Logs(ctx context.Context, options *cli.ProjectOptions, w
} }
consumer := logConsumer{ consumer := logConsumer{
colors: map[string]ColorFunc{}, colors: map[string]colorFunc{},
width: 0, width: 0,
writer: writer, 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) { func (l *logConsumer) Log(service, container, message string) {
cf, ok := l.colors[service] cf, ok := l.colors[service]
if !ok { if !ok {
cf = <-Rainbow cf = <-loop
l.colors[service] = cf l.colors[service] = cf
l.computeWidth() l.computeWidth()
} }
@ -81,7 +81,7 @@ func (l *logConsumer) computeWidth() {
} }
type logConsumer struct { type logConsumer struct {
colors map[string]ColorFunc colors map[string]colorFunc
width int width int
writer io.Writer writer io.Writer
} }

View File

@ -24,7 +24,7 @@ import (
"github.com/awslabs/goformation/v4/cloudformation" "github.com/awslabs/goformation/v4/cloudformation"
) )
func Marshall(template *cloudformation.Template) ([]byte, error) { func marshall(template *cloudformation.Template) ([]byte, error) {
raw, err := template.JSON() raw, err := template.JSON()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -22,11 +22,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/docker/api/compose" "github.com/docker/api/compose"
"github.com/docker/api/secrets" "github.com/docker/api/secrets"
"github.com/aws/aws-sdk-go/aws" "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"
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface" "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
@ -46,17 +47,16 @@ import (
) )
type sdk struct { type sdk struct {
sess *session.Session ECS ecsiface.ECSAPI
ECS ecsiface.ECSAPI EC2 ec2iface.EC2API
EC2 ec2iface.EC2API ELB elbv2iface.ELBV2API
ELB elbv2iface.ELBV2API CW cloudwatchlogsiface.CloudWatchLogsAPI
CW cloudwatchlogsiface.CloudWatchLogsAPI IAM iamiface.IAMAPI
IAM iamiface.IAMAPI CF cloudformationiface.CloudFormationAPI
CF cloudformationiface.CloudFormationAPI SM secretsmanageriface.SecretsManagerAPI
SM secretsmanageriface.SecretsManagerAPI
} }
func NewSDK(sess *session.Session) sdk { func newSDK(sess client.ConfigProvider) sdk {
return sdk{ return sdk{
ECS: ecs.New(sess), ECS: ecs.New(sess),
EC2: ec2.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 { func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template, parameters map[string]string) error {
logrus.Debug("Create CloudFormation stack") logrus.Debug("Create CloudFormation stack")
json, err := Marshall(template) json, err := marshall(template)
if err != nil { if err != nil {
return err 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) { func (s sdk) CreateChangeSet(ctx context.Context, name string, template *cf.Template, parameters map[string]string) (string, error) {
logrus.Debug("Create CloudFormation Changeset") logrus.Debug("Create CloudFormation Changeset")
json, err := Marshall(template) json, err := marshall(template)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -258,9 +258,9 @@ func (s sdk) UpdateStack(ctx context.Context, changeset string) error {
} }
const ( const (
StackCreate = iota stackCreate = iota
StackUpdate stackUpdate
StackDelete stackDelete
) )
func (s sdk) WaitStackComplete(ctx context.Context, name string, operation int) error { 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), StackName: aws.String(name),
} }
switch operation { switch operation {
case StackCreate: case stackCreate:
return s.CF.WaitUntilStackCreateCompleteWithContext(ctx, input) return s.CF.WaitUntilStackCreateCompleteWithContext(ctx, input)
case StackDelete: case stackDelete:
return s.CF.WaitUntilStackDeleteCompleteWithContext(ctx, input) return s.CF.WaitUntilStackDeleteCompleteWithContext(ctx, input)
default: default:
return fmt.Errorf("internal error: unexpected stack operation %d", operation) 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 return parameters, nil
} }
type StackResource struct { type stackResource struct {
LogicalID string LogicalID string
Type string Type string
ARN string ARN string
Status 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 // FIXME handle pagination
res, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{ res, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{
StackName: aws.String(name), StackName: aws.String(name),
@ -338,9 +338,9 @@ func (s sdk) ListStackResources(ctx context.Context, name string) ([]StackResour
return nil, err return nil, err
} }
resources := []StackResource{} resources := []stackResource{}
for _, r := range res.StackResourceSummaries { for _, r := range res.StackResourceSummaries {
resources = append(resources, StackResource{ resources = append(resources, stackResource{
LogicalID: aws.StringValue(r.LogicalResourceId), LogicalID: aws.StringValue(r.LogicalResourceId),
Type: aws.StringValue(r.ResourceType), Type: aws.StringValue(r.ResourceType),
ARN: aws.StringValue(r.PhysicalResourceId), ARN: aws.StringValue(r.PhysicalResourceId),
@ -495,11 +495,11 @@ func (s sdk) DescribeServices(ctx context.Context, cluster string, arns []string
return nil, err return nil, err
} }
status = append(status, compose.ServiceStatus{ status = append(status, compose.ServiceStatus{
ID: aws.StringValue(service.ServiceName), ID: aws.StringValue(service.ServiceName),
Name: name, Name: name,
Replicas: int(aws.Int64Value(service.RunningCount)), Replicas: int(aws.Int64Value(service.RunningCount)),
Desired: int(aws.Int64Value(service.DesiredCount)), Desired: int(aws.Int64Value(service.DesiredCount)),
LoadBalancers: loadBalancers, Publishers: loadBalancers,
}) })
} }
return status, nil return status, nil

View File

@ -24,11 +24,13 @@ import (
"path/filepath" "path/filepath"
) )
// Secret define sensitive data to be bound as file
type Secret struct { type Secret struct {
Name string Name string
Keys []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 { func CreateSecretFiles(secret Secret, path string) error {
value, ok := os.LookupEnv(secret.Name) value, ok := os.LookupEnv(secret.Name)
if !ok { if !ok {

View File

@ -28,21 +28,21 @@ const secretsFolder = "/run/secrets"
func main() { func main() {
if len(os.Args) != 2 { if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "usage: secrets <json encoded []Secret>") fmt.Fprint(os.Stderr, "usage: secrets <json encoded []Secret>")
os.Exit(1) os.Exit(1)
} }
var input []secrets.Secret var input []secrets.Secret
err := json.Unmarshal([]byte(os.Args[1]), &input) err := json.Unmarshal([]byte(os.Args[1]), &input)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprint(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
for _, secret := range input { for _, secret := range input {
err := secrets.CreateSecretFiles(secret, secretsFolder) err := secrets.CreateSecretFiles(secret, secretsFolder)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprint(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
} }

View File

@ -67,20 +67,20 @@ func (b *ecsAPIService) Up(ctx context.Context, options *cli.ProjectOptions) err
} }
parameters := map[string]string{ parameters := map[string]string{
ParameterClusterName: cluster, parameterClusterName: cluster,
ParameterVPCId: vpc, parameterVPCId: vpc,
ParameterSubnet1Id: subNets[0], parameterSubnet1Id: subNets[0],
ParameterSubnet2Id: subNets[1], parameterSubnet2Id: subNets[1],
ParameterLoadBalancerARN: lb, parameterLoadBalancerARN: lb,
} }
update, err := b.SDK.StackExists(ctx, project.Name) update, err := b.SDK.StackExists(ctx, project.Name)
if err != nil { if err != nil {
return err return err
} }
operation := StackCreate operation := stackCreate
if update { if update {
operation = StackUpdate operation = stackUpdate
changeset, err := b.SDK.CreateChangeSet(ctx, project.Name, template, parameters) changeset, err := b.SDK.CreateChangeSet(ctx, project.Name, template, parameters)
if err != nil { if err != nil {
return err 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) { func (b ecsAPIService) GetVPC(ctx context.Context, project *types.Project) (string, error) {
//check compose file for custom VPC selected //check compose file for custom VPC selected
if vpc, ok := project.Extensions[ExtensionVPC]; ok { if vpc, ok := project.Extensions[extensionVPC]; ok {
vpcID := vpc.(string) vpcID := vpc.(string)
ok, err := b.SDK.VpcExists(ctx, vpcID) ok, err := b.SDK.VpcExists(ctx, vpcID)
if err != nil { 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) { func (b ecsAPIService) GetLoadBalancer(ctx context.Context, project *types.Project) (string, error) {
//check compose file for custom VPC selected //check compose file for custom VPC selected
if ext, ok := project.Extensions[ExtensionLB]; ok { if ext, ok := project.Extensions[extensionLB]; ok {
lb := ext.(string) lb := ext.(string)
ok, err := b.SDK.LoadBalancerExists(ctx, lb) ok, err := b.SDK.LoadBalancerExists(ctx, lb)
if err != nil { 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) { func (b ecsAPIService) GetCluster(ctx context.Context, project *types.Project) (string, error) {
//check compose file for custom VPC selected //check compose file for custom VPC selected
if ext, ok := project.Extensions[ExtensionCluster]; ok { if ext, ok := project.Extensions[extensionCluster]; ok {
cluster := ext.(string) cluster := ext.(string)
ok, err := b.SDK.ClusterExists(ctx, cluster) ok, err := b.SDK.ClusterExists(ctx, cluster)
if err != nil { if err != nil {

View File

@ -28,7 +28,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "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{}{} knownEvents := map[string]struct{}{}
// progress writer // progress writer
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
@ -76,23 +76,23 @@ func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, op
switch status { switch status {
case "CREATE_COMPLETE": case "CREATE_COMPLETE":
if operation == StackCreate { if operation == stackCreate {
progressStatus = progress.Done progressStatus = progress.Done
} }
case "UPDATE_COMPLETE": case "UPDATE_COMPLETE":
if operation == StackUpdate { if operation == stackUpdate {
progressStatus = progress.Done progressStatus = progress.Done
} }
case "DELETE_COMPLETE": case "DELETE_COMPLETE":
if operation == StackDelete { if operation == stackDelete {
progressStatus = progress.Done progressStatus = progress.Done
} }
default: default:
if strings.HasSuffix(status, "_FAILED") { if strings.HasSuffix(status, "_FAILED") {
progressStatus = progress.Error progressStatus = progress.Error
if stackErr == nil { if stackErr == nil {
operation = StackDelete operation = stackDelete
stackErr = fmt.Errorf(reason) stackErr = fmt.Errorf(reason)
} }
} }

View File

@ -17,15 +17,15 @@
package ecs package ecs
const ( const (
ExtensionSecurityGroup = "x-aws-securitygroup" extensionSecurityGroup = "x-aws-securitygroup"
ExtensionVPC = "x-aws-vpc" extensionVPC = "x-aws-vpc"
ExtensionPullCredentials = "x-aws-pull_credentials" extensionPullCredentials = "x-aws-pull_credentials"
ExtensionLB = "x-aws-loadbalancer" extensionLB = "x-aws-loadbalancer"
ExtensionCluster = "x-aws-cluster" extensionCluster = "x-aws-cluster"
ExtensionKeys = "x-aws-keys" extensionKeys = "x-aws-keys"
ExtensionMinPercent = "x-aws-min_percent" extensionMinPercent = "x-aws-min_percent"
ExtensionMaxPercent = "x-aws-max_percent" extensionMaxPercent = "x-aws-max_percent"
ExtensionRetention = "x-aws-logs_retention" extensionRetention = "x-aws-logs_retention"
ExtensionRole = "x-aws-role" extensionRole = "x-aws-role"
ExtensionManagedPolicies = "x-aws-policies" extensionManagedPolicies = "x-aws-policies"
) )

View File

@ -29,6 +29,7 @@ type Service interface {
DeleteSecret(ctx context.Context, id string, recover bool) error DeleteSecret(ctx context.Context, id string, recover bool) error
} }
// Secret hold sensitive data
type Secret struct { type Secret struct {
ID string `json:"ID"` ID string `json:"ID"`
Name string `json:"Name"` Name string `json:"Name"`
@ -38,6 +39,7 @@ type Secret struct {
password string password string
} }
// NewSecret builds a secret
func NewSecret(name, username, password, description string) Secret { func NewSecret(name, username, password, description string) Secret {
return Secret{ return Secret{
Name: name, 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) { func (s Secret) ToJSON() (string, error) {
b, err := json.MarshalIndent(&s, "", "\t") b, err := json.MarshalIndent(&s, "", "\t")
if err != nil { if err != nil {
@ -55,6 +58,7 @@ func (s Secret) ToJSON() (string, error) {
return string(b), nil return string(b), nil
} }
// GetCredString marshall a Secret's sensitive data into JSON string
func (s Secret) GetCredString() (string, error) { func (s Secret) GetCredString() (string, error) {
creds := map[string]string{ creds := map[string]string{
"username": s.username, "username": s.username,