diff --git a/internal/pkg/deploy/cloudformation/stack/backend_svc.go b/internal/pkg/deploy/cloudformation/stack/backend_svc.go index b869d4cf301..a1aa67fc8f7 100644 --- a/internal/pkg/deploy/cloudformation/stack/backend_svc.go +++ b/internal/pkg/deploy/cloudformation/stack/backend_svc.go @@ -109,6 +109,7 @@ func (s *BackendService) Template() (string, error) { WorkloadType: manifest.BackendServiceType, HealthCheck: s.manifest.BackendServiceConfig.ImageConfig.HealthCheckOpts(), LogConfig: convertLogging(s.manifest.Logging), + DockerLabels: s.manifest.ImageConfig.DockerLabels, DesiredCountLambda: desiredCountLambda.String(), EnvControllerLambda: envControllerLambda.String(), Storage: storage, diff --git a/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go b/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go index 8b5bdae7183..7aa8fcede16 100644 --- a/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go +++ b/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go @@ -132,6 +132,7 @@ func (s *LoadBalancedWebService) Template() (string, error) { NestedStack: outputs, Sidecars: sidecars, LogConfig: convertLogging(s.manifest.Logging), + DockerLabels: s.manifest.ImageConfig.DockerLabels, Autoscaling: autoscaling, ExecuteCommand: convertExecuteCommand(&s.manifest.ExecuteCommand), WorkloadType: manifest.LoadBalancedWebServiceType, diff --git a/internal/pkg/deploy/cloudformation/stack/scheduled_job.go b/internal/pkg/deploy/cloudformation/stack/scheduled_job.go index d31b3d9a164..6a513fa5d46 100644 --- a/internal/pkg/deploy/cloudformation/stack/scheduled_job.go +++ b/internal/pkg/deploy/cloudformation/stack/scheduled_job.go @@ -164,6 +164,7 @@ func (j *ScheduledJob) Template() (string, error) { ScheduleExpression: schedule, StateMachine: stateMachine, LogConfig: convertLogging(j.manifest.Logging), + DockerLabels: j.manifest.ImageConfig.DockerLabels, Storage: storage, Network: convertNetworkConfig(j.manifest.Network), EntryPoint: entrypoint, diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/job-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/job-manifest.yml index 0084f014c74..6061892d84b 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/job-manifest.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/job-manifest.yml @@ -9,6 +9,8 @@ name: job type: Scheduled Job image: + labels: + com.amazonaws.ecs.copilot.description: Hello world! location: alpine # Number of CPU units for the task. @@ -44,6 +46,15 @@ sidecars: path: '/var/www' variables: NGINX_PORT: 8080 + labels: + com.amazonaws.ecs.copilot.sidecars.nginx.description: tricky + +environments: + test: + image: + labels: + com.amazonaws.ecs.copilot.coollabel: Synecdoche + # Optional fields for more advanced use-cases. # #variables: # Pass environment variables as key value pairs. diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/job-test.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/job-test.stack.yml index 0e96e7b400c..fef74ed1890 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/job-test.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/job-test.stack.yml @@ -148,6 +148,9 @@ Resources: - ContainerPath: /etc/mount2 ReadOnly: false SourceVolume: managedEFSVolume + DockerLabels: + com.amazonaws.ecs.copilot.coollabel: Synecdoche + com.amazonaws.ecs.copilot.description: Hello world! - Name: nginx Image: 'public.ecr.aws/nginx/nginx' LogConfiguration: @@ -167,6 +170,8 @@ Resources: Value: '8080' - Name: COPILOT_MOUNT_POINTS Value: '{"myEFSVolume":"/var/www"}' + DockerLabels: + com.amazonaws.ecs.copilot.sidecars.nginx.description: tricky Volumes: - Name: managedEFSVolume diff --git a/internal/pkg/deploy/cloudformation/stack/transformers.go b/internal/pkg/deploy/cloudformation/stack/transformers.go index 48a90bb12d5..1760145c797 100644 --- a/internal/pkg/deploy/cloudformation/stack/transformers.go +++ b/internal/pkg/deploy/cloudformation/stack/transformers.go @@ -63,15 +63,16 @@ func convertSidecar(s map[string]*manifest.SidecarConfig) ([]*template.SidecarOp return nil, err } sidecars = append(sidecars, &template.SidecarOpts{ - Name: aws.String(name), - Image: config.Image, - Essential: config.Essential, - Port: port, - Protocol: protocol, - CredsParam: config.CredsParam, - Secrets: config.Secrets, - Variables: config.Variables, - MountPoints: mp, + Name: aws.String(name), + Image: config.Image, + Essential: config.Essential, + Port: port, + Protocol: protocol, + CredsParam: config.CredsParam, + Secrets: config.Secrets, + Variables: config.Variables, + MountPoints: mp, + DockerLabels: config.DockerLabels, }) } return sidecars, nil diff --git a/internal/pkg/deploy/cloudformation/stack/transformers_test.go b/internal/pkg/deploy/cloudformation/stack/transformers_test.go index 00a20bab2f2..e6fb2fb2587 100644 --- a/internal/pkg/deploy/cloudformation/stack/transformers_test.go +++ b/internal/pkg/deploy/cloudformation/stack/transformers_test.go @@ -21,6 +21,7 @@ func Test_convertSidecar(t *testing.T) { testCases := map[string]struct { inPort string inEssential bool + inLabels map[string]string wanted *template.SidecarOpts wantedErr error @@ -62,6 +63,9 @@ func Test_convertSidecar(t *testing.T) { "specify essential as false": { inPort: "2000", inEssential: false, + inLabels: map[string]string{ + "com.amazonaws.ecs.copilot.sidecar.description": "wow", + }, wanted: &template.SidecarOpts{ Name: aws.String("foo"), @@ -71,6 +75,9 @@ func Test_convertSidecar(t *testing.T) { Secrets: mockMap, Variables: mockMap, Essential: aws.Bool(false), + DockerLabels: map[string]string{ + "com.amazonaws.ecs.copilot.sidecar.description": "wow", + }, }, }, } @@ -78,12 +85,13 @@ func Test_convertSidecar(t *testing.T) { t.Run(name, func(t *testing.T) { sidecar := map[string]*manifest.SidecarConfig{ "foo": { - CredsParam: mockCredsParam, - Image: mockImage, - Secrets: mockMap, - Variables: mockMap, - Essential: aws.Bool(tc.inEssential), - Port: aws.String(tc.inPort), + CredsParam: mockCredsParam, + Image: mockImage, + Secrets: mockMap, + Variables: mockMap, + Essential: aws.Bool(tc.inEssential), + Port: aws.String(tc.inPort), + DockerLabels: tc.inLabels, }, } got, err := convertSidecar(sidecar) diff --git a/internal/pkg/manifest/backend_svc_test.go b/internal/pkg/manifest/backend_svc_test.go index a49c681615c..66b5bad7482 100644 --- a/internal/pkg/manifest/backend_svc_test.go +++ b/internal/pkg/manifest/backend_svc_test.go @@ -239,8 +239,14 @@ func TestBackendSvc_ApplyEnv(t *testing.T) { ImageConfig: imageWithPortAndHealthcheck{ ServiceImageWithPort: ServiceImageWithPort{ Port: aws.Uint16(80), + Image: Image{ + DockerLabels: map[string]string{ + "com.amazonaws.ecs.copilot.description": "Hello world!", + }, + }, }, }, + TaskConfig: TaskConfig{ CPU: aws.Int(256), Memory: aws.Int(256), @@ -263,6 +269,15 @@ func TestBackendSvc_ApplyEnv(t *testing.T) { }, Environments: map[string]*BackendServiceConfig{ "test": { + ImageConfig: imageWithPortAndHealthcheck{ + ServiceImageWithPort: ServiceImageWithPort{ + Image: Image{ + DockerLabels: map[string]string{ + "com.amazonaws.ecs.copilot.description": "Overridden!", + }, + }, + }, + }, TaskConfig: TaskConfig{ Count: Count{ Autoscaling: Autoscaling{ @@ -326,6 +341,11 @@ func TestBackendSvc_ApplyEnv(t *testing.T) { ImageConfig: imageWithPortAndHealthcheck{ ServiceImageWithPort: ServiceImageWithPort{ Port: aws.Uint16(80), + Image: Image{ + DockerLabels: map[string]string{ + "com.amazonaws.ecs.copilot.description": "Overridden!", + }, + }, }, }, TaskConfig: TaskConfig{ diff --git a/internal/pkg/manifest/workload.go b/internal/pkg/manifest/workload.go index 2c47043f632..fd7bc5707a4 100644 --- a/internal/pkg/manifest/workload.go +++ b/internal/pkg/manifest/workload.go @@ -56,8 +56,9 @@ type Workload struct { // Image represents the workload's container image. type Image struct { - Build BuildArgsOrString `yaml:"build"` // Build an image from a Dockerfile. - Location *string `yaml:"location"` // Use an existing image instead. + Build BuildArgsOrString `yaml:"build"` // Build an image from a Dockerfile. + Location *string `yaml:"location"` // Use an existing image instead. + DockerLabels map[string]string `yaml:"labels,flow"` // Apply Docker labels to the container at runtime. } // GetLocation returns the location of the image. @@ -356,13 +357,14 @@ func (lc *Logging) GetEnableMetadata() *string { // SidecarConfig represents the configurable options for setting up a sidecar container. type SidecarConfig struct { - Port *string `yaml:"port"` - Image *string `yaml:"image"` - Essential *bool `yaml:"essential"` - CredsParam *string `yaml:"credentialsParameter"` - Variables map[string]string `yaml:"variables"` - Secrets map[string]string `yaml:"secrets"` - MountPoints []SidecarMountPoint `yaml:"mount_points"` + Port *string `yaml:"port"` + Image *string `yaml:"image"` + Essential *bool `yaml:"essential"` + CredsParam *string `yaml:"credentialsParameter"` + Variables map[string]string `yaml:"variables"` + Secrets map[string]string `yaml:"secrets"` + MountPoints []SidecarMountPoint `yaml:"mount_points"` + DockerLabels map[string]string `yaml:"labels"` } // TaskConfig represents the resource boundaries and environment variables for the containers in the task. diff --git a/internal/pkg/template/workload.go b/internal/pkg/template/workload.go index 4d47a104d41..8333d552263 100644 --- a/internal/pkg/template/workload.go +++ b/internal/pkg/template/workload.go @@ -46,6 +46,7 @@ var ( "secrets", "executionrole", "taskrole", + "workload-container", "fargate-taskdef-base-properties", "service-base-properties", "servicediscovery", @@ -76,15 +77,16 @@ type WorkloadNestedStackOpts struct { // SidecarOpts holds configuration that's needed if the service has sidecar containers. type SidecarOpts struct { - Name *string - Image *string - Essential *bool - Port *string - Protocol *string - CredsParam *string - Variables map[string]string - Secrets map[string]string - MountPoints []*MountPoint + Name *string + Image *string + Essential *bool + Port *string + Protocol *string + CredsParam *string + Variables map[string]string + Secrets map[string]string + MountPoints []*MountPoint + DockerLabels map[string]string } // StorageOpts holds data structures for rendering Volumes and Mount Points @@ -207,6 +209,7 @@ type WorkloadOpts struct { EntryPoint []string Command []string DomainAlias string + DockerLabels map[string]string // Additional options for service templates. WorkloadType string diff --git a/internal/pkg/template/workload_test.go b/internal/pkg/template/workload_test.go index 7c5ba150a8f..cc48cd0cceb 100644 --- a/internal/pkg/template/workload_test.go +++ b/internal/pkg/template/workload_test.go @@ -34,6 +34,7 @@ func TestTemplate_ParseSvc(t *testing.T) { mockBox.AddString("workloads/partials/cf/secrets.yml", "secrets") mockBox.AddString("workloads/partials/cf/executionrole.yml", "executionrole") mockBox.AddString("workloads/partials/cf/taskrole.yml", "taskrole") + mockBox.AddString("workloads/partials/cf/workload-container.yml", "workload-container") mockBox.AddString("workloads/partials/cf/fargate-taskdef-base-properties.yml", "fargate-taskdef-base-properties") mockBox.AddString("workloads/partials/cf/service-base-properties.yml", "service-base-properties") mockBox.AddString("workloads/partials/cf/servicediscovery.yml", "servicediscovery") @@ -57,6 +58,7 @@ func TestTemplate_ParseSvc(t *testing.T) { secrets executionrole taskrole + workload-container fargate-taskdef-base-properties service-base-properties servicediscovery diff --git a/site/content/docs/developing/sidecars.md b/site/content/docs/developing/sidecars.md index cbfacbb6c24..9d4d883227c 100644 --- a/site/content/docs/developing/sidecars.md +++ b/site/content/docs/developing/sidecars.md @@ -32,6 +32,10 @@ sidecars: path: {{ path }} # Whether to allow the sidecar read-only access to the volume. (Default true) read_only: {{ bool }} + # Optional Docker labels to apply to this container. + labels: + {{ label key }} : {{ label value }} + ``` Below is an example of specifying the [nginx](https://www.nginx.com/) sidecar container in a load balanced web service manifest. diff --git a/site/content/docs/manifest/backend-service.md b/site/content/docs/manifest/backend-service.md index 03edc0f5313..e3e1cfb9ce1 100644 --- a/site/content/docs/manifest/backend-service.md +++ b/site/content/docs/manifest/backend-service.md @@ -120,6 +120,9 @@ How long to wait before considering the health check failed, in seconds. Default image.healthcheck.`start_period` Duration Grace period within which to provide containers time to bootstrap before failed health checks count towards the maximum number of retries. Default is 0s. +image.`labels`Map +An optional key/value map of [Docker labels](https://docs.docker.com/config/labels-custom-metadata/) to add to the container. +
`entrypoint` String or Array of Strings @@ -257,3 +260,4 @@ Optional. Defaults to `""`. The ID of the EFS access point to connect to. If usi `environments` Map The environment section lets you override any value in your manifest based on the environment you're in. In the example manifest above, we're overriding the count parameter so that we can run 2 copies of our service in our prod environment. + diff --git a/site/content/docs/manifest/lb-web-service.md b/site/content/docs/manifest/lb-web-service.md index d1ab0a2d091..ba3bbaf2a05 100644 --- a/site/content/docs/manifest/lb-web-service.md +++ b/site/content/docs/manifest/lb-web-service.md @@ -144,6 +144,9 @@ The `location` field follows the same definition as the [`image` parameter](http image.`port` Integer The port exposed in your Dockerfile. Copilot should parse this value for you from your `EXPOSE` instruction. +image.`labels`Map +An optional key/value map of [Docker labels](https://docs.docker.com/config/labels-custom-metadata/) to add to the container. +
`entrypoint` String or Array of Strings @@ -288,3 +291,4 @@ Optional. Defaults to `""`. The ID of the EFS access point to connect to. If usi `environments` Map The environment section lets you override any value in your manifest based on the environment you're in. In the example manifest above, we're overriding the count parameter so that we can run 2 copies of our service in our prod environment. + diff --git a/site/content/docs/manifest/scheduled-job.md b/site/content/docs/manifest/scheduled-job.md index 5227ad04e48..786a848f15c 100644 --- a/site/content/docs/manifest/scheduled-job.md +++ b/site/content/docs/manifest/scheduled-job.md @@ -90,6 +90,9 @@ All paths are relative to your workspace root. Instead of building a container from a Dockerfile, you can specify an existing image name. Mutually exclusive with [`image.build`](#image-build). The `location` field follows the same definition as the [`image` parameter](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definition_image) in the Amazon ECS task definition. +image.`labels`Map +An optional key/value map of [Docker labels](https://docs.docker.com/config/labels-custom-metadata/) to add to the container. +
`entrypoint` String or Array of Strings diff --git a/templates/workloads/jobs/scheduled-job/cf.yml b/templates/workloads/jobs/scheduled-job/cf.yml index 914d36f1e32..82fdda15727 100644 --- a/templates/workloads/jobs/scheduled-job/cf.yml +++ b/templates/workloads/jobs/scheduled-job/cf.yml @@ -41,15 +41,7 @@ Resources: Properties: {{include "fargate-taskdef-base-properties" . | indent 6}} ContainerDefinitions: - - Name: !Ref WorkloadName - Image: !Ref ContainerImage -{{include "envvars" . | indent 10}} -{{include "secrets" . | indent 10}} -{{include "logconfig" . | indent 10}} -{{include "image-overrides" . | indent 10}} -{{- if .Storage -}} -{{include "mount-points" . | indent 10}} -{{- end -}} +{{include "workload-container" . | indent 8}} {{include "sidecars" . | indent 8}} {{- if .Storage -}} {{include "volumes" . | indent 6}} diff --git a/templates/workloads/partials/cf/envvars.yml b/templates/workloads/partials/cf/envvars.yml index 6b00d635997..c12a151b603 100644 --- a/templates/workloads/partials/cf/envvars.yml +++ b/templates/workloads/partials/cf/envvars.yml @@ -18,4 +18,8 @@ Environment: {{- if .Storage}}{{if .Storage.MountPoints}} - Name: COPILOT_MOUNT_POINTS Value: '{{jsonMountPoints .Storage.MountPoints}}' -{{- end}}{{end}} \ No newline at end of file +{{- end}}{{end}} +{{- if eq .WorkloadType "Load Balanced Web Service"}} +- Name: COPILOT_LB_DNS + Value: !GetAtt EnvControllerAction.PublicLoadBalancerDNSName +{{- end}} diff --git a/templates/workloads/partials/cf/sidecars.yml b/templates/workloads/partials/cf/sidecars.yml index d044731165e..52af9a44fa1 100644 --- a/templates/workloads/partials/cf/sidecars.yml +++ b/templates/workloads/partials/cf/sidecars.yml @@ -45,6 +45,10 @@ awslogs-region: !Ref AWS::Region awslogs-group: !Ref LogGroup awslogs-stream-prefix: copilot +{{- if $sidecar.DockerLabels}} + DockerLabels:{{range $name, $value := $sidecar.DockerLabels}} + {{$name | printf "%q"}}: {{$value | printf "%q"}}{{end}} +{{- end -}} {{- if $sidecar.CredsParam}} RepositoryCredentials: CredentialsParameter: {{$sidecar.CredsParam}} diff --git a/templates/workloads/partials/cf/workload-container.yml b/templates/workloads/partials/cf/workload-container.yml new file mode 100644 index 00000000000..02bad0d74f9 --- /dev/null +++ b/templates/workloads/partials/cf/workload-container.yml @@ -0,0 +1,28 @@ +- Name: !Ref WorkloadName + Image: !Ref ContainerImage +{{include "secrets" . | indent 2}} +{{include "envvars" . | indent 2}} +{{include "logconfig" . | indent 2}} +{{include "image-overrides" . | indent 2}} +{{- if .Storage -}} +{{include "mount-points" . | indent 2}} +{{- end -}} +{{- if .DockerLabels}} + DockerLabels:{{range $name, $value := .DockerLabels}} + {{$name | printf "%q"}}: {{$value | printf "%q"}}{{end}} +{{- end}} +{{- if eq .WorkloadType "Load Balanced Web Service"}} + PortMappings: + - ContainerPort: !Ref ContainerPort +{{- end}} +{{- if eq .WorkloadType "Backend Service"}} + PortMappings: !If [ExposePort, [{ContainerPort: !Ref ContainerPort}], !Ref "AWS::NoValue"] +{{- end}} +{{- if .HealthCheck}} + HealthCheck: + Command: {{quoteSlice .HealthCheck.Command | fmtSlice}} + Interval: {{.HealthCheck.Interval}} + Retries: {{.HealthCheck.Retries}} + StartPeriod: {{.HealthCheck.StartPeriod}} + Timeout: {{.HealthCheck.Timeout}} +{{- end}} \ No newline at end of file diff --git a/templates/workloads/services/backend/cf.yml b/templates/workloads/services/backend/cf.yml index a8e2b3c159e..c49279519c5 100644 --- a/templates/workloads/services/backend/cf.yml +++ b/templates/workloads/services/backend/cf.yml @@ -42,24 +42,7 @@ Resources: Properties: {{include "fargate-taskdef-base-properties" . | indent 6}} ContainerDefinitions: - - Name: !Ref WorkloadName - Image: !Ref ContainerImage - PortMappings: !If [ExposePort, [{ContainerPort: !Ref ContainerPort}], !Ref "AWS::NoValue"] -{{include "envvars" . | indent 10}} -{{include "secrets" . | indent 10}} -{{include "logconfig" . | indent 10}} -{{include "image-overrides" . | indent 10}} -{{- if .HealthCheck}} - HealthCheck: - Command: {{quoteSlice .HealthCheck.Command | fmtSlice}} - Interval: {{.HealthCheck.Interval}} - Retries: {{.HealthCheck.Retries}} - StartPeriod: {{.HealthCheck.StartPeriod}} - Timeout: {{.HealthCheck.Timeout}} -{{- end}} -{{- if .Storage -}} -{{include "mount-points" . | indent 10}} -{{- end -}} +{{include "workload-container" . | indent 8}} {{include "sidecars" . | indent 8}} {{- if .Storage -}} {{include "volumes" . | indent 6}} diff --git a/templates/workloads/services/lb-web/cf.yml b/templates/workloads/services/lb-web/cf.yml index 76bb3c469b0..c6a9d5a6302 100644 --- a/templates/workloads/services/lb-web/cf.yml +++ b/templates/workloads/services/lb-web/cf.yml @@ -58,20 +58,9 @@ Resources: Properties: {{include "fargate-taskdef-base-properties" . | indent 6}} ContainerDefinitions: - - Name: !Ref WorkloadName - Image: !Ref ContainerImage - PortMappings: - - ContainerPort: !Ref ContainerPort -{{include "envvars" . | indent 10}} - - Name: COPILOT_LB_DNS - Value: !GetAtt EnvControllerAction.PublicLoadBalancerDNSName -{{include "image-overrides" . | indent 10}} -{{include "secrets" . | indent 10}} -{{include "logconfig" . | indent 10}} -{{- if .Storage -}} -{{include "mount-points" . | indent 10}} -{{- end -}} -{{include "sidecars" . | indent 8}} +{{include "workload-container" . | indent 8}} +{{- include "sidecars" . | indent 8}} + {{if .Storage -}} {{include "volumes" . | indent 6}} {{- end}}