From 409d11af02cabae193786ca5047727907949911c Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Thu, 21 Sep 2023 10:33:21 -0700 Subject: [PATCH 1/8] filter only for public hostedzones --- internal/pkg/aws/route53/route53.go | 6 +- internal/pkg/aws/route53/route53_test.go | 78 +++++++++++++++++++++ internal/pkg/cli/app_init.go | 2 +- internal/pkg/cli/app_init_test.go | 4 +- internal/pkg/template/templates/app/app.yml | 4 +- 5 files changed, 89 insertions(+), 5 deletions(-) diff --git a/internal/pkg/aws/route53/route53.go b/internal/pkg/aws/route53/route53.go index 40faac4a44f..9ea4dafcdcc 100644 --- a/internal/pkg/aws/route53/route53.go +++ b/internal/pkg/aws/route53/route53.go @@ -152,7 +152,7 @@ type filterZoneFunc func(*route53.HostedZone) bool func filterHostedZones(zones []*route53.HostedZone, fn filterZoneFunc) []*route53.HostedZone { var hostedZones []*route53.HostedZone for _, hostedZone := range zones { - if fn(hostedZone) { + if fn(hostedZone) && !isPrivateHostedZone(hostedZone.Config) { hostedZones = append(hostedZones, hostedZone) } } @@ -166,6 +166,10 @@ func matchesDomain(domain string) filterZoneFunc { } } +func isPrivateHostedZone(cfg *route53.HostedZoneConfig) bool { + return aws.BoolValue(cfg.PrivateZone) == true +} + func cleanNSRecord(record string) string { if !strings.HasSuffix(record, ".") { return record diff --git a/internal/pkg/aws/route53/route53_test.go b/internal/pkg/aws/route53/route53_test.go index 455c1e6ba78..6e0a53809a4 100644 --- a/internal/pkg/aws/route53/route53_test.go +++ b/internal/pkg/aws/route53/route53_test.go @@ -35,6 +35,9 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { { Name: aws.String("mockDomain.com"), Id: aws.String("mockID"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, }, }, }, nil) @@ -53,6 +56,9 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { { Name: aws.String("mockDomain.subdomain.com."), Id: aws.String("mockID"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, }, }, }, nil) @@ -73,6 +79,9 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { { Name: aws.String("mockDomain1.com"), Id: aws.String("mockID1"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, }, }, }, nil) @@ -85,10 +94,16 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { { Name: aws.String("mockDomain2.com"), Id: aws.String("mockID2"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, }, { Name: aws.String("mockDomain3.com"), Id: aws.String("mockID3"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, }, }, }, nil) @@ -108,6 +123,9 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { HostedZones: []*route53.HostedZone{ { Name: aws.String("mockDomain1.com"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, }, }, }, nil) @@ -119,9 +137,15 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { HostedZones: []*route53.HostedZone{ { Name: aws.String("mockDomain2.com"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, }, { Name: aws.String("mockDomain3.com"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, }, }, }, nil) @@ -139,6 +163,57 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { }, wantErr: fmt.Errorf("list hosted zone for mockDomain.com: some error"), }, + "filter and pick the first public hosted zone": { + domainName: "mockDomain3.com", + mockRoute53Client: func(m *mocks.Mockapi) { + m.EXPECT().ListHostedZonesByName(&route53.ListHostedZonesByNameInput{ + DNSName: aws.String("mockDomain3.com"), + }).Return(&route53.ListHostedZonesByNameOutput{ + IsTruncated: aws.Bool(true), + NextDNSName: aws.String("mockDomain2.com"), + NextHostedZoneId: aws.String("mockID"), + HostedZones: []*route53.HostedZone{ + { + Name: aws.String("mockDomain1.com"), + Id: aws.String("mockID1"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, + }, + }, + }, nil) + m.EXPECT().ListHostedZonesByName(&route53.ListHostedZonesByNameInput{ + DNSName: aws.String("mockDomain2.com"), + HostedZoneId: aws.String("mockID"), + }).Return(&route53.ListHostedZonesByNameOutput{ + IsTruncated: aws.Bool(false), + HostedZones: []*route53.HostedZone{ + { + Name: aws.String("mockDomain3.com"), + Id: aws.String("mockID2"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(true), + }, + }, + { + Name: aws.String("mockDomain3.com"), + Id: aws.String("mockID3"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, + }, + { + Name: aws.String("mockDomain3.com"), + Id: aws.String("mockID4"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, + }, + }, + }, nil) + }, + wantHostedZoneID: "mockID3", + }, } for name, tc := range testCases { @@ -179,6 +254,9 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { { Name: aws.String("example.com"), Id: aws.String("Z0698117FUWMJ87C39TF"), + Config: &route53.HostedZoneConfig{ + PrivateZone: aws.Bool(false), + }, }, }, }, nil).Times(1) diff --git a/internal/pkg/cli/app_init.go b/internal/pkg/cli/app_init.go index 97b06cc3c71..5110de83629 100644 --- a/internal/pkg/cli/app_init.go +++ b/internal/pkg/cli/app_init.go @@ -321,7 +321,7 @@ func (o *initAppOpts) domainHostedZoneID(domainName string) (string, error) { } hostedZoneID, err := o.route53.DomainHostedZoneID(domainName) if err != nil { - return "", fmt.Errorf("get hosted zone ID for domain %s: %w", domainName, err) + return "", fmt.Errorf("get public hosted zone ID for domain %s: %w", domainName, err) } return hostedZoneID, nil } diff --git a/internal/pkg/cli/app_init_test.go b/internal/pkg/cli/app_init_test.go index 6c61e4d6aed..898ef23db49 100644 --- a/internal/pkg/cli/app_init_test.go +++ b/internal/pkg/cli/app_init_test.go @@ -159,7 +159,7 @@ func TestInitAppOpts_Validate(t *testing.T) { m.mockRoute53Svc.EXPECT().ValidateDomainOwnership("badMockDomain.com").Return(nil) m.mockRoute53Svc.EXPECT().DomainHostedZoneID("badMockDomain.com").Return("", &route53.ErrDomainHostedZoneNotFound{}) }, - wantedError: fmt.Errorf("get hosted zone ID for domain badMockDomain.com: %w", &route53.ErrDomainHostedZoneNotFound{}), + wantedError: fmt.Errorf("get public hosted zone ID for domain badMockDomain.com: %w", &route53.ErrDomainHostedZoneNotFound{}), }, "errors if failed to validate that domain has a hosted zone": { inDomainName: "mockDomain.com", @@ -169,7 +169,7 @@ func TestInitAppOpts_Validate(t *testing.T) { m.mockRoute53Svc.EXPECT().ValidateDomainOwnership("mockDomain.com").Return(&route53.ErrUnmatchedNSRecords{}) m.mockRoute53Svc.EXPECT().DomainHostedZoneID("mockDomain.com").Return("", errors.New("some error")) }, - wantedError: errors.New("get hosted zone ID for domain mockDomain.com: some error"), + wantedError: errors.New("get public hosted zone ID for domain mockDomain.com: some error"), }, "valid": { inPBPolicyName: "arn:aws:iam::1234567890:policy/myPermissionsBoundaryPolicy", diff --git a/internal/pkg/template/templates/app/app.yml b/internal/pkg/template/templates/app/app.yml index 028c4b0a6bd..ccf102630e4 100644 --- a/internal/pkg/template/templates/app/app.yml +++ b/internal/pkg/template/templates/app/app.yml @@ -188,7 +188,9 @@ Resources: Type: AWS::Route53::RecordSet Condition: DelegateDNS Properties: - HostedZoneName: !Sub ${AppDomainName}. + # Use HostedZoneId in case of multiple hosted zones. + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordset.html#cfn-route53-recordset-hostedzoneid + HostedZoneId: !Sub ${AppDomainHostedZoneID} Comment: !Sub "Record for copilot domain delegation for application ${AppDomainName}" Name: !Sub ${AppName}.${AppDomainName}. Type: NS From 72219dd67023c9fd5ae77ab89b62d9db04945a07 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Thu, 21 Sep 2023 10:35:36 -0700 Subject: [PATCH 2/8] remove doc comment --- internal/pkg/template/templates/app/app.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/pkg/template/templates/app/app.yml b/internal/pkg/template/templates/app/app.yml index ccf102630e4..06a0563c2c8 100644 --- a/internal/pkg/template/templates/app/app.yml +++ b/internal/pkg/template/templates/app/app.yml @@ -188,8 +188,6 @@ Resources: Type: AWS::Route53::RecordSet Condition: DelegateDNS Properties: - # Use HostedZoneId in case of multiple hosted zones. - # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordset.html#cfn-route53-recordset-hostedzoneid HostedZoneId: !Sub ${AppDomainHostedZoneID} Comment: !Sub "Record for copilot domain delegation for application ${AppDomainName}" Name: !Sub ${AppName}.${AppDomainName}. From 7158428825809ee98bad1bfbec6a746c34ca2268 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Thu, 21 Sep 2023 11:01:05 -0700 Subject: [PATCH 3/8] fix static check --- internal/pkg/aws/route53/route53.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/aws/route53/route53.go b/internal/pkg/aws/route53/route53.go index 9ea4dafcdcc..ac6fef47714 100644 --- a/internal/pkg/aws/route53/route53.go +++ b/internal/pkg/aws/route53/route53.go @@ -167,7 +167,7 @@ func matchesDomain(domain string) filterZoneFunc { } func isPrivateHostedZone(cfg *route53.HostedZoneConfig) bool { - return aws.BoolValue(cfg.PrivateZone) == true + return aws.BoolValue(cfg.PrivateZone) } func cleanNSRecord(record string) string { From e74b841cd9cafbfa62310b0fdb0b0286d194f9a9 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Thu, 21 Sep 2023 13:58:33 -0700 Subject: [PATCH 4/8] address fb: make function more generic --- internal/pkg/aws/route53/route53.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/internal/pkg/aws/route53/route53.go b/internal/pkg/aws/route53/route53.go index ac6fef47714..5ff586aa343 100644 --- a/internal/pkg/aws/route53/route53.go +++ b/internal/pkg/aws/route53/route53.go @@ -60,7 +60,7 @@ func (r53 *Route53) DomainHostedZoneID(domainName string) (string, error) { return "", fmt.Errorf("list hosted zone for %s: %w", domainName, err) } for { - hostedZones := filterHostedZones(resp.HostedZones, matchesDomain(domainName)) + hostedZones := filterHostedZones(resp.HostedZones, matchesDomain(domainName), matchesPublic()) if len(hostedZones) > 0 { // return the first match. id := strings.TrimPrefix(aws.StringValue(hostedZones[0].Id), "/hostedzone/") @@ -149,10 +149,18 @@ func (r53 *Route53) lookupNSRecords(domainName string) ([]string, error) { type filterZoneFunc func(*route53.HostedZone) bool -func filterHostedZones(zones []*route53.HostedZone, fn filterZoneFunc) []*route53.HostedZone { +func filterHostedZones(zones []*route53.HostedZone, filterFuncs ...filterZoneFunc) []*route53.HostedZone { var hostedZones []*route53.HostedZone + passesAllFilters := func(zone *route53.HostedZone) bool { + for _, fn := range filterFuncs { + if !fn(zone) { + return false + } + } + return true + } for _, hostedZone := range zones { - if fn(hostedZone) && !isPrivateHostedZone(hostedZone.Config) { + if passesAllFilters(hostedZone) { hostedZones = append(hostedZones, hostedZone) } } @@ -166,8 +174,10 @@ func matchesDomain(domain string) filterZoneFunc { } } -func isPrivateHostedZone(cfg *route53.HostedZoneConfig) bool { - return aws.BoolValue(cfg.PrivateZone) +func matchesPublic() filterZoneFunc { + return func(config *route53.HostedZone) bool { + return !aws.BoolValue(config.Config.PrivateZone) + } } func cleanNSRecord(record string) string { From 3e4acab03c1817a82380e5ba0f11c157ce617581 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Tue, 26 Sep 2023 09:58:10 -0700 Subject: [PATCH 5/8] send hostedzones through custom resources --- .../lib/custom-domain-app-runner.js | 5 +- cf-custom-resources/lib/custom-domain.js | 33 ++++++++-- cf-custom-resources/lib/dns-cert-validator.js | 24 ++++++- cf-custom-resources/lib/dns-delegation.js | 24 ++++--- .../lib/wkld-cert-validator.js | 14 +++- cf-custom-resources/lib/wkld-custom-domain.js | 14 +++- .../test/custom-domain-app-runner-test.js | 13 +--- .../test/custom-domain-test.js | 66 +++++++++++++++++++ internal/pkg/aws/route53/route53.go | 6 +- internal/pkg/aws/route53/route53_test.go | 6 +- internal/pkg/cli/app_init.go | 2 +- internal/pkg/cli/app_init_test.go | 10 +-- internal/pkg/cli/app_upgrade.go | 2 +- internal/pkg/cli/app_upgrade_test.go | 4 +- internal/pkg/cli/deploy/env.go | 34 ++++++++-- internal/pkg/cli/deploy/env_test.go | 25 +++++-- internal/pkg/cli/deploy/lbws.go | 8 +++ internal/pkg/cli/deploy/mocks/mock_env.go | 38 +++++++++++ internal/pkg/cli/deploy/rdws.go | 9 +-- internal/pkg/cli/deploy/static_site.go | 9 +++ internal/pkg/cli/deploy/static_site_test.go | 14 ++++ internal/pkg/cli/deploy/workload.go | 31 +++++---- internal/pkg/cli/deploy/workload_test.go | 26 +++++--- internal/pkg/cli/interfaces.go | 2 +- internal/pkg/cli/mocks/mock_interfaces.go | 12 ++-- internal/pkg/deploy/app.go | 10 +-- .../pkg/deploy/cloudformation/stack/env.go | 1 + .../deploy/cloudformation/stack/env_test.go | 8 +++ ...lb_network_web_service_integration_test.go | 5 +- .../deploy/cloudformation/stack/lb_web_svc.go | 10 ++- .../cloudformation/stack/lb_web_svc_test.go | 10 ++- .../deploy/cloudformation/stack/rd_web_svc.go | 1 + .../cloudformation/stack/static_site.go | 10 ++- .../stack/static_site_integration_test.go | 6 +- .../template-with-basic-manifest.yml | 1 + ...emplate-with-default-access-log-config.yml | 1 + .../template-with-defaultvpc-flowlogs.yml | 1 + .../template-with-importedvpc-flowlogs.yml | 1 + .../testdata/workloads/static-site.stack.yml | 4 ++ .../testdata/workloads/svc-nlb-prod.stack.yml | 4 ++ .../testdata/workloads/svc-nlb-test.stack.yml | 2 + .../cloudformation/stack/transformers.go | 10 +++ internal/pkg/template/env.go | 7 ++ .../environment/partials/custom-resources.yml | 17 ++++- .../templates/workloads/partials/cf/nlb.yml | 8 +++ .../workloads/services/rd-web/cf.yml | 3 + .../workloads/services/static-site/cf.yml | 8 +++ internal/pkg/template/workload.go | 1 + 48 files changed, 445 insertions(+), 115 deletions(-) diff --git a/cf-custom-resources/lib/custom-domain-app-runner.js b/cf-custom-resources/lib/custom-domain-app-runner.js index bbe0533c261..613c6430116 100644 --- a/cf-custom-resources/lib/custom-domain-app-runner.js +++ b/cf-custom-resources/lib/custom-domain-app-runner.js @@ -95,7 +95,10 @@ exports.handler = async function (event, context) { }), }); appRunnerClient = new AWS.AppRunner(); - appHostedZoneID = await domainHostedZoneID(appDNSName); + appHostedZoneID = props.RootHostedZoneId + if (!appHostedZoneID){ + appHostedZoneID = await domainHostedZoneID(appDNSName); + } switch (event.RequestType) { case "Create": case "Update": diff --git a/cf-custom-resources/lib/custom-domain.js b/cf-custom-resources/lib/custom-domain.js index 23a2ef81ef2..56eb6cf37d7 100644 --- a/cf-custom-resources/lib/custom-domain.js +++ b/cf-custom-resources/lib/custom-domain.js @@ -98,7 +98,10 @@ const writeCustomDomainRecord = async function ( accessDNS, accessHostedZone, aliasTypes, - action + action, + rootHostedZoneId, + appHostedZoneId, + envHostedZoneId ) { const actions = []; for (const alias of aliases) { @@ -111,7 +114,8 @@ const writeCustomDomainRecord = async function ( accessDNS, accessHostedZone, aliasType.domain, - action + action, + envHostedZoneId )); break; case aliasTypes.AppDomainZone: @@ -121,7 +125,8 @@ const writeCustomDomainRecord = async function ( accessDNS, accessHostedZone, aliasType.domain, - action + action, + appHostedZoneId )); break; case aliasTypes.RootDomainZone: @@ -131,7 +136,8 @@ const writeCustomDomainRecord = async function ( accessDNS, accessHostedZone, aliasType.domain, - action + action, + rootHostedZoneId )); break; // We'll skip if it is the other alias type since it will be in another account's route53. @@ -147,9 +153,10 @@ const writeARecord = async function ( accessDNS, accessHostedZone, domain, - action + action, + hostedZoneID ) { - let hostedZoneId = hostedZoneCache.get(domain); + let hostedZoneId = hostedZoneID || hostedZoneCache.get(domain); if (!hostedZoneId) { const hostedZones = await route53 .listHostedZonesByName({ @@ -233,6 +240,9 @@ exports.handler = async function (event, context) { props.PublicAccessHostedZone, aliasTypes, changeRecordAction.Upsert, + props.RootHostedZoneId, + props.AppHostedZoneId, + props.EnvHostedZoneId ); break; case "Update": @@ -244,6 +254,9 @@ exports.handler = async function (event, context) { props.PublicAccessHostedZone, aliasTypes, changeRecordAction.Upsert, + props.RootHostedZoneId, + props.AppHostedZoneId, + props.EnvHostedZoneId ); // After upserting new aliases, delete unused ones. For example: previously we have ["foo.com", "bar.com"], // and now the aliases param is updated to just ["foo.com"] then we'll delete "bar.com". @@ -261,6 +274,9 @@ exports.handler = async function (event, context) { props.PublicAccessHostedZone, aliasTypes, changeRecordAction.Delete, + props.RootHostedZoneId, + props.AppHostedZoneId, + props.EnvHostedZoneId ); break; case "Delete": @@ -271,7 +287,10 @@ exports.handler = async function (event, context) { props.PublicAccessDNS, props.PublicAccessHostedZone, aliasTypes, - changeRecordAction.Delete + changeRecordAction.Delete, + props.RootHostedZoneId, + props.AppHostedZoneId, + props.EnvHostedZoneId ); break; default: diff --git a/cf-custom-resources/lib/dns-cert-validator.js b/cf-custom-resources/lib/dns-cert-validator.js index 5c2d980fff9..ac1994805cf 100644 --- a/cf-custom-resources/lib/dns-cert-validator.js +++ b/cf-custom-resources/lib/dns-cert-validator.js @@ -212,6 +212,8 @@ const validateCertificate = async function( options, envRoute53, appRoute53, + rootHostedZoneId, + appHostedZoneId, envHostedZoneId, certificateARN, acm @@ -221,6 +223,8 @@ const validateCertificate = async function( options, envRoute53, appRoute53, + rootHostedZoneId, + appHostedZoneId, envHostedZoneId ); @@ -241,7 +245,9 @@ const updateHostedZoneRecords = async function ( options, envRoute53, appRoute53, - envHostedZoneId + rootHostedZoneId, + appHostedZoneId, + envHostedZoneId, ) { const promises = []; for (const option of options) { @@ -265,6 +271,7 @@ const updateHostedZoneRecords = async function ( record: option.ResourceRecord, action: action, domainName: domainType.domain, + hostedZoneId: appHostedZoneId, }) ); break; @@ -275,6 +282,7 @@ const updateHostedZoneRecords = async function ( record: option.ResourceRecord, action: action, domainName: domainType.domain, + hostedZoneId: rootHostedZoneId, }) ); break; @@ -293,6 +301,8 @@ const deleteHostedZoneRecords = async function ( envRoute53, appRoute53, acm, + rootHostedZoneId, + appHostedZoneId, envHostedZoneId ) { let listCertificatesInput = {}; @@ -354,6 +364,8 @@ const deleteHostedZoneRecords = async function ( filteredRecordOption, envRoute53, appRoute53, + rootHostedZoneId, + appHostedZoneId, envHostedZoneId ); } catch (e) { @@ -416,6 +428,8 @@ const deleteCertificate = async function ( arn, certDomain, region, + rootHostedZoneId, + appHostedZoneId, envHostedZoneId, rootDnsRole ) { @@ -463,6 +477,8 @@ const deleteCertificate = async function ( envRoute53, appRoute53, acm, + rootHostedZoneId, + appHostedZoneId, envHostedZoneId ); @@ -626,7 +642,7 @@ exports.certificateRequestHandler = async function (event, context) { ); responseData.Arn = physicalResourceId = response.CertificateArn; // Set physicalResourceId as soon as we can. options = await waitForValidationOptionsToBeReady(response.CertificateArn, sansToUse, acm); - await validateCertificate(options, envRoute53, appRoute53, props.EnvHostedZoneId, response.CertificateArn, acm); + await validateCertificate(options, envRoute53, appRoute53, props.RootHostedZoneId, props.AppHostedZoneId, props.EnvHostedZoneId, response.CertificateArn, acm); break; case "Update": // Exit early if cert doesn't change. @@ -644,7 +660,7 @@ exports.certificateRequestHandler = async function (event, context) { ); responseData.Arn = physicalResourceId = response.CertificateArn; options = await waitForValidationOptionsToBeReady(response.CertificateArn, sansToUse, acm); - await validateCertificate(options, envRoute53, appRoute53, props.EnvHostedZoneId, response.CertificateArn, acm); + await validateCertificate(options, envRoute53, appRoute53, props.RootHostedZoneId, props.AppHostedZoneId, props.EnvHostedZoneId, response.CertificateArn, acm); break; case "Delete": // If the resource didn't create correctly, the physical resource ID won't be the @@ -654,6 +670,8 @@ exports.certificateRequestHandler = async function (event, context) { physicalResourceId, certDomain, props.Region, + props.RootHostedZoneId, + props.AppHostedZoneId, props.EnvHostedZoneId, props.RootDNSRole ); diff --git a/cf-custom-resources/lib/dns-delegation.js b/cf-custom-resources/lib/dns-delegation.js index 5bbe12eca1b..1bd7abf981c 100644 --- a/cf-custom-resources/lib/dns-delegation.js +++ b/cf-custom-resources/lib/dns-delegation.js @@ -90,7 +90,8 @@ const createSubdomainInRoot = async function ( domainName, subDomain, nameServers, - rootDnsRole + rootDnsRole, + hostedZoneId ) { const route53 = new aws.Route53({ credentials: new aws.ChainableTemporaryCredentials({ @@ -98,7 +99,7 @@ const createSubdomainInRoot = async function ( masterCredentials: new aws.EnvironmentCredentials("AWS"), }), }); - + if (!hostedZoneId) { const hostedZones = await route53 .listHostedZonesByName({ DNSName: domainName, @@ -115,8 +116,8 @@ const createSubdomainInRoot = async function ( // HostedZoneIDs are of the form /hostedzone/1234455, but the actual // ID is after the last slash. - const hostedZoneId = domainHostedZone.Id.split("/").pop(); - + hostedZoneId = domainHostedZone.Id.split("/").pop(); + } const changeBatch = await route53 .changeResourceRecordSets({ ChangeBatch: { @@ -158,7 +159,8 @@ const deleteSubdomainInRoot = async function ( requestId, domainName, subDomain, - rootDnsRole + rootDnsRole, + hostedZoneId ) { const route53 = new aws.Route53({ credentials: new aws.ChainableTemporaryCredentials({ @@ -166,7 +168,7 @@ const deleteSubdomainInRoot = async function ( masterCredentials: new aws.EnvironmentCredentials("AWS"), }), }); - + if (!hostedZoneId) { const hostedZones = await route53 .listHostedZonesByName({ DNSName: domainName, @@ -183,8 +185,8 @@ const deleteSubdomainInRoot = async function ( // HostedZoneIDs are of the form /hostedzone/1234455, but the actual // ID is after the last slash. - const hostedZoneId = domainHostedZone.Id.split("/").pop(); - + hostedZoneId = domainHostedZone.Id.split("/").pop(); + } // Find the recordsets for this subdomain, and then remove it // from the hosted zone. const recordSets = await route53 @@ -275,7 +277,8 @@ exports.domainDelegationHandler = async function (event, context) { props.DomainName, props.SubdomainName, props.NameServers, - props.RootDNSRole + props.RootDNSRole, + props.RootHostedZoneId ); break; case "Delete": @@ -283,7 +286,8 @@ exports.domainDelegationHandler = async function (event, context) { event.RequestId, props.DomainName, props.SubdomainName, - props.RootDNSRole + props.RootDNSRole, + props.RootHostedZoneId ); break; default: diff --git a/cf-custom-resources/lib/wkld-cert-validator.js b/cf-custom-resources/lib/wkld-cert-validator.js index 52f38dd802a..138b5fa8e46 100644 --- a/cf-custom-resources/lib/wkld-cert-validator.js +++ b/cf-custom-resources/lib/wkld-cert-validator.js @@ -10,7 +10,7 @@ const ATTEMPTS_CERTIFICATE_VALIDATED = 19; const ATTEMPTS_CERTIFICATE_NOT_IN_USE = 12; const DELAY_CERTIFICATE_VALIDATED_IN_S = 30; -let envHostedZoneID, appName, envName, serviceName, certificateDomain, domainTypes, rootDNSRole, domainName, isCloudFrontCert; +let rootHostedZoneID,appHostedZoneID,envHostedZoneID, appName, envName, serviceName, certificateDomain, domainTypes, rootDNSRole, domainName, isCloudFrontCert; let defaultSleep = function (ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }; @@ -168,6 +168,8 @@ exports.handler = async function (event, context) { const aliases = new Set(props.Aliases); // Initialize global variables. + rootHostedZoneID = props.RootHostedZoneId; + appHostedZoneID = props.AppHostedZoneId; envHostedZoneID = props.EnvHostedZoneId; envName = props.EnvName; appName = props.AppName; @@ -748,17 +750,23 @@ async function domainResources(alias) { }; } if (domainTypes.AppDomainZone.regex.test(alias)) { + if (!appHostedZoneID){ + appHostedZoneID = await hostedZoneID.app() + } return { domain: domainTypes.AppDomainZone.domain, route53Client: clients.app.route53(), - hostedZoneID: await hostedZoneID.app(), + hostedZoneID: appHostedZoneID, }; } if (domainTypes.RootDomainZone.regex.test(alias)) { + if (!rootHostedZoneID){ + rootHostedZoneID = await hostedZoneID.root() + } return { domain: domainTypes.RootDomainZone.domain, route53Client: clients.root.route53(), - hostedZoneID: await hostedZoneID.root(), + hostedZoneID: rootHostedZoneID, }; } throw new UnrecognizedDomainTypeError(`unrecognized domain type for ${alias}`); diff --git a/cf-custom-resources/lib/wkld-custom-domain.js b/cf-custom-resources/lib/wkld-custom-domain.js index 2b0736b7389..88d415906de 100644 --- a/cf-custom-resources/lib/wkld-custom-domain.js +++ b/cf-custom-resources/lib/wkld-custom-domain.js @@ -6,7 +6,7 @@ const ATTEMPTS_VALIDATION_OPTIONS_READY = 10; const ATTEMPTS_RECORD_SETS_CHANGE = 10; const DELAY_RECORD_SETS_CHANGE_IN_S = 30; -let envHostedZoneID, appName, envName, serviceName, domainTypes, rootDNSRole, domainName; +let rootHostedZoneID,appHostedZoneID,envHostedZoneID, appName, envName, serviceName, domainTypes, rootDNSRole, domainName; let defaultSleep = function (ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }; @@ -157,6 +157,8 @@ exports.handler = async function (event, context) { const aliases = new Set(props.Aliases); // Initialize global variables. + rootHostedZoneID = props.RootHostedZoneId; + appHostedZoneID = props.AppHostedZoneId; envHostedZoneID = props.EnvHostedZoneId; envName = props.EnvName; appName = props.AppName; @@ -444,17 +446,23 @@ async function domainResources(alias) { }; } if (domainTypes.AppDomainZone.regex.test(alias)) { + if (!appHostedZoneID){ + appHostedZoneID = await hostedZoneID.app() + } return { domain: domainTypes.AppDomainZone.domain, route53Client: clients.app.route53(), - hostedZoneID: await hostedZoneID.app(), + hostedZoneID: appHostedZoneID, }; } if (domainTypes.RootDomainZone.regex.test(alias)) { + if (!rootHostedZoneID){ + rootHostedZoneID = await hostedZoneID.root() + } return { domain: domainTypes.RootDomainZone.domain, route53Client: clients.root.route53(), - hostedZoneID: await hostedZoneID.root(), + hostedZoneID: rootHostedZoneID, }; } throw new UnrecognizedDomainTypeError(`unrecognized domain type for ${alias}`); diff --git a/cf-custom-resources/test/custom-domain-app-runner-test.js b/cf-custom-resources/test/custom-domain-app-runner-test.js index 783dfeb7ab1..90f3a6875e7 100644 --- a/cf-custom-resources/test/custom-domain-app-runner-test.js +++ b/cf-custom-resources/test/custom-domain-app-runner-test.js @@ -13,8 +13,8 @@ const nock = require("nock"); let origLog = console.log; describe("Custom Domain for App Runner Service", () => { - const [mockServiceARN, mockCustomDomain, mockHostedZoneID, mockResponseURL, mockPhysicalResourceID, mockLogicalResourceID, mockTarget, mockAppDNSName] = - ["mockService", "mockDomain", "mockHostedZoneID", "https://mock.com/", "mockPhysicalResourceID", "mockLogicalResourceID", "mockTarget", "mockAppDNSName", ]; + const [mockServiceARN, mockCustomDomain, mockHostedZoneID, mockResponseURL, mockPhysicalResourceID, mockLogicalResourceID, mockTarget, mockAppDNSName,mockAppHostedZoneID] = + ["mockService", "mockDomain", "mockHostedZoneID", "https://mock.com/", "mockPhysicalResourceID", "mockLogicalResourceID", "mockTarget", "mockAppDNSName","Z00ABC" ]; beforeEach(() => { // Prevent logging. @@ -635,13 +635,6 @@ describe("Custom Domain for App Runner Service", () => { test("success", () => { const mockTarget = "mockTarget"; - const mockListHostedZonesByName = sinon.fake.resolves({ - HostedZones: [ - { - Id: "/hostedzone/mockHostedZoneID", - }, - ], - }); // Able to retrieve the hosted zone ID. const mockAssociateCustomDomain = sinon.fake.resolves({DNSTarget: mockTarget,}); const mockWaitFor = sinon.fake.resolves(); const mockDescribeCustomDomains = sinon.stub(); @@ -689,7 +682,6 @@ describe("Custom Domain for App Runner Service", () => { AWS.mock("Route53", "changeResourceRecordSets", mockChangeResourceRecordSets); AWS.mock("Route53", "waitFor", mockWaitFor); AWS.mock("AppRunner", "describeCustomDomains", mockDescribeCustomDomains); - AWS.mock("Route53", "listHostedZonesByName", mockListHostedZonesByName); const expectedResponse = nock(mockResponseURL) .put("/", (body) => { @@ -705,6 +697,7 @@ describe("Custom Domain for App Runner Service", () => { ServiceARN: mockServiceARN, AppDNSRole: "", CustomDomain: mockCustomDomain, + RootHostedZoneId: mockAppHostedZoneID, }, PhysicalResourceId: mockPhysicalResourceID, LogicalResourceId: mockLogicalResourceID, diff --git a/cf-custom-resources/test/custom-domain-test.js b/cf-custom-resources/test/custom-domain-test.js index 341e40a57eb..5c1aa1af3f9 100644 --- a/cf-custom-resources/test/custom-domain-test.js +++ b/cf-custom-resources/test/custom-domain-test.js @@ -17,12 +17,16 @@ describe("DNS Validated Certificate Handler", () => { const testEnvName = "test"; const testDomainName = "example.com"; const testAliases = `{"frontend": ["v1.${testEnvName}.${testAppName}.${testDomainName}", "foobar.com"]}`; + const testAliases2 = `{"frontend": ["v2.${testEnvName}.${testAppName}.${testDomainName}", "foobar.com"]}`; const testUpdatedAliases = `{"frontend": ["v2.${testEnvName}.${testAppName}.${testDomainName}", "foobar.com"]}`; const testAccessDNS = "examp-publi-gsedbvf8t12c-852245110.us-west-1.elb.amazonaws.com."; const testLBHostedZone = "Z1H1FL5HABSF5"; const testHostedZoneId = "Z3P5QSUBK4POTI"; const testRootDNSRole = "mockRole"; + const mockRootHostedZoneId = "Z00ABC" + const mockAppHostedZoneID = "Z00DEF" + const mockEnvHostedZoneID = "Z00GHI" beforeEach(() => { handler.withDefaultResponseURL(ResponseURL); @@ -242,6 +246,68 @@ describe("DNS Validated Certificate Handler", () => { }); }); + test("Create success with out listhostedzones api call", () => { + const changeResourceRecordSetsFake = sinon.fake.resolves({ + ChangeInfo: { + Id: "bogus", + }, + }); + + AWS.mock( + "Route53", + "changeResourceRecordSets", + changeResourceRecordSetsFake + ); + + const request = nock(ResponseURL) + .put("/", (body) => { + return body.Status === "SUCCESS"; + }) + .reply(200); + return LambdaTester(handler.handler) + .event({ + RequestType: "Create", + ResourceProperties: { + AppName: testAppName, + EnvName: testEnvName, + DomainName: testDomainName, + Aliases: testAliases2, + Region: "us-east-1", + PublicAccessDNS: testAccessDNS, + PublicAccessHostedZone: testLBHostedZone, + AppDNSRole: testRootDNSRole, + RootHostedZoneId: mockRootHostedZoneId, + AppHostedZoneId:mockAppHostedZoneID, + EnvHostedZoneId: mockEnvHostedZoneID, + }, + }) + .expectResolve(() => { + sinon.assert.calledWith( + changeResourceRecordSetsFake, + sinon.match({ + ChangeBatch: { + Changes: [ + { + Action: "UPSERT", + ResourceRecordSet: { + Name: `v2.${testEnvName}.${testAppName}.${testDomainName}`, + Type: "A", + AliasTarget: { + HostedZoneId: testLBHostedZone, + DNSName: testAccessDNS, + EvaluateTargetHealth: true, + }, + }, + }, + ], + }, + HostedZoneId: mockEnvHostedZoneID, + }) + ); + expect(request.isDone()).toBe(true); + }); + }); + test("Update success", () => { const changeResourceRecordSetsFake = sinon.fake.resolves({ ChangeInfo: { diff --git a/internal/pkg/aws/route53/route53.go b/internal/pkg/aws/route53/route53.go index 5ff586aa343..dba9258ab5e 100644 --- a/internal/pkg/aws/route53/route53.go +++ b/internal/pkg/aws/route53/route53.go @@ -48,8 +48,8 @@ func New(s *session.Session) *Route53 { } } -// DomainHostedZoneID returns the Hosted Zone ID of a domain. -func (r53 *Route53) DomainHostedZoneID(domainName string) (string, error) { +// PublicDomainHostedZoneID returns the public Hosted Zone ID of a domain. +func (r53 *Route53) PublicDomainHostedZoneID(domainName string) (string, error) { if id, ok := r53.hostedZoneIDFor[domainName]; ok { return id, nil } @@ -84,7 +84,7 @@ func (r53 *Route53) DomainHostedZoneID(domainName string) (string, error) { // route53 hosted zone for the domain. // If there are missing NS records returns ErrUnmatchedNSRecords. func (r53 *Route53) ValidateDomainOwnership(domainName string) error { - hzID, err := r53.DomainHostedZoneID(domainName) + hzID, err := r53.PublicDomainHostedZoneID(domainName) if err != nil { return err } diff --git a/internal/pkg/aws/route53/route53_test.go b/internal/pkg/aws/route53/route53_test.go index 6e0a53809a4..681d5dc9b44 100644 --- a/internal/pkg/aws/route53/route53_test.go +++ b/internal/pkg/aws/route53/route53_test.go @@ -230,7 +230,7 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { hostedZoneIDFor: make(map[string]string), } - gotID, gotErr := service.DomainHostedZoneID(tc.domainName) + gotID, gotErr := service.PublicDomainHostedZoneID(tc.domainName) if gotErr != nil { require.EqualError(t, tc.wantErr, gotErr.Error()) @@ -267,12 +267,12 @@ func TestRoute53_DomainHostedZoneID(t *testing.T) { } // Call once and make the request. - actual, err := service.DomainHostedZoneID("example.com") + actual, err := service.PublicDomainHostedZoneID("example.com") require.NoError(t, err) require.Equal(t, "Z0698117FUWMJ87C39TF", actual) // Call again and Times should be 1. - actual, err = service.DomainHostedZoneID("example.com") + actual, err = service.PublicDomainHostedZoneID("example.com") require.NoError(t, err) require.Equal(t, "Z0698117FUWMJ87C39TF", actual) }) diff --git a/internal/pkg/cli/app_init.go b/internal/pkg/cli/app_init.go index 5110de83629..763c9d1154e 100644 --- a/internal/pkg/cli/app_init.go +++ b/internal/pkg/cli/app_init.go @@ -319,7 +319,7 @@ func (o *initAppOpts) domainHostedZoneID(domainName string) (string, error) { if o.cachedHostedZoneID != "" { return o.cachedHostedZoneID, nil } - hostedZoneID, err := o.route53.DomainHostedZoneID(domainName) + hostedZoneID, err := o.route53.PublicDomainHostedZoneID(domainName) if err != nil { return "", fmt.Errorf("get public hosted zone ID for domain %s: %w", domainName, err) } diff --git a/internal/pkg/cli/app_init_test.go b/internal/pkg/cli/app_init_test.go index 898ef23db49..0a0c266a66e 100644 --- a/internal/pkg/cli/app_init_test.go +++ b/internal/pkg/cli/app_init_test.go @@ -133,7 +133,7 @@ func TestInitAppOpts_Validate(t *testing.T) { m.mockProg.EXPECT().Start(gomock.Any()) m.mockProg.EXPECT().Stop(gomock.Any()).AnyTimes() m.mockRoute53Svc.EXPECT().ValidateDomainOwnership("something.com").Return(errors.New("some error")) - m.mockRoute53Svc.EXPECT().DomainHostedZoneID("something.com").Return("mockHostedZoneID", nil) + m.mockRoute53Svc.EXPECT().PublicDomainHostedZoneID("something.com").Return("mockHostedZoneID", nil) }, }, "wrap error from ListPolicies": { @@ -157,7 +157,7 @@ func TestInitAppOpts_Validate(t *testing.T) { m.mockProg.EXPECT().Start(gomock.Any()) m.mockProg.EXPECT().Stop(gomock.Any()).AnyTimes() m.mockRoute53Svc.EXPECT().ValidateDomainOwnership("badMockDomain.com").Return(nil) - m.mockRoute53Svc.EXPECT().DomainHostedZoneID("badMockDomain.com").Return("", &route53.ErrDomainHostedZoneNotFound{}) + m.mockRoute53Svc.EXPECT().PublicDomainHostedZoneID("badMockDomain.com").Return("", &route53.ErrDomainHostedZoneNotFound{}) }, wantedError: fmt.Errorf("get public hosted zone ID for domain badMockDomain.com: %w", &route53.ErrDomainHostedZoneNotFound{}), }, @@ -167,7 +167,7 @@ func TestInitAppOpts_Validate(t *testing.T) { m.mockProg.EXPECT().Start(gomock.Any()) m.mockProg.EXPECT().Stop(gomock.Any()).AnyTimes() m.mockRoute53Svc.EXPECT().ValidateDomainOwnership("mockDomain.com").Return(&route53.ErrUnmatchedNSRecords{}) - m.mockRoute53Svc.EXPECT().DomainHostedZoneID("mockDomain.com").Return("", errors.New("some error")) + m.mockRoute53Svc.EXPECT().PublicDomainHostedZoneID("mockDomain.com").Return("", errors.New("some error")) }, wantedError: errors.New("get public hosted zone ID for domain mockDomain.com: some error"), }, @@ -180,7 +180,7 @@ func TestInitAppOpts_Validate(t *testing.T) { m.mockRoute53Svc.EXPECT().ValidateDomainOwnership("mockDomain.com").Return(nil) m.mockPolicyLister.EXPECT().ListPolicyNames().Return( []string{"myPermissionsBoundaryPolicy"}, nil) - m.mockRoute53Svc.EXPECT().DomainHostedZoneID("mockDomain.com").Return("mockHostedZoneID", nil) + m.mockRoute53Svc.EXPECT().PublicDomainHostedZoneID("mockDomain.com").Return("mockHostedZoneID", nil) }, }, "valid domain name containing multiple dots": { @@ -189,7 +189,7 @@ func TestInitAppOpts_Validate(t *testing.T) { m.mockProg.EXPECT().Start(gomock.Any()) m.mockProg.EXPECT().Stop(gomock.Any()).AnyTimes() m.mockRoute53Svc.EXPECT().ValidateDomainOwnership("hello.dog.com").Return(nil) - m.mockRoute53Svc.EXPECT().DomainHostedZoneID("hello.dog.com").Return("mockHostedZoneID", nil) + m.mockRoute53Svc.EXPECT().PublicDomainHostedZoneID("hello.dog.com").Return("mockHostedZoneID", nil) }, }, } diff --git a/internal/pkg/cli/app_upgrade.go b/internal/pkg/cli/app_upgrade.go index f9f7c61d9ee..e67e8bf886d 100644 --- a/internal/pkg/cli/app_upgrade.go +++ b/internal/pkg/cli/app_upgrade.go @@ -188,7 +188,7 @@ func (o *appUpgradeOpts) upgradeApplication(app *config.Application, fromVersion func (o *appUpgradeOpts) upgradeAppSSMStore(app *config.Application) error { if app.Domain != "" && app.DomainHostedZoneID == "" { - hostedZoneID, err := o.route53.DomainHostedZoneID(app.Domain) + hostedZoneID, err := o.route53.PublicDomainHostedZoneID(app.Domain) if err != nil { return fmt.Errorf("get hosted zone ID for domain %s: %w", app.Domain, err) } diff --git a/internal/pkg/cli/app_upgrade_test.go b/internal/pkg/cli/app_upgrade_test.go index 0f61e823025..dd54a874d23 100644 --- a/internal/pkg/cli/app_upgrade_test.go +++ b/internal/pkg/cli/app_upgrade_test.go @@ -254,7 +254,7 @@ func TestAppUpgradeOpts_Execute(t *testing.T) { }, nil) mockRoute53 := mocks.NewMockdomainHostedZoneGetter(ctrl) - mockRoute53.EXPECT().DomainHostedZoneID("foobar.com").Return("", errors.New("some error")) + mockRoute53.EXPECT().PublicDomainHostedZoneID("foobar.com").Return("", errors.New("some error")) return &appUpgradeOpts{ appUpgradeVars: appUpgradeVars{ @@ -309,7 +309,7 @@ func TestAppUpgradeOpts_Execute(t *testing.T) { }).Return(nil) mockRoute53 := mocks.NewMockdomainHostedZoneGetter(ctrl) - mockRoute53.EXPECT().DomainHostedZoneID("hello.com").Return("2klfqok3", nil) + mockRoute53.EXPECT().PublicDomainHostedZoneID("hello.com").Return("2klfqok3", nil) mockUpgrader := mocks.NewMockappUpgrader(ctrl) mockUpgrader.EXPECT().UpgradeApplication(&deploy.CreateAppInput{ diff --git a/internal/pkg/cli/deploy/env.go b/internal/pkg/cli/deploy/env.go index f9fc1f0530c..8ff3cda44b6 100644 --- a/internal/pkg/cli/deploy/env.go +++ b/internal/pkg/cli/deploy/env.go @@ -20,6 +20,7 @@ import ( "github.com/aws/copilot-cli/internal/pkg/aws/ec2" "github.com/aws/copilot-cli/internal/pkg/aws/elbv2" "github.com/aws/copilot-cli/internal/pkg/aws/partitions" + "github.com/aws/copilot-cli/internal/pkg/aws/route53" awss3 "github.com/aws/copilot-cli/internal/pkg/aws/s3" "github.com/aws/copilot-cli/internal/pkg/aws/sessions" "github.com/aws/copilot-cli/internal/pkg/cli/deploy/patch" @@ -78,6 +79,9 @@ type stackDescriber interface { Resources() ([]*stack.Resource, error) } +type domainHostedZoneGetter interface { + PublicDomainHostedZoneID(domainName string) (string, error) +} type envDeployer struct { app *config.Application env *config.Environment @@ -96,6 +100,7 @@ type envDeployer struct { envDescriber envDescriber lbDescriber lbDescriber newServiceStackDescriber func(string) stackDescriber + domainHostedZoneGetter domainHostedZoneGetter // Dependencies for parsing addons. ws WorkspaceAddonsReaderPathGetter @@ -173,7 +178,8 @@ func NewEnvDeployer(in *NewEnvDeployerInput) (*envDeployer, error) { parseAddons: sync.OnceValues(func() (stackBuilder, error) { return addon.ParseFromEnv(in.Workspace) }), - ws: in.Workspace, + ws: in.Workspace, + domainHostedZoneGetter: route53.New(defaultSession), } return deployer, nil } @@ -398,12 +404,21 @@ func (d *envDeployer) buildStackInput(in *DeployEnvironmentInput) (*cfnstack.Env if err != nil { return nil, err } + var appHostedZoneID string + if d.app.Domain != "" { + appHostedZoneID, err = appDomainHostedZoneId(d.app.Name, d.app.Domain, d.domainHostedZoneGetter) + if err != nil { + return nil, err + } + } return &cfnstack.EnvConfig{ Name: d.env.Name, App: deploy.AppInformation{ - Name: d.app.Name, - Domain: d.app.Domain, - AccountPrincipalARN: in.RootUserARN, + Name: d.app.Name, + Domain: d.app.Domain, + AccountPrincipalARN: in.RootUserARN, + RootDomainHostedZoneId: d.app.DomainHostedZoneID, + AppDomainHostedZoneId: appHostedZoneID, }, AdditionalTags: d.app.Tags, Addons: addons, @@ -582,3 +597,14 @@ func (d *envDeployer) cfManagedPrefixListID() (string, error) { return id, nil } + +func appDomainHostedZoneId(appName, domain string, domainHostedZoneGetter domainHostedZoneGetter) (string, error) { + if domain == "" { + return "", nil + } + appHostedZoneID, err := domainHostedZoneGetter.PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", appName, domain)) + if err != nil { + return "", fmt.Errorf("get hostedzoneid for domain %s", fmt.Sprintf("%s.%s", appName, domain)) + } + return appHostedZoneID, nil +} diff --git a/internal/pkg/cli/deploy/env_test.go b/internal/pkg/cli/deploy/env_test.go index a40355411a5..9d5b5c0a072 100644 --- a/internal/pkg/cli/deploy/env_test.go +++ b/internal/pkg/cli/deploy/env_test.go @@ -44,8 +44,9 @@ type envDeployerMocks struct { stackDescribers map[string]*mocks.MockstackDescriber ws *mocks.MockWorkspaceAddonsReaderPathGetter - parseAddons func() (stackBuilder, error) - addons *mocks.MockstackBuilder + parseAddons func() (stackBuilder, error) + addons *mocks.MockstackBuilder + domainHostedZoneGetter *mocks.MockdomainHostedZoneGetter } func TestEnvDeployer_UploadArtifacts(t *testing.T) { @@ -392,7 +393,9 @@ func TestEnvDeployer_GenerateCloudFormationTemplate(t *testing.T) { ) mockError := errors.New("some error") mockApp := &config.Application{ - Name: mockAppName, + Name: mockAppName, + Domain: "example.com", + DomainHostedZoneID: "v1.example.com", } testCases := map[string]struct { inManifest manifest.Environment @@ -417,6 +420,7 @@ func TestEnvDeployer_GenerateCloudFormationTemplate(t *testing.T) { m.parseAddons = func() (stackBuilder, error) { return nil, &addon.ErrAddonsNotFound{} } + m.domainHostedZoneGetter.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", mockAppName, mockApp.Domain)).Return("Z00ABC", nil) m.envDeployer.EXPECT().DeployedEnvironmentParameters(gomock.Any(), gomock.Any()).Return(nil, mockError) }, wantedError: errors.New("describe environment stack parameters: some error"), @@ -431,6 +435,7 @@ func TestEnvDeployer_GenerateCloudFormationTemplate(t *testing.T) { } m.envDeployer.EXPECT().DeployedEnvironmentParameters(gomock.Any(), gomock.Any()).Return(nil, nil) m.envDeployer.EXPECT().ForceUpdateOutputID(gomock.Any(), gomock.Any()).Return("", mockError) + m.domainHostedZoneGetter.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", mockAppName, mockApp.Domain)).Return("Z00ABC", nil) }, wantedError: errors.New("retrieve environment stack force update ID: some error"), }, @@ -445,6 +450,7 @@ func TestEnvDeployer_GenerateCloudFormationTemplate(t *testing.T) { m.envDeployer.EXPECT().DeployedEnvironmentParameters(gomock.Any(), gomock.Any()).Return(nil, nil) m.envDeployer.EXPECT().ForceUpdateOutputID(gomock.Any(), gomock.Any()).Return("", nil) m.stackSerializer.EXPECT().Template().Return("", mockError) + m.domainHostedZoneGetter.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", mockAppName, mockApp.Domain)).Return("Z00ABC", nil) }, wantedError: errors.New("generate stack template: some error"), }, @@ -460,6 +466,7 @@ func TestEnvDeployer_GenerateCloudFormationTemplate(t *testing.T) { m.envDeployer.EXPECT().ForceUpdateOutputID(gomock.Any(), gomock.Any()).Return("", nil) m.stackSerializer.EXPECT().Template().Return("", nil) m.stackSerializer.EXPECT().SerializedParameters().Return("", mockError) + m.domainHostedZoneGetter.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", mockAppName, mockApp.Domain)).Return("Z00ABC", nil) }, wantedError: errors.New("generate stack template parameters: some error"), }, @@ -499,6 +506,7 @@ func TestEnvDeployer_GenerateCloudFormationTemplate(t *testing.T) { m.envDeployer.EXPECT().ForceUpdateOutputID(gomock.Any(), gomock.Any()).Return("", nil) m.stackSerializer.EXPECT().Template().Return("aloo", nil) m.stackSerializer.EXPECT().SerializedParameters().Return("gobi", nil) + m.domainHostedZoneGetter.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", mockAppName, mockApp.Domain)).Return("Z00ABC", nil).Return("Z00ABC", nil) }, wantedTemplate: "aloo", @@ -518,6 +526,7 @@ func TestEnvDeployer_GenerateCloudFormationTemplate(t *testing.T) { m.envDeployer.EXPECT().ForceUpdateOutputID(gomock.Any(), gomock.Any()).Return("", nil) m.stackSerializer.EXPECT().Template().Return("aloo", nil) m.stackSerializer.EXPECT().SerializedParameters().Return("gobi", nil) + m.domainHostedZoneGetter.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", mockAppName, mockApp.Domain)).Return("Z00ABC", nil) }, wantedTemplate: "aloo", @@ -530,9 +539,10 @@ func TestEnvDeployer_GenerateCloudFormationTemplate(t *testing.T) { defer ctrl.Finish() m := &envDeployerMocks{ - appCFN: mocks.NewMockappResourcesGetter(ctrl), - envDeployer: mocks.NewMockenvironmentDeployer(ctrl), - stackSerializer: cfnmocks.NewMockStackConfiguration(ctrl), + appCFN: mocks.NewMockappResourcesGetter(ctrl), + envDeployer: mocks.NewMockenvironmentDeployer(ctrl), + stackSerializer: cfnmocks.NewMockStackConfiguration(ctrl), + domainHostedZoneGetter: mocks.NewMockdomainHostedZoneGetter(ctrl), } tc.setUpMocks(m, ctrl) d := envDeployer{ @@ -546,7 +556,8 @@ func TestEnvDeployer_GenerateCloudFormationTemplate(t *testing.T) { newStack: func(_ *cfnstack.EnvConfig, _ string, _ []*awscfn.Parameter) (cloudformation.StackConfiguration, error) { return m.stackSerializer, nil }, - parseAddons: m.parseAddons, + parseAddons: m.parseAddons, + domainHostedZoneGetter: m.domainHostedZoneGetter, } actual, err := d.GenerateCloudFormationTemplate(&DeployEnvironmentInput{ Manifest: &tc.inManifest, diff --git a/internal/pkg/cli/deploy/lbws.go b/internal/pkg/cli/deploy/lbws.go index cc6e4409621..9e3f2088c21 100644 --- a/internal/pkg/cli/deploy/lbws.go +++ b/internal/pkg/cli/deploy/lbws.go @@ -162,6 +162,13 @@ func (d *lbWebSvcDeployer) stackConfiguration(in *StackRuntimeConfiguration) (*s } opts = append(opts, stack.WithNLB(cidrBlocks)) } + var appHostedZoneID string + if d.app.Domain != "" { + appHostedZoneID, err = appDomainHostedZoneId(d.app.Name, d.app.Domain, d.domainHostedZoneGetter) + if err != nil { + return nil, err + } + } var conf cloudformation.StackConfiguration switch { @@ -177,6 +184,7 @@ func (d *lbWebSvcDeployer) stackConfiguration(in *StackRuntimeConfiguration) (*s RuntimeConfig: *rc, RootUserARN: in.RootUserARN, Addons: d.addons, + AppHostedZoneID: appHostedZoneID, }, opts...) if err != nil { return nil, fmt.Errorf("create stack configuration: %w", err) diff --git a/internal/pkg/cli/deploy/mocks/mock_env.go b/internal/pkg/cli/deploy/mocks/mock_env.go index c1982de6a8b..3fcbe918c7a 100644 --- a/internal/pkg/cli/deploy/mocks/mock_env.go +++ b/internal/pkg/cli/deploy/mocks/mock_env.go @@ -453,3 +453,41 @@ func (mr *MockstackDescriberMockRecorder) Resources() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resources", reflect.TypeOf((*MockstackDescriber)(nil).Resources)) } + +// MockdomainHostedZoneGetter is a mock of domainHostedZoneGetter interface. +type MockdomainHostedZoneGetter struct { + ctrl *gomock.Controller + recorder *MockdomainHostedZoneGetterMockRecorder +} + +// MockdomainHostedZoneGetterMockRecorder is the mock recorder for MockdomainHostedZoneGetter. +type MockdomainHostedZoneGetterMockRecorder struct { + mock *MockdomainHostedZoneGetter +} + +// NewMockdomainHostedZoneGetter creates a new mock instance. +func NewMockdomainHostedZoneGetter(ctrl *gomock.Controller) *MockdomainHostedZoneGetter { + mock := &MockdomainHostedZoneGetter{ctrl: ctrl} + mock.recorder = &MockdomainHostedZoneGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockdomainHostedZoneGetter) EXPECT() *MockdomainHostedZoneGetterMockRecorder { + return m.recorder +} + +// PublicDomainHostedZoneID mocks base method. +func (m *MockdomainHostedZoneGetter) PublicDomainHostedZoneID(domainName string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PublicDomainHostedZoneID", domainName) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PublicDomainHostedZoneID indicates an expected call of PublicDomainHostedZoneID. +func (mr *MockdomainHostedZoneGetterMockRecorder) PublicDomainHostedZoneID(domainName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublicDomainHostedZoneID", reflect.TypeOf((*MockdomainHostedZoneGetter)(nil).PublicDomainHostedZoneID), domainName) +} diff --git a/internal/pkg/cli/deploy/rdws.go b/internal/pkg/cli/deploy/rdws.go index 7e8470dd71a..c13ef5581e3 100644 --- a/internal/pkg/cli/deploy/rdws.go +++ b/internal/pkg/cli/deploy/rdws.go @@ -143,10 +143,11 @@ func (d *rdwsDeployer) stackConfiguration(in *StackRuntimeConfiguration) (*rdwsS default: conf, err = stack.NewRequestDrivenWebService(stack.RequestDrivenWebServiceConfig{ App: deploy.AppInformation{ - Name: d.app.Name, - Domain: d.app.Domain, - PermissionsBoundary: d.app.PermissionsBoundary, - AccountPrincipalARN: in.RootUserARN, + Name: d.app.Name, + Domain: d.app.Domain, + PermissionsBoundary: d.app.PermissionsBoundary, + AccountPrincipalARN: in.RootUserARN, + RootDomainHostedZoneId: d.app.DomainHostedZoneID, }, Env: d.env.Name, Manifest: d.rdwsMft, diff --git a/internal/pkg/cli/deploy/static_site.go b/internal/pkg/cli/deploy/static_site.go index 7e409d268ea..92426cabbab 100644 --- a/internal/pkg/cli/deploy/static_site.go +++ b/internal/pkg/cli/deploy/static_site.go @@ -163,6 +163,14 @@ func (d *staticSiteDeployer) stackConfiguration(in *StackRuntimeConfiguration) ( if err := validateMinAppVersion(d.app.Name, d.name, d.appVersionGetter, version.AppTemplateMinStaticSite); err != nil { return nil, fmt.Errorf("static sites not supported: %w", err) } + var appHostedZoneID string + if d.app.Domain != "" { + appHostedZoneID, err = appDomainHostedZoneId(d.app.Name, d.app.Domain, d.domainHostedZoneGetter) + if err != nil { + return nil, err + } + } + conf, err := d.newStack(&stack.StaticSiteConfig{ App: d.app, EnvManifest: d.envConfig, @@ -173,6 +181,7 @@ func (d *staticSiteDeployer) stackConfiguration(in *StackRuntimeConfiguration) ( RootUserARN: in.RootUserARN, Addons: d.addons, AssetMappingURL: in.StaticSiteAssetMappingURL, + AppHostedZoneID: appHostedZoneID, }) if err != nil { return nil, fmt.Errorf("create stack configuration: %w", err) diff --git a/internal/pkg/cli/deploy/static_site_test.go b/internal/pkg/cli/deploy/static_site_test.go index 0b5b29f2eae..74727d70a61 100644 --- a/internal/pkg/cli/deploy/static_site_test.go +++ b/internal/pkg/cli/deploy/static_site_test.go @@ -139,6 +139,7 @@ func TestStaticSiteDeployer_UploadArtifacts(t *testing.T) { func TestStaticSiteDeployer_stackConfiguration(t *testing.T) { tests := map[string]struct { + setUpMocks func(m *mocks.MockdomainHostedZoneGetter) deployer *staticSiteDeployer wantErr string wantTemplate string @@ -330,6 +331,9 @@ func TestStaticSiteDeployer_stackConfiguration(t *testing.T) { }, }, "success with app alias": { + setUpMocks: func(m *mocks.MockdomainHostedZoneGetter) { + m.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", "mockApp", "example.com")).Return("Z00AB", nil).Return("Z00AB", nil) + }, deployer: &staticSiteDeployer{ svcDeployer: &svcDeployer{ workloadDeployer: &workloadDeployer{ @@ -402,6 +406,9 @@ func TestStaticSiteDeployer_stackConfiguration(t *testing.T) { }, }, "success with overrider": { + setUpMocks: func(m *mocks.MockdomainHostedZoneGetter) { + m.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", "mockApp", "example.com")).Return("Z00AB", nil) + }, deployer: &staticSiteDeployer{ svcDeployer: &svcDeployer{ workloadDeployer: &workloadDeployer{ @@ -443,6 +450,13 @@ func TestStaticSiteDeployer_stackConfiguration(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + if tc.setUpMocks != nil { + mocks := mocks.NewMockdomainHostedZoneGetter(ctrl) + tc.setUpMocks(mocks) + tc.deployer.domainHostedZoneGetter = mocks + } out, gotErr := tc.deployer.stackConfiguration(&StackRuntimeConfiguration{}) if tc.wantErr != "" { require.EqualError(t, gotErr, tc.wantErr) diff --git a/internal/pkg/cli/deploy/workload.go b/internal/pkg/cli/deploy/workload.go index 064123d8d93..6213257b407 100644 --- a/internal/pkg/cli/deploy/workload.go +++ b/internal/pkg/cli/deploy/workload.go @@ -24,6 +24,7 @@ import ( "github.com/aws/copilot-cli/internal/pkg/aws/ecr" "github.com/aws/copilot-cli/internal/pkg/aws/identity" "github.com/aws/copilot-cli/internal/pkg/aws/partitions" + "github.com/aws/copilot-cli/internal/pkg/aws/route53" "github.com/aws/copilot-cli/internal/pkg/aws/s3" "github.com/aws/copilot-cli/internal/pkg/aws/sessions" "github.com/aws/copilot-cli/internal/pkg/config" @@ -180,20 +181,21 @@ type workloadDeployer struct { workspacePath string // Dependencies. - fs afero.Fs - s3Client uploader - addons stackBuilder - repository repositoryService - deployer serviceDeployer - tmplGetter deployedTemplateGetter - endpointGetter endpointGetter - spinner spinner - templateFS template.Reader - envVersionGetter versionGetter - overrider Overrider - docker dockerEngineRunChecker - customResources customResourcesFunc - labeledTermPrinter func(fw syncbuffer.FileWriter, bufs []*syncbuffer.LabeledSyncBuffer, opts ...syncbuffer.LabeledTermPrinterOption) LabeledTermPrinter + fs afero.Fs + s3Client uploader + addons stackBuilder + repository repositoryService + deployer serviceDeployer + tmplGetter deployedTemplateGetter + endpointGetter endpointGetter + spinner spinner + templateFS template.Reader + envVersionGetter versionGetter + overrider Overrider + docker dockerEngineRunChecker + customResources customResourcesFunc + labeledTermPrinter func(fw syncbuffer.FileWriter, bufs []*syncbuffer.LabeledSyncBuffer, opts ...syncbuffer.LabeledTermPrinterOption) LabeledTermPrinter + domainHostedZoneGetter domainHostedZoneGetter // Cached variables. defaultSess *session.Session @@ -335,6 +337,7 @@ func newWorkloadDeployer(in *WorkloadDeployerInput) (*workloadDeployer, error) { store: store, envConfig: envConfig, labeledTermPrinter: labeledTermPrinter, + domainHostedZoneGetter: route53.New(defaultSession), mft: in.Mft, rawMft: in.RawMft, diff --git a/internal/pkg/cli/deploy/workload_test.go b/internal/pkg/cli/deploy/workload_test.go index 4277b24129b..f748c293b65 100644 --- a/internal/pkg/cli/deploy/workload_test.go +++ b/internal/pkg/cli/deploy/workload_test.go @@ -62,6 +62,7 @@ type deployMocks struct { mockValidator *mocks.MockaliasCertValidator mockLabeledTermPrinter *mocks.MockLabeledTermPrinter mockdockerEngineRunChecker *mocks.MockdockerEngineRunChecker + mockdomainHostedZonegetter *mocks.MockdomainHostedZoneGetter } type mockTemplateFS struct { @@ -1231,6 +1232,7 @@ func TestWorkloadDeployer_DeployWorkload(t *testing.T) { m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockEnvVersionGetter.EXPECT().Version().Return("v1.42.0", nil) m.mockServiceDeployer.EXPECT().DeployService(gomock.Any(), "mockBucket", false, gomock.Any()).Return(nil) + m.mockdomainHostedZonegetter.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", mockAppName, "mockDomain")).Return("Z00ABC", nil) }, }, "success": { @@ -1255,6 +1257,7 @@ func TestWorkloadDeployer_DeployWorkload(t *testing.T) { m.mockEnvVersionGetter.EXPECT().Version().Return("v1.42.0", nil) m.mockValidator.EXPECT().ValidateCertAliases([]string{"example.com", "foobar.com"}, mockCertARNs).Return(nil).Times(2) m.mockServiceDeployer.EXPECT().DeployService(gomock.Any(), "mockBucket", false, gomock.Any()).Return(nil) + m.mockdomainHostedZonegetter.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", mockAppName, "mockDomain")).Return("Z00ABC", nil) }, }, "success with http redirect disabled and alb certs imported": { @@ -1330,6 +1333,7 @@ func TestWorkloadDeployer_DeployWorkload(t *testing.T) { m.mockEnvVersionGetter.EXPECT().Version().Return("v1.42.0", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil).Times(2) m.mockServiceDeployer.EXPECT().DeployService(gomock.Any(), "mockBucket", false, gomock.Any()).Return(nil) + m.mockdomainHostedZonegetter.EXPECT().PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", mockAppName, "mockDomain")).Return("Z00ABC", nil) }, }, "success with force update": { @@ -1369,6 +1373,7 @@ func TestWorkloadDeployer_DeployWorkload(t *testing.T) { mockSpinner: mocks.NewMockspinner(ctrl), mockPublicCIDRBlocksGetter: mocks.NewMockpublicCIDRBlocksGetter(ctrl), mockValidator: mocks.NewMockaliasCertValidator(ctrl), + mockdomainHostedZonegetter: mocks.NewMockdomainHostedZoneGetter(ctrl), } tc.mock(m) @@ -1380,16 +1385,17 @@ func TestWorkloadDeployer_DeployWorkload(t *testing.T) { deployer := lbWebSvcDeployer{ svcDeployer: &svcDeployer{ workloadDeployer: &workloadDeployer{ - name: mockName, - app: tc.inApp, - env: tc.inEnvironment, - envConfig: tc.inEnvironmentConfig(), - resources: mockResources, - deployer: m.mockServiceDeployer, - endpointGetter: m.mockEndpointGetter, - spinner: m.mockSpinner, - envVersionGetter: m.mockEnvVersionGetter, - overrider: new(override.Noop), + name: mockName, + app: tc.inApp, + env: tc.inEnvironment, + envConfig: tc.inEnvironmentConfig(), + resources: mockResources, + deployer: m.mockServiceDeployer, + endpointGetter: m.mockEndpointGetter, + spinner: m.mockSpinner, + envVersionGetter: m.mockEnvVersionGetter, + overrider: new(override.Noop), + domainHostedZoneGetter: m.mockdomainHostedZonegetter, }, newSvcUpdater: func(f func(*session.Session) serviceForceUpdater) serviceForceUpdater { return m.mockServiceForceUpdater diff --git a/internal/pkg/cli/interfaces.go b/internal/pkg/cli/interfaces.go index c14dc34cd04..fcf955413d6 100644 --- a/internal/pkg/cli/interfaces.go +++ b/internal/pkg/cli/interfaces.go @@ -455,7 +455,7 @@ type deployer interface { } type domainHostedZoneGetter interface { - DomainHostedZoneID(domainName string) (string, error) + PublicDomainHostedZoneID(domainName string) (string, error) ValidateDomainOwnership(domainName string) error } diff --git a/internal/pkg/cli/mocks/mock_interfaces.go b/internal/pkg/cli/mocks/mock_interfaces.go index 32dfff16721..899ad15927f 100644 --- a/internal/pkg/cli/mocks/mock_interfaces.go +++ b/internal/pkg/cli/mocks/mock_interfaces.go @@ -5052,19 +5052,19 @@ func (m *MockdomainHostedZoneGetter) EXPECT() *MockdomainHostedZoneGetterMockRec return m.recorder } -// DomainHostedZoneID mocks base method. -func (m *MockdomainHostedZoneGetter) DomainHostedZoneID(domainName string) (string, error) { +// PublicDomainHostedZoneID mocks base method. +func (m *MockdomainHostedZoneGetter) PublicDomainHostedZoneID(domainName string) (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DomainHostedZoneID", domainName) + ret := m.ctrl.Call(m, "PublicDomainHostedZoneID", domainName) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } -// DomainHostedZoneID indicates an expected call of DomainHostedZoneID. -func (mr *MockdomainHostedZoneGetterMockRecorder) DomainHostedZoneID(domainName interface{}) *gomock.Call { +// PublicDomainHostedZoneID indicates an expected call of PublicDomainHostedZoneID. +func (mr *MockdomainHostedZoneGetterMockRecorder) PublicDomainHostedZoneID(domainName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DomainHostedZoneID", reflect.TypeOf((*MockdomainHostedZoneGetter)(nil).DomainHostedZoneID), domainName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublicDomainHostedZoneID", reflect.TypeOf((*MockdomainHostedZoneGetter)(nil).PublicDomainHostedZoneID), domainName) } // ValidateDomainOwnership mocks base method. diff --git a/internal/pkg/deploy/app.go b/internal/pkg/deploy/app.go index 0ec649571c9..50c060df389 100644 --- a/internal/pkg/deploy/app.go +++ b/internal/pkg/deploy/app.go @@ -27,10 +27,12 @@ type CreateAppInput struct { // AppInformation holds information about the application that need to be propagated to the env stacks and workload stacks. type AppInformation struct { - AccountPrincipalARN string - Domain string - Name string - PermissionsBoundary string + AccountPrincipalARN string + Domain string + Name string + PermissionsBoundary string + RootDomainHostedZoneId string + AppDomainHostedZoneId string } // DNSDelegationRole returns the ARN of the app's DNS delegation role. diff --git a/internal/pkg/deploy/cloudformation/stack/env.go b/internal/pkg/deploy/cloudformation/stack/env.go index bfbddfc4d25..b5b4b2ce033 100644 --- a/internal/pkg/deploy/cloudformation/stack/env.go +++ b/internal/pkg/deploy/cloudformation/stack/env.go @@ -219,6 +219,7 @@ func (e *Env) Template() (string, error) { SerializedManifest: string(e.in.RawMft), ForceUpdateID: forceUpdateID, DelegateDNS: e.in.App.Domain != "", + HostedZones: convertHostedZones(e.in.App), }) if err != nil { return "", err diff --git a/internal/pkg/deploy/cloudformation/stack/env_test.go b/internal/pkg/deploy/cloudformation/stack/env_test.go index e538b74d17a..bc3dea79e6d 100644 --- a/internal/pkg/deploy/cloudformation/stack/env_test.go +++ b/internal/pkg/deploy/cloudformation/stack/env_test.go @@ -134,6 +134,9 @@ func TestEnv_Template(t *testing.T) { // GIVEN inEnvConfig := mockDeployEnvironmentInput() + inEnvConfig.App.Domain = "example.com" + inEnvConfig.App.RootDomainHostedZoneId = "Z00ABC" + inEnvConfig.App.AppDomainHostedZoneId = "Z00DEF" mockParser := mocks.NewMockembedFS(ctrl) mockParser.EXPECT().Read(gomock.Any()).Return(&template.Content{Buffer: bytes.NewBufferString("data")}, nil).AnyTimes() mockParser.EXPECT().ParseEnv(gomock.Any()).DoAndReturn(func(data *template.EnvOpts) (*template.Content, error) { @@ -170,6 +173,11 @@ func TestEnv_Template(t *testing.T) { ArtifactBucketARN: "arn:aws:s3:::mockbucket", SerializedManifest: "name: env\ntype: Environment\n", ForceUpdateID: "mockPreviousForceUpdateID", + DelegateDNS: true, + HostedZones: &template.HostedZones{ + RootDomainHostedZoneId: "Z00ABC", + AppDomainHostedZoneId: "Z00DEF", + }, }, data) return &template.Content{Buffer: bytes.NewBufferString("mockTemplate")}, nil }) 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 b363ab5dd3c..d61176f6d51 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 @@ -102,7 +102,7 @@ func TestNetworkLoadBalancedWebService_Template(t *testing.T) { }, } serializer, err := stack.NewLoadBalancedWebService(stack.LoadBalancedWebServiceConfig{ - App: &config.Application{Name: appName, Domain: "example.com"}, + App: &config.Application{Name: appName, Domain: "example.com", DomainHostedZoneID: "Z00ABC"}, EnvManifest: envConfig, Manifest: v, ArtifactBucketName: "bucket", @@ -113,7 +113,8 @@ func TestNetworkLoadBalancedWebService_Template(t *testing.T) { EnvVersion: "v1.42.0", Version: "v1.29.0", }, - RootUserARN: "arn:aws:iam::123456789123:root", + RootUserARN: "arn:aws:iam::123456789123:root", + AppHostedZoneID: "Z00DEF", }, stack.WithNLB([]string{"10.0.0.0/24", "10.1.0.0/24"})) tpl, err := serializer.Template() require.NoError(t, err, "template should render") diff --git a/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go b/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go index ac41383e036..72240c9ae69 100644 --- a/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go +++ b/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go @@ -59,6 +59,7 @@ type LoadBalancedWebServiceConfig struct { RootUserARN string ArtifactBucketName string Addons NestedStackConfigurer + AppHostedZoneID string } // NewLoadBalancedWebService creates a new CFN stack with an ECS service from a manifest file, given the options. @@ -75,9 +76,11 @@ func NewLoadBalancedWebService(conf LoadBalancedWebServiceConfig, if conf.App.Domain != "" { dnsDelegationEnabled = true appInfo = deploy.AppInformation{ - Name: conf.App.Name, - Domain: conf.App.Domain, - AccountPrincipalARN: conf.RootUserARN, + Name: conf.App.Name, + Domain: conf.App.Domain, + AccountPrincipalARN: conf.RootUserARN, + RootDomainHostedZoneId: conf.App.DomainHostedZoneID, + AppDomainHostedZoneId: conf.AppHostedZoneID, } httpsEnabled = true } @@ -240,6 +243,7 @@ func (s *LoadBalancedWebService) Template() (string, error) { // NLB configs. AppDNSName: nlbConfig.appDNSName, AppDNSDelegationRole: nlbConfig.appDNSDelegationRole, + HostedZones: convertHostedZones(s.appInfo), NLB: nlbConfig.settings, // service connect and service discovery options. 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 e880d7d0917..3318fe0584a 100644 --- a/internal/pkg/deploy/cloudformation/stack/lb_web_svc_test.go +++ b/internal/pkg/deploy/cloudformation/stack/lb_web_svc_test.go @@ -233,8 +233,9 @@ Outputs: lbws, err := NewLoadBalancedWebService(LoadBalancedWebServiceConfig{ App: &config.Application{ - Name: "phonetool", - Domain: "phonetool.com", + Name: "phonetool", + Domain: "phonetool.com", + DomainHostedZoneID: "Z00ABC", }, EnvManifest: &manifest.Environment{ Workload: manifest.Workload{ @@ -254,6 +255,7 @@ Outputs: }, Addons: mockAddons{}, ArtifactBucketName: "bucket", + AppHostedZoneID: "Z00DEF", }, func(s *LoadBalancedWebService) { s.parser = parser }) @@ -352,6 +354,10 @@ Outputs: ContainerName: "frontend", }, }, + HostedZones: &template.HostedZones{ + RootDomainHostedZoneId: "Z00ABC", + AppDomainHostedZoneId: "Z00DEF", + }, }, actual) }) diff --git a/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go b/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go index 7d57f4b1114..39e6226daf2 100644 --- a/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go +++ b/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go @@ -148,6 +148,7 @@ func (s *RequestDrivenWebService) Template() (string, error) { AppRunnerVPCEndpoint: s.manifest.Private.Advanced.Endpoint, Count: s.manifest.Count, Secrets: convertSecrets(s.manifest.RequestDrivenWebServiceConfig.Secrets), + HostedZones: convertHostedZones(s.app), }) if err != nil { return "", err diff --git a/internal/pkg/deploy/cloudformation/stack/static_site.go b/internal/pkg/deploy/cloudformation/stack/static_site.go index 2f6d0f4921a..bf22ad94d86 100644 --- a/internal/pkg/deploy/cloudformation/stack/static_site.go +++ b/internal/pkg/deploy/cloudformation/stack/static_site.go @@ -39,6 +39,7 @@ type StaticSiteConfig struct { ArtifactBucketName string Addons NestedStackConfigurer AssetMappingURL string + AppHostedZoneID string } // NewStaticSite creates a new CFN stack from a manifest file, given the options. @@ -54,9 +55,11 @@ func NewStaticSite(cfg *StaticSiteConfig) (*StaticSite, error) { if cfg.App.Domain != "" { dnsDelegationEnabled = true appInfo = deploy.AppInformation{ - Name: cfg.App.Name, - Domain: cfg.App.Domain, - AccountPrincipalARN: cfg.RootUserARN, + Name: cfg.App.Name, + Domain: cfg.App.Domain, + AccountPrincipalARN: cfg.RootUserARN, + RootDomainHostedZoneId: cfg.App.DomainHostedZoneID, + AppDomainHostedZoneId: cfg.AppHostedZoneID, } } return &StaticSite{ @@ -133,6 +136,7 @@ func (s *StaticSite) Template() (string, error) { AppDNSName: dnsName, AppDNSDelegationRole: dnsDelegationRole, + HostedZones: convertHostedZones(s.appInfo), AssetMappingFileBucket: bucket, AssetMappingFilePath: path, StaticSiteAlias: staticSiteAlias, diff --git a/internal/pkg/deploy/cloudformation/stack/static_site_integration_test.go b/internal/pkg/deploy/cloudformation/stack/static_site_integration_test.go index 68eae0b1b6d..e4b005d396d 100644 --- a/internal/pkg/deploy/cloudformation/stack/static_site_integration_test.go +++ b/internal/pkg/deploy/cloudformation/stack/static_site_integration_test.go @@ -91,8 +91,9 @@ func TestStaticSiteService_TemplateAndParamsGeneration(t *testing.T) { } serializer, err := stack.NewStaticSite(&stack.StaticSiteConfig{ App: &config.Application{ - Name: appName, - Domain: "example.com", + Name: appName, + Domain: "example.com", + DomainHostedZoneID: "Z00ABC", }, EnvManifest: envConfig, Manifest: v, @@ -104,6 +105,7 @@ func TestStaticSiteService_TemplateAndParamsGeneration(t *testing.T) { ArtifactBucketName: "bucket", AssetMappingURL: "s3://stackset-bucket/mappingfile", RootUserARN: "arn:aws:iam::123456789123:root", + AppHostedZoneID: "Z00DEF", }) require.NoError(t, err, "stack should be able to be initialized") tpl, err := serializer.Template() 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 2b2ff63f168..12e1b13ac93 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 @@ -986,6 +986,7 @@ Resources: Aliases: !Ref Aliases AppDNSRole: !Ref AppDNSDelegationRole DomainName: !Ref AppDNSName + EnvHostedZoneId: !Ref EnvironmentHostedZone PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID AppRunnerVpcEndpointSecurityGroup: 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 a2e2613d80a..307dbfea12b 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 @@ -1094,6 +1094,7 @@ Resources: Aliases: !Ref Aliases AppDNSRole: !Ref AppDNSDelegationRole DomainName: !Ref AppDNSName + EnvHostedZoneId: !Ref EnvironmentHostedZone PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID AppRunnerVpcEndpointSecurityGroup: diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml index c5e2a9211fb..b3cf95f5c18 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml @@ -992,6 +992,7 @@ Resources: Aliases: !Ref Aliases AppDNSRole: !Ref AppDNSDelegationRole DomainName: !Ref AppDNSName + EnvHostedZoneId: !Ref EnvironmentHostedZone PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID VpcFlowLogGroup: diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml index 5b46c4ae25d..2bfd3fcdd9f 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml @@ -821,6 +821,7 @@ Resources: Aliases: !Ref Aliases AppDNSRole: !Ref AppDNSDelegationRole DomainName: !Ref AppDNSName + EnvHostedZoneId: !Ref EnvironmentHostedZone PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID VpcFlowLogGroup: diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/static-site.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/static-site.stack.yml index 5d5a54a6fee..c9325353803 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/static-site.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/static-site.stack.yml @@ -368,6 +368,8 @@ Resources: RootDNSRole: arn:aws:iam::123456789123:role/my-app-DNSDelegationRole DomainName: example.com Aliases: ["*.example.com"] + RootHostedZoneId: "Z00ABC" + AppHostedZoneId: "Z00DEF" CustomDomainFunction: Type: AWS::Lambda::Function @@ -430,6 +432,8 @@ Resources: DomainName: example.com IsCloudFrontCertificate: true Aliases: ["*.example.com"] + RootHostedZoneId: "Z00ABC" + AppHostedZoneId: "Z00DEF" CertificateValidationFunction: Type: AWS::Lambda::Function diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-prod.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-prod.stack.yml index 4da2e25d7ce..9fe65603482 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-prod.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-nlb-prod.stack.yml @@ -632,6 +632,8 @@ Resources: # If a bucket URL is specified, that means the template exists. DomainName: example.com Aliases: - nlb.example.com + RootHostedZoneId: "Z00ABC" + AppHostedZoneId: "Z00DEF" NLBCustomDomainFunction: Type: AWS::Lambda::Function Condition: HasAssociatedDomain @@ -697,6 +699,8 @@ Resources: # If a bucket URL is specified, that means the template exists. DomainName: example.com Aliases: - nlb.example.com + RootHostedZoneId: "Z00ABC" + AppHostedZoneId: "Z00DEF" NLBCertValidatorFunction: Type: AWS::Lambda::Function Condition: HasAssociatedDomain 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 b7f7b64598c..5a3299da354 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 @@ -511,6 +511,8 @@ Resources: # If a bucket URL is specified, that means the template exists. RootDNSRole: arn:aws:iam::123456789123:role/my-app-DNSDelegationRole DomainName: example.com Aliases: [] + RootHostedZoneId: "Z00ABC" + AppHostedZoneId: "Z00DEF" NLBCertValidatorFunction: Type: AWS::Lambda::Function Condition: HasAssociatedDomain diff --git a/internal/pkg/deploy/cloudformation/stack/transformers.go b/internal/pkg/deploy/cloudformation/stack/transformers.go index 3a4b6ff88d1..05b31175e1a 100644 --- a/internal/pkg/deploy/cloudformation/stack/transformers.go +++ b/internal/pkg/deploy/cloudformation/stack/transformers.go @@ -1333,3 +1333,13 @@ func (in uploadableCRs) convert() []uploadable { } return out } + +func convertHostedZones(app deploy.AppInformation) *template.HostedZones { + if app.Domain == "" { + return nil + } + return &template.HostedZones{ + RootDomainHostedZoneId: app.RootDomainHostedZoneId, + AppDomainHostedZoneId: app.AppDomainHostedZoneId, + } +} diff --git a/internal/pkg/template/env.go b/internal/pkg/template/env.go index 7a777a8bd6a..4504129f5e3 100644 --- a/internal/pkg/template/env.go +++ b/internal/pkg/template/env.go @@ -131,6 +131,7 @@ type EnvOpts struct { ForceUpdateID string DelegateDNS bool + HostedZones *HostedZones } // PublicHTTPConfig represents configuration for a public facing Load Balancer. @@ -237,6 +238,12 @@ type VPCFlowLogs struct { Retention *int } +// HostedZones represents copilot managed hostedzones to route traffic to specific domain. +type HostedZones struct { + RootDomainHostedZoneId string + AppDomainHostedZoneId string +} + // ParseEnv parses an environment's CloudFormation template with the specified data object and returns its content. func (t *Template) ParseEnv(data *EnvOpts) (*Content, error) { tpl, err := t.parse("base", envCFTemplatePath, withEnvParsingFuncs()) diff --git a/internal/pkg/template/templates/environment/partials/custom-resources.yml b/internal/pkg/template/templates/environment/partials/custom-resources.yml index 178da537b61..907c9a7416d 100644 --- a/internal/pkg/template/templates/environment/partials/custom-resources.yml +++ b/internal/pkg/template/templates/environment/partials/custom-resources.yml @@ -12,6 +12,11 @@ DelegateDNSAction: SubdomainName: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} NameServers: !GetAtt EnvironmentHostedZone.NameServers RootDNSRole: !Ref AppDNSDelegationRole + {{- if .HostedZones}} + RootHostedZoneId: {{.HostedZones.RootDomainHostedZoneId}} + AppHostedZoneId: {{.HostedZones.AppDomainHostedZoneId}} + EnvHostedZoneId: !Ref EnvironmentHostedZone + {{- end}} HTTPSCert: Metadata: @@ -31,6 +36,11 @@ HTTPSCert: EnvHostedZoneId: !Ref EnvironmentHostedZone Region: !Ref AWS::Region RootDNSRole: !Ref AppDNSDelegationRole + {{- if .HostedZones}} + RootHostedZoneId: {{.HostedZones.RootDomainHostedZoneId}} + AppHostedZoneId: {{.HostedZones.AppDomainHostedZoneId}} + {{- end}} + CustomDomainAction: Metadata: @@ -44,10 +54,15 @@ CustomDomainAction: Aliases: !Ref Aliases AppDNSRole: !Ref AppDNSDelegationRole DomainName: !Ref AppDNSName + EnvHostedZoneId: !Ref EnvironmentHostedZone {{- if .CDNConfig}} PublicAccessDNS: !GetAtt CloudFrontDistribution.DomainName PublicAccessHostedZone: Z2FDTNDATAQYW2 # See https://go.aws/3cPhvlX {{- else}} PublicAccessDNS: !GetAtt PublicLoadBalancer.DNSName PublicAccessHostedZone: !GetAtt PublicLoadBalancer.CanonicalHostedZoneID - {{- end}} \ No newline at end of file + {{- end}} + {{- if .HostedZones}} + RootHostedZoneId: {{.HostedZones.RootDomainHostedZoneId}} + AppHostedZoneId: {{.HostedZones.AppDomainHostedZoneId}} + {{- end}} \ No newline at end of file diff --git a/internal/pkg/template/templates/workloads/partials/cf/nlb.yml b/internal/pkg/template/templates/workloads/partials/cf/nlb.yml index 5f12c91635e..62a0f20d89c 100644 --- a/internal/pkg/template/templates/workloads/partials/cf/nlb.yml +++ b/internal/pkg/template/templates/workloads/partials/cf/nlb.yml @@ -241,6 +241,10 @@ NLBCustomDomainAction: RootDNSRole: {{ .AppDNSDelegationRole }} DomainName: {{ .AppDNSName }} Aliases: {{ if .NLB.Aliases }} {{ fmtSlice .NLB.Aliases }} {{ else }} [] {{ end }} + {{- if .HostedZones}} + RootHostedZoneId: {{.HostedZones.RootDomainHostedZoneId}} + AppHostedZoneId: {{.HostedZones.AppDomainHostedZoneId}} + {{- end}} NLBCustomDomainFunction: Type: AWS::Lambda::Function @@ -325,6 +329,10 @@ NLBCertValidatorAction: RootDNSRole: {{ .AppDNSDelegationRole }} DomainName: {{ .AppDNSName }} Aliases: {{ if .NLB.Aliases }} {{ fmtSlice .NLB.Aliases }} {{ else }} [] {{ end }} + {{- if .HostedZones}} + RootHostedZoneId: {{.HostedZones.RootDomainHostedZoneId}} + AppHostedZoneId: {{.HostedZones.AppDomainHostedZoneId}} + {{- end}} NLBCertValidatorFunction: Type: AWS::Lambda::Function diff --git a/internal/pkg/template/templates/workloads/services/rd-web/cf.yml b/internal/pkg/template/templates/workloads/services/rd-web/cf.yml index 7d9e0b49298..6602f010ee8 100644 --- a/internal/pkg/template/templates/workloads/services/rd-web/cf.yml +++ b/internal/pkg/template/templates/workloads/services/rd-web/cf.yml @@ -239,6 +239,9 @@ Resources: CustomDomain: {{ .Alias }} AppDNSRole: {{ .AppDNSDelegationRole }} AppDNSName: {{ .AppDNSName }} + {{- if .HostedZones}} + RootHostedZoneId: {{.HostedZones.RootDomainHostedZoneId}} + {{- end}} CustomResourceRole: Metadata: diff --git a/internal/pkg/template/templates/workloads/services/static-site/cf.yml b/internal/pkg/template/templates/workloads/services/static-site/cf.yml index f23f2d4b7a3..14f5964bda5 100644 --- a/internal/pkg/template/templates/workloads/services/static-site/cf.yml +++ b/internal/pkg/template/templates/workloads/services/static-site/cf.yml @@ -407,6 +407,10 @@ Resources: RootDNSRole: {{ .AppDNSDelegationRole }} DomainName: {{ .AppDNSName }} Aliases: {{ if .StaticSiteAlias }} [{{ quote .StaticSiteAlias }}] {{ else }} [] {{ end }} + {{- if .HostedZones}} + RootHostedZoneId: {{.HostedZones.RootDomainHostedZoneId}} + AppHostedZoneId: {{.HostedZones.AppDomainHostedZoneId}} + {{- end}} CustomDomainFunction: Type: AWS::Lambda::Function @@ -478,6 +482,10 @@ Resources: DomainName: {{ .AppDNSName }} IsCloudFrontCertificate: true Aliases: {{ if .StaticSiteAlias }} [{{ quote .StaticSiteAlias }}] {{ else }} [] {{ end }} + {{- if .HostedZones}} + RootHostedZoneId: {{.HostedZones.RootDomainHostedZoneId}} + AppHostedZoneId: {{.HostedZones.AppDomainHostedZoneId}} + {{- end}} CertificateValidationFunction: Type: AWS::Lambda::Function diff --git a/internal/pkg/template/workload.go b/internal/pkg/template/workload.go index c2a24ee47e6..66fde8471bb 100644 --- a/internal/pkg/template/workload.go +++ b/internal/pkg/template/workload.go @@ -853,6 +853,7 @@ type WorkloadOpts struct { AWSSDKLayer *string AppDNSDelegationRole *string AppDNSName *string + HostedZones *HostedZones // Additional options for worker service templates. Subscribe *SubscribeOpts From e6730742f5f232e84b46b0abdc3f95d0bcc71ae1 Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Tue, 10 Oct 2023 16:57:13 -0700 Subject: [PATCH 6/8] address fb --- internal/pkg/cli/deploy/env.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/cli/deploy/env.go b/internal/pkg/cli/deploy/env.go index 8ff3cda44b6..9deb7450b0e 100644 --- a/internal/pkg/cli/deploy/env.go +++ b/internal/pkg/cli/deploy/env.go @@ -604,7 +604,7 @@ func appDomainHostedZoneId(appName, domain string, domainHostedZoneGetter domain } appHostedZoneID, err := domainHostedZoneGetter.PublicDomainHostedZoneID(fmt.Sprintf("%s.%s", appName, domain)) if err != nil { - return "", fmt.Errorf("get hostedzoneid for domain %s", fmt.Sprintf("%s.%s", appName, domain)) + return "", fmt.Errorf("get public public hosted zone ID for domain %s", fmt.Sprintf("%s.%s", appName, domain)) } return appHostedZoneID, nil } From c2a16a8a9c5db251d2a7b24636634e883271f90d Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Mon, 16 Oct 2023 11:51:20 -0700 Subject: [PATCH 7/8] address more fb --- .../testdata/environments/template-with-basic-manifest.yml | 1 + .../environments/template-with-default-access-log-config.yml | 3 ++- .../environments/template-with-defaultvpc-flowlogs.yml | 1 + .../environments/template-with-importedvpc-flowlogs.yml | 1 + .../templates/environment/partials/custom-resources.yml | 2 +- 5 files changed, 6 insertions(+), 2 deletions(-) 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 12e1b13ac93..8eacf13f998 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 @@ -954,6 +954,7 @@ Resources: SubdomainName: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} NameServers: !GetAtt EnvironmentHostedZone.NameServers RootDNSRole: !Ref AppDNSDelegationRole + EnvHostedZoneId: !Ref EnvironmentHostedZone HTTPSCert: Metadata: 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 307dbfea12b..a67d0e4196f 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 @@ -1061,7 +1061,8 @@ Resources: DomainName: !Sub ${AppName}.${AppDNSName} SubdomainName: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} NameServers: !GetAtt EnvironmentHostedZone.NameServers - RootDNSRole: !Ref AppDNSDelegationRole + RootDNSRole: !Ref AppDNSDelegationRole\ + EnvHostedZoneId: !Ref EnvironmentHostedZone HTTPSCert: Metadata: diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml index b3cf95f5c18..23a562dbba3 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-defaultvpc-flowlogs.yml @@ -960,6 +960,7 @@ Resources: SubdomainName: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} NameServers: !GetAtt EnvironmentHostedZone.NameServers RootDNSRole: !Ref AppDNSDelegationRole + EnvHostedZoneId: !Ref EnvironmentHostedZone HTTPSCert: Metadata: diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml index 2bfd3fcdd9f..01cbe915544 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/environments/template-with-importedvpc-flowlogs.yml @@ -789,6 +789,7 @@ Resources: SubdomainName: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} NameServers: !GetAtt EnvironmentHostedZone.NameServers RootDNSRole: !Ref AppDNSDelegationRole + EnvHostedZoneId: !Ref EnvironmentHostedZone HTTPSCert: Metadata: diff --git a/internal/pkg/template/templates/environment/partials/custom-resources.yml b/internal/pkg/template/templates/environment/partials/custom-resources.yml index 907c9a7416d..9f55e84bac5 100644 --- a/internal/pkg/template/templates/environment/partials/custom-resources.yml +++ b/internal/pkg/template/templates/environment/partials/custom-resources.yml @@ -12,10 +12,10 @@ DelegateDNSAction: SubdomainName: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} NameServers: !GetAtt EnvironmentHostedZone.NameServers RootDNSRole: !Ref AppDNSDelegationRole + EnvHostedZoneId: !Ref EnvironmentHostedZone {{- if .HostedZones}} RootHostedZoneId: {{.HostedZones.RootDomainHostedZoneId}} AppHostedZoneId: {{.HostedZones.AppDomainHostedZoneId}} - EnvHostedZoneId: !Ref EnvironmentHostedZone {{- end}} HTTPSCert: From fb7008244d0eb7ba0fdf57a69ed6bf99b9ac7cfa Mon Sep 17 00:00:00 2001 From: Adithya Kolla Date: Mon, 16 Oct 2023 12:11:36 -0700 Subject: [PATCH 8/8] fix test --- .../environments/template-with-default-access-log-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a67d0e4196f..f2e1234c0f1 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 @@ -1061,7 +1061,7 @@ Resources: DomainName: !Sub ${AppName}.${AppDNSName} SubdomainName: !Sub ${EnvironmentName}.${AppName}.${AppDNSName} NameServers: !GetAtt EnvironmentHostedZone.NameServers - RootDNSRole: !Ref AppDNSDelegationRole\ + RootDNSRole: !Ref AppDNSDelegationRole EnvHostedZoneId: !Ref EnvironmentHostedZone HTTPSCert: