Repository: klothoplatform/klotho
Pull request #893 (2024-01-18 22:02:41)

Ecs fixes fg
ecs fixes which got sample multi container deployments to work

### Standard checks

- **Unit tests**: Any special considerations?
- **Docs**: Do we need to update any docs, internal or public?
- **Backwards compatibility**: Will this break existing apps? If so, what would be the extra work required to keep them working?



Patch:
diff --git a/pkg/engine2/operational_eval/vertex_property.go b/pkg/engine2/operational_eval/vertex_property.go
index a81ea48c..3f313900 100644
--- a/pkg/engine2/operational_eval/vertex_property.go
+++ b/pkg/engine2/operational_eval/vertex_property.go
@@ -402,6 +402,7 @@ func addConfigurationRuleToPropertyVertex(
 			))
 			continue
 		}
+
 		key := Key{Ref: ref}
 		vertex, err := eval.graph.Vertex(key)
 		if err != nil {
diff --git a/pkg/infra/iac3/templates/aws/ecs_task_definition/container_definitions.ts.tmpl b/pkg/infra/iac3/templates/aws/ecs_task_definition/container_definitions.ts.tmpl
new file mode 100644
index 00000000..054f5bae
--- /dev/null
+++ b/pkg/infra/iac3/templates/aws/ecs_task_definition/container_definitions.ts.tmpl
@@ -0,0 +1,91 @@
+[
+    {{- range $i, $cd := . }}
+    {
+        {{- if $cd.Command }}
+        command: [
+        {{ range $i, $cmd := $cd.Command }}
+            "{{ $cmd }}",
+        {{- end }}
+        ],
+        {{- end }}
+        {{- if $cd.Cpu }}
+        cpu: {{ $cd.Cpu }},
+        {{- end }}
+        {{- if $cd.Environment }}
+        environment: [
+            {{- range $key, $value := $cd.Environment }}
+            {
+                name: "{{ $value.Name }}",
+                value: {{ modelCase $value.Value }},
+            },
+            {{- end }}
+        ],
+        {{- end }}
+        {{- if $cd.DependsOn }}
+        dependsOn: [
+            {{- range $cd.DependsOn }}
+            {
+                containerName: "{{ .ContainerName }}",
+                condition: "{{ .Condition }}",
+            },
+            {{- end }}
+        ],
+        {{- end }}
+        {{- if $cd.EntryPoint }}
+        entryPoint: {{ $cd.EntryPoint }},
+        {{- end }}
+        essential: {{ $cd.Essential }},
+        {{- if $cd.Image }}
+        image: {{ getVar $cd.Image }}.imageName,
+        {{- end }}
+        {{- if $cd.LogConfiguration }}
+        logConfiguration: {
+            logDriver: "{{ $cd.LogConfiguration.LogDriver }}",
+            options: {
+                {{- range $key, $value := $cd.LogConfiguration.Options }}
+                "{{ $key }}": {{ modelCase $value }},
+                {{- end }}
+            },
+            {{- if $cd.LogConfiguration.SecretOptions }}
+            secretOptions: [
+                {{- range $key, $value := $cd.LogConfiguration.SecretOptions }}
+                {
+                    name: "{{ $value.Name }}",
+                    valueFrom: "{{ $value.ValueFrom }}",
+                },
+                {{- end }}
+            ]
+            {{- end }}
+        },
+        {{- end }}
+        {{- if $cd.Memory }}
+        memory: {{ $cd.Memory }},
+        {{- end }}
+        {{- if $cd.MountPoints }}
+        mountPoints: [
+            {{- range $key, $value := $cd.MountPoints.M }}
+            {
+                containerPath: "{{ $value.ContainerPath }}",
+                readOnly: {{ $value.ReadOnly }},
+                sourceVolume: "{{ $value.SourceVolume }}",
+            },
+            {{- end }}
+        ],
+        {{- end }}
+        {{- if $cd.Name }}
+        name: "{{ $cd.Name }}",
+        {{- end }}
+        {{- if $cd.PortMappings }}
+        portMappings: [
+            {{- range $key, $value := $cd.PortMappings }}
+            {
+                containerPort: {{ $value.ContainerPort }},
+                hostPort: {{ $value.HostPort }},
+                protocol: "{{ $value.Protocol }}",
+            },
+            {{- end }}
+        ],
+        {{- end }}        
+    },
+    {{- end }}
+]
\ No newline at end of file
diff --git a/pkg/infra/iac3/templates/aws/ecs_task_definition/environment_variables.ts.tmpl b/pkg/infra/iac3/templates/aws/ecs_task_definition/environment_variables.ts.tmpl
deleted file mode 100644
index 752b6267..00000000
--- a/pkg/infra/iac3/templates/aws/ecs_task_definition/environment_variables.ts.tmpl
+++ /dev/null
@@ -1,5 +0,0 @@
-[
-    {{- range $name, $value := . }}
-    { name: "{{ $name }}", value: {{ modelCase $value }} },
-    {{- end }}
-]
\ No newline at end of file
diff --git a/pkg/infra/iac3/templates/aws/rds_instance/factory.ts b/pkg/infra/iac3/templates/aws/rds_instance/factory.ts
index ca1aea19..805dbeb9 100644
--- a/pkg/infra/iac3/templates/aws/rds_instance/factory.ts
+++ b/pkg/infra/iac3/templates/aws/rds_instance/factory.ts
@@ -49,7 +49,9 @@ function properties(object: aws.rds.Instance, args: Args) {
         }),
         RdsConnectionArn: pulumi.interpolate`arn:aws:rds-db:${region.name}:${accountId.accountId}:dbuser:${object.resourceId}/${object.username}`,
         Endpoint: object.endpoint,
-        ConnectionString: pulumi.interpolate`${args.Engine}://${object.username}:${object.password}@${object.endpoint.address}:${object.endpoint.port}/${args.DatabaseName}`,
+        ConnectionString: pulumi.interpolate`${args.Engine}://${object.username}:${object.password}@${object.endpoint}/${args.DatabaseName}`,
+        Host: object.endpoint.apply((endpoint) => endpoint.split(':')[0]),
+        Port: object.endpoint.apply((endpoint) => endpoint.split(':')[1]),
     }
 }
 
diff --git a/pkg/knowledge_base2/dynamic_value.go b/pkg/knowledge_base2/dynamic_value.go
index f124e411..0878337f 100644
--- a/pkg/knowledge_base2/dynamic_value.go
+++ b/pkg/knowledge_base2/dynamic_value.go
@@ -52,20 +52,21 @@ func (ctx DynamicValueContext) KB() TemplateKB {
 
 func (ctx DynamicValueContext) TemplateFunctions() template.FuncMap {
 	return template.FuncMap{
-		"hasUpstream":       ctx.HasUpstream,
-		"upstream":          ctx.Upstream,
-		"layeredUpstream":   ctx.LayeredUpstream,
-		"allUpstream":       ctx.AllUpstream,
-		"hasDownstream":     ctx.HasDownstream,
-		"layeredDownstream": ctx.LayeredDownstream,
-		"downstream":        ctx.Downstream,
-		"closestDownstream": ctx.ClosestDownstream,
-		"allDownstream":     ctx.AllDownstream,
-		"shortestPath":      ctx.ShortestPath,
-		"fieldValue":        ctx.FieldValue,
-		"hasField":          ctx.HasField,
-		"fieldRef":          ctx.FieldRef,
-		"pathAncestor":      ctx.PathAncestor,
+		"hasUpstream":        ctx.HasUpstream,
+		"upstream":           ctx.Upstream,
+		"layeredUpstream":    ctx.LayeredUpstream,
+		"allUpstream":        ctx.AllUpstream,
+		"hasDownstream":      ctx.HasDownstream,
+		"layeredDownstream":  ctx.LayeredDownstream,
+		"downstream":         ctx.Downstream,
+		"closestDownstream":  ctx.ClosestDownstream,
+		"allDownstream":      ctx.AllDownstream,
+		"shortestPath":       ctx.ShortestPath,
+		"fieldValue":         ctx.FieldValue,
+		"hasField":           ctx.HasField,
+		"fieldRef":           ctx.FieldRef,
+		"pathAncestor":       ctx.PathAncestor,
+		"pathAncestorExists": ctx.PathAncestorExists,
 
 		"toJson": ctx.toJson,
 
@@ -575,6 +576,10 @@ func (ctx DynamicValueContext) PathAncestor(path construct.PropertyPath, depth i
 	return path[:len(path)-depth].String(), nil
 }
 
+func (ctx DynamicValueContext) PathAncestorExists(path construct.PropertyPath, depth int) bool {
+	return len(path) > depth
+}
+
 // filterMatch returns a json array by filtering the values array with the regex pattern
 func filterMatch(pattern string, values []string) ([]string, error) {
 	re, err := regexp.Compile(pattern)
diff --git a/pkg/templates/aws/edges/ecs_task_definition-efs_access_point.yaml b/pkg/templates/aws/edges/ecs_task_definition-efs_access_point.yaml
index 1021983a..40305694 100644
--- a/pkg/templates/aws/edges/ecs_task_definition-efs_access_point.yaml
+++ b/pkg/templates/aws/edges/ecs_task_definition-efs_access_point.yaml
@@ -14,7 +14,7 @@ operational_rules:
               Name: '{{ .Target.Name }}'
       - resource: '{{ .Source }}'
         configuration:
-          field: MountPoints
+          field: ContainerDefinitions[0].MountPoints
           value:
             - ContainerPath: '{{ fieldValue "RootDirectory.Path" .Target }}'
               SourceVolume: '{{ .Target.Name }}'
diff --git a/pkg/templates/aws/resources/ecs_service.yaml b/pkg/templates/aws/resources/ecs_service.yaml
index a86939da..0566e634 100644
--- a/pkg/templates/aws/resources/ecs_service.yaml
+++ b/pkg/templates/aws/resources/ecs_service.yaml
@@ -86,8 +86,8 @@ consumption:
         {{ $i := 0}}
         {{ range $key, $value := . }}
           {
-            "name": "{{ $key }}",
-            "value": "{{ $value }}"
+            "Name": "{{ $key }}",
+            "Value": "{{ $value }}"
           }{{if ne $i (sub (len $) 1)}},{{end}}{{ $i = add $i 1 }}
         {{ end }}
         ]
diff --git a/pkg/templates/aws/resources/ecs_task_definition.yaml b/pkg/templates/aws/resources/ecs_task_definition.yaml
index b23c5d55..f37c117d 100644
--- a/pkg/templates/aws/resources/ecs_task_definition.yaml
+++ b/pkg/templates/aws/resources/ecs_task_definition.yaml
@@ -2,6 +2,14 @@ qualified_type_name: aws:ecs_task_definition
 display_name: ECS Task Definition
 
 properties:
+  Cpu:
+    type: string
+    default_value: '256'
+    description: The number of CPU units reserved for the task
+  Memory:
+    type: string
+    default_value: '512'
+    description: The hard limit (in MiB) of memory to present to the task
   ContainerDefinitions:
     default_value:
       - Name: '{{ .Self.Name }}'
@@ -11,8 +19,8 @@ properties:
         type: list(string)
         description: The command that is passed to the container
       Cpu:
-        type: string
-        default_value: '256'
+        type: int
+        default_value: 256
         description: The number of CPU units reserved for the container
       DependsOn:
         type: list
@@ -20,6 +28,11 @@ properties:
           Condition:
             type: string
             description: The dependency condition of the container
+            allowed_values:
+              - START
+              - COMPLETE
+              - SUCCESS
+              - HEALTHY
           ContainerName:
             type: string
             description: The name of the container to depend on
@@ -43,6 +56,12 @@ properties:
             type: string
             description: The value of the environment variable
         description: The environment variables to pass to a container
+      Essential:
+        type: bool
+        default_value: true
+        description: If the essential parameter of a container is marked as true, and
+          that container fails or stops for any reason, all other containers that
+          are part of the task are stopped
       Image:
         type: resource(aws:ecr_image)
         required: true
@@ -50,7 +69,7 @@ properties:
           step:
             direction: downstream
             resources:
-              - aws:ecr_image:{{ .Self.Name }}-{{ fieldValue (printf "%s.Name" (pathAncestor .Path 1)) .Self}}
+              - aws:ecr_image:{{ .Self.Name }}-{{ if pathAncestorExists .Path 1}}{{ if hasField (printf "%s.Name" (pathAncestor .Path 1)) .Self}}{{ fieldValue (printf "%s.Name" (pathAncestor .Path 1)) .Self}}{{else}}image{{end}}{{else}}image{{end}}
             unique: true
         description: Reference to an Amazon Elastic Container Registry (ECR) image that
           will be used for the container within the task
@@ -78,11 +97,11 @@ properties:
               awslogs-region:
                 type: string
                 description: The region which your log group will exist in
-                default_value: '{{ fieldRef "RegionName" (downstream "aws:region" (upstream "aws:ecs_service" .Self)) }}'
+                default_value: '{{ fieldRef "Name" (downstream "aws:region" (upstream "aws:ecs_service" .Self)) }}'
               awslogs-stream-prefix:
                 type: string
                 description: The prefix to use when creating the log stream
-                default_value: '{{ .Self.Name }}-{{ fieldValue (printf "%s.Name" (pathAncestor .Path 3)) .Self }}'
+                default_value: '{{ .Self.Name }}{{ if hasField (printf "%s.Name" (pathAncestor .Path 3)) .Self}}-{{ fieldValue (printf "%s.Name" (pathAncestor .Path 3)) .Self }}{{else}}{{end}}'
           SecretOptions:
             type: list
             properties:
@@ -95,8 +114,8 @@ properties:
             description: The secrets to pass to the log configuration
         description: The log configuration specification for the container
       Memory:
-        type: string
-        default_value: '512'
+        type: int
+        default_value: 512
         description: The hard limit (in MiB) of memory to present to the container
       MountPoints:
         type: set
@@ -204,8 +223,8 @@ consumption:
         {{ $i := 0}}
         {{ range $key, $value := . }}
           {
-            "name": "{{ $key }}",
-            "value": "{{ $value }}"
+            "Name": "{{ $key }}",
+            "Value": "{{ $value }}"
           }{{if ne $i (sub (len $) 1)}},{{end}}{{ $i = add $i 1 }}
         {{ end }}
         ]
diff --git a/pkg/templates/aws/resources/rds_instance.yaml b/pkg/templates/aws/resources/rds_instance.yaml
index 65f49b8d..c07de70f 100644
--- a/pkg/templates/aws/resources/rds_instance.yaml
+++ b/pkg/templates/aws/resources/rds_instance.yaml
@@ -103,6 +103,14 @@ properties:
     type: string
     configuration_disabled: true
     deploy_time: true
+  Host:
+    type: string
+    configuration_disabled: true
+    deploy_time: true
+  Port:
+    type: string
+    configuration_disabled: true
+    deploy_time: true
 
 consumption:
   emitted:
diff --git a/pkg/templates/aws/resources/region.yaml b/pkg/templates/aws/resources/region.yaml
index 1866b941..662ef87b 100644
--- a/pkg/templates/aws/resources/region.yaml
+++ b/pkg/templates/aws/resources/region.yaml
@@ -2,7 +2,7 @@ qualified_type_name: aws:region
 display_name: Region
 
 properties:
-  RegionName:
+  Name:
     description: The name of the region
     type: string
     deploy_time: true


Test patch:
diff --git a/pkg/engine2/testdata/ecs_rds.expect.yaml b/pkg/engine2/testdata/ecs_rds.expect.yaml
index 798155f5..c0123f6b 100755
--- a/pkg/engine2/testdata/ecs_rds.expect.yaml
+++ b/pkg/engine2/testdata/ecs_rds.expect.yaml
@@ -29,26 +29,33 @@ resources:
     aws:ecs_cluster:ecs_cluster-0:
     aws:ecs_task_definition:ecs_service_0:
         ContainerDefinitions:
-            - Cpu: "256"
+            - Cpu: 256
               Environment:
-                - {}
-                - {}
-                - {}
-                - {}
+                - Name: RDS_INSTANCE_2_RDS_CONNECTION_ARN
+                  Value: aws:rds_instance:rds-instance-2#RdsConnectionArn
+                - Name: RDS_INSTANCE_2_RDS_ENDPOINT
+                  Value: aws:rds_instance:rds-instance-2#Endpoint
+                - Name: RDS_INSTANCE_2_RDS_PASSWORD
+                  Value: aws:rds_instance:rds-instance-2#Password
+                - Name: RDS_INSTANCE_2_RDS_USERNAME
+                  Value: aws:rds_instance:rds-instance-2#Username
+              Essential: true
               Image: aws:ecr_image:ecs_service_0-ecs_service_0
               LogConfiguration:
                 LogDriver: awslogs
                 Options:
                     awslogs-group: aws:log_group:ecs_service_0-log-group#LogGroupName
-                    awslogs-region: aws:region:region-0#RegionName
+                    awslogs-region: aws:region:region-0#Name
                     awslogs-stream-prefix: ecs_service_0-ecs_service_0
-              Memory: "512"
+              Memory: 512
               Name: ecs_service_0
               PortMappings:
                 - ContainerPort: 80
                   HostPort: 80
                   Protocol: TCP
+        Cpu: "256"
         ExecutionRole: aws:iam_role:ecs_service_0-execution-role
+        Memory: "512"
         NetworkMode: awsvpc
         RequiresCompatibilities:
             - FARGATE
diff --git a/pkg/engine2/testdata/ecs_rds.iac-viz.yaml b/pkg/engine2/testdata/ecs_rds.iac-viz.yaml
index 77740931..df8250f5 100755
--- a/pkg/engine2/testdata/ecs_rds.iac-viz.yaml
+++ b/pkg/engine2/testdata/ecs_rds.iac-viz.yaml
@@ -87,6 +87,7 @@ resources:
   ecs_task_definition/ecs_service_0 -> ecr_image/ecs_service_0-ecs_service_0:
   ecs_task_definition/ecs_service_0 -> iam_role/ecs_service_0-execution-role:
   ecs_task_definition/ecs_service_0 -> log_group/ecs_service_0-log-group:
+  ecs_task_definition/ecs_service_0 -> rds_instance/rds-instance-2:
   ecs_task_definition/ecs_service_0 -> region/region-0:
   aws:security_group:vpc-0/ecs_service_0-security_group: