Selaa lähdekoodia

Introduce a sidecard to setup resolv.conf search domain

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 5 vuotta sitten
vanhempi
sitoutus
1444d68685

+ 1 - 1
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
 		}

+ 19 - 5
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]
+}

+ 11 - 13
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 <project>.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{

+ 1 - 0
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 (

+ 2 - 1
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
 }

+ 3 - 1
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)
 }

+ 24 - 14
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,