Date: Wed, 12 Oct 2022 15:40:28 -0500
Subject: [PATCH 09/14] feat: add service connect mft (#4046)
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.
---
.../cloudformation/stack/backend_svc.go | 13 +++-
.../cloudformation/stack/backend_svc_test.go | 23 +++---
...lb_network_web_service_integration_test.go | 1 +
.../stack/lb_web_service_integration_test.go | 1 +
.../deploy/cloudformation/stack/lb_web_svc.go | 13 +++-
.../cloudformation/stack/lb_web_svc_test.go | 16 +++-
.../stack/testdata/stacklocal/manifest.yml | 2 +
.../backend/http-autoscaling-manifest.yml | 3 +
.../backend/http-full-config-manifest.yml | 3 +
.../backend/http-only-path-manifest.yml | 3 +
.../backend/https-path-alias-manifest.yml | 3 +
.../workloads/backend/simple-template.yml | 23 ++++--
.../testdata/workloads/svc-grpc-manifest.yml | 3 +
.../stack/testdata/workloads/svc-manifest.yml | 11 ++-
.../testdata/workloads/svc-nlb-manifest.yml | 4 +
.../testdata/workloads/svc-nlb-test.stack.yml | 17 ++++
.../testdata/workloads/svc-prod.params.json | 4 +-
.../testdata/workloads/svc-prod.stack.yml | 29 +++++--
.../testdata/workloads/svc-test.stack.yml | 17 ++++
.../workloads/windows-svc-manifest.yml | 3 +
.../cloudformation/stack/transformers.go | 8 +-
.../cloudformation/stack/transformers_test.go | 20 ++---
.../deploy/cloudformation/stack/worker_svc.go | 2 +-
internal/pkg/manifest/backend_svc.go | 17 ++++
internal/pkg/manifest/backend_svc_test.go | 77 +++++++++++++++++++
internal/pkg/manifest/lb_web_svc.go | 5 ++
internal/pkg/manifest/lb_web_svc_test.go | 35 +++++++++
internal/pkg/manifest/svc_test.go | 5 ++
internal/pkg/manifest/transform.go | 27 +++++++
internal/pkg/manifest/transform_test.go | 58 ++++++++++++++
internal/pkg/manifest/validate.go | 36 ++++++++-
internal/pkg/manifest/validate_test.go | 45 +++++++++--
internal/pkg/manifest/workload.go | 53 +++++++++++--
internal/pkg/manifest/workload_test.go | 43 +++++++++++
.../partials/cf/service-base-properties.yml | 6 +-
.../workloads/partials/cf/sidecars.yml | 9 +--
.../partials/cf/workload-container.yml | 13 ++--
internal/pkg/template/workload.go | 11 +--
38 files changed, 581 insertions(+), 81 deletions(-)
diff --git a/internal/pkg/deploy/cloudformation/stack/backend_svc.go b/internal/pkg/deploy/cloudformation/stack/backend_svc.go
index 622824ec047..a2da5bc51d4 100644
--- a/internal/pkg/deploy/cloudformation/stack/backend_svc.go
+++ b/internal/pkg/deploy/cloudformation/stack/backend_svc.go
@@ -33,7 +33,8 @@ type BackendService struct {
httpsEnabled bool
albEnabled bool
- parser backendSvcReadParser
+ parser backendSvcReadParser
+ SCFeatureFlag bool
}
// BackendServiceConfig contains data required to initialize a backend service stack.
@@ -141,8 +142,11 @@ func (s *BackendService) Template() (string, error) {
for _, ipNet := range s.manifest.RoutingRule.AllowedSourceIps {
allowedSourceIPs = append(allowedSourceIPs, string(ipNet))
}
-
- _, targetContainerPort := s.httpLoadBalancerTarget()
+ var scConfig *template.ServiceConnect
+ if s.manifest.ServiceConnectEnabled() {
+ scConfig = convertServiceConnect(s.manifest.Network.Connect)
+ }
+ targetContainer, targetContainerPort := s.httpLoadBalancerTarget()
content, err := s.parser.ParseBackendService(template.WorkloadOpts{
AppName: s.app,
EnvName: s.env,
@@ -166,7 +170,9 @@ func (s *BackendService) Template() (string, error) {
HealthCheck: convertContainerHealthCheck(s.manifest.BackendServiceConfig.ImageConfig.HealthCheck),
HTTPTargetContainer: template.HTTPTargetContainer{
Port: aws.StringValue(targetContainerPort),
+ Name: aws.StringValue(targetContainer),
},
+ ServiceConnect: scConfig,
HTTPHealthCheck: convertHTTPHealthCheck(&s.manifest.RoutingRule.HealthCheck),
DeregistrationDelay: deregistrationDelay,
AllowedSourceIps: allowedSourceIPs,
@@ -190,6 +196,7 @@ func (s *BackendService) Template() (string, error) {
},
HostedZoneAliases: hostedZoneAliases,
PermissionsBoundary: s.permBound,
+ SCFeatureFlag: s.SCFeatureFlag,
})
if err != nil {
return "", fmt.Errorf("parse backend service template: %w", err)
diff --git a/internal/pkg/deploy/cloudformation/stack/backend_svc_test.go b/internal/pkg/deploy/cloudformation/stack/backend_svc_test.go
index e0ad2d2f0d7..0bb472bf5c6 100644
--- a/internal/pkg/deploy/cloudformation/stack/backend_svc_test.go
+++ b/internal/pkg/deploy/cloudformation/stack/backend_svc_test.go
@@ -271,6 +271,7 @@ Outputs:
HostedZoneAliases: make(template.AliasesForHostedZone),
HTTPTargetContainer: template.HTTPTargetContainer{
Port: "8080",
+ Name: "api",
},
HTTPHealthCheck: template.HTTPHealthCheckOpts{
HealthCheckPath: manifest.DefaultHealthCheckPath,
@@ -287,6 +288,7 @@ Outputs:
Key: "sha2/count.zip",
},
},
+ ServiceConnect: &template.ServiceConnect{},
ExecuteCommand: &template.ExecuteCommandOpts{},
NestedStack: &template.WorkloadNestedStackOpts{
StackName: addon.StackName,
@@ -430,13 +432,15 @@ Outputs:
},
Sidecars: []*template.SidecarOpts{
{
- Name: aws.String("envoy"),
+ Name: "envoy",
Port: aws.String("443"),
},
},
HTTPTargetContainer: template.HTTPTargetContainer{
+ Name: "envoy",
Port: "443",
},
+ ServiceConnect: &template.ServiceConnect{},
HTTPHealthCheck: template.HTTPHealthCheckOpts{
HealthCheckPath: "/healthz",
Port: "4200",
@@ -537,14 +541,6 @@ func TestBackendService_Parameters(t *testing.T) {
ParameterKey: aws.String(WorkloadContainerPortParamKey),
ParameterValue: aws.String("8080"),
},
- {
- ParameterKey: aws.String(WorkloadTargetContainerParamKey),
- ParameterValue: aws.String("frontend"),
- },
- {
- ParameterKey: aws.String(WorkloadTargetPortParamKey),
- ParameterValue: aws.String("8080"),
- },
{
ParameterKey: aws.String(WorkloadTaskCPUParamKey),
ParameterValue: aws.String("256"),
@@ -569,6 +565,14 @@ func TestBackendService_Parameters(t *testing.T) {
ParameterKey: aws.String(WorkloadEnvFileARNParamKey),
ParameterValue: aws.String(""),
},
+ {
+ ParameterKey: aws.String(WorkloadTargetContainerParamKey),
+ ParameterValue: aws.String("frontend"),
+ },
+ {
+ ParameterKey: aws.String(WorkloadTargetPortParamKey),
+ ParameterValue: aws.String("8080"),
+ },
}, params)
}
@@ -650,6 +654,7 @@ func TestBackendService_TemplateAndParamsGeneration(t *testing.T) {
EnvVersion: "v1.42.0",
},
})
+ serializer.SCFeatureFlag = true
require.NoError(t, err)
// mock parser for lambda functions
diff --git a/internal/pkg/deploy/cloudformation/stack/lb_network_web_service_integration_test.go b/internal/pkg/deploy/cloudformation/stack/lb_network_web_service_integration_test.go
index 9f997e833ec..503a3263e3a 100644
--- a/internal/pkg/deploy/cloudformation/stack/lb_network_web_service_integration_test.go
+++ b/internal/pkg/deploy/cloudformation/stack/lb_network_web_service_integration_test.go
@@ -105,6 +105,7 @@ func TestNetworkLoadBalancedWebService_Template(t *testing.T) {
},
RootUserARN: "arn:aws:iam::123456789123:root",
}, stack.WithNLB([]string{"10.0.0.0/24", "10.1.0.0/24"}))
+ serializer.SCFeatureFlag = true
tpl, err := serializer.Template()
require.NoError(t, err, "template should render")
regExpGUID := regexp.MustCompile(`([a-f\d]{8}-)([a-f\d]{4}-){3}([a-f\d]{12})`) // Matches random guids
diff --git a/internal/pkg/deploy/cloudformation/stack/lb_web_service_integration_test.go b/internal/pkg/deploy/cloudformation/stack/lb_web_service_integration_test.go
index 34042cac16c..59fb5dd04ac 100644
--- a/internal/pkg/deploy/cloudformation/stack/lb_web_service_integration_test.go
+++ b/internal/pkg/deploy/cloudformation/stack/lb_web_service_integration_test.go
@@ -105,6 +105,7 @@ func TestLoadBalancedWebService_Template(t *testing.T) {
EnvVersion: "v1.42.0",
},
})
+ serializer.SCFeatureFlag = true
tpl, err := serializer.Template()
require.NoError(t, err, "template should render")
regExpGUID := regexp.MustCompile(`([a-f\d]{8}-)([a-f\d]{4}-){3}([a-f\d]{12})`) // Matches random guids
diff --git a/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go b/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go
index 25916287594..73bf9ed985a 100644
--- a/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go
+++ b/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go
@@ -38,7 +38,8 @@ type LoadBalancedWebService struct {
publicSubnetCIDRBlocks []string
appInfo deploy.AppInformation
- parser loadBalancedWebSvcReadParser
+ parser loadBalancedWebSvcReadParser
+ SCFeatureFlag bool
}
// LoadBalancedWebServiceOption is used to configuring an optional field for LoadBalancedWebService.
@@ -190,6 +191,10 @@ func (s *LoadBalancedWebService) Template() (string, error) {
if s.manifest.RoutingRule.RedirectToHTTPS != nil {
httpRedirect = aws.BoolValue(s.manifest.RoutingRule.RedirectToHTTPS)
}
+ var scConfig *template.ServiceConnect
+ if s.manifest.ServiceConnectEnabled() {
+ scConfig = convertServiceConnect(s.manifest.Network.Connect)
+ }
targetContainer, targetContainerPort := s.httpLoadBalancerTarget()
content, err := s.parser.ParseLoadBalancedWebService(template.WorkloadOpts{
AppName: s.app,
@@ -214,9 +219,10 @@ func (s *LoadBalancedWebService) Template() (string, error) {
ExecuteCommand: convertExecuteCommand(&s.manifest.ExecuteCommand),
WorkloadType: manifest.LoadBalancedWebServiceType,
HTTPTargetContainer: template.HTTPTargetContainer{
- Port: aws.StringValue(targetContainerPort),
- Container: aws.StringValue(targetContainer),
+ Port: aws.StringValue(targetContainerPort),
+ Name: aws.StringValue(targetContainer),
},
+ ServiceConnect: scConfig,
HealthCheck: convertContainerHealthCheck(s.manifest.ImageConfig.HealthCheck),
HTTPHealthCheck: convertHTTPHealthCheck(&s.manifest.RoutingRule.HealthCheck),
DeregistrationDelay: deregistrationDelay,
@@ -242,6 +248,7 @@ func (s *LoadBalancedWebService) Template() (string, error) {
},
HostedZoneAliases: aliasesFor,
PermissionsBoundary: s.permBound,
+ SCFeatureFlag: s.SCFeatureFlag,
})
if err != nil {
return "", err
diff --git a/internal/pkg/deploy/cloudformation/stack/lb_web_svc_test.go b/internal/pkg/deploy/cloudformation/stack/lb_web_svc_test.go
index 066207281dc..7d9d57e977f 100644
--- a/internal/pkg/deploy/cloudformation/stack/lb_web_svc_test.go
+++ b/internal/pkg/deploy/cloudformation/stack/lb_web_svc_test.go
@@ -207,6 +207,11 @@ Outputs:
String: nil,
StringSlice: []string{"world"},
}
+ mft.Network.Connect = manifest.ServiceConnectBoolOrArgs{
+ ServiceConnectArgs: manifest.ServiceConnectArgs{
+ Alias: aws.String("frontend"),
+ },
+ }
var actual template.WorkloadOpts
parser := mocks.NewMockloadBalancedWebSvcReadParser(ctrl)
@@ -262,8 +267,11 @@ Outputs:
HTTPRedirect: true,
DeregistrationDelay: aws.Int64(60),
HTTPTargetContainer: template.HTTPTargetContainer{
- Container: "frontend",
- Port: "80",
+ Name: "frontend",
+ Port: "80",
+ },
+ ServiceConnect: &template.ServiceConnect{
+ Alias: aws.String("frontend"),
},
HealthCheck: &template.ContainerHealthCheck{
Command: []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"},
@@ -415,8 +423,8 @@ Outputs:
// THEN
require.NoError(t, err)
require.Equal(t, template.HTTPTargetContainer{
- Port: "443",
- Container: "envoy",
+ Port: "443",
+ Name: "envoy",
}, actual.HTTPTargetContainer)
})
}
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/stacklocal/manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/stacklocal/manifest.yml
index bb95b6f1bb2..be2565b4a81 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/stacklocal/manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/stacklocal/manifest.yml
@@ -31,6 +31,8 @@ count:
memory_percentage: 80
# Enable running commands in your container.
exec: true
+network:
+ connect: false
taskdef_overrides:
- path: "ContainerDefinitions[0].Ulimits[-].Name"
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-autoscaling-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-autoscaling-manifest.yml
index 0547c19f8f7..f77c95217ce 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-autoscaling-manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-autoscaling-manifest.yml
@@ -10,6 +10,9 @@ image:
# Port exposed through your container to route traffic to it.
port: 8080
+network:
+ connect: false
+
cpu: 512 # Number of CPU units for the task.
memory: 1024 # Amount of memory in MiB used by the task.
exec: true # Enable running commands in your container.
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-full-config-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-full-config-manifest.yml
index e856a9f9207..409382394aa 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-full-config-manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-full-config-manifest.yml
@@ -16,6 +16,9 @@ http:
timeout: 10s
grace_period: 45s
+network:
+ connect: false
+
image:
# Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/backend-service/#image-build
build: Dockerfile
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-only-path-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-only-path-manifest.yml
index 677fb876bd7..6a0fd564f78 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-only-path-manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/http-only-path-manifest.yml
@@ -10,6 +10,9 @@ image:
# Port exposed through your container to route traffic to it.
port: 8080
+network:
+ connect: false
+
cpu: 512 # Number of CPU units for the task.
memory: 1024 # Amount of memory in MiB used by the task.
count: 1 # Number of tasks that should be running in your service.
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/https-path-alias-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/https-path-alias-manifest.yml
index 91e06831d51..cb80c24ceef 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/https-path-alias-manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/https-path-alias-manifest.yml
@@ -10,6 +10,9 @@ http:
- name: "*.foobar.com"
hosted_zone: mockHostedZone1
+network:
+ connect: false
+
image:
# Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/backend-service/#image-build
build: Dockerfile
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/simple-template.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/simple-template.yml
index d15c1f13db2..7757d5601af 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/simple-template.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/backend/simple-template.yml
@@ -89,12 +89,7 @@ Resources:
awslogs-region: !Ref AWS::Region
awslogs-group: !Ref LogGroup
awslogs-stream-prefix: copilot
- PortMappings:
- !If [
- ExposePort,
- [{ ContainerPort: !Ref ContainerPort }],
- !Ref "AWS::NoValue",
- ]
+ PortMappings: !If [ExposePort, [{ContainerPort: !Ref ContainerPort, Name: target}], !Ref "AWS::NoValue"]
ExecutionRole:
Metadata:
"aws:copilot:description": "An IAM Role for the Fargate agent to make AWS API calls on your behalf"
@@ -279,6 +274,22 @@ Resources:
PropagateTags: SERVICE
EnableExecuteCommand: true
LaunchType: FARGATE
+ ServiceConnectConfiguration:
+ Enabled: True
+ Namespace: my-env.my-app.local
+ LogConfiguration:
+ LogDriver: awslogs
+ Options:
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Ref LogGroup
+ awslogs-stream-prefix: copilot
+ Services:
+ - PortName: target
+ # Avoid using the same service with Service Discovery in a namespace.
+ DiscoveryName: !Join ["-", [!Ref WorkloadName, "sc"]]
+ ClientAliases:
+ - Port: !Ref TargetPort
+ DnsName: !Ref WorkloadName
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-grpc-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-grpc-manifest.yml
index 32ecf794a21..5478c9d5504 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-grpc-manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-grpc-manifest.yml
@@ -37,6 +37,9 @@ publish:
topics:
- name: givesdogs
+network:
+ connect: false
+
# 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/svc-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-manifest.yml
index 87c8d6dc759..07ddad01086 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-manifest.yml
@@ -60,6 +60,8 @@ environments:
path: /
grace_period: 30s
deregistration_delay: 30s
+ network:
+ connect: false
prod:
count:
range:
@@ -80,9 +82,16 @@ environments:
TEST: TEST
secrets:
GITHUB_TOKEN: GITHUB_TOKEN
+ http:
+ path: '/'
+ alias: example.com
+ target_container: nginx
+ network:
+ connect:
+ alias: api
sidecars:
nginx:
- port: 80
+ port: 8080
image: 1234567890.dkr.ecr.us-west-2.amazonaws.com/reverse-proxy:revision_1
variables:
NGINX_PORT: 80
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-manifest.yml
index 2c2e8e5a404..f6a11dd4f3a 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-manifest.yml
@@ -10,6 +10,8 @@ image:
build: ./Dockerfile
port: 80
http: false
+network:
+ connect: false
nlb:
port: 443/tls
count: 5
@@ -26,6 +28,8 @@ environments:
nlb:
healthcheck:
port: 80
+ network:
+ connect: true
dev:
http:
path: '/'
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-test.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-test.stack.yml
index 317e9e88e8d..16616e3d6e0 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-test.stack.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-test.stack.yml
@@ -95,6 +95,7 @@ Resources: # If a bucket URL is specified, that means the template exists.
awslogs-stream-prefix: copilot
PortMappings:
- ContainerPort: !Ref ContainerPort
+ Name: target
- ContainerPort: 443
Protocol: tcp
ExecutionRole:
@@ -294,6 +295,22 @@ Resources: # If a bucket URL is specified, that means the template exists.
MaximumPercent: 200
PropagateTags: SERVICE
LaunchType: FARGATE
+ ServiceConnectConfiguration:
+ Enabled: True
+ Namespace: test.my-app.local
+ LogConfiguration:
+ LogDriver: awslogs
+ Options:
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Ref LogGroup
+ awslogs-stream-prefix: copilot
+ Services:
+ - PortName: target
+ # Avoid using the same service with Service Discovery in a namespace.
+ DiscoveryName: !Join ["-", [!Ref WorkloadName, "sc"]]
+ ClientAliases:
+ - Port: !Ref TargetPort
+ DnsName: !Ref WorkloadName
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-prod.params.json b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-prod.params.json
index 48bae789f8e..25d6ed6f63e 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-prod.params.json
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-prod.params.json
@@ -11,8 +11,8 @@
"LogRetention": "1",
"RulePath": "/",
"Stickiness": "false",
- "TargetContainer": "fe",
- "TargetPort": "4000",
+ "TargetContainer": "nginx",
+ "TargetPort": "8080",
"TaskCPU": "256",
"TaskCount": "3",
"TaskMemory": "512",
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-prod.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-prod.stack.yml
index 7c7f08f223d..8003a5f4009 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-prod.stack.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-prod.stack.yml
@@ -143,7 +143,8 @@ Resources: # If a bucket URL is specified, that means the template exists.
- Name: nginx
Image: 1234567890.dkr.ecr.us-west-2.amazonaws.com/reverse-proxy:revision_1
PortMappings:
- - ContainerPort: 80
+ - ContainerPort: 8080
+ Name: target
HealthCheck:
Command: ["CMD-SHELL", "curl -f http://localhost:8080 || exit 1"]
Interval: 10
@@ -190,11 +191,11 @@ Resources: # If a bucket URL is specified, that means the template exists.
- Name: COPILOT_LB_DNS
Value: !GetAtt EnvControllerAction.PublicLoadBalancerDNSName
LogConfiguration:
- LogDriver: awslogs
- Options:
- awslogs-region: !Ref AWS::Region
- awslogs-group: !Ref LogGroup
- awslogs-stream-prefix: copilot
+ LogDriver: awslogs
+ Options:
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Ref LogGroup
+ awslogs-stream-prefix: copilot
Volumes:
- Name: persistence
ExecutionRole:
@@ -518,6 +519,22 @@ Resources: # If a bucket URL is specified, that means the template exists.
- CapacityProvider: FARGATE
Weight: 0
Base: 5
+ ServiceConnectConfiguration:
+ Enabled: True
+ Namespace: prod.my-app.local
+ LogConfiguration:
+ LogDriver: awslogs
+ Options:
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Ref LogGroup
+ awslogs-stream-prefix: copilot
+ Services:
+ - PortName: target
+ # Avoid using the same service with Service Discovery in a namespace.
+ DiscoveryName: !Join ["-", [!Ref WorkloadName, "sc"]]
+ ClientAliases:
+ - Port: !Ref TargetPort
+ DnsName: api
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-test.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-test.stack.yml
index a71f21d5ea2..fd0fc9a4f43 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-test.stack.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-test.stack.yml
@@ -109,6 +109,7 @@ Resources: # If a bucket URL is specified, that means the template exists.
SourceVolume: persistence
PortMappings:
- ContainerPort: !Ref ContainerPort
+ Name: target
Volumes:
- Name: persistence
ExecutionRole:
@@ -430,6 +431,22 @@ Resources: # If a bucket URL is specified, that means the template exists.
MaximumPercent: 200
PropagateTags: SERVICE
LaunchType: FARGATE
+ ServiceConnectConfiguration:
+ Enabled: True
+ Namespace: test.my-app.local
+ LogConfiguration:
+ LogDriver: awslogs
+ Options:
+ awslogs-region: !Ref AWS::Region
+ awslogs-group: !Ref LogGroup
+ awslogs-stream-prefix: copilot
+ Services:
+ - PortName: target
+ # Avoid using the same service with Service Discovery in a namespace.
+ DiscoveryName: !Join ["-", [!Ref WorkloadName, "sc"]]
+ ClientAliases:
+ - Port: !Ref TargetPort
+ DnsName: !Ref WorkloadName
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/windows-svc-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/windows-svc-manifest.yml
index 8b8638ab2e9..2138052f58a 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/windows-svc-manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/windows-svc-manifest.yml
@@ -21,6 +21,9 @@ image:
# Port exposed through your container to route traffic to it.
port: 80
+network:
+ connect: false
+
cpu: 1024 # Number of CPU units for the task.
memory: 2048 # Amount of memory in MiB used by the task.
count: 1 # Number of tasks that should be running in your service.
diff --git a/internal/pkg/deploy/cloudformation/stack/transformers.go b/internal/pkg/deploy/cloudformation/stack/transformers.go
index b69966f058c..88fb9267082 100644
--- a/internal/pkg/deploy/cloudformation/stack/transformers.go
+++ b/internal/pkg/deploy/cloudformation/stack/transformers.go
@@ -96,7 +96,7 @@ func convertSidecar(s map[string]*manifest.SidecarConfig) ([]*template.SidecarOp
}
mp := convertSidecarMountPoints(config.MountPoints)
sidecars = append(sidecars, &template.SidecarOpts{
- Name: aws.String(name),
+ Name: name,
Image: config.Image,
Essential: config.Essential,
Port: port,
@@ -496,6 +496,12 @@ func convertExecuteCommand(e *manifest.ExecuteCommand) *template.ExecuteCommandO
return &template.ExecuteCommandOpts{}
}
+func convertServiceConnect(s manifest.ServiceConnectBoolOrArgs) *template.ServiceConnect {
+ return &template.ServiceConnect{
+ Alias: s.ServiceConnectArgs.Alias,
+ }
+}
+
func convertLogging(lc manifest.Logging) *template.LogConfigOpts {
if lc.IsEmpty() {
return nil
diff --git a/internal/pkg/deploy/cloudformation/stack/transformers_test.go b/internal/pkg/deploy/cloudformation/stack/transformers_test.go
index 37ac078239d..9baaca2bce0 100644
--- a/internal/pkg/deploy/cloudformation/stack/transformers_test.go
+++ b/internal/pkg/deploy/cloudformation/stack/transformers_test.go
@@ -44,7 +44,7 @@ func Test_convertSidecar(t *testing.T) {
inEssential: true,
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
Port: aws.String("2000"),
CredsParam: mockCredsParam,
Image: mockImage,
@@ -58,7 +58,7 @@ func Test_convertSidecar(t *testing.T) {
inEssential: true,
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
Port: aws.String("2000"),
Protocol: aws.String("udp"),
CredsParam: mockCredsParam,
@@ -76,7 +76,7 @@ func Test_convertSidecar(t *testing.T) {
},
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
Port: aws.String("2000"),
CredsParam: mockCredsParam,
Image: mockImage,
@@ -96,7 +96,7 @@ func Test_convertSidecar(t *testing.T) {
},
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
Port: aws.String("2000"),
CredsParam: mockCredsParam,
Image: mockImage,
@@ -110,7 +110,7 @@ func Test_convertSidecar(t *testing.T) {
},
"do not specify image override": {
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
CredsParam: mockCredsParam,
Image: mockImage,
Secrets: mockSecrets,
@@ -126,7 +126,7 @@ func Test_convertSidecar(t *testing.T) {
},
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
CredsParam: mockCredsParam,
Image: mockImage,
Secrets: mockSecrets,
@@ -142,7 +142,7 @@ func Test_convertSidecar(t *testing.T) {
},
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
CredsParam: mockCredsParam,
Image: mockImage,
Secrets: mockSecrets,
@@ -158,7 +158,7 @@ func Test_convertSidecar(t *testing.T) {
},
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
CredsParam: mockCredsParam,
Image: mockImage,
Secrets: mockSecrets,
@@ -174,7 +174,7 @@ func Test_convertSidecar(t *testing.T) {
},
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
CredsParam: mockCredsParam,
Image: mockImage,
Secrets: mockSecrets,
@@ -190,7 +190,7 @@ func Test_convertSidecar(t *testing.T) {
},
wanted: &template.SidecarOpts{
- Name: aws.String("foo"),
+ Name: "foo",
CredsParam: mockCredsParam,
Image: mockImage,
Secrets: mockSecrets,
diff --git a/internal/pkg/deploy/cloudformation/stack/worker_svc.go b/internal/pkg/deploy/cloudformation/stack/worker_svc.go
index 73abd19eef4..f40a5f97311 100644
--- a/internal/pkg/deploy/cloudformation/stack/worker_svc.go
+++ b/internal/pkg/deploy/cloudformation/stack/worker_svc.go
@@ -5,11 +5,11 @@ package stack
import (
"fmt"
- "github.com/aws/copilot-cli/internal/pkg/config"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudformation"
+ "github.com/aws/copilot-cli/internal/pkg/config"
"github.com/aws/copilot-cli/internal/pkg/manifest"
"github.com/aws/copilot-cli/internal/pkg/template"
"github.com/aws/copilot-cli/internal/pkg/template/override"
diff --git a/internal/pkg/manifest/backend_svc.go b/internal/pkg/manifest/backend_svc.go
index f79f9243db3..e064eabcfc8 100644
--- a/internal/pkg/manifest/backend_svc.go
+++ b/internal/pkg/manifest/backend_svc.go
@@ -98,6 +98,23 @@ func (s *BackendService) Port() (port uint16, ok bool) {
return aws.Uint16Value(value), true
}
+// ServiceConnectEnabled returns if ServiceConnect is enabled or not.
+// Unless explicitly disabled in the manifest or if no server is configured we default to true.
+func (s *BackendService) ServiceConnectEnabled() bool {
+ if s.Network.Connect.EnableServiceConnect != nil {
+ return *s.Network.Connect.EnableServiceConnect
+ }
+ if !s.Network.Connect.ServiceConnectArgs.isEmpty() {
+ return true
+ }
+ // Try our best to enable Service Connect by default.
+ if s.BackendServiceConfig.ImageConfig.Port != nil ||
+ s.BackendServiceConfig.RoutingRule.TargetContainer != nil {
+ return true
+ }
+ return false
+}
+
// Publish returns the list of topics where notifications can be published.
func (s *BackendService) Publish() []Topic {
return s.BackendServiceConfig.PublishConfig.publishedTopics()
diff --git a/internal/pkg/manifest/backend_svc_test.go b/internal/pkg/manifest/backend_svc_test.go
index aaa883bd831..84ac90473cd 100644
--- a/internal/pkg/manifest/backend_svc_test.go
+++ b/internal/pkg/manifest/backend_svc_test.go
@@ -297,6 +297,83 @@ func TestBackendService_Port(t *testing.T) {
}
}
+func TestBackendService_ServiceConnectEnabled(t *testing.T) {
+ testCases := map[string]struct {
+ mft *BackendService
+
+ wanted bool
+ }{
+ "enabled by default if main container exposes port": {
+ mft: &BackendService{
+ BackendServiceConfig: BackendServiceConfig{
+ ImageConfig: ImageWithHealthcheckAndOptionalPort{
+ ImageWithOptionalPort: ImageWithOptionalPort{
+ Port: uint16P(80),
+ },
+ },
+ },
+ },
+ wanted: true,
+ },
+ "enabled by default if target container is set": {
+ mft: &BackendService{
+ BackendServiceConfig: BackendServiceConfig{
+ RoutingRule: RoutingRuleConfiguration{
+ TargetContainer: aws.String("nginx"),
+ },
+ },
+ },
+ wanted: true,
+ },
+ "disabled by default if no exposed port or no target container": {
+ mft: &BackendService{
+ BackendServiceConfig: BackendServiceConfig{
+ Network: NetworkConfig{
+ Connect: ServiceConnectBoolOrArgs{},
+ },
+ },
+ },
+ wanted: false,
+ },
+ "set by bool": {
+ mft: &BackendService{
+ BackendServiceConfig: BackendServiceConfig{
+ Network: NetworkConfig{
+ Connect: ServiceConnectBoolOrArgs{
+ EnableServiceConnect: aws.Bool(true),
+ },
+ },
+ },
+ },
+ wanted: true,
+ },
+ "set by args": {
+ mft: &BackendService{
+ BackendServiceConfig: BackendServiceConfig{
+ Network: NetworkConfig{
+ Connect: ServiceConnectBoolOrArgs{
+ ServiceConnectArgs: ServiceConnectArgs{
+ Alias: aws.String("api"),
+ },
+ },
+ },
+ },
+ },
+ wanted: true,
+ },
+ }
+
+ for name, tc := range testCases {
+ t.Run(name, func(t *testing.T) {
+ // WHEN
+ enabled := tc.mft.ServiceConnectEnabled()
+
+ // THEN
+ require.Equal(t, tc.wanted, enabled)
+ })
+ }
+}
+
func TestBackendService_Publish(t *testing.T) {
testCases := map[string]struct {
mft *BackendService
diff --git a/internal/pkg/manifest/lb_web_svc.go b/internal/pkg/manifest/lb_web_svc.go
index 7d9b23ecf26..0f6cd323dd0 100644
--- a/internal/pkg/manifest/lb_web_svc.go
+++ b/internal/pkg/manifest/lb_web_svc.go
@@ -169,6 +169,11 @@ func (s *LoadBalancedWebService) Port() (port uint16, ok bool) {
return aws.Uint16Value(s.ImageConfig.Port), true
}
+// ServiceConnectEnabled returns if ServiceConnect is enabled or not.
+func (s *LoadBalancedWebService) ServiceConnectEnabled() bool {
+ return s.Network.Connect.EnableServiceConnect == nil || *s.Network.Connect.EnableServiceConnect
+}
+
// Publish returns the list of topics where notifications can be published.
func (s *LoadBalancedWebService) Publish() []Topic {
return s.LoadBalancedWebServiceConfig.PublishConfig.publishedTopics()
diff --git a/internal/pkg/manifest/lb_web_svc_test.go b/internal/pkg/manifest/lb_web_svc_test.go
index 27ad87879bf..2cf7fcbce31 100644
--- a/internal/pkg/manifest/lb_web_svc_test.go
+++ b/internal/pkg/manifest/lb_web_svc_test.go
@@ -1338,6 +1338,41 @@ func TestLoadBalancedWebService_Port(t *testing.T) {
require.Equal(t, uint16(80), actual)
}
+func TestLoadBalancedWebService_ServiceConnectEnabled(t *testing.T) {
+ testCases := map[string]struct {
+ mft *LoadBalancedWebService
+
+ wanted bool
+ }{
+ "enabled by default": {
+ mft: &LoadBalancedWebService{},
+ wanted: true,
+ },
+ "disable if explicitly set": {
+ mft: &LoadBalancedWebService{
+ LoadBalancedWebServiceConfig: LoadBalancedWebServiceConfig{
+ Network: NetworkConfig{
+ Connect: ServiceConnectBoolOrArgs{
+ EnableServiceConnect: aws.Bool(false),
+ },
+ },
+ },
+ },
+ wanted: false,
+ },
+ }
+
+ for name, tc := range testCases {
+ t.Run(name, func(t *testing.T) {
+ // WHEN
+ enabled := tc.mft.ServiceConnectEnabled()
+
+ // THEN
+ require.Equal(t, tc.wanted, enabled)
+ })
+ }
+}
+
func TestLoadBalancedWebService_Publish(t *testing.T) {
testCases := map[string]struct {
mft *LoadBalancedWebService
diff --git a/internal/pkg/manifest/svc_test.go b/internal/pkg/manifest/svc_test.go
index b0ddf5bc17a..192e6b4bad0 100644
--- a/internal/pkg/manifest/svc_test.go
+++ b/internal/pkg/manifest/svc_test.go
@@ -41,6 +41,8 @@ cpu: 512
memory: 1024
count: 1
exec: true
+network:
+ connect: true
http:
path: "svc"
target_container: "frontend"
@@ -161,6 +163,9 @@ environments:
PlacementString: placementStringP(PublicSubnetPlacement),
},
},
+ Connect: ServiceConnectBoolOrArgs{
+ EnableServiceConnect: aws.Bool(true),
+ },
},
TaskDefOverrides: []OverrideRule{
{
diff --git a/internal/pkg/manifest/transform.go b/internal/pkg/manifest/transform.go
index b28b294109b..79cc426968c 100644
--- a/internal/pkg/manifest/transform.go
+++ b/internal/pkg/manifest/transform.go
@@ -23,6 +23,7 @@ var defaultTransformers = []mergo.Transformers{
stringSliceOrStringTransformer{},
platformArgsOrStringTransformer{},
securityGroupsIDsOrConfigTransformer{},
+ serviceConnectTransformer{},
placementArgOrStringTransformer{},
subnetListOrArgsTransformer{},
healthCheckArgsOrStringTransformer{},
@@ -238,6 +239,32 @@ func (t placementArgOrStringTransformer) Transformer(typ reflect.Type) func(dst,
}
}
+type serviceConnectTransformer struct{}
+
+// Transformer returns custom merge logic for serviceConnectTransformer's fields.
+func (t serviceConnectTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
+ if typ != reflect.TypeOf(ServiceConnectBoolOrArgs{}) {
+ return nil
+ }
+
+ return func(dst, src reflect.Value) error {
+ dstStruct, srcStruct := dst.Interface().(ServiceConnectBoolOrArgs), src.Interface().(ServiceConnectBoolOrArgs)
+
+ if srcStruct.EnableServiceConnect != nil {
+ dstStruct.ServiceConnectArgs = ServiceConnectArgs{}
+ }
+
+ if !srcStruct.ServiceConnectArgs.isEmpty() {
+ dstStruct.EnableServiceConnect = nil
+ }
+
+ if dst.CanSet() { // For extra safety to prevent panicking.
+ dst.Set(reflect.ValueOf(dstStruct))
+ }
+ return nil
+ }
+}
+
type subnetListOrArgsTransformer struct{}
// Transformer returns custom merge logic for subnetListOrArgsTransformer's fields.
diff --git a/internal/pkg/manifest/transform_test.go b/internal/pkg/manifest/transform_test.go
index 0286bfbeaa4..9f7ed4cdf7c 100644
--- a/internal/pkg/manifest/transform_test.go
+++ b/internal/pkg/manifest/transform_test.go
@@ -592,6 +592,64 @@ func TestSubnetListOrArgsTransformer_Transformer(t *testing.T) {
}
}
+func TestServiceConnectTransformer_Transformer(t *testing.T) {
+ testCases := map[string]struct {
+ original func(p *ServiceConnectBoolOrArgs)
+ override func(p *ServiceConnectBoolOrArgs)
+ wanted func(p *ServiceConnectBoolOrArgs)
+ }{
+ "bool set to empty if args is not nil": {
+ original: func(s *ServiceConnectBoolOrArgs) {
+ s.EnableServiceConnect = aws.Bool(false)
+ },
+ override: func(s *ServiceConnectBoolOrArgs) {
+ s.ServiceConnectArgs = ServiceConnectArgs{
+ Alias: aws.String("api"),
+ }
+ },
+ wanted: func(s *ServiceConnectBoolOrArgs) {
+ s.ServiceConnectArgs = ServiceConnectArgs{
+ Alias: aws.String("api"),
+ }
+ },
+ },
+ "args set to empty if bool is not nil": {
+ original: func(s *ServiceConnectBoolOrArgs) {
+ s.ServiceConnectArgs = ServiceConnectArgs{
+ Alias: aws.String("api"),
+ }
+ },
+ override: func(s *ServiceConnectBoolOrArgs) {
+ s.EnableServiceConnect = aws.Bool(true)
+ },
+ wanted: func(s *ServiceConnectBoolOrArgs) {
+ s.EnableServiceConnect = aws.Bool(true)
+ },
+ },
+ }
+
+ for name, tc := range testCases {
+ t.Run(name, func(t *testing.T) {
+ var dst, override, wanted ServiceConnectBoolOrArgs
+
+ tc.original(&dst)
+ tc.override(&override)
+ tc.wanted(&wanted)
+
+ // Perform default merge.
+ err := mergo.Merge(&dst, override, mergo.WithOverride)
+ require.NoError(t, err)
+
+ // Use custom transformer.
+ err = mergo.Merge(&dst, override, mergo.WithOverride, mergo.WithTransformers(serviceConnectTransformer{}))
+ require.NoError(t, err)
+
+ require.NoError(t, err)
+ require.Equal(t, wanted, dst)
+ })
+ }
+}
+
func TestHealthCheckArgsOrStringTransformer_Transformer(t *testing.T) {
testCases := map[string]struct {
original func(h *HealthCheckArgsOrString)
diff --git a/internal/pkg/manifest/validate.go b/internal/pkg/manifest/validate.go
index 0c8d511b7f9..bd4a442ee8e 100644
--- a/internal/pkg/manifest/validate.go
+++ b/internal/pkg/manifest/validate.go
@@ -78,6 +78,7 @@ func (l LoadBalancedWebService) validate() error {
}
if err = validateTargetContainer(validateTargetContainerOpts{
mainContainerName: aws.StringValue(l.Name),
+ mainContainerPort: l.ImageConfig.Port,
targetContainer: l.RoutingRule.GetTargetContainer(),
sidecarConfig: l.Sidecars,
}); err != nil {
@@ -85,6 +86,7 @@ func (l LoadBalancedWebService) validate() error {
}
if err = validateTargetContainer(validateTargetContainerOpts{
mainContainerName: aws.StringValue(l.Name),
+ mainContainerPort: l.ImageConfig.Port,
targetContainer: l.NLBConfig.TargetContainer,
sidecarConfig: l.Sidecars,
}); err != nil {
@@ -193,6 +195,19 @@ func (b BackendService) validate() error {
if err = b.Workload.validate(); err != nil {
return err
}
+ if err = validateTargetContainer(validateTargetContainerOpts{
+ mainContainerName: aws.StringValue(b.Name),
+ mainContainerPort: b.ImageConfig.Port,
+ targetContainer: b.RoutingRule.GetTargetContainer(),
+ sidecarConfig: b.Sidecars,
+ }); err != nil {
+ return fmt.Errorf("validate HTTP load balancer target: %w", err)
+ }
+ if b.ServiceConnectEnabled() {
+ if b.RoutingRule.GetTargetContainer() == nil && b.ImageConfig.Port == nil {
+ return fmt.Errorf(`cannot enable "network.connect" when no port exposed`)
+ }
+ }
if err = validateContainerDeps(validateDependenciesOpts{
sidecarConfig: b.Sidecars,
imageConfig: b.ImageConfig.Image,
@@ -1245,6 +1260,19 @@ func (n NetworkConfig) validate() error {
if err := n.VPC.validate(); err != nil {
return fmt.Errorf(`validate "vpc": %w`, err)
}
+ if err := n.Connect.validate(); err != nil {
+ return fmt.Errorf(`validate "connect": %w`, err)
+ }
+ return nil
+}
+
+// validate returns nil if ServiceConnectBoolOrArgs is configured correctly.
+func (s ServiceConnectBoolOrArgs) validate() error {
+ return s.ServiceConnectArgs.validate()
+}
+
+// validate is a no-op for ServiceConnectArgs.
+func (ServiceConnectArgs) validate() error {
return nil
}
@@ -1590,6 +1618,7 @@ type containerDependency struct {
type validateTargetContainerOpts struct {
mainContainerName string
+ mainContainerPort *uint16
targetContainer *string
sidecarConfig map[string]*SidecarConfig
}
@@ -1609,14 +1638,17 @@ func validateTargetContainer(opts validateTargetContainerOpts) error {
}
targetContainer := aws.StringValue(opts.targetContainer)
if targetContainer == opts.mainContainerName {
+ if opts.mainContainerPort == nil {
+ return fmt.Errorf("target container %q doesn't expose a port", targetContainer)
+ }
return nil
}
sidecar, ok := opts.sidecarConfig[targetContainer]
if !ok {
- return fmt.Errorf("target container %s doesn't exist", targetContainer)
+ return fmt.Errorf("target container %q doesn't exist", targetContainer)
}
if sidecar.Port == nil {
- return fmt.Errorf("target container %s doesn't expose any port", targetContainer)
+ return fmt.Errorf("target container %q doesn't expose a port", targetContainer)
}
return nil
}
diff --git a/internal/pkg/manifest/validate_test.go b/internal/pkg/manifest/validate_test.go
index 7a123d4f2b1..d1d0515f7d3 100644
--- a/internal/pkg/manifest/validate_test.go
+++ b/internal/pkg/manifest/validate_test.go
@@ -82,7 +82,7 @@ func TestLoadBalancedWebService_validate(t *testing.T) {
LoadBalancedWebServiceConfig: LoadBalancedWebServiceConfig{
ImageConfig: testImageConfig,
Network: NetworkConfig{
- vpcConfig{
+ VPC: vpcConfig{
Placement: PlacementArgOrString{
PlacementString: (*PlacementString)(aws.String("")),
},
@@ -409,7 +409,7 @@ func TestBackendService_validate(t *testing.T) {
BackendServiceConfig: BackendServiceConfig{
ImageConfig: testImageConfig,
Network: NetworkConfig{
- vpcConfig{
+ VPC: vpcConfig{
Placement: PlacementArgOrString{
PlacementString: (*PlacementString)(aws.String("")),
},
@@ -580,6 +580,37 @@ func TestBackendService_validate(t *testing.T) {
},
wantedErrorMsgPrefix: `validate "publish": `,
},
+ "error if target container not found": {
+ config: BackendService{
+ BackendServiceConfig: BackendServiceConfig{
+ ImageConfig: testImageConfig,
+ RoutingRule: RoutingRuleConfiguration{
+ TargetContainer: aws.String("api"),
+ Path: aws.String("/"),
+ },
+ },
+ Workload: Workload{
+ Name: aws.String("api"),
+ },
+ },
+ wantedError: fmt.Errorf(`validate HTTP load balancer target: target container "api" doesn't expose a port`),
+ },
+ "error if service connect is enabled without any port exposed": {
+ config: BackendService{
+ BackendServiceConfig: BackendServiceConfig{
+ ImageConfig: testImageConfig,
+ Network: NetworkConfig{
+ Connect: ServiceConnectBoolOrArgs{
+ EnableServiceConnect: aws.Bool(true),
+ },
+ },
+ },
+ Workload: Workload{
+ Name: aws.String("api"),
+ },
+ },
+ wantedError: fmt.Errorf(`cannot enable "network.connect" when no port exposed`),
+ },
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
@@ -786,7 +817,7 @@ func TestWorkerService_validate(t *testing.T) {
WorkerServiceConfig: WorkerServiceConfig{
ImageConfig: testImageConfig,
Network: NetworkConfig{
- vpcConfig{
+ VPC: vpcConfig{
Placement: PlacementArgOrString{
PlacementString: (*PlacementString)(aws.String("")),
},
@@ -992,7 +1023,7 @@ func TestScheduledJob_validate(t *testing.T) {
ScheduledJobConfig: ScheduledJobConfig{
ImageConfig: testImageConfig,
Network: NetworkConfig{
- vpcConfig{
+ VPC: vpcConfig{
Placement: PlacementArgOrString{
PlacementString: (*PlacementString)(aws.String("")),
},
@@ -3033,9 +3064,9 @@ func TestValidateLoadBalancerTarget(t *testing.T) {
mainContainerName: "mockMainContainer",
targetContainer: aws.String("foo"),
},
- wanted: fmt.Errorf("target container foo doesn't exist"),
+ wanted: fmt.Errorf(`target container "foo" doesn't exist`),
},
- "should return an error if target container doesn't expose any port": {
+ "should return an error if target container doesn't expose a port": {
in: validateTargetContainerOpts{
mainContainerName: "mockMainContainer",
targetContainer: aws.String("foo"),
@@ -3043,7 +3074,7 @@ func TestValidateLoadBalancerTarget(t *testing.T) {
"foo": {},
},
},
- wanted: fmt.Errorf("target container foo doesn't expose any port"),
+ wanted: fmt.Errorf(`target container "foo" doesn't expose a port`),
},
"success with no target container set": {
in: validateTargetContainerOpts{
diff --git a/internal/pkg/manifest/workload.go b/internal/pkg/manifest/workload.go
index 2767a3a9066..8a02e0184b8 100644
--- a/internal/pkg/manifest/workload.go
+++ b/internal/pkg/manifest/workload.go
@@ -47,13 +47,14 @@ var (
var (
ErrAppRunnerInvalidPlatformWindows = errors.New("Windows is not supported for App Runner services")
- errUnmarshalBuildOpts = errors.New("unable to unmarshal build field into string or compose-style map")
- errUnmarshalPlatformOpts = errors.New("unable to unmarshal platform field into string or compose-style map")
- errUnmarshalSecurityGroupOpts = errors.New(`unable to unmarshal "security_groups" field into slice of strings or compose-style map`)
- errUnmarshalPlacementOpts = errors.New("unable to unmarshal placement field into string or compose-style map")
- errUnmarshalSubnetsOpts = errors.New("unable to unmarshal subnets field into string slice or compose-style map")
- errUnmarshalCountOpts = errors.New(`unable to unmarshal "count" field to an integer or autoscaling configuration`)
- errUnmarshalRangeOpts = errors.New(`unable to unmarshal "range" field`)
+ errUnmarshalBuildOpts = errors.New("unable to unmarshal build field into string or compose-style map")
+ errUnmarshalPlatformOpts = errors.New("unable to unmarshal platform field into string or compose-style map")
+ errUnmarshalSecurityGroupOpts = errors.New(`unable to unmarshal "security_groups" field into slice of strings or compose-style map`)
+ errUnmarshalPlacementOpts = errors.New("unable to unmarshal placement field into string or compose-style map")
+ errUnmarshalServiceConnectOpts = errors.New(`unable to unmarshal "connect" field into boolean or compose-style map`)
+ errUnmarshalSubnetsOpts = errors.New("unable to unmarshal subnets field into string slice or compose-style map")
+ errUnmarshalCountOpts = errors.New(`unable to unmarshal "count" field to an integer or autoscaling configuration`)
+ errUnmarshalRangeOpts = errors.New(`unable to unmarshal "range" field`)
errUnmarshalExec = errors.New(`unable to unmarshal "exec" field into boolean or exec configuration`)
errUnmarshalEntryPoint = errors.New(`unable to unmarshal "entrypoint" into string or slice of strings`)
@@ -458,7 +459,8 @@ func (t *FIFOTopicAdvanceConfigOrBool) UnmarshalYAML(value *yaml.Node) error {
// NetworkConfig represents options for network connection to AWS resources within a VPC.
type NetworkConfig struct {
- VPC vpcConfig `yaml:"vpc"`
+ VPC vpcConfig `yaml:"vpc"`
+ Connect ServiceConnectBoolOrArgs `yaml:"connect"`
}
// IsEmpty returns empty if the struct has all zero members.
@@ -473,6 +475,41 @@ func (c *NetworkConfig) requiredEnvFeatures() []string {
return nil
}
+// ServiceConnectBoolOrArgs represents ECS Service Connect configuration.
+type ServiceConnectBoolOrArgs struct {
+ EnableServiceConnect *bool
+ ServiceConnectArgs
+}
+
+// UnmarshalYAML overrides the default YAML unmarshaling logic for the ServiceConnect
+// struct, allowing it to perform more complex unmarshaling behavior.
+// This method implements the yaml.Unmarshaler (v3) interface.
+func (s *ServiceConnectBoolOrArgs) UnmarshalYAML(value *yaml.Node) error {
+ if err := value.Decode(&s.ServiceConnectArgs); err != nil {
+ var yamlTypeErr *yaml.TypeError
+ if !errors.As(err, &yamlTypeErr) {
+ return err
+ }
+ }
+ if !s.ServiceConnectArgs.isEmpty() {
+ s.EnableServiceConnect = nil
+ return nil
+ }
+ if err := value.Decode(&s.EnableServiceConnect); err != nil {
+ return errUnmarshalServiceConnectOpts
+ }
+ return nil
+}
+
+// ServiceConnectArgs includes the advanced configuration for ECS Service Connect.
+type ServiceConnectArgs struct {
+ Alias *string
+}
+
+func (s *ServiceConnectArgs) isEmpty() bool {
+ return s.Alias == nil
+}
+
// PlacementArgOrString represents where to place tasks.
type PlacementArgOrString struct {
*PlacementString
diff --git a/internal/pkg/manifest/workload_test.go b/internal/pkg/manifest/workload_test.go
index bf18c7eb5cd..9fa794ee71b 100644
--- a/internal/pkg/manifest/workload_test.go
+++ b/internal/pkg/manifest/workload_test.go
@@ -354,6 +354,49 @@ func TestPlatformArgsOrString_UnmarshalYAML(t *testing.T) {
}
}
+func TestServiceConnect_UnmarshalYAML(t *testing.T) {
+ testCases := map[string]struct {
+ inContent []byte
+
+ wantedStruct ServiceConnectBoolOrArgs
+ wantedError error
+ }{
+ "returns error if both bool and args specified": {
+ inContent: []byte(`connect: true
+ alias: api`),
+
+ wantedError: errors.New("yaml: line 2: mapping values are not allowed in this context"),
+ },
+ "error if unmarshalable": {
+ inContent: []byte(`connect:
+ ohess: linus
+ archie: leg64`),
+ wantedError: errUnmarshalServiceConnectOpts,
+ },
+ "success": {
+ inContent: []byte(`connect:
+ alias: api`),
+ wantedStruct: ServiceConnectBoolOrArgs{
+ ServiceConnectArgs: ServiceConnectArgs{
+ Alias: aws.String("api"),
+ },
+ },
+ },
+ }
+ for name, tc := range testCases {
+ t.Run(name, func(t *testing.T) {
+ v := NetworkConfig{}
+ err := yaml.Unmarshal(tc.inContent, &v)
+ if tc.wantedError != nil {
+ require.EqualError(t, err, tc.wantedError.Error())
+ } else {
+ require.NoError(t, err)
+ require.Equal(t, tc.wantedStruct, v.Connect)
+ }
+ })
+ }
+}
+
func TestPlacementArgOrString_UnmarshalYAML(t *testing.T) {
testCases := map[string]struct {
inContent []byte
diff --git a/internal/pkg/template/templates/workloads/partials/cf/service-base-properties.yml b/internal/pkg/template/templates/workloads/partials/cf/service-base-properties.yml
index 03e9a27ec1d..e066eaf68f6 100644
--- a/internal/pkg/template/templates/workloads/partials/cf/service-base-properties.yml
+++ b/internal/pkg/template/templates/workloads/partials/cf/service-base-properties.yml
@@ -33,10 +33,10 @@ CapacityProviderStrategy:
{{- end}}
{{- end}}
{{- end }}
-{{- if .ServiceConnect }}
+{{- if and .ServiceConnect .SCFeatureFlag }}
ServiceConnectConfiguration:
Enabled: True
- Namespace: {{.ServiceConnect.Namespace}}
+ Namespace: {{.ServiceDiscoveryEndpoint}}
LogConfiguration:
LogDriver: awslogs
Options:
@@ -44,7 +44,7 @@ ServiceConnectConfiguration:
awslogs-group: !Ref LogGroup
awslogs-stream-prefix: copilot
Services:
- - PortName: TargetPort
+ - PortName: target
# Avoid using the same service with Service Discovery in a namespace.
DiscoveryName: !Join ["-", [!Ref WorkloadName, "sc"]]
ClientAliases:
diff --git a/internal/pkg/template/templates/workloads/partials/cf/sidecars.yml b/internal/pkg/template/templates/workloads/partials/cf/sidecars.yml
index 770df616c85..64691acb590 100644
--- a/internal/pkg/template/templates/workloads/partials/cf/sidecars.yml
+++ b/internal/pkg/template/templates/workloads/partials/cf/sidecars.yml
@@ -48,11 +48,10 @@
{{- if $sidecar.Port}}
PortMappings:
- ContainerPort: {{$sidecar.Port}}
- {{- if $.ServiceConnect }} # remove when release
- {{- if eq $.HTTPTargetContainer.Container $sidecar.Name}}
- {{- if eq $.HTTPTargetContainer.Port $sidecar.Port}}
- Name: TargetPort
- {{- end}}
+ {{- if and $.ServiceConnect $.SCFeatureFlag}} # remove when release
+ {{/* Multiple exposed port is not supported yet. Therefore, if the target container is the sidecar, the target port must be the one and only one port that the container exposes. */}}
+ {{- if eq $.HTTPTargetContainer.Name $sidecar.Name}}
+ Name: target
{{- end}}
{{- end}}
{{- if $sidecar.Protocol}}
diff --git a/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml b/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml
index 9845a28f0cb..e451cb152ad 100644
--- a/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml
+++ b/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml
@@ -29,9 +29,10 @@
{{- if eq .WorkloadType "Load Balanced Web Service"}}
PortMappings:
- ContainerPort: !Ref ContainerPort
- {{- if .ServiceConnect }} # remove when release
- {{- if eq .HTTPTargetContainer.Container .WorkloadName}}
- Name: TargetPort
+ {{- if and .ServiceConnect .SCFeatureFlag}} # remove when release
+ {{/* Multiple exposed port is not supported yet. Therefore, if the target container is the sidecar, the target port must be the one and only one port that the container exposes. */}}
+ {{- if eq .HTTPTargetContainer.Name .WorkloadName}}
+ Name: target
{{- end}}
{{- end}}
{{- if .NLB}} {{ $nlbListener := .NLB.Listener }}
@@ -43,12 +44,14 @@
{{- end}}
{{- end}} {{/* end if eq .WorkloadType "Load Balanced Web Service"*/}}
{{- if eq .WorkloadType "Backend Service"}}
-{{- if .ServiceConnect }} # remove when release
- PortMappings: !If [ExposePort, [{ContainerPort: !Ref ContainerPort, Name: !Ref WorkloadName}], !Ref "AWS::NoValue"]
+{{- if eq .HTTPTargetContainer.Name .WorkloadName}}
+{{- if and .ServiceConnect .SCFeatureFlag}} # remove when release
+ PortMappings: !If [ExposePort, [{ContainerPort: !Ref ContainerPort, Name: target}], !Ref "AWS::NoValue"]
{{- else}}
PortMappings: !If [ExposePort, [{ContainerPort: !Ref ContainerPort}], !Ref "AWS::NoValue"]
{{- end}}
{{- end}}
+{{- end}}
{{- if .HealthCheck}}
HealthCheck:
Command: {{quoteSlice .HealthCheck.Command | fmtSlice}}
diff --git a/internal/pkg/template/workload.go b/internal/pkg/template/workload.go
index a5bd3f555cb..b10baf5933a 100644
--- a/internal/pkg/template/workload.go
+++ b/internal/pkg/template/workload.go
@@ -121,7 +121,7 @@ type WorkloadNestedStackOpts struct {
// SidecarOpts holds configuration that's needed if the service has sidecar containers.
type SidecarOpts struct {
- Name *string
+ Name string
Image *string
Essential *bool
Port *string
@@ -210,8 +210,8 @@ type LogConfigOpts struct {
// HTTPTargetContainer represents the target group of a load balancer that points to a container.
type HTTPTargetContainer struct {
- Container string
- Port string // Port of the container.
+ Name string
+ Port string
}
// IsHTTPS returns true if the target container's port is 443.
@@ -331,8 +331,7 @@ type NetworkLoadBalancer struct {
// ServiceConnect holds configuration for ECS Service Connect.
type ServiceConnect struct {
- Namespace string
- Alias *string
+ Alias *string
}
// AdvancedCount holds configuration for autoscaling and capacity provider
@@ -597,6 +596,8 @@ type WorkloadOpts struct {
// Additional options for worker service templates.
Subscribe *SubscribeOpts
+
+ SCFeatureFlag bool
}
// HealthCheckProtocol returns the protocol for the Load Balancer health check,
From 2a387f56d293aa5c8bad5157e5068fe2544ff92c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 13 Oct 2022 17:00:41 +0000
Subject: [PATCH 10/14] chore: Bump github.com/onsi/gomega from 1.20.2 to
1.21.1 (#4073)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.20.2 to 1.21.1.
Release notes
Sourced from github.com/onsi/gomega's releases.
v1.21.1
Features
- Eventually and Consistently that are passed a SpecContext can provide reports when an interrupt occurs [0d063c9]
v1.21.0
1.21.0
Features
- Eventually and Consistently can take a context.Context [65c01bc]
This enables integration with Ginkgo 2.3.0's interruptible nodes and node timeouts.
- Introduces Eventually.Within.ProbeEvery with tests and documentation (#591) [f633800]
- New BeKeyOf matcher with documentation and unit tests (#590) [fb586b3]
Fixes
- Cover the entire gmeasure suite with leak detection [8c54344]
- Fix gmeasure leak [119d4ce]
- Ignore new Ginkgo ProgressSignal goroutine in gleak [ba548e2]
Maintenance
- Fixes crashes on newer Ruby 3 installations by upgrading github-pages gem dependency (#596) [12469a0]
Changelog
Sourced from github.com/onsi/gomega's changelog.
v1.21.1
Features
- Eventually and Consistently that are passed a SpecContext can provide reports when an interrupt occurs [0d063c9]
1.21.0
Features
- Eventually and Consistently can take a context.Context [65c01bc]
This enables integration with Ginkgo 2.3.0's interruptible nodes and node timeouts.
- Introduces Eventually.Within.ProbeEvery with tests and documentation (#591) [f633800]
- New BeKeyOf matcher with documentation and unit tests (#590) [fb586b3]
Fixes
- Cover the entire gmeasure suite with leak detection [8c54344]
- Fix gmeasure leak [119d4ce]
- Ignore new Ginkgo ProgressSignal goroutine in gleak [ba548e2]
Maintenance
- Fixes crashes on newer Ruby 3 installations by upgrading github-pages gem dependency (#596) [12469a0]
Commits
2e34979 v1.21.1
0d063c9 Eventually and Consistently that are passed a SpecContext can provide reports...
2ba5763 v1.21.0
65c01bc Eventually and Consistently can take a context.Context
12469a0 fixes crashes on newer Ruby 3 installations by upgrading github-pages gem dep...
b8636ad documentation updates for BeKeyOf and well-known non-leaky goroutines (#592)
f633800 introduces Eventually.Within.ProbeEvery with tests and documentation (#591)
fb586b3 new BeKeyOf matcher with documentation and unit tests (#590)
647a36b welp; remove local Ginkgo replace directive in go.mod
8c54344 cover the entire gmeasure suite with leak detection
- Additional commits viewable in compare view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 1d29538e224..bcfc20296e0 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
github.com/lnquy/cron v1.1.1
github.com/moby/buildkit v0.9.3
github.com/onsi/ginkgo v1.16.5
- github.com/onsi/gomega v1.20.2
+ github.com/onsi/gomega v1.21.1
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/afero v1.9.2
github.com/spf13/cobra v1.5.0
diff --git a/go.sum b/go.sum
index 1c596af2f45..33975da8e8c 100644
--- a/go.sum
+++ b/go.sum
@@ -889,8 +889,8 @@ github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
-github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY=
-github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
+github.com/onsi/gomega v1.21.1 h1:OB/euWYIExnPBohllTicTHmGTrMaqJ67nIu80j0/uEM=
+github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
From 08983f7fd662621f6cb0c59964122ab65ee2c941 Mon Sep 17 00:00:00 2001
From: KollaAdithya <71282729+KollaAdithya@users.noreply.github.com>
Date: Thu, 13 Oct 2022 10:21:21 -0700
Subject: [PATCH 11/14] feat: Show permissions boundary policy name in app show
(#4071)
This PR resolves #4064
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.
---
internal/pkg/cli/app_show.go | 17 ++--
internal/pkg/cli/app_show_test.go | 152 +++++++++++++++++++++++-------
internal/pkg/describe/app.go | 24 +++--
3 files changed, 142 insertions(+), 51 deletions(-)
diff --git a/internal/pkg/cli/app_show.go b/internal/pkg/cli/app_show.go
index a996af17779..19ad5570576 100644
--- a/internal/pkg/cli/app_show.go
+++ b/internal/pkg/cli/app_show.go
@@ -217,14 +217,15 @@ func (o *showAppOpts) description() (*describe.App, error) {
return nil, fmt.Errorf("get version for application %s: %w", o.name, err)
}
return &describe.App{
- Name: app.Name,
- Version: version,
- URI: app.Domain,
- Envs: trimmedEnvs,
- Services: trimmedSvcs,
- Jobs: trimmedJobs,
- Pipelines: pipelineInfo,
- WkldDeployedtoEnvs: wkldDeployedtoEnvs,
+ Name: app.Name,
+ Version: version,
+ URI: app.Domain,
+ PermissionsBoundary: app.PermissionsBoundary,
+ Envs: trimmedEnvs,
+ Services: trimmedSvcs,
+ Jobs: trimmedJobs,
+ Pipelines: pipelineInfo,
+ WkldDeployedtoEnvs: wkldDeployedtoEnvs,
}, nil
}
diff --git a/internal/pkg/cli/app_show_test.go b/internal/pkg/cli/app_show_test.go
index b179636e6b9..03e4b4d454d 100644
--- a/internal/pkg/cli/app_show_test.go
+++ b/internal/pkg/cli/app_show_test.go
@@ -188,8 +188,9 @@ func TestShowAppOpts_Execute(t *testing.T) {
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListServices("my-app").Return([]*config.Workload{
{
@@ -231,13 +232,14 @@ func TestShowAppOpts_Execute(t *testing.T) {
m.versionGetter.EXPECT().Version().Return("v0.0.0", nil)
},
- wantedContent: "{\"name\":\"my-app\",\"version\":\"v0.0.0\",\"uri\":\"example.com\",\"environments\":[{\"app\":\"\",\"name\":\"test\",\"region\":\"us-west-2\",\"accountID\":\"123456789\",\"registryURL\":\"\",\"executionRoleARN\":\"\",\"managerRoleARN\":\"\"},{\"app\":\"\",\"name\":\"prod\",\"region\":\"us-west-1\",\"accountID\":\"123456789\",\"registryURL\":\"\",\"executionRoleARN\":\"\",\"managerRoleARN\":\"\"}],\"services\":[{\"app\":\"\",\"name\":\"my-svc\",\"type\":\"lb-web-svc\"}],\"jobs\":[{\"app\":\"\",\"name\":\"my-job\",\"type\":\"Scheduled Job\"}],\"pipelines\":[{\"pipelineName\":\"my-pipeline-repo\",\"region\":\"\",\"accountId\":\"\",\"stages\":null,\"createdAt\":\"0001-01-01T00:00:00Z\",\"updatedAt\":\"0001-01-01T00:00:00Z\"},{\"pipelineName\":\"bad-goose\",\"region\":\"\",\"accountId\":\"\",\"stages\":null,\"createdAt\":\"0001-01-01T00:00:00Z\",\"updatedAt\":\"0001-01-01T00:00:00Z\"}]}\n",
+ wantedContent: "{\"name\":\"my-app\",\"version\":\"v0.0.0\",\"uri\":\"example.com\",\"permissionsBoundary\":\"examplePermissionsBoundaryPolicy\",\"environments\":[{\"app\":\"\",\"name\":\"test\",\"region\":\"us-west-2\",\"accountID\":\"123456789\",\"registryURL\":\"\",\"executionRoleARN\":\"\",\"managerRoleARN\":\"\"},{\"app\":\"\",\"name\":\"prod\",\"region\":\"us-west-1\",\"accountID\":\"123456789\",\"registryURL\":\"\",\"executionRoleARN\":\"\",\"managerRoleARN\":\"\"}],\"services\":[{\"app\":\"\",\"name\":\"my-svc\",\"type\":\"lb-web-svc\"}],\"jobs\":[{\"app\":\"\",\"name\":\"my-job\",\"type\":\"Scheduled Job\"}],\"pipelines\":[{\"pipelineName\":\"my-pipeline-repo\",\"region\":\"\",\"accountId\":\"\",\"stages\":null,\"createdAt\":\"0001-01-01T00:00:00Z\",\"updatedAt\":\"0001-01-01T00:00:00Z\"},{\"pipelineName\":\"bad-goose\",\"region\":\"\",\"accountId\":\"\",\"stages\":null,\"createdAt\":\"0001-01-01T00:00:00Z\",\"updatedAt\":\"0001-01-01T00:00:00Z\"}]}\n",
},
"correctly shows human output": {
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListServices("my-app").Return([]*config.Workload{
{
@@ -281,9 +283,10 @@ func TestShowAppOpts_Execute(t *testing.T) {
wantedContent: `About
- Name my-app
- Version v0.0.0 (latest available: v1.1.0)
- URI example.com
+ Name my-app
+ Version v0.0.0 (latest available: v1.1.0)
+ URI example.com
+ Permissions Boundary examplePermissionsBoundaryPolicy
Environments
@@ -310,8 +313,9 @@ Pipelines
"correctly shows human output with latest version": {
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListServices("my-app").Return([]*config.Workload{
{
@@ -347,9 +351,77 @@ Pipelines
wantedContent: `About
- Name my-app
- Version v1.1.0
- URI example.com
+ Name my-app
+ Version v1.1.0
+ URI example.com
+ Permissions Boundary examplePermissionsBoundaryPolicy
+
+Environments
+
+ Name AccountID Region
+ ---- --------- ------
+ test 123456789 us-west-2
+ prod 123456789 us-west-1
+
+Workloads
+
+ Name Type Environments
+ ---- ---- ------------
+ my-svc lb-web-svc prod, test
+ my-job Scheduled Job prod, test
+
+Pipelines
+
+ Name
+ ----
+`,
+ },
+ "correctly shows human output when URI and Permissions Boundary are empty": {
+ setupMocks: func(m showAppMocks) {
+ m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
+ Name: "my-app",
+ Domain: "",
+ PermissionsBoundary: "",
+ }, nil)
+ m.storeSvc.EXPECT().ListServices("my-app").Return([]*config.Workload{
+ {
+ Name: "my-svc",
+ Type: "lb-web-svc",
+ },
+ }, nil)
+ m.storeSvc.EXPECT().ListJobs("my-app").Return([]*config.Workload{
+ {
+ Name: "my-job",
+ Type: "Scheduled Job",
+ },
+ }, nil)
+ m.storeSvc.EXPECT().ListEnvironments("my-app").Return([]*config.Environment{
+ {
+ Name: "test",
+ Region: "us-west-2",
+ AccountID: "123456789",
+ },
+ {
+ Name: "prod",
+ AccountID: "123456789",
+ Region: "us-west-1",
+ },
+ }, nil)
+ m.deployStore.EXPECT().ListDeployedJobs("my-app", "test").Return([]string{"my-job"}, nil)
+ m.deployStore.EXPECT().ListDeployedJobs("my-app", "prod").Return([]string{"my-job"}, nil)
+ m.deployStore.EXPECT().ListDeployedServices("my-app", "test").Return([]string{"my-svc"}, nil)
+ m.deployStore.EXPECT().ListDeployedServices("my-app", "prod").Return([]string{"my-svc"}, nil)
+ m.pipelineLister.EXPECT().ListDeployedPipelines(mockAppName).Return([]deploy.Pipeline{}, nil)
+ m.versionGetter.EXPECT().Version().Return(deploy.LatestAppTemplateVersion, nil)
+
+ },
+
+ wantedContent: `About
+
+ Name my-app
+ Version v1.1.0
+ URI N/A
+ Permissions Boundary N/A
Environments
@@ -374,8 +446,9 @@ Pipelines
"when service/job is not deployed": {
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListServices("my-app").Return([]*config.Workload{
{
@@ -415,9 +488,10 @@ Pipelines
wantedContent: `About
- Name my-app
- Version v1.1.0
- URI example.com
+ Name my-app
+ Version v1.1.0
+ URI example.com
+ Permissions Boundary examplePermissionsBoundaryPolicy
Environments
@@ -442,8 +516,9 @@ Pipelines
}, "when multiple services/jobs are deployed": {
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListServices("my-app").Return([]*config.Workload{
{
@@ -504,9 +579,10 @@ Pipelines
wantedContent: `About
- Name my-app
- Version v1.1.0
- URI example.com
+ Name my-app
+ Version v1.1.0
+ URI example.com
+ Permissions Boundary examplePermissionsBoundaryPolicy
Environments
@@ -544,8 +620,9 @@ Pipelines
"returns error if fail to list environment": {
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListEnvironments("my-app").Return(nil, testError)
},
@@ -557,8 +634,9 @@ Pipelines
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListEnvironments("my-app").Return([]*config.Environment{
{
@@ -582,8 +660,9 @@ Pipelines
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListEnvironments("my-app").Return([]*config.Environment{
{
@@ -613,8 +692,9 @@ Pipelines
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListEnvironments("my-app").Return([]*config.Environment{
{
@@ -653,8 +733,9 @@ Pipelines
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListEnvironments("my-app").Return([]*config.Environment{
{
@@ -695,8 +776,9 @@ Pipelines
setupMocks: func(m showAppMocks) {
m.storeSvc.EXPECT().GetApplication("my-app").Return(&config.Application{
- Name: "my-app",
- Domain: "example.com",
+ Name: "my-app",
+ Domain: "example.com",
+ PermissionsBoundary: "examplePermissionsBoundaryPolicy",
}, nil)
m.storeSvc.EXPECT().ListEnvironments("my-app").Return([]*config.Environment{
{
diff --git a/internal/pkg/describe/app.go b/internal/pkg/describe/app.go
index 60a99dfc274..14265be48a4 100644
--- a/internal/pkg/describe/app.go
+++ b/internal/pkg/describe/app.go
@@ -23,14 +23,15 @@ import (
// App contains serialized parameters for an application.
type App struct {
- Name string `json:"name"`
- Version string `json:"version"`
- URI string `json:"uri"`
- Envs []*config.Environment `json:"environments"`
- Services []*config.Workload `json:"services"`
- Jobs []*config.Workload `json:"jobs"`
- Pipelines []*codepipeline.Pipeline `json:"pipelines"`
- WkldDeployedtoEnvs map[string][]string `json:"-"`
+ Name string `json:"name"`
+ Version string `json:"version"`
+ URI string `json:"uri"`
+ PermissionsBoundary string `json:"permissionsBoundary"`
+ Envs []*config.Environment `json:"environments"`
+ Services []*config.Workload `json:"services"`
+ Jobs []*config.Workload `json:"jobs"`
+ Pipelines []*codepipeline.Pipeline `json:"pipelines"`
+ WkldDeployedtoEnvs map[string][]string `json:"-"`
}
// JSONString returns the stringified App struct with json format.
@@ -53,8 +54,15 @@ func (a *App) HumanString() string {
if deploy.LatestAppTemplateVersion != a.Version {
availableVersion = color.Yellow.Sprintf("(latest available: %s)", deploy.LatestAppTemplateVersion)
}
+ if a.URI == "" {
+ a.URI = "N/A"
+ }
+ if a.PermissionsBoundary == "" {
+ a.PermissionsBoundary = "N/A"
+ }
fmt.Fprintf(writer, " %s\t%s %s\n", "Version", a.Version, availableVersion)
fmt.Fprintf(writer, " %s\t%s\n", "URI", a.URI)
+ fmt.Fprintf(writer, " %s\t%s\n", "Permissions Boundary", a.PermissionsBoundary)
fmt.Fprint(writer, color.Bold.Sprint("\nEnvironments\n\n"))
writer.Flush()
headers := []string{"Name", "AccountID", "Region"}
From 2d07c856f51e8bd5b2384773cff4a586b2caf99e Mon Sep 17 00:00:00 2001
From: KollaAdithya <71282729+KollaAdithya@users.noreply.github.com>
Date: Thu, 13 Oct 2022 13:12:20 -0700
Subject: [PATCH 12/14] feat: Set ReadonlyRootFilesystem to true by default in
manifest (#4062)
This PR resolves #4010
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.
---
.../deploy/cloudformation/stack/transformers.go | 1 +
internal/pkg/manifest/storage.go | 7 ++++---
internal/pkg/manifest/storage_test.go | 5 +++++
.../testdata/backend-svc-customhealthcheck.yml | 3 +++
.../testdata/backend-svc-nohealthcheck.yml | 3 +++
internal/pkg/manifest/testdata/lb-svc.yml | 5 ++++-
.../manifest/testdata/worker-svc-nosubscribe.yml | 6 +++++-
.../pkg/manifest/testdata/worker-svc-subscribe.yml | 6 +++++-
.../worker-svc-with-default-fifo-queue.yml | 6 +++++-
internal/pkg/manifest/validate.go | 8 ++++++++
internal/pkg/manifest/validate_test.go | 14 +++++++++++++-
.../workloads/partials/cf/workload-container.yml | 3 +++
.../workloads/services/backend/manifest.yml | 5 +++++
.../workloads/services/lb-web/manifest.yml | 7 ++++++-
.../workloads/services/worker/manifest.yml | 8 +++++++-
internal/pkg/template/workload.go | 1 +
16 files changed, 78 insertions(+), 10 deletions(-)
diff --git a/internal/pkg/deploy/cloudformation/stack/transformers.go b/internal/pkg/deploy/cloudformation/stack/transformers.go
index 88fb9267082..8f6878e5ffa 100644
--- a/internal/pkg/deploy/cloudformation/stack/transformers.go
+++ b/internal/pkg/deploy/cloudformation/stack/transformers.go
@@ -537,6 +537,7 @@ func convertStorageOpts(wlName *string, in manifest.Storage) *template.StorageOp
}
return &template.StorageOpts{
Ephemeral: convertEphemeral(in.Ephemeral),
+ ReadonlyRootFS: in.ReadonlyRootFS,
Volumes: convertVolumes(in.Volumes),
MountPoints: convertMountPoints(in.Volumes),
EFSPerms: convertEFSPermissions(in.Volumes),
diff --git a/internal/pkg/manifest/storage.go b/internal/pkg/manifest/storage.go
index a83c79f415a..f0db4f4baf0 100644
--- a/internal/pkg/manifest/storage.go
+++ b/internal/pkg/manifest/storage.go
@@ -17,13 +17,14 @@ var (
// Storage represents the options for external and native storage.
type Storage struct {
- Ephemeral *int `yaml:"ephemeral"`
- Volumes map[string]*Volume `yaml:"volumes"` // NOTE: keep the pointers because `mergo` doesn't automatically deep merge map's value unless it's a pointer type.
+ Ephemeral *int `yaml:"ephemeral"`
+ ReadonlyRootFS *bool `yaml:"readonly_fs"`
+ Volumes map[string]*Volume `yaml:"volumes"` // NOTE: keep the pointers because `mergo` doesn't automatically deep merge map's value unless it's a pointer type.
}
// IsEmpty returns empty if the struct has all zero members.
func (s *Storage) IsEmpty() bool {
- return s.Ephemeral == nil && s.Volumes == nil
+ return s.Ephemeral == nil && s.Volumes == nil && s.ReadonlyRootFS == nil
}
func (s *Storage) requiredEnvFeatures() []string {
diff --git a/internal/pkg/manifest/storage_test.go b/internal/pkg/manifest/storage_test.go
index 848a7183609..4d49d698345 100644
--- a/internal/pkg/manifest/storage_test.go
+++ b/internal/pkg/manifest/storage_test.go
@@ -257,6 +257,11 @@ func TestStorage_IsEmpty(t *testing.T) {
in: Storage{},
wanted: true,
},
+ "non empty storage with ReadOnlyFS": {
+ in: Storage{
+ ReadonlyRootFS: aws.Bool(true),
+ },
+ },
"non empty storage": {
in: Storage{
Volumes: map[string]*Volume{
diff --git a/internal/pkg/manifest/testdata/backend-svc-customhealthcheck.yml b/internal/pkg/manifest/testdata/backend-svc-customhealthcheck.yml
index 82f3dc93561..d6d74c13ff5 100644
--- a/internal/pkg/manifest/testdata/backend-svc-customhealthcheck.yml
+++ b/internal/pkg/manifest/testdata/backend-svc-customhealthcheck.yml
@@ -26,6 +26,9 @@ memory: 512 # Amount of memory in MiB used by the task.
count: 1 # Number of tasks that should be running in your service.
exec: true # Enable running commands in your container.
+storage:
+ readonly_fs: true # Limit to read-only access to mounted root filesystems by default.
+
# Optional fields for more advanced use-cases.
#
#variables: # Pass environment variables as key value pairs.
diff --git a/internal/pkg/manifest/testdata/backend-svc-nohealthcheck.yml b/internal/pkg/manifest/testdata/backend-svc-nohealthcheck.yml
index d8854755d4c..e23f81f8337 100644
--- a/internal/pkg/manifest/testdata/backend-svc-nohealthcheck.yml
+++ b/internal/pkg/manifest/testdata/backend-svc-nohealthcheck.yml
@@ -18,6 +18,9 @@ memory: 512 # Amount of memory in MiB used by the task.
count: 1 # Number of tasks that should be running in your service.
exec: true # Enable running commands in your container.
+storage:
+ readonly_fs: true # Limit to read-only access to mounted root filesystems by default.
+
# Optional fields for more advanced use-cases.
#
#variables: # Pass environment variables as key value pairs.
diff --git a/internal/pkg/manifest/testdata/lb-svc.yml b/internal/pkg/manifest/testdata/lb-svc.yml
index acf26414154..bf8ea1bce0d 100644
--- a/internal/pkg/manifest/testdata/lb-svc.yml
+++ b/internal/pkg/manifest/testdata/lb-svc.yml
@@ -26,6 +26,9 @@ memory: 512 # Amount of memory in MiB used by the task.
count: 1 # Number of tasks that should be running in your service.
exec: true # Enable running commands in your container.
+storage:
+ readonly_fs: true # Limit to read-only access to mounted root filesystems by default.
+
# Optional fields for more advanced use-cases.
#
#variables: # Pass environment variables as key value pairs.
@@ -39,4 +42,4 @@ exec: true # Enable running commands in your container.
# test:
# count: 2 # Number of tasks to run for the "test" environment.
# deployment: # The deployment strategy for the "test" environment.
-# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
+# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
\ No newline at end of file
diff --git a/internal/pkg/manifest/testdata/worker-svc-nosubscribe.yml b/internal/pkg/manifest/testdata/worker-svc-nosubscribe.yml
index 2bf3924104b..47ab8e26710 100644
--- a/internal/pkg/manifest/testdata/worker-svc-nosubscribe.yml
+++ b/internal/pkg/manifest/testdata/worker-svc-nosubscribe.yml
@@ -16,6 +16,10 @@ memory: 512 # Amount of memory in MiB used by the task.
count: 1 # Number of tasks that should be running in your service.
exec: true # Enable running commands in your container.
+storage:
+ readonly_fs: true # Limit to read-only access to mounted root filesystems by default.
+
+
# You can register to topics from other services.
# The events can be be received from an SQS queue via the env var $COPILOT_QUEUE_URI.
# subscribe:
@@ -36,4 +40,4 @@ exec: true # Enable running commands in your container.
# test:
# count: 2 # Number of tasks to run for the "test" environment.
# deployment: # The deployment strategy for the "test" environment.
-# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
+# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
\ No newline at end of file
diff --git a/internal/pkg/manifest/testdata/worker-svc-subscribe.yml b/internal/pkg/manifest/testdata/worker-svc-subscribe.yml
index 457dbabf944..45532afecd8 100644
--- a/internal/pkg/manifest/testdata/worker-svc-subscribe.yml
+++ b/internal/pkg/manifest/testdata/worker-svc-subscribe.yml
@@ -16,6 +16,10 @@ memory: 512 # Amount of memory in MiB used by the task.
count: 1 # Number of tasks that should be running in your service.
exec: true # Enable running commands in your container.
+storage:
+ readonly_fs: true # Limit to read-only access to mounted root filesystems by default.
+
+
# The events can be be received from an SQS queue via the env var $COPILOT_QUEUE_URI.
subscribe:
topics:
@@ -37,4 +41,4 @@ subscribe:
# test:
# count: 2 # Number of tasks to run for the "test" environment.
# deployment: # The deployment strategy for the "test" environment.
-# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
+# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
\ No newline at end of file
diff --git a/internal/pkg/manifest/testdata/worker-svc-with-default-fifo-queue.yml b/internal/pkg/manifest/testdata/worker-svc-with-default-fifo-queue.yml
index 00e1da5e041..ad30eadcb99 100644
--- a/internal/pkg/manifest/testdata/worker-svc-with-default-fifo-queue.yml
+++ b/internal/pkg/manifest/testdata/worker-svc-with-default-fifo-queue.yml
@@ -16,6 +16,10 @@ memory: 512 # Amount of memory in MiB used by the task.
count: 1 # Number of tasks that should be running in your service.
exec: true # Enable running commands in your container.
+storage:
+ readonly_fs: true # Limit to read-only access to mounted root filesystems by default.
+
+
# The events can be be received from an SQS queue via the env var $COPILOT_QUEUE_URI.
subscribe:
topics:
@@ -37,4 +41,4 @@ subscribe:
# test:
# count: 2 # Number of tasks to run for the "test" environment.
# deployment: # The deployment strategy for the "test" environment.
-# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
+# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
\ No newline at end of file
diff --git a/internal/pkg/manifest/validate.go b/internal/pkg/manifest/validate.go
index bd4a442ee8e..716efe366bd 100644
--- a/internal/pkg/manifest/validate.go
+++ b/internal/pkg/manifest/validate.go
@@ -162,6 +162,7 @@ func (l LoadBalancedWebServiceConfig) validate() error {
if l.TaskConfig.IsWindows() {
if err = validateWindows(validateWindowsOpts{
efsVolumes: l.Storage.Volumes,
+ readOnlyFS: l.Storage.ReadonlyRootFS,
}); err != nil {
return fmt.Errorf("validate Windows: %w", err)
}
@@ -262,6 +263,7 @@ func (b BackendServiceConfig) validate() error {
if b.TaskConfig.IsWindows() {
if err = validateWindows(validateWindowsOpts{
efsVolumes: b.Storage.Volumes,
+ readOnlyFS: b.Storage.ReadonlyRootFS,
}); err != nil {
return fmt.Errorf("validate Windows: %w", err)
}
@@ -374,6 +376,7 @@ func (w WorkerServiceConfig) validate() error {
if w.TaskConfig.IsWindows() {
if err = validateWindows(validateWindowsOpts{
efsVolumes: w.Storage.Volumes,
+ readOnlyFS: w.Storage.ReadonlyRootFS,
}); err != nil {
return fmt.Errorf(`validate Windows: %w`, err)
}
@@ -449,6 +452,7 @@ func (s ScheduledJobConfig) validate() error {
if s.TaskConfig.IsWindows() {
if err = validateWindows(validateWindowsOpts{
efsVolumes: s.Storage.Volumes,
+ readOnlyFS: s.Storage.ReadonlyRootFS,
}); err != nil {
return fmt.Errorf(`validate Windows: %w`, err)
}
@@ -1624,6 +1628,7 @@ type validateTargetContainerOpts struct {
}
type validateWindowsOpts struct {
+ readOnlyFS *bool
efsVolumes map[string]*Volume
}
@@ -1772,6 +1777,9 @@ func isValidSubSvcName(name string) bool {
}
func validateWindows(opts validateWindowsOpts) error {
+ if aws.BoolValue(opts.readOnlyFS) {
+ return fmt.Errorf(`%q can not be set to 'true' when deploying a Windows container`, "readonly_fs")
+ }
for _, volume := range opts.efsVolumes {
if !volume.EmptyVolume() {
return errors.New(`'EFS' is not supported when deploying a Windows container`)
diff --git a/internal/pkg/manifest/validate_test.go b/internal/pkg/manifest/validate_test.go
index d1d0515f7d3..f8aaeb82e94 100644
--- a/internal/pkg/manifest/validate_test.go
+++ b/internal/pkg/manifest/validate_test.go
@@ -3269,10 +3269,22 @@ func TestValidateWindows(t *testing.T) {
},
wantedError: errors.New(`'EFS' is not supported when deploying a Windows container`),
},
- "should return nil efs not specified": {
+ "should return nil when no fields are specified": {
in: validateWindowsOpts{},
wantedError: nil,
},
+ "error if readonlyfs is true": {
+ in: validateWindowsOpts{
+ readOnlyFS: aws.Bool(true),
+ },
+ wantedError: fmt.Errorf(`%q can not be set to 'true' when deploying a Windows container`, "readonly_fs"),
+ },
+ "should return nil if readonly_fs is false": {
+ in: validateWindowsOpts{
+ readOnlyFS: aws.Bool(false),
+ },
+ wantedError: nil,
+ },
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
diff --git a/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml b/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml
index e451cb152ad..595c2ef2c91 100644
--- a/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml
+++ b/internal/pkg/template/templates/workloads/partials/cf/workload-container.yml
@@ -60,6 +60,9 @@
StartPeriod: {{.HealthCheck.StartPeriod}}
Timeout: {{.HealthCheck.Timeout}}
{{- end}}
+{{- if and .Storage .Storage.ReadonlyRootFS}}
+ ReadonlyRootFilesystem: {{.Storage.ReadonlyRootFS}}
+{{- end}}
{{- if .CredentialsParameter}}
RepositoryCredentials:
CredentialsParameter: {{.CredentialsParameter}}
diff --git a/internal/pkg/template/templates/workloads/services/backend/manifest.yml b/internal/pkg/template/templates/workloads/services/backend/manifest.yml
index 1e644fdb871..6b4be00d4d2 100644
--- a/internal/pkg/template/templates/workloads/services/backend/manifest.yml
+++ b/internal/pkg/template/templates/workloads/services/backend/manifest.yml
@@ -44,6 +44,11 @@ count: {{.Count.Value}} # Number of tasks that should be running in your s
exec: true # Enable running commands in your container.
{{- end}}
+storage:
+{{- if not .TaskConfig.IsWindows}}
+ readonly_fs: true # Limit to read-only access to mounted root filesystems by default.
+{{- end}}
+
# Optional fields for more advanced use-cases.
#
#variables: # Pass environment variables as key value pairs.
diff --git a/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml b/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml
index 7f0c4f4b7fe..8667f3574bb 100644
--- a/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml
+++ b/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml
@@ -39,6 +39,11 @@ count: {{.Count.Value}} # Number of tasks that should be running in your s
exec: true # Enable running commands in your container.
{{- end}}
+storage:
+{{- if not .TaskConfig.IsWindows}}
+ readonly_fs: true # Limit to read-only access to mounted root filesystems by default.
+{{- end}}
+
# Optional fields for more advanced use-cases.
#
#variables: # Pass environment variables as key value pairs.
@@ -52,4 +57,4 @@ exec: true # Enable running commands in your container.
# test:
# count: 2 # Number of tasks to run for the "test" environment.
# deployment: # The deployment strategy for the "test" environment.
-# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
+# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
\ No newline at end of file
diff --git a/internal/pkg/template/templates/workloads/services/worker/manifest.yml b/internal/pkg/template/templates/workloads/services/worker/manifest.yml
index a2f36c7a064..7c478504886 100644
--- a/internal/pkg/template/templates/workloads/services/worker/manifest.yml
+++ b/internal/pkg/template/templates/workloads/services/worker/manifest.yml
@@ -34,6 +34,12 @@ count: {{.Count.Value}} # Number of tasks that should be running in your s
{{- if not .TaskConfig.IsWindows }}
exec: true # Enable running commands in your container.
{{- end}}
+
+storage:
+{{- if not .TaskConfig.IsWindows}}
+ readonly_fs: true # Limit to read-only access to mounted root filesystems by default.
+{{- end}}
+
{{if .Subscribe}}{{- if .Subscribe.Topics}}
# The events can be be received from an SQS queue via the env var $COPILOT_QUEUE_URI.
subscribe:
@@ -71,4 +77,4 @@ subscribe:
# test:
# count: 2 # Number of tasks to run for the "test" environment.
# deployment: # The deployment strategy for the "test" environment.
-# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
+# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
\ No newline at end of file
diff --git a/internal/pkg/template/workload.go b/internal/pkg/template/workload.go
index b10baf5933a..9d33608caaf 100644
--- a/internal/pkg/template/workload.go
+++ b/internal/pkg/template/workload.go
@@ -145,6 +145,7 @@ type SidecarStorageOpts struct {
// StorageOpts holds data structures for rendering Volumes and Mount Points
type StorageOpts struct {
Ephemeral *int
+ ReadonlyRootFS *bool
Volumes []*Volume
MountPoints []*MountPoint
EFSPerms []*EFSPermission
From fe62ac57e946cd147db7d640b6219999b8717fa4 Mon Sep 17 00:00:00 2001
From: Parag Sanjay Bhingre
Date: Thu, 13 Oct 2022 15:40:35 -0700
Subject: [PATCH 13/14] chore: add description for creating Elastic IPs (#4084)
This resolves #4070 by adding entry for the creation of the elastic IPs in the progress tracker and showing failure early if there is any.
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.
---
.../testdata/environments/template-with-basic-manifest.yml | 4 ++++
.../template-with-custom-empty-security-group.yml | 4 ++++
.../environments/template-with-custom-security-group.yml | 4 ++++
.../environments/template-with-default-access-log-config.yml | 4 ++++
.../template-with-imported-certs-observability.yml | 4 ++++
.../template/templates/environment/partials/nat-gateways.yml | 2 ++
6 files changed, 22 insertions(+)
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml
index 42095000dd3..c906eb6fd6a 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-basic-manifest.yml
@@ -480,6 +480,8 @@ Resources:
SubnetId: !Ref PublicSubnet2
NatGateway1Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 1'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
@@ -515,6 +517,8 @@ Resources:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
NatGateway2Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 2'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-empty-security-group.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-empty-security-group.yml
index 669b4efd734..333e6d8245f 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-empty-security-group.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-empty-security-group.yml
@@ -171,6 +171,8 @@ Resources:
SubnetId: !Ref PublicSubnet2
NatGateway1Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 1'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
@@ -206,6 +208,8 @@ Resources:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
NatGateway2Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 2'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml
index 0673cbe29d9..3ad2b44dd8b 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-custom-security-group.yml
@@ -182,6 +182,8 @@ Resources:
SubnetId: !Ref PublicSubnet2
NatGateway1Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 1'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
@@ -217,6 +219,8 @@ Resources:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
NatGateway2Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 2'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml
index 3a051429693..5475b10cedb 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-default-access-log-config.yml
@@ -537,6 +537,8 @@ Resources:
SubnetId: !Ref PublicSubnet2
NatGateway1Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 1'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
@@ -572,6 +574,8 @@ Resources:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
NatGateway2Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 2'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml
index 3541cd4ed09..3170eaef594 100644
--- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-imported-certs-observability.yml
@@ -181,6 +181,8 @@ Resources:
SubnetId: !Ref PublicSubnet2
NatGateway1Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 1'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
@@ -216,6 +218,8 @@ Resources:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
NatGateway2Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway 2'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
diff --git a/internal/pkg/template/templates/environment/partials/nat-gateways.yml b/internal/pkg/template/templates/environment/partials/nat-gateways.yml
index 9b6762a3ea8..69e87a979e9 100644
--- a/internal/pkg/template/templates/environment/partials/nat-gateways.yml
+++ b/internal/pkg/template/templates/environment/partials/nat-gateways.yml
@@ -1,5 +1,7 @@
{{- range $ind, $cidr := .PrivateSubnetCIDRs}}
NatGateway{{inc $ind}}Attachment:
+ Metadata:
+ 'aws:copilot:description': 'An Elastic IP for NAT Gateway {{inc $ind}}'
Type: AWS::EC2::EIP
Condition: CreateNATGateways
DependsOn: InternetGatewayAttachment
From d603bb85010a0ecdca0f5b1afd5277087a3792d2 Mon Sep 17 00:00:00 2001
From: genbit
Date: Fri, 14 Oct 2022 09:35:21 -0700
Subject: [PATCH 14/14] chore: update recommended actions for missing
credentials (#4089)
This removes link to AWS Go SDK and uses only link to AWS Copilot docs
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.
---
internal/pkg/aws/sessions/errors.go | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/internal/pkg/aws/sessions/errors.go b/internal/pkg/aws/sessions/errors.go
index a3fa934f5b4..132d2ace7a5 100644
--- a/internal/pkg/aws/sessions/errors.go
+++ b/internal/pkg/aws/sessions/errors.go
@@ -39,16 +39,11 @@ func (e *errCredRetrieval) Error() string {
// RecommendActions returns recommended actions to be taken after the error.
// Implements main.actionRecommender interface.
func (e *errCredRetrieval) RecommendActions() string {
- notice := "It looks like your credential settings are misconfigured or missing"
+ notice := "It looks like AWS credentials are misconfigured or missing"
if e.profile != "" {
- notice = fmt.Sprintf("It looks like your profile [%s] is misconfigured or missing", e.profile)
+ notice = fmt.Sprintf("It looks like AWS profile [%s] is misconfigured or missing", e.profile)
}
return fmt.Sprintf(`%s:
-https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials
-- We recommend including your credentials in the shared credentials file.
-- Alternatively, you can also set credentials through
- * Environment Variables
- * EC2 Instance Metadata (credentials only)
More information: https://aws.github.io/copilot-cli/docs/credentials/`, notice)
}