From 1444d68685f1989d323f68db4e7d224dbc9fb6fe Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 28 Sep 2020 13:36:12 +0200 Subject: [PATCH] Introduce a sidecard to setup resolv.conf search domain Signed-off-by: Nicolas De Loof --- ecs/cloudformation.go | 2 +- ecs/cloudformation_test.go | 24 +++++++++--- ecs/convert.go | 24 ++++++------ ecs/resolv/main/main.go | 1 + ecs/resolv/resolv.go | 3 +- ecs/resolv/resolv_test.go | 4 +- .../simple-cloudformation-conversion.golden | 38 ++++++++++++------- 7 files changed, 61 insertions(+), 35 deletions(-) diff --git a/ecs/cloudformation.go b/ecs/cloudformation.go index 0b9cb93e8..43d72dc40 100644 --- a/ecs/cloudformation.go +++ b/ecs/cloudformation.go @@ -93,7 +93,7 @@ func (b *ecsAPIService) convert(project *types.Project, resources awsResources) taskExecutionRole := b.createTaskExecutionRole(project, service, template) taskRole := b.createTaskRole(service, template) - definition, err := b.createTaskExecution(project, service) + definition, err := b.createTaskDefinition(project, service) if err != nil { return nil, err } diff --git a/ecs/cloudformation_test.go b/ecs/cloudformation_test.go index 87491a419..1929ba8cf 100644 --- a/ecs/cloudformation_test.go +++ b/ecs/cloudformation_test.go @@ -56,8 +56,12 @@ services: x-aws-logs_retention: 10 `) def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition) - logging := def.ContainerDefinitions[0].LogConfiguration - assert.Equal(t, logging.Options["awslogs-datetime-pattern"], "FOO") + logging := getMainContainer(def, t).LogConfiguration + if logging != nil { + assert.Equal(t, logging.Options["awslogs-datetime-pattern"], "FOO") + } else { + t.Fatal("Logging not configured") + } logGroup := template.Resources["LogGroup"].(*logs.LogGroup) assert.Equal(t, logGroup.RetentionInDays, 10) @@ -72,7 +76,7 @@ services: - testdata/input/envfile `) def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition) - env := def.ContainerDefinitions[0].Environment + env := getMainContainer(def, t).Environment var found bool for _, pair := range env { if pair.Name == "FOO" { @@ -94,7 +98,7 @@ services: - "FOO=ZOT" `) def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition) - env := def.ContainerDefinitions[0].Environment + env := getMainContainer(def, t).Environment var found bool for _, pair := range env { if pair.Name == "FOO" { @@ -358,7 +362,7 @@ services: working_dir: "working_dir" `) def := template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition) - container := def.ContainerDefinitions[0] + container := getMainContainer(def, t) assert.Equal(t, container.Image, "image") assert.Equal(t, container.Command[0], "command") assert.Equal(t, container.EntryPoint[0], "entrypoint") @@ -446,3 +450,13 @@ func loadConfig(t *testing.T, yaml string) *types.Project { assert.NilError(t, err) return model } + +func getMainContainer(def *ecs.TaskDefinition, t *testing.T) ecs.TaskDefinition_ContainerDefinition { + for _, c := range def.ContainerDefinitions { + if c.Essential { + return c + } + } + t.Fail() + return def.ContainerDefinitions[0] +} diff --git a/ecs/convert.go b/ecs/convert.go index 3b0636fb7..8537a0a15 100644 --- a/ecs/convert.go +++ b/ecs/convert.go @@ -26,7 +26,7 @@ import ( "strings" "time" - "github.com/aws/aws-sdk-go/aws" + "github.com/docker/compose-cli/ecs/secrets" ecsapi "github.com/aws/aws-sdk-go/service/ecs" "github.com/awslabs/goformation/v4/cloudformation" @@ -34,13 +34,12 @@ import ( "github.com/compose-spec/compose-go/types" "github.com/docker/cli/opts" "github.com/joho/godotenv" - - "github.com/docker/compose-cli/ecs/secrets" ) const secretsInitContainerImage = "docker/ecs-secrets-sidecar" +const searchDomainInitContainerImage = "docker/ecs-searchdomain-sidecar" -func (b *ecsAPIService) createTaskExecution(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) { +func (b *ecsAPIService) createTaskDefinition(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) { cpu, mem, err := toLimits(service) if err != nil { return nil, err @@ -48,15 +47,6 @@ func (b *ecsAPIService) createTaskExecution(project *types.Project, service type _, memReservation := toContainerReservation(service) credential := getRepoCredentials(service) - // override resolve.conf search directive to also search .local - // TODO remove once ECS support hostname-only service discovery - service.Environment["LOCALDOMAIN"] = aws.String( - cloudformation.Join("", []string{ - cloudformation.Ref("AWS::Region"), - ".compute.internal", - fmt.Sprintf(" %s.local", project.Name), - })) - logConfiguration := getLogConfiguration(service, project) var ( @@ -74,6 +64,14 @@ func (b *ecsAPIService) createTaskExecution(project *types.Project, service type mounts = append(mounts, secretsMount) } + initContainers = append(initContainers, ecs.TaskDefinition_ContainerDefinition{ + Name: fmt.Sprintf("%s_ResolvConf_InitContainer", normalizeResourceName(service.Name)), + Image: searchDomainInitContainerImage, + Essential: false, + Command: []string{b.Region + ".compute.internal", project.Name + ".local"}, + LogConfiguration: logConfiguration, + }) + var dependencies []ecs.TaskDefinition_ContainerDependency for _, c := range initContainers { dependencies = append(dependencies, ecs.TaskDefinition_ContainerDependency{ diff --git a/ecs/resolv/main/main.go b/ecs/resolv/main/main.go index e8ac924af..56663c925 100644 --- a/ecs/resolv/main/main.go +++ b/ecs/resolv/main/main.go @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ + package main import ( diff --git a/ecs/resolv/resolv.go b/ecs/resolv/resolv.go index d3dc04116..f516405a0 100644 --- a/ecs/resolv/resolv.go +++ b/ecs/resolv/resolv.go @@ -21,6 +21,7 @@ import ( "strings" ) +// SetSearchDomains appends a `search` directive to resolv.conf file for domains func SetSearchDomains(file string, domains ...string) error { search := strings.Join(domains, " ") @@ -28,7 +29,7 @@ func SetSearchDomains(file string, domains ...string) error { if err != nil { return err } - defer f.Close() + defer f.Close() //nolint:errcheck _, err = f.WriteString("\nsearch " + search) return err } diff --git a/ecs/resolv/resolv_test.go b/ecs/resolv/resolv_test.go index ffa2f0c0f..2dd37e320 100644 --- a/ecs/resolv/resolv_test.go +++ b/ecs/resolv/resolv_test.go @@ -36,11 +36,13 @@ func TestSetDomain(t *testing.T) { assert.NilError(t, err) got, err := ioutil.ReadFile(f) + assert.NilError(t, err) golden.Assert(t, string(got), "resolv.conf.golden") } func touch(t *testing.T, f string) { file, err := os.Create(f) assert.NilError(t, err) - defer file.Close() + err = file.Close() + assert.NilError(t, err) } diff --git a/ecs/testdata/simple/simple-cloudformation-conversion.golden b/ecs/testdata/simple/simple-cloudformation-conversion.golden index 56ec12f06..2f878b8e5 100644 --- a/ecs/testdata/simple/simple-cloudformation-conversion.golden +++ b/ecs/testdata/simple/simple-cloudformation-conversion.golden @@ -227,21 +227,31 @@ "Properties": { "ContainerDefinitions": [ { - "Environment": [ + "Command": [ + ".compute.internal", + "TestSimpleConvert.local" + ], + "Essential": "false", + "Image": "docker/ecs-searchdomain-sidecar", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "LogGroup" + }, + "awslogs-region": { + "Ref": "AWS::Region" + }, + "awslogs-stream-prefix": "TestSimpleConvert" + } + }, + "Name": "Simple_ResolvConf_InitContainer" + }, + { + "DependsOn": [ { - "Name": "LOCALDOMAIN", - "Value": { - "Fn::Join": [ - "", - [ - { - "Ref": "AWS::Region" - }, - ".compute.internal", - " TestSimpleConvert.local" - ] - ] - } + "Condition": "SUCCESS", + "ContainerName": "Simple_ResolvConf_InitContainer" } ], "Essential": true,