From eab994c3915a990d6a90f57c8bd912cca0ee6c60 Mon Sep 17 00:00:00 2001 From: Andres Villarroel Date: Fri, 13 Dec 2019 10:45:20 -0800 Subject: [PATCH] Add support for "update log driver options" (#10) * Upgrade AWS to v1.20.21 * Add alterLogConfigurationLogDriverOptions * Add alterLogConfigurations * Add new flags * Update documentation for new feature --- .gitignore | 1 + README.md | 15 + cmd/update-aws-ecs-service/flags.go | 23 + cmd/update-aws-ecs-service/flags_test.go | 42 ++ cmd/update-aws-ecs-service/main.go | 24 +- ecs-alter-service.go | 4 +- ecs.go | 55 +- ecs_logconfig.go | 159 +++++ ecs_logconfig_test.go | 720 +++++++++++++++++++++++ go.mod | 5 +- go.sum | 10 +- 11 files changed, 1010 insertions(+), 48 deletions(-) create mode 100644 cmd/update-aws-ecs-service/flags.go create mode 100644 cmd/update-aws-ecs-service/flags_test.go create mode 100644 ecs_logconfig.go create mode 100644 ecs_logconfig_test.go diff --git a/.gitignore b/.gitignore index 6cd6b8c..1f68a9f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ *.zip .idea/ +.DS_Store diff --git a/README.md b/README.md index d6f9101..0d8c2f4 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,10 @@ Usage of update-aws-ecs-service: container-name=envvar-name=envvar-value -container-image value container-name=image + -container-logopt value + container-name=logdriver=logopt=value + -container-logsecret value + container-name=logdriver=logsecret=valuefrom -container-secret value container-name=secret-name=secret-valuefrom -desired-count int @@ -107,6 +111,17 @@ update-aws-ecs-service \ -container-secret mycontainer=mysecretname= \ ``` +💡 Combined updates are possible. For example: "Update the application container image and adjust the `awslogs` log driver options for the sidecar container." + +``` +update-aws-ecs-service \ + -cluster example \ + -service service1-application1 \ + -container-image application=example.com/service1/application1:1a2b3c4 \ + -container-logopt sidecar=awslogs=awslogs-group=/com/example/service1/application1 \ + -container-logopt sidecar=awslogs=awslogs-stream-prefix=sidecar-1a2b3c4 +``` + ### update-aws-ecs-service compared to AWS CodePipeline - With `update-aws-ecs-service` there is no need to create individual AWS CodePipeline pipelines per service diff --git a/cmd/update-aws-ecs-service/flags.go b/cmd/update-aws-ecs-service/flags.go new file mode 100644 index 0000000..025da17 --- /dev/null +++ b/cmd/update-aws-ecs-service/flags.go @@ -0,0 +1,23 @@ +package main + +import "fmt" + +type mapMapMapFlag map[string]map[string]map[string]string + +func (kvs *mapMapMapFlag) String() string { + return fmt.Sprintf("%v", *kvs) +} + +func (kvs mapMapMapFlag) Set(value string) error { + key, value := keyEqValue(value) + valueKey, value := keyEqValue(value) + valueValueKey, value := keyEqValue(value) + if kvs[key] == nil { + kvs[key] = map[string]map[string]string{} + } + if kvs[key][valueKey] == nil { + kvs[key][valueKey] = map[string]string{} + } + kvs[key][valueKey][valueValueKey] = value + return nil +} diff --git a/cmd/update-aws-ecs-service/flags_test.go b/cmd/update-aws-ecs-service/flags_test.go new file mode 100644 index 0000000..8e4ba09 --- /dev/null +++ b/cmd/update-aws-ecs-service/flags_test.go @@ -0,0 +1,42 @@ +package main + +import ( + "reflect" + "testing" +) + +func TestMapMapMapFlag_Set(t *testing.T) { + actualStruct := mapMapMapFlag{} + if err := actualStruct.Set("container1=awslogs=region=us-west-2"); err != nil { + t.Fatal(err) + } + if err := actualStruct.Set("container1=awslogs=loggroup=group1"); err != nil { + t.Fatal(err) + } + if err := actualStruct.Set("container2=awslogs=="); err != nil { + t.Fatal(err) + } + if err := actualStruct.Set("container2=fluentd=option1=value1"); err != nil { + t.Fatal(err) + } + var expectedStruct mapMapMapFlag + expectedStruct = map[string]map[string]map[string]string{ + "container1": { + "awslogs": { + "region": "us-west-2", + "loggroup": "group1", + }, + }, + "container2": { + "awslogs": { + "": "", + }, + "fluentd": { + "option1": "value1", + }, + }, + } + if !reflect.DeepEqual(expectedStruct, actualStruct) { + t.Fatal() + } +} diff --git a/cmd/update-aws-ecs-service/main.go b/cmd/update-aws-ecs-service/main.go index dcab611..f8d62e3 100644 --- a/cmd/update-aws-ecs-service/main.go +++ b/cmd/update-aws-ecs-service/main.go @@ -64,10 +64,14 @@ func main() { var images mapFlag = map[string]string{} var envs mapMapFlag = map[string]map[string]string{} var secrets mapMapFlag = map[string]map[string]string{} + var logopts mapMapMapFlag = map[string]map[string]map[string]string{} + var logsecrets mapMapMapFlag = map[string]map[string]map[string]string{} flag.Var(&images, "container-image", "container-name=image") flag.Var(&envs, "container-envvar", "container-name=envvar-name=envvar-value") flag.Var(&secrets, "container-secret", "container-name=secret-name=secret-valuefrom") + flag.Var(&logopts, "container-logopt", "container-name=logdriver=logopt=value") + flag.Var(&logsecrets, "container-logsecret", "container-name=logdriver=logsecret=valuefrom") flag.Parse() sess := session.Must(session.NewSessionWithOptions(session.Options{ @@ -79,15 +83,17 @@ func main() { } esu := awsecs.ECSServiceUpdate{ - API: *ecs.New(sess), - Cluster: *cluster, - Service: *service, - Image: images, - Environment: envs, - Secrets: secrets, - DesiredCount: int64ptr(*desiredCount), - Taskdef: *taskdef, - BackOff: backoff.NewExponentialBackOff(), + API: *ecs.New(sess), + Cluster: *cluster, + Service: *service, + Image: images, + Environment: envs, + Secrets: secrets, + LogDriverOptions: logopts, + LogDriverSecrets: logsecrets, + DesiredCount: int64ptr(*desiredCount), + Taskdef: *taskdef, + BackOff: backoff.NewExponentialBackOff(), } if err := esu.Apply(); err != nil { diff --git a/ecs-alter-service.go b/ecs-alter-service.go index c5f3cf8..c06ecf9 100644 --- a/ecs-alter-service.go +++ b/ecs-alter-service.go @@ -19,8 +19,8 @@ var ( ErrFailedRollback = errors.New("failed rollback") ) -func alterServiceOrValidatedRollBack(api ecs.ECS, cluster, service string, imageMap map[string]string, envMaps map[string]map[string]string, secretMaps map[string]map[string]string, desiredCount *int64, taskdef string, bo backoff.BackOff) error { - oldsvc, alterSvcErr := alterServiceValidateDeployment(api, cluster, service, imageMap, envMaps, secretMaps, desiredCount, taskdef, bo) +func alterServiceOrValidatedRollBack(api ecs.ECS, cluster, service string, imageMap map[string]string, envMaps map[string]map[string]string, secretMaps map[string]map[string]string, logopts map[string]map[string]map[string]string, logsecrets map[string]map[string]map[string]string, desiredCount *int64, taskdef string, bo backoff.BackOff) error { + oldsvc, alterSvcErr := alterServiceValidateDeployment(api, cluster, service, imageMap, envMaps, secretMaps, logopts, logsecrets, desiredCount, taskdef, bo) if alterSvcErr != nil { operation := func() error { if oldsvc.ServiceName == nil { diff --git a/ecs.go b/ecs.go index 154e35b..2ffab0d 100644 --- a/ecs.go +++ b/ecs.go @@ -166,36 +166,27 @@ func alterSecret(copy ecs.ContainerDefinition, secretMap map[string]string) ecs. return copy } -func copyTaskDef(api ecs.ECS, taskdef string, imageMap map[string]string, envMaps map[string]map[string]string, secretMaps map[string]map[string]string) (string, error) { +func copyTaskDef(api ecs.ECS, taskdef string, imageMap map[string]string, envMaps map[string]map[string]string, secretMaps map[string]map[string]string, logopts map[string]map[string]map[string]string, logsecrets map[string]map[string]map[string]string) (string, error) { output, err := api.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{TaskDefinition: aws.String(taskdef)}) if err != nil { return "", err } asRegisterTaskDefinitionInput := copyTd(*output.TaskDefinition, output.Tags) - tdCopy := alterSecrets(alterEnvironments(alterImages(asRegisterTaskDefinitionInput, imageMap), envMaps), secretMaps) - - // if os.Getenv("DEBUG") == "YES" { - // out, _ := json.Marshal(asRegisterTaskDefinitionInput) - // fmt.Println(string(out)) - // out, _ = json.Marshal(tdCopy) - // fmt.Println(string(out)) - // panic("something") - // } + tdCopy := alterLogConfigurations(alterSecrets(alterEnvironments(alterImages(asRegisterTaskDefinitionInput, imageMap), envMaps), secretMaps), logopts, logsecrets) if reflect.DeepEqual(asRegisterTaskDefinitionInput, tdCopy) { return *output.TaskDefinition.TaskDefinitionArn, nil - } else { - tdNew, err := api.RegisterTaskDefinition(&tdCopy) - if err != nil { - return "", err - } - arn := tdNew.TaskDefinition.TaskDefinitionArn - return *arn, nil } + tdNew, err := api.RegisterTaskDefinition(&tdCopy) + if err != nil { + return "", err + } + arn := tdNew.TaskDefinition.TaskDefinitionArn + return *arn, nil } -func alterService(api ecs.ECS, cluster, service string, imageMap map[string]string, envMaps map[string]map[string]string, secretMaps map[string]map[string]string, desiredCount *int64, taskdef string) (ecs.Service, ecs.Service, error) { +func alterService(api ecs.ECS, cluster, service string, imageMap map[string]string, envMaps map[string]map[string]string, secretMaps map[string]map[string]string, logopts map[string]map[string]map[string]string, logsecrets map[string]map[string]map[string]string, desiredCount *int64, taskdef string) (ecs.Service, ecs.Service, error) { output, err := api.DescribeServices(&ecs.DescribeServicesInput{Cluster: aws.String(cluster), Services: []*string{aws.String(service)}}) if err != nil { return ecs.Service{}, ecs.Service{}, err @@ -205,7 +196,7 @@ func alterService(api ecs.ECS, cluster, service string, imageMap map[string]stri if taskdef != "" { srcTaskDef = &taskdef } - newTd, err := copyTaskDef(api, *srcTaskDef, imageMap, envMaps, secretMaps) + newTd, err := copyTaskDef(api, *srcTaskDef, imageMap, envMaps, secretMaps, logopts, logsecrets) if err != nil { return *svc, ecs.Service{}, err } @@ -273,8 +264,8 @@ func validateDeployment(api ecs.ECS, ecsService ecs.Service, bo backoff.BackOff) return errNoPrimaryDeployment } -func alterServiceValidateDeployment(api ecs.ECS, cluster, service string, imageMap map[string]string, envMaps map[string]map[string]string, secretMaps map[string]map[string]string, desiredCount *int64, taskdef string, bo backoff.BackOff) (ecs.Service, error) { - oldsvc, newsvc, err := alterService(api, cluster, service, imageMap, envMaps, secretMaps, desiredCount, taskdef) +func alterServiceValidateDeployment(api ecs.ECS, cluster, service string, imageMap map[string]string, envMaps map[string]map[string]string, secretMaps map[string]map[string]string, logopts map[string]map[string]map[string]string, logsecrets map[string]map[string]map[string]string, desiredCount *int64, taskdef string, bo backoff.BackOff) (ecs.Service, error) { + oldsvc, newsvc, err := alterService(api, cluster, service, imageMap, envMaps, secretMaps, logopts, logsecrets, desiredCount, taskdef) if err != nil { return oldsvc, err } @@ -292,18 +283,20 @@ func alterServiceValidateDeployment(api ecs.ECS, cluster, service string, imageM // ECSServiceUpdate encapsulates the attributes of an ECS service update type ECSServiceUpdate struct { - API ecs.ECS // ECS Api - Cluster string // Cluster which the service is deployed to - Service string // Name of the service - Image map[string]string // Map of container names and images - Environment map[string]map[string]string // Map of container names environment variable name and value - Secrets map[string]map[string]string // Map of container names environment variable name and valueFrom - DesiredCount *int64 // If nil the service desired count is not altered - BackOff backoff.BackOff // BackOff strategy to use when validating the update - Taskdef string // If non empty used as base task definition instead of the current task definition + API ecs.ECS // ECS Api + Cluster string // Cluster which the service is deployed to + Service string // Name of the service + Image map[string]string // Map of container names and images + Environment map[string]map[string]string // Map of container names environment variable name and value + Secrets map[string]map[string]string // Map of container names environment variable name and valueFrom + LogDriverOptions map[string]map[string]map[string]string // Map of container names log driver name log driver option and value + LogDriverSecrets map[string]map[string]map[string]string // Map of container names log driver name log driver secret and valueFrom + DesiredCount *int64 // If nil the service desired count is not altered + BackOff backoff.BackOff // BackOff strategy to use when validating the update + Taskdef string // If non empty used as base task definition instead of the current task definition } // Apply the ECS Service Update func (e *ECSServiceUpdate) Apply() error { - return alterServiceOrValidatedRollBack(e.API, e.Cluster, e.Service, e.Image, e.Environment, e.Secrets, e.DesiredCount, e.Taskdef, e.BackOff) + return alterServiceOrValidatedRollBack(e.API, e.Cluster, e.Service, e.Image, e.Environment, e.Secrets, e.LogDriverOptions, e.LogDriverSecrets, e.DesiredCount, e.Taskdef, e.BackOff) } diff --git a/ecs_logconfig.go b/ecs_logconfig.go new file mode 100644 index 0000000..8e4e20e --- /dev/null +++ b/ecs_logconfig.go @@ -0,0 +1,159 @@ +package awsecs + +import ( + "encoding/json" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" +) + +func alterLogConfigurationLogDriverOptions(copy ecs.LogConfiguration, overrides map[string]map[string]string) ecs.LogConfiguration { + knockOutDriver := "" + for logDriver, overrides := range overrides { + if copy.LogDriver != nil && *copy.LogDriver == logDriver { + for optionName, optionValue := range overrides { + if optionName == EnvKnockOutValue { + knockOutDriver = logDriver + } + if copy.Options == nil { + copy.Options = map[string]*string{} + } + copy.Options[optionName] = aws.String(optionValue) + if optionValue == EnvKnockOutValue || optionName == EnvKnockOutValue { + delete(copy.Options, optionName) + } + } + } + } + if knockOutDriver != "" { + delete(overrides, knockOutDriver) + copy.LogDriver = nil + } + if copy.LogDriver == nil && len(overrides) == 1 { + for logDriver, options := range overrides { + copy.LogDriver = aws.String(logDriver) + for optionName, optionValue := range options { + if copy.Options == nil { + copy.Options = map[string]*string{} + } + copy.Options[optionName] = aws.String(optionValue) + if optionValue == EnvKnockOutValue || optionName == EnvKnockOutValue { + delete(copy.Options, optionName) + } + } + } + } + return copy +} + +func alterLogConfigurationLogDriverSecrets(copy ecs.LogConfiguration, overrides map[string]map[string]string) ecs.LogConfiguration { + knockOutDriver := "" + for logDriver, overrides := range overrides { + if copy.LogDriver != nil && *copy.LogDriver == logDriver { + for optionName, optionValue := range overrides { + if optionName == EnvKnockOutValue { + knockOutDriver = logDriver + } + thisOptionChanged := false + for _, secretOption := range copy.SecretOptions { + if secretOption != nil && secretOption.Name != nil && *secretOption.Name == optionName { + thisOptionChanged = true + secretOption.ValueFrom = aws.String(optionValue) + } + } + if !thisOptionChanged { + copy.SecretOptions = append(copy.SecretOptions, &ecs.Secret{Name: aws.String(optionName), ValueFrom: aws.String(optionValue)}) + } + var filteredSecretOptions []*ecs.Secret + for _, secretOption := range copy.SecretOptions { + if secretOption != nil && secretOption.Name != nil && *secretOption.Name == optionName { + if optionValue != EnvKnockOutValue && optionName != EnvKnockOutValue { + filteredSecretOptions = append(filteredSecretOptions, secretOption) + } + } else { + filteredSecretOptions = append(filteredSecretOptions, secretOption) + } + } + copy.SecretOptions = filteredSecretOptions + } + } + } + if knockOutDriver != "" { + delete(overrides, knockOutDriver) + copy.LogDriver = nil + } + if copy.LogDriver == nil && len(overrides) == 1 { + for logDriver, options := range overrides { + copy.LogDriver = aws.String(logDriver) + for optionName, optionValue := range options { + thisOptionChanged := false + for _, secretOption := range copy.SecretOptions { + if secretOption != nil && secretOption.Name != nil && *secretOption.Name == optionName { + thisOptionChanged = true + } + } + if !thisOptionChanged { + copy.SecretOptions = append(copy.SecretOptions, &ecs.Secret{Name: aws.String(optionName), ValueFrom: aws.String(optionValue)}) + } + var filteredSecretOptions []*ecs.Secret + for _, secretOption := range copy.SecretOptions { + if secretOption != nil && secretOption.Name != nil && *secretOption.Name == optionName { + if optionValue != EnvKnockOutValue && optionName != EnvKnockOutValue { + filteredSecretOptions = append(filteredSecretOptions, secretOption) + } + } else { + filteredSecretOptions = append(filteredSecretOptions, secretOption) + } + } + copy.SecretOptions = filteredSecretOptions + } + } + } + return copy +} + +func alterLogConfigurations(copy ecs.RegisterTaskDefinitionInput, containersOptions map[string]map[string]map[string]string, containersSecrets map[string]map[string]map[string]string) ecs.RegisterTaskDefinitionInput { + obj, err := json.Marshal(copy) + if err != nil { + panic(err) + } + copyClone := ecs.RegisterTaskDefinitionInput{} + err = json.Unmarshal(obj, ©Clone) + if err != nil { + panic(err) + } + for _, containerDefinition := range copyClone.ContainerDefinitions { + for containerName, containerOptions := range containersOptions { + if containerDefinition.Name != nil && *containerDefinition.Name == containerName { + var logConfiguration *ecs.LogConfiguration + if containerDefinition.LogConfiguration != nil { + logConfiguration = containerDefinition.LogConfiguration + } else { + logConfiguration = &ecs.LogConfiguration{} + } + *logConfiguration = alterLogConfigurationLogDriverOptions(*logConfiguration, containerOptions) + if logConfiguration.LogDriver == nil || (logConfiguration.LogDriver != nil && *logConfiguration.LogDriver == "") { + containerDefinition.LogConfiguration = nil + } else { + containerDefinition.LogConfiguration = logConfiguration + } + } + } + for containerName, containerOptions := range containersSecrets { + if containerDefinition.Name != nil && *containerDefinition.Name == containerName { + var logConfiguration *ecs.LogConfiguration + if containerDefinition.LogConfiguration != nil { + logConfiguration = containerDefinition.LogConfiguration + } else { + logConfiguration = &ecs.LogConfiguration{} + } + *logConfiguration = alterLogConfigurationLogDriverSecrets(*logConfiguration, containerOptions) + if logConfiguration.LogDriver == nil || (logConfiguration.LogDriver != nil && *logConfiguration.LogDriver == "") { + containerDefinition.LogConfiguration = nil + } else { + containerDefinition.LogConfiguration = logConfiguration + } + } + } + } + return copyClone +} diff --git a/ecs_logconfig_test.go b/ecs_logconfig_test.go new file mode 100644 index 0000000..1ea97c2 --- /dev/null +++ b/ecs_logconfig_test.go @@ -0,0 +1,720 @@ +package awsecs + +import ( + "encoding/json" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/sergi/go-diff/diffmatchpatch" + "reflect" + "testing" +) + +func TestAlterLogConfigurationLogDriverOptions(t *testing.T) { + optionToDelete := "myDeletedOption" + logConfig := ecs.LogConfiguration{ + LogDriver: aws.String("mydriver"), + Options: map[string]*string{ + "myUntouchedOption": aws.String("myUntouchedOptionOriginalValue"), + "myTouchedOption": aws.String("myTouchedOptionOriginalValue"), + optionToDelete: aws.String("myDeletedOptionOriginalValue"), + }, + SecretOptions: []*ecs.Secret{}, + } + logConfig = alterLogConfigurationLogDriverOptions(logConfig, map[string]map[string]string{ + "mydriver": { + "myTouchedOption": "myTouchedOptionNewValue", + optionToDelete: EnvKnockOutValue, + }, + }) + if *logConfig.LogDriver != "mydriver" { + t.Fatal() + } + if *logConfig.Options["myUntouchedOption"] != "myUntouchedOptionOriginalValue" { + t.Fatal() + } + if *logConfig.Options["myTouchedOption"] != "myTouchedOptionNewValue" { + t.Fatal() + } + if len(logConfig.Options) != 2 { + t.Fatal() + } +} + +func TestDeleteLogConfigurationLogDriverOptions(t *testing.T) { + optionToDelete1 := "myDeletedOption1" + optionToDelete2 := "myDeletedOption2" + logConfig := ecs.LogConfiguration{ + LogDriver: aws.String("olddriver"), + Options: map[string]*string{ + "myUntouchedOption": aws.String("myUntouchedOptionOriginalValue"), + optionToDelete1: aws.String("myDeletedOptionOriginalValue1"), + optionToDelete2: aws.String("myDeletedOptionOriginalValue2"), + }, + SecretOptions: []*ecs.Secret{}, + } + logConfig = alterLogConfigurationLogDriverOptions(logConfig, map[string]map[string]string{ + "olddriver": { + "": "ignored", + optionToDelete1: "", + }, + "newdriver": { + "myOptionToAdd": "myOptionToAddValue", + optionToDelete2: "", + }, + }) + if *logConfig.LogDriver != "newdriver" { + t.Fatal() + } + if *logConfig.Options["myUntouchedOption"] != "myUntouchedOptionOriginalValue" { + t.Fatal() + } + if *logConfig.Options["myOptionToAdd"] != "myOptionToAddValue" { + t.Fatal() + } + if len(logConfig.Options) != 2 { + t.Fatal() + } +} + +func TestAlterLogConfigurationLogDriverSecrets(t *testing.T) { + optionToDelete := "myDeletedOption" + logConfig := ecs.LogConfiguration{ + LogDriver: aws.String("mydriver"), + Options: map[string]*string{}, + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("myUntouchedSecretOption"), + ValueFrom: aws.String("myUntouchedSecretOptionOriginalValue"), + }, + { + Name: aws.String("myTouchedSecretOption"), + ValueFrom: aws.String("myTouchedSecretOptionOriginalValue"), + }, + { + Name: aws.String(optionToDelete), + ValueFrom: aws.String("myDeletedSecretOption"), + }, + }, + } + logConfig = alterLogConfigurationLogDriverSecrets(logConfig, map[string]map[string]string{ + "mydriver": { + "myTouchedSecretOption": "myTouchedSecretOptionNewValue", + "myAddedSecretOption": "myAddedSecretOptionNewValue", + optionToDelete: EnvKnockOutValue, + }, + }) + if *logConfig.LogDriver != "mydriver" { + t.Fatal() + } + for _, secretOption := range logConfig.SecretOptions { + if *secretOption.Name == "myUntouchedSecretOption" { + if *secretOption.ValueFrom != "myUntouchedSecretOptionOriginalValue" { + t.Fatal() + } + } + if *secretOption.Name == "myTouchedSecretOption" { + if *secretOption.ValueFrom != "myTouchedSecretOptionNewValue" { + t.Fatal() + } + } + if *secretOption.Name == optionToDelete { + t.Fatal() + } + if *secretOption.Name == "myAddedSecretOption" { + if *secretOption.ValueFrom != "myAddedSecretOptionNewValue" { + t.Fatal() + } + } + } + if len(logConfig.SecretOptions) != 3 { + t.Fatal() + } +} + +func TestDeleteLogConfigurationLogDriverSecrets(t *testing.T) { + optionToDelete1 := "myDeletedOption1" + optionToDelete2 := "myDeletedOption2" + logConfig := ecs.LogConfiguration{ + LogDriver: aws.String("myolddriver"), + Options: map[string]*string{}, + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("myUntouchedSecretOption"), + ValueFrom: aws.String("myUntouchedSecretOptionOriginalValue"), + }, + { + Name: aws.String(optionToDelete1), + ValueFrom: aws.String("myDeletedSecretOption1"), + }, + { + Name: aws.String(optionToDelete2), + ValueFrom: aws.String("myDeletedSecretOption2"), + }, + }, + } + logConfig = alterLogConfigurationLogDriverSecrets(logConfig, map[string]map[string]string{ + "myolddriver": { + "": "ignore", + optionToDelete1: EnvKnockOutValue, + }, + "mynewdriver": { + "myAddedSecretOption": "myAddedSecretOptionNewValue", + optionToDelete2: EnvKnockOutValue, + }, + }) + if *logConfig.LogDriver != "mynewdriver" { + t.Fatal() + } + for _, secretOption := range logConfig.SecretOptions { + if *secretOption.Name == "myUntouchedSecretOption" { + if *secretOption.ValueFrom != "myUntouchedSecretOptionOriginalValue" { + t.Fatal() + } + } + if *secretOption.Name == optionToDelete1 { + t.Fatal() + } + if *secretOption.Name == optionToDelete2 { + t.Fatal() + } + if *secretOption.Name == "myAddedSecretOption" { + if *secretOption.ValueFrom != "myAddedSecretOptionNewValue" { + t.Fatal() + } + } + } + if len(logConfig.SecretOptions) != 2 { + t.Fatal() + } +} + +func TestAlterLogConfigurationsExistingOption(t *testing.T) { + testObject := ecs.RegisterTaskDefinitionInput{ + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + Name: aws.String("container1"), + }, + { + Name: aws.String("container2"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver2"), + Options: map[string]*string{ + "driver2Option1": aws.String("driver2Value1"), + "driver2Option2": aws.String("driver2Value2"), + }, + }, + }, + { + Name: aws.String("container3"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver3"), + Options: map[string]*string{ + "driver3KeepOption1": aws.String("driver3Value1"), + "driver3KeepOption2": aws.String("driver3Value2"), + "driver3RemoveOption1": aws.String("driver3Value3"), + }, + }, + }, + { + Name: aws.String("container4"), + }, + { + Name: aws.String("container5"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver5"), + Options: map[string]*string{ + "driver5Option1": aws.String("driver5Value1"), + "driver5Option2": aws.String("driver5Value2"), + }, + }, + }, + { + Name: aws.String("container6"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver6"), + Options: map[string]*string{ + "driver6Option1": aws.String("driver6Value1"), + "driver6Option2": aws.String("driver6Value2"), + "driver6UpdateOption1": aws.String("driver6OldValue1"), + }, + }, + }, + { + Name: aws.String("container7"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("oldDriver7"), + Options: map[string]*string{ + "driver7KeepOption1": aws.String("driver7Value1"), + "driver7KeepOption2": aws.String("driver7Value2"), + }, + }, + }, + { + Name: aws.String("container8"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver8"), + Options: map[string]*string{ + "driver8Option1": aws.String("driver8Value1"), + "driver8Option2": aws.String("driver8Value2"), + }, + }, + }, + { + Name: aws.String("container9"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver9"), + Options: map[string]*string{ + "driver9Option1": aws.String("driver9Value1"), + "driver9Option2": aws.String("driver9Value2"), + }, + }, + }, + }, + } + actualResult := alterLogConfigurations(testObject, map[string]map[string]map[string]string{ + "container3": { + "driver3": { + "driver3RemoveOption1": "", + }, + }, + "container4": { + "driver4": { + "driver4Option1": "driver4Value1", + "driver4Option2": "driver4Value2", + }, + }, + "container5": { + "driver5": { + "driver5Option3": "driver5Value3", + }, + }, + "container6": { + "driver6": { + "driver6UpdateOption1": "driver6NewValue1", + }, + }, + "container7": { + "oldDriver7": { + "": "", + }, + "newDriver7": { + "newDriver7NewOption1": "newDriver7Value1", + }, + }, + "container8": { + "newDriver8": { + "newDriver8Option1": "newDriver8Value1", + }, + }, + "container9": { + "driver9": { + "": "", + }, + }, + }, nil) + expectedResult := []*ecs.ContainerDefinition{ + { + Name: aws.String("container1"), + }, + { + Name: aws.String("container2"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver2"), + Options: map[string]*string{ + "driver2Option1": aws.String("driver2Value1"), + "driver2Option2": aws.String("driver2Value2"), + }, + }, + }, + { + Name: aws.String("container3"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver3"), + Options: map[string]*string{ + "driver3KeepOption1": aws.String("driver3Value1"), + "driver3KeepOption2": aws.String("driver3Value2"), + }, + }, + }, + { + Name: aws.String("container4"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver4"), + Options: map[string]*string{ + "driver4Option1": aws.String("driver4Value1"), + "driver4Option2": aws.String("driver4Value2"), + }, + }, + }, + { + Name: aws.String("container5"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver5"), + Options: map[string]*string{ + "driver5Option1": aws.String("driver5Value1"), + "driver5Option2": aws.String("driver5Value2"), + "driver5Option3": aws.String("driver5Value3"), + }, + }, + }, + { + Name: aws.String("container6"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver6"), + Options: map[string]*string{ + "driver6Option1": aws.String("driver6Value1"), + "driver6Option2": aws.String("driver6Value2"), + "driver6UpdateOption1": aws.String("driver6NewValue1"), + }, + }, + }, + { + Name: aws.String("container7"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("newDriver7"), + Options: map[string]*string{ + "driver7KeepOption1": aws.String("driver7Value1"), + "driver7KeepOption2": aws.String("driver7Value2"), + "newDriver7NewOption1": aws.String("newDriver7Value1"), + }, + }, + }, + { + Name: aws.String("container8"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver8"), + Options: map[string]*string{ + "driver8Option1": aws.String("driver8Value1"), + "driver8Option2": aws.String("driver8Value2"), + }, + }, + }, + { + Name: aws.String("container9"), + }, + } + if !reflect.DeepEqual(actualResult.ContainerDefinitions, expectedResult) { + actualObject, _ := json.MarshalIndent(actualResult.ContainerDefinitions, "", " ") + actualJSON := string(actualObject[:]) + expectedObject, _ := json.MarshalIndent(expectedResult, "", " ") + expectedJSON := string(expectedObject[:]) + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(expectedJSON, actualJSON, true) + t.Fatal(dmp.DiffPrettyText(diffs)) + } +} + +func TestAlterLogConfigurationsExistingSecret(t *testing.T) { + testObject := ecs.RegisterTaskDefinitionInput{ + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + Name: aws.String("container1"), + }, + { + Name: aws.String("container2"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver2"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver2Option1"), + ValueFrom: aws.String("driver2Value1"), + }, + { + Name: aws.String("driver2Option2"), + ValueFrom: aws.String("driver2Value2"), + }, + }, + }, + }, + { + Name: aws.String("container3"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver3"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver3KeepOption1"), + ValueFrom: aws.String("driver3Value1"), + }, + { + Name: aws.String("driver3KeepOption2"), + ValueFrom: aws.String("driver3Value2"), + }, + { + Name: aws.String("driver3RemoveOption1"), + ValueFrom: aws.String("driver3Value3"), + }, + }, + }, + }, + { + Name: aws.String("container4"), + }, + { + Name: aws.String("container5"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver5"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver5Option1"), + ValueFrom: aws.String("driver5Value1"), + }, + { + Name: aws.String("driver5Option2"), + ValueFrom: aws.String("driver5Value2"), + }, + }, + }, + }, + { + Name: aws.String("container6"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver6"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver6Option1"), + ValueFrom: aws.String("driver6Value1"), + }, + { + Name: aws.String("driver6Option2"), + ValueFrom: aws.String("driver6Value2"), + }, + { + Name: aws.String("driver6UpdateOption1"), + ValueFrom: aws.String("driver6OldValue1"), + }, + }, + }, + }, + { + Name: aws.String("container7"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("oldDriver7"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver7KeepOption1"), + ValueFrom: aws.String("driver7Value1"), + }, + { + Name: aws.String("driver7KeepOption2"), + ValueFrom: aws.String("driver7Value2"), + }, + }, + }, + }, + { + Name: aws.String("container8"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver8"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver8Option1"), + ValueFrom: aws.String("driver8Value1"), + }, + { + Name: aws.String("driver8Option2"), + ValueFrom: aws.String("driver8Value2"), + }, + }, + }, + }, + { + Name: aws.String("container9"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver9"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver9Option1"), + ValueFrom: aws.String("driver9Value1"), + }, + { + Name: aws.String("driver9Option2"), + ValueFrom: aws.String("driver9Value2"), + }, + }, + }, + }, + }, + } + actualResult := alterLogConfigurations(testObject, nil, map[string]map[string]map[string]string{ + "container3": { + "driver3": { + "driver3RemoveOption1": "", + }, + }, + "container4": { + "driver4": { + "driver4Option1": "driver4Value1", + "driver4Option2": "driver4Value2", + }, + }, + "container5": { + "driver5": { + "driver5Option3": "driver5Value3", + }, + }, + "container6": { + "driver6": { + "driver6UpdateOption1": "driver6NewValue1", + }, + }, + "container7": { + "oldDriver7": { + "": "", + }, + "newDriver7": { + "newDriver7NewOption1": "newDriver7Value1", + }, + }, + "container8": { + "newDriver8": { + "newDriver8Option1": "newDriver8Value1", + }, + }, + "container9": { + "driver9": { + "": "", + }, + }, + }) + expectedResult := []*ecs.ContainerDefinition{ + { + Name: aws.String("container1"), + }, + { + Name: aws.String("container2"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver2"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver2Option1"), + ValueFrom: aws.String("driver2Value1"), + }, + { + Name: aws.String("driver2Option2"), + ValueFrom: aws.String("driver2Value2"), + }, + }, + }, + }, + { + Name: aws.String("container3"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver3"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver3KeepOption1"), + ValueFrom: aws.String("driver3Value1"), + }, + { + Name: aws.String("driver3KeepOption2"), + ValueFrom: aws.String("driver3Value2"), + }, + }, + }, + }, + { + Name: aws.String("container4"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver4"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver4Option1"), + ValueFrom: aws.String("driver4Value1"), + }, + { + Name: aws.String("driver4Option2"), + ValueFrom: aws.String("driver4Value2"), + }, + }, + }, + }, + { + Name: aws.String("container5"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver5"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver5Option1"), + ValueFrom: aws.String("driver5Value1"), + }, + { + Name: aws.String("driver5Option2"), + ValueFrom: aws.String("driver5Value2"), + }, + { + Name: aws.String("driver5Option3"), + ValueFrom: aws.String("driver5Value3"), + }, + }, + }, + }, + { + Name: aws.String("container6"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver6"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver6Option1"), + ValueFrom: aws.String("driver6Value1"), + }, + { + Name: aws.String("driver6Option2"), + ValueFrom: aws.String("driver6Value2"), + }, + { + Name: aws.String("driver6UpdateOption1"), + ValueFrom: aws.String("driver6NewValue1"), + }, + }, + }, + }, + { + Name: aws.String("container7"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("newDriver7"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver7KeepOption1"), + ValueFrom: aws.String("driver7Value1"), + }, + { + Name: aws.String("driver7KeepOption2"), + ValueFrom: aws.String("driver7Value2"), + }, + { + Name: aws.String("newDriver7NewOption1"), + ValueFrom: aws.String("newDriver7Value1"), + }, + }, + }, + }, + { + Name: aws.String("container8"), + LogConfiguration: &ecs.LogConfiguration{ + LogDriver: aws.String("driver8"), + SecretOptions: []*ecs.Secret{ + { + Name: aws.String("driver8Option1"), + ValueFrom: aws.String("driver8Value1"), + }, + { + Name: aws.String("driver8Option2"), + ValueFrom: aws.String("driver8Value2"), + }, + }, + }, + }, + { + Name: aws.String("container9"), + }, + } + if !reflect.DeepEqual(actualResult.ContainerDefinitions, expectedResult) { + actualObject, _ := json.MarshalIndent(actualResult.ContainerDefinitions, "", " ") + actualJSON := string(actualObject[:]) + expectedObject, _ := json.MarshalIndent(expectedResult, "", " ") + expectedJSON := string(expectedObject[:]) + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(expectedJSON, actualJSON, true) + t.Fatal(dmp.DiffPrettyText(diffs)) + } +} diff --git a/go.mod b/go.mod index 7d5d7f4..56be22b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Autodesk/go-awsecs go 1.13 require ( - github.com/aws/aws-sdk-go v1.19.49 - github.com/cenkalti/backoff/v3 v3.0.0 + github.com/aws/aws-sdk-go v1.20.21 + github.com/cenkalti/backoff/v3 v3.1.1 + github.com/sergi/go-diff v1.0.0 ) diff --git a/go.sum b/go.sum index 737e31d..ea0a803 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ -github.com/aws/aws-sdk-go v1.19.49 h1:GUlenK625g5iKrIiRcqRS/CvPMLc8kZRtMxXuXBhFx4= -github.com/aws/aws-sdk-go v1.19.49/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= -github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/aws/aws-sdk-go v1.20.21 h1:22vHWL9rur+SRTYPHAXlxJMFIA9OSYsYDIAHFDhQ7Z0= +github.com/aws/aws-sdk-go v1.20.21/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/cenkalti/backoff/v3 v3.1.1 h1:UBHElAnr3ODEbpqPzX8g5sBcASjoLFtt3L/xwJ01L6E= +github.com/cenkalti/backoff/v3 v3.1.1/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=