From 3558adcc6857dd8ce562b47483507ef5b6b23f63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:46:14 +0000 Subject: [PATCH 1/3] chore: Bump word-wrap from 1.2.3 to 1.2.4 in /cf-custom-resources (#5100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
Release notes

Sourced from word-wrap's releases.

1.2.4

What's Changed

New Contributors

Full Changelog: https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=word-wrap&package-manager=npm_and_yarn&previous-version=1.2.3&new-version=1.2.4)](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) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/aws/copilot-cli/network/alerts).
--- cf-custom-resources/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cf-custom-resources/package-lock.json b/cf-custom-resources/package-lock.json index 70359d5e61b..b24cc0ccfba 100644 --- a/cf-custom-resources/package-lock.json +++ b/cf-custom-resources/package-lock.json @@ -10271,9 +10271,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -18611,9 +18611,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true }, "wrap-ansi": { From 5109d750667027c80faf63a683c46cb46cc63eb0 Mon Sep 17 00:00:00 2001 From: Adithya Kolla <71282729+KollaAdithya@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:00:38 -0700 Subject: [PATCH 2/3] chore: enable ctrl-c only after changeset is created and executed (#5112) Even though this PR looks big, half of the changes are just unit tests. Better solution than #5097 **Old Approach:** I closed PR 5097. In the previous PR #5097, there is a drawback with the logic when users initiated a ctrl-c interruption during the process of `proposing infrastructure changes`. The issue was that the interruption signal (SIGINT) was being captured even before the changeset is created and executed. As a result, when the user pressed ctrl-c during `proposing infrastructure changes`, we display a message like `Received interrupt for ctrl-c`; However, despite the interruption, the code continued to create and execute the changeset, leading to the stack creation process continuing even after the user intended to stop it. **New Approach:** To address the issue, this PR had a better solution for handling ctrl-c interruptions during the process of proposing infrastructure changes. Listening to the SIGINT signal is performed only after the changeset is created and executed. The necessary changes to achieve this have been made by relocating the complete logic for waiting and handling the signal to the `deploy/cloudformation` package. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License. --- .../cloudformationtest/cloudformationtest.go | 6 + internal/pkg/cli/env_delete_test.go | 7 +- internal/pkg/cli/job_deploy.go | 10 + internal/pkg/cli/svc_deploy.go | 14 +- .../deploy/cloudformation/cloudformation.go | 170 +++++++- .../cloudformation/cloudformation_test.go | 375 +++++++++++++++++- .../mocks/mock_cloudformation.go | 14 + .../pkg/deploy/cloudformation/workload.go | 2 +- .../deploy/cloudformation/workload_test.go | 12 + 9 files changed, 590 insertions(+), 20 deletions(-) diff --git a/internal/pkg/aws/cloudformation/cloudformationtest/cloudformationtest.go b/internal/pkg/aws/cloudformation/cloudformationtest/cloudformationtest.go index 5412e494a5e..b822bf1d65a 100644 --- a/internal/pkg/aws/cloudformation/cloudformationtest/cloudformationtest.go +++ b/internal/pkg/aws/cloudformation/cloudformationtest/cloudformationtest.go @@ -33,6 +33,7 @@ type Double struct { ErrorEventsFn func(stackName string) ([]cfn.StackEvent, error) ListStacksWithTagsFn func(tags map[string]string) ([]cfn.StackDescription, error) DescribeStackEventsFn func(input *sdk.DescribeStackEventsInput) (*sdk.DescribeStackEventsOutput, error) + CancelUpdateStackFn func(stackName string) error } // Create calls the stubbed function. @@ -139,3 +140,8 @@ func (d *Double) ListStacksWithTags(tags map[string]string) ([]cfn.StackDescript func (d *Double) DescribeStackEvents(input *sdk.DescribeStackEventsInput) (*sdk.DescribeStackEventsOutput, error) { return d.DescribeStackEventsFn(input) } + +// CancelUpdateStack calls the stubbed function. +func (d *Double) CancelUpdateStack(stackName string) error { + return d.CancelUpdateStackFn(stackName) +} diff --git a/internal/pkg/cli/env_delete_test.go b/internal/pkg/cli/env_delete_test.go index 797b14eb0d0..db67ebc13c7 100644 --- a/internal/pkg/cli/env_delete_test.go +++ b/internal/pkg/cli/env_delete_test.go @@ -6,9 +6,10 @@ package cli import ( "errors" "fmt" - "github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation" "testing" + "github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" "github.com/aws/copilot-cli/internal/pkg/cli/mocks" @@ -353,7 +354,7 @@ Resources: store := mocks.NewMockenvironmentStore(ctrl) store.EXPECT().ListEnvironments("phonetool").Return([]*config.Environment{ &mockEnv, - &config.Environment{ + { Name: "prod", Region: "us-west-2", AccountID: "5678", @@ -431,7 +432,7 @@ Resources: store := mocks.NewMockenvironmentStore(ctrl) store.EXPECT().ListEnvironments("phonetool").Return([]*config.Environment{ &mockEnv, - &config.Environment{ + { Name: "prod", Region: "us-west-2", AccountID: "5678", diff --git a/internal/pkg/cli/job_deploy.go b/internal/pkg/cli/job_deploy.go index eb751f2404e..97f0bd00a36 100644 --- a/internal/pkg/cli/job_deploy.go +++ b/internal/pkg/cli/job_deploy.go @@ -16,6 +16,7 @@ import ( "github.com/aws/copilot-cli/internal/pkg/version" "github.com/spf13/afero" + deploycfn "github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation" "github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation/stack" "github.com/aws/copilot-cli/internal/pkg/exec" "github.com/aws/copilot-cli/internal/pkg/term/log" @@ -236,6 +237,8 @@ func (o *deployJobOpts) Execute() error { return nil } } + var errStackDeletedOnInterrupt *deploycfn.ErrStackDeletedOnInterrupt + var errStackUpdateCanceledOnInterrupt *deploycfn.ErrStackUpdateCanceledOnInterrupt if _, err = deployer.DeployWorkload(&deploy.DeployWorkloadInput{ StackRuntimeConfiguration: deploy.StackRuntimeConfiguration{ ImageDigests: uploadOut.ImageDigests, @@ -250,6 +253,13 @@ func (o *deployJobOpts) Execute() error { DisableRollback: o.disableRollback, }, }); err != nil { + if errors.As(err, &errStackDeletedOnInterrupt) { + return nil + } + if errors.As(err, &errStackUpdateCanceledOnInterrupt) { + log.Successf("Successfully rolled back service %s to the previous configuration.\n", color.HighlightUserInput(o.name)) + return nil + } if o.disableRollback { stackName := stack.NameForWorkload(o.targetApp.Name, o.targetEnv.Name, o.name) rollbackCmd := fmt.Sprintf("aws cloudformation rollback-stack --stack-name %s --role-arn %s", stackName, o.targetEnv.ExecutionRoleARN) diff --git a/internal/pkg/cli/svc_deploy.go b/internal/pkg/cli/svc_deploy.go index ddef47e2de0..2a6bcd13eb2 100644 --- a/internal/pkg/cli/svc_deploy.go +++ b/internal/pkg/cli/svc_deploy.go @@ -17,6 +17,7 @@ import ( "github.com/aws/copilot-cli/internal/pkg/aws/cloudformation" "github.com/aws/copilot-cli/internal/pkg/aws/identity" "github.com/aws/copilot-cli/internal/pkg/aws/tags" + deploycfn "github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation" "github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation/stack" "github.com/aws/copilot-cli/internal/pkg/manifest/manifestinfo" "github.com/aws/copilot-cli/internal/pkg/template" @@ -291,6 +292,8 @@ func (o *deploySvcOpts) Execute() error { return nil } } + var errStackDeletedOnInterrupt *deploycfn.ErrStackDeletedOnInterrupt + var errStackUpdateCanceledOnInterrupt *deploycfn.ErrStackUpdateCanceledOnInterrupt deployRecs, err := deployer.DeployWorkload(&clideploy.DeployWorkloadInput{ StackRuntimeConfiguration: clideploy.StackRuntimeConfiguration{ ImageDigests: uploadOut.ImageDigests, @@ -308,6 +311,15 @@ func (o *deploySvcOpts) Execute() error { }, }) if err != nil { + if errors.As(err, &errStackDeletedOnInterrupt) { + o.noDeploy = true + return nil + } + if errors.As(err, &errStackUpdateCanceledOnInterrupt) { + log.Successf("Successfully rolled back service %s to the previous configuration.\n", color.HighlightUserInput(o.name)) + o.noDeploy = true + return nil + } if o.disableRollback { stackName := stack.NameForWorkload(o.targetApp.Name, o.targetEnv.Name, o.name) rollbackCmd := fmt.Sprintf("aws cloudformation rollback-stack --stack-name %s --role-arn %s", stackName, o.targetEnv.ExecutionRoleARN) @@ -321,8 +333,8 @@ After fixing the deployment, you can: } return fmt.Errorf("deploy service %s to environment %s: %w", o.name, o.envName, err) } - o.deployRecs = deployRecs log.Successf("Deployed service %s.\n", color.HighlightUserInput(o.name)) + o.deployRecs = deployRecs return nil } diff --git a/internal/pkg/deploy/cloudformation/cloudformation.go b/internal/pkg/deploy/cloudformation/cloudformation.go index 68ec1a278d1..aefdb20f72d 100644 --- a/internal/pkg/deploy/cloudformation/cloudformation.go +++ b/internal/pkg/deploy/cloudformation/cloudformation.go @@ -8,13 +8,16 @@ import ( "context" "errors" "fmt" - "github.com/aws/copilot-cli/internal/pkg/aws/ecr" - "github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation/stack" "io" "os" + "os/signal" "strings" + "syscall" "time" + "github.com/aws/copilot-cli/internal/pkg/aws/ecr" + "github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation/stack" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" sdkcloudformation "github.com/aws/aws-sdk-go/service/cloudformation" @@ -117,6 +120,7 @@ type cfnClient interface { Outputs(stack *cloudformation.Stack) (map[string]string, error) StackResources(name string) ([]*cloudformation.StackResource, error) Metadata(opts cloudformation.MetadataOpts) (string, error) + CancelUpdateStack(stackName string) error // Methods vended by the aws sdk struct. DescribeStackEvents(*sdkcloudformation.DescribeStackEventsInput) (*sdkcloudformation.DescribeStackEventsOutput, error) @@ -198,6 +202,7 @@ type CloudFormation struct { // Overridden in tests. renderStackSet func(input renderStackSetInput) error dnsDelegatedAccountsForStack func(stack *sdkcloudformation.Stack) []string + notifySignals func() chan os.Signal } // New returns a configured CloudFormation client. @@ -233,6 +238,7 @@ func New(sess *session.Session, opts ...OptFn) CloudFormation { } client.renderStackSet = client.renderStackSetImpl client.dnsDelegatedAccountsForStack = stack.DNSDelegatedAccountsForStack + client.notifySignals = notifySignals return client } @@ -269,6 +275,15 @@ type executeAndRenderChangeSetInput struct { stackName string stackDescription string createChangeSet func() (string, error) + enableInterrupt bool +} + +type executeAndRenderChangeSetOption func(in *executeAndRenderChangeSetInput) + +func withEnableInterrupt() executeAndRenderChangeSetOption { + return func(in *executeAndRenderChangeSetInput) { + in.enableInterrupt = true + } } func (cf CloudFormation) newCreateChangeSetInput(w progress.FileWriter, stack *cloudformation.Stack) *executeAndRenderChangeSetInput { @@ -293,7 +308,7 @@ func (cf CloudFormation) newCreateChangeSetInput(w progress.FileWriter, stack *c return in } -func (cf CloudFormation) newUpsertChangeSetInput(w progress.FileWriter, stack *cloudformation.Stack) *executeAndRenderChangeSetInput { +func (cf CloudFormation) newUpsertChangeSetInput(w progress.FileWriter, stack *cloudformation.Stack, opts ...executeAndRenderChangeSetOption) *executeAndRenderChangeSetInput { in := &executeAndRenderChangeSetInput{ stackName: stack.Name, stackDescription: fmt.Sprintf("Creating the infrastructure for stack %s", stack.Name), @@ -331,6 +346,9 @@ func (cf CloudFormation) newUpsertChangeSetInput(w progress.FileWriter, stack *c spinner.Stop(log.Ssuccessf("%s\n", label)) return changeSetID, nil } + for _, opt := range opts { + opt(in) + } return in } @@ -339,10 +357,35 @@ func (cf CloudFormation) executeAndRenderChangeSet(in *executeAndRenderChangeSet if err != nil { return err } + var sigChannel chan os.Signal + if in.enableInterrupt { + sigChannel = cf.notifySignals() + } + g, ctx := errgroup.WithContext(context.Background()) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + g.Go(func() error { + defer cancel() + if err := cf.renderChangeSet(ctx, changeSetID, in); err != nil { + if !errors.Is(err, context.Canceled) { + return err + } + } + return nil + }) + if in.enableInterrupt { + g.Go(func() error { + return cf.waitForSignalAndHandleInterrupt(ctx, cancel, sigChannel, in.stackName) + }) + } + return g.Wait() +} + +func (cf CloudFormation) renderChangeSet(ctx context.Context, changeSetID string, in *executeAndRenderChangeSetInput) error { if _, ok := cf.console.(*discardFile); ok { // If we don't have to render skip the additional network calls. return nil } - waitCtx, cancelWait := context.WithTimeout(context.Background(), waitForStackTimeout) + waitCtx, cancelWait := context.WithTimeout(ctx, waitForStackTimeout) defer cancelWait() g, ctx := errgroup.WithContext(waitCtx) @@ -363,6 +406,125 @@ func (cf CloudFormation) executeAndRenderChangeSet(in *executeAndRenderChangeSet return nil } +func (cf CloudFormation) waitForSignalAndHandleInterrupt(ctx context.Context, cancelFn context.CancelFunc, sigCh chan os.Signal, stackName string) error { + for { + select { + case <-sigCh: + cancelFn() + stopCatchSignals(sigCh) + stackDescr, err := cf.cfnClient.Describe(stackName) + if err != nil { + return fmt.Errorf("describe stack %s: %w", stackName, err) + } + switch aws.StringValue(stackDescr.StackStatus) { + case sdkcloudformation.StackStatusCreateInProgress: + log.Infof(`Received Interrupt for Ctrl-C. +Pressing Ctrl-C again will exit immediately but the deletion of stack %s will continue +`, stackName) + description := fmt.Sprintf("Delete stack %s", stackName) + if err := cf.deleteAndRenderStack(stackName, description, func() error { + return cf.cfnClient.DeleteAndWait(stackName) + }); err != nil { + return err + } + return &ErrStackDeletedOnInterrupt{stackName: stackName} + case sdkcloudformation.StackStatusUpdateInProgress: + log.Infof(`Received Interrupt for Ctrl-C. +Pressing Ctrl-C again will exit immediately but stack %s rollback will continue +`, stackName) + description := fmt.Sprintf("Canceling stack update %s", stackName) + if err := cf.cancelUpdateAndRender(&cancelUpdateAndRenderInput{ + stackName: stackName, + description: description, + cancelUpdateFn: func() error { + return cf.cfnClient.CancelUpdateStack(stackName) + }, + }); err != nil { + return err + } + return &ErrStackUpdateCanceledOnInterrupt{stackName: stackName} + } + return nil + case <-ctx.Done(): + stopCatchSignals(sigCh) + return nil + } + } +} + +type cancelUpdateAndRenderInput struct { + stackName string + description string + cancelUpdateFn func() error +} + +func (cf CloudFormation) cancelUpdateAndRender(in *cancelUpdateAndRenderInput) error { + stackDescr, err := cf.cfnClient.Describe(in.stackName) + if err != nil { + return fmt.Errorf("describe stack %s: %w", in.stackName, err) + } + if stackDescr.ChangeSetId == nil { + return fmt.Errorf("ChangeSetID not found for stack %s", in.stackName) + + } + ctx, cancel := context.WithTimeout(context.Background(), waitForStackTimeout) + defer cancel() + g, ctx := errgroup.WithContext(ctx) + renderer, err := cf.createChangeSetRenderer(g, ctx, aws.StringValue(stackDescr.ChangeSetId), in.stackName, in.description, progress.RenderOptions{}) + if err != nil { + return err + } + g.Go(in.cancelUpdateFn) + g.Go(func() error { + _, err := progress.Render(ctx, progress.NewTabbedFileWriter(cf.console), renderer) + return err + }) + if err := g.Wait(); err != nil { + return err + } + return cf.errOnFailedCancelUpdate(in.stackName) +} +func (cf CloudFormation) errOnFailedCancelUpdate(stackName string) error { + stack, err := cf.cfnClient.Describe(stackName) + if err != nil { + return fmt.Errorf("describe stack %s: %w", stackName, err) + } + status := aws.StringValue(stack.StackStatus) + if status != sdkcloudformation.StackStatusUpdateRollbackComplete { + return fmt.Errorf("stack %s did not rollback successfully and exited with status %s", stackName, status) + } + return nil +} + +func notifySignals() chan os.Signal { + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT) + return sigCh +} + +func stopCatchSignals(sigCh chan os.Signal) { + signal.Stop(sigCh) + close(sigCh) +} + +// ErrStackDeletedOnInterrupt means stack is deleted on interrupt. +type ErrStackDeletedOnInterrupt struct { + stackName string +} + +func (e *ErrStackDeletedOnInterrupt) Error() string { + return fmt.Sprintf("stack %s was deleted on interrupt signal", e.stackName) +} + +// ErrStackUpdateCanceledOnInterrupt means stack update is canceled on interrupt. +type ErrStackUpdateCanceledOnInterrupt struct { + stackName string +} + +func (e *ErrStackUpdateCanceledOnInterrupt) Error() string { + return fmt.Sprintf("update for stack %s was canceled on interrupt signal", e.stackName) +} + func (cf CloudFormation) createChangeSetRenderer(group *errgroup.Group, ctx context.Context, changeSetID, stackName, description string, opts progress.RenderOptions) (progress.DynamicRenderer, error) { changeSet, err := cf.cfnClient.DescribeChangeSet(changeSetID, stackName) if err != nil { diff --git a/internal/pkg/deploy/cloudformation/cloudformation_test.go b/internal/pkg/deploy/cloudformation/cloudformation_test.go index 05b80e73283..b391adc00b0 100644 --- a/internal/pkg/deploy/cloudformation/cloudformation_test.go +++ b/internal/pkg/deploy/cloudformation/cloudformation_test.go @@ -7,7 +7,9 @@ import ( "errors" "fmt" "io" + "os" "strings" + "syscall" "testing" "time" @@ -104,6 +106,10 @@ func testDeployWorkload_OnPushToS3Failure(t *testing.T, when func(cf CloudFormat client := CloudFormation{ s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, } // WHEN @@ -124,7 +130,12 @@ func testDeployWorkload_OnCreateChangeSetFailure(t *testing.T, when func(cf Clou m.EXPECT().Create(gomock.Any()).Return("", wantedErr) m.EXPECT().ErrorEvents(gomock.Any()).Return(nil, nil) buf := new(strings.Builder) - client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}} + client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, + } // WHEN err := when(client) @@ -145,7 +156,12 @@ func testDeployWorkload_OnUpdateChangeSetFailure(t *testing.T, when func(cf Clou m.EXPECT().Update(gomock.Any()).Return("", wantedErr) m.EXPECT().ErrorEvents(gomock.Any()).Return(nil, nil) buf := new(strings.Builder) - client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}} + client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, + } // WHEN err := when(client) @@ -164,8 +180,12 @@ func testDeployWorkload_OnDescribeChangeSetFailure(t *testing.T, when func(cf Cl m.EXPECT().Create(gomock.Any()).Return("1234", nil) m.EXPECT().DescribeChangeSet(gomock.Any(), gomock.Any()).Return(nil, errors.New("DescribeChangeSet error")) buf := new(strings.Builder) - client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}} - + client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, + } // WHEN err := when(client) @@ -184,7 +204,12 @@ func testDeployWorkload_OnTemplateBodyFailure(t *testing.T, when func(cf CloudFo m.EXPECT().DescribeChangeSet(gomock.Any(), gomock.Any()).Return(&cloudformation.ChangeSetDescription{}, nil) m.EXPECT().TemplateBodyFromChangeSet(gomock.Any(), gomock.Any()).Return("", errors.New("TemplateBody error")) buf := new(strings.Builder) - client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}} + client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, + } // WHEN err := when(client) @@ -206,8 +231,12 @@ func testDeployWorkload_StackStreamerFailureShouldCancelRenderer(t *testing.T, w m.EXPECT().TemplateBodyFromChangeSet(gomock.Any(), gomock.Any()).Return("", nil) m.EXPECT().DescribeStackEvents(gomock.Any()).Return(nil, wantedErr) buf := new(strings.Builder) - client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}} - + client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, + } // WHEN err := when(client) @@ -250,7 +279,12 @@ func testDeployWorkload_StreamUntilStackCreationFails(t *testing.T, stackName st }, }, nil) buf := new(strings.Builder) - client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}} + client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, + } // WHEN err := when(client) @@ -330,7 +364,12 @@ Resources: StackStatus: aws.String("CREATE_COMPLETE"), }, nil) buf := new(strings.Builder) - client := CloudFormation{cfnClient: mockCFN, ecsClient: mockECS, s3Client: mS3Client, console: mockFileWriter{Writer: buf}} + client := CloudFormation{cfnClient: mockCFN, ecsClient: mockECS, s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, + } // WHEN err := when(client) @@ -410,7 +449,12 @@ Resources: StackStatus: aws.String("CREATE_COMPLETE"), }, nil) buf := new(strings.Builder) - client := CloudFormation{cfnClient: mockCFN, s3Client: mS3Client, console: mockFileWriter{Writer: buf}} + client := CloudFormation{cfnClient: mockCFN, s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, + } // WHEN err := when(client) @@ -531,7 +575,12 @@ Resources: }, }, nil).AnyTimes() buf := new(strings.Builder) - client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}} + client := CloudFormation{cfnClient: m, s3Client: mS3Client, console: mockFileWriter{Writer: buf}, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + return sigCh + }, + } // WHEN err := when(client) @@ -817,6 +866,310 @@ Resources: require.Contains(t, buf.String(), "A DynamoDB table to store data") } +func testDeployWorkload_OnCancelUpdateHelper(ctrl *gomock.Controller, stackName string) (*mocks.MockcfnClient, CloudFormation) { + mS3Client := mocks.NewMocks3Client(ctrl) + mS3Client.EXPECT().Upload(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) + mockcfnClient := mocks.NewMockcfnClient(ctrl) + mockcfnClient.EXPECT().Create(gomock.Any()).Return("", &cloudformation.ErrStackAlreadyExists{}) + mockcfnClient.EXPECT().Update(gomock.Any()).Return("1234", nil) + mockcfnClient.EXPECT().Describe(gomock.Any()).Return(&cloudformation.StackDescription{ + StackId: aws.String("stack/webhook/1111"), + StackStatus: aws.String("UPDATE_IN_PROGRESS"), + ChangeSetId: aws.String("1234"), + }, nil).Times(2) + mockcfnClient.EXPECT().DescribeChangeSet("1234", stackName).Return(&cloudformation.ChangeSetDescription{ + Changes: []*sdkcloudformation.Change{ + { + ResourceChange: &sdkcloudformation.ResourceChange{ + LogicalResourceId: aws.String("log group"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + }, + }, + }, + }, nil).Times(2) + mockcfnClient.EXPECT().TemplateBodyFromChangeSet("1234", stackName).Return(` +Resources: + LogGroup: + Metadata: + 'aws:copilot:description': 'A CloudWatch log group to hold your service logs' + Type: AWS::Logs::LogGroup`, nil).Times(2) + mockcfnClient.EXPECT().CancelUpdateStack(stackName).Return(nil) + client := CloudFormation{cfnClient: mockcfnClient, s3Client: mS3Client, console: os.Stderr, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + sigCh <- syscall.SIGINT + return sigCh + }, + } + return mockcfnClient, client +} + +func testDeployWorkload_OnCancelUpdateSuccess(t *testing.T, stackName string, when func(cf CloudFormation) error) { + // GIVEN + ctrl := gomock.NewController(t) + defer ctrl.Finish() + wantedErr := &ErrStackUpdateCanceledOnInterrupt{stackName: stackName} + deploymentTime := time.Time{} + mockcfnClient, client := testDeployWorkload_OnCancelUpdateHelper(ctrl, stackName) + mockcfnClient.EXPECT().DescribeStackEvents(&sdkcloudformation.DescribeStackEventsInput{ + StackName: aws.String(stackName), + }).Return(&sdkcloudformation.DescribeStackEventsOutput{ + StackEvents: []*sdkcloudformation.StackEvent{ + { + EventId: aws.String("1"), + LogicalResourceId: aws.String("log group"), + PhysicalResourceId: aws.String("arn:aws:logs:us-west-2:1111:loggroup/copilot"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + ResourceStatus: aws.String("UPDATE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("2"), + LogicalResourceId: aws.String("log group"), + PhysicalResourceId: aws.String("arn:aws:logs:us-west-2:1111:loggroup/copilot"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + ResourceStatus: aws.String("UPDATE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("3"), + LogicalResourceId: aws.String(stackName), + ResourceType: aws.String("AWS::CloudFormation::Stack"), + ResourceStatus: aws.String("UPDATE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("4"), + LogicalResourceId: aws.String(stackName), + ResourceType: aws.String("AWS::CloudFormation::Stack"), + ResourceStatus: aws.String("UPDATE_ROLLBACK_COMPLETE"), + Timestamp: aws.Time(deploymentTime), + }, + }, + }, nil).AnyTimes() + mockcfnClient.EXPECT().Describe(gomock.Any()).Return(&cloudformation.StackDescription{ + StackId: aws.String("stack/webhook/1111"), + StackStatus: aws.String("UPDATE_ROLLBACK_COMPLETE"), + }, nil) + client.cfnClient = mockcfnClient + + // WHEN + gotErr := when(client) + + // THEN + require.EqualError(t, gotErr, wantedErr.Error()) +} + +func testDeployWorkload_OnCancelUpdateFAILED(t *testing.T, stackName string, when func(cf CloudFormation) error) { + // GIVEN + ctrl := gomock.NewController(t) + defer ctrl.Finish() + wantedError := errors.New("stack myapp-myenv-mysvc did not rollback successfully and exited with status UPDATE_ROLLBACK_FAILED") + deploymentTime := time.Time{} + mockcfnClient, client := testDeployWorkload_OnCancelUpdateHelper(ctrl, stackName) + mockcfnClient.EXPECT().DescribeStackEvents(&sdkcloudformation.DescribeStackEventsInput{ + StackName: aws.String(stackName), + }).Return(&sdkcloudformation.DescribeStackEventsOutput{ + StackEvents: []*sdkcloudformation.StackEvent{ + { + EventId: aws.String("1"), + LogicalResourceId: aws.String("log group"), + PhysicalResourceId: aws.String("arn:aws:logs:us-west-2:1111:loggroup/copilot"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + ResourceStatus: aws.String("UPDATE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("2"), + LogicalResourceId: aws.String(stackName), + ResourceType: aws.String("AWS::CloudFormation::Stack"), + ResourceStatus: aws.String("UPDATE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("3"), + LogicalResourceId: aws.String("log group"), + PhysicalResourceId: aws.String("arn:aws:logs:us-west-2:1111:loggroup/copilot"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + ResourceStatus: aws.String("UPDATE_ROLLBACK_FAILED"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("4"), + LogicalResourceId: aws.String(stackName), + ResourceType: aws.String("AWS::CloudFormation::Stack"), + ResourceStatus: aws.String("UPDATE_ROLLBACK_FAILED"), + Timestamp: aws.Time(deploymentTime), + }, + }, + }, nil).AnyTimes() + mockcfnClient.EXPECT().Describe(stackName).Return(&cloudformation.StackDescription{ + StackId: aws.String("stack/webhook/1111"), + StackName: aws.String(stackName), + StackStatus: aws.String("UPDATE_ROLLBACK_FAILED"), + }, nil) + client.cfnClient = mockcfnClient + + // WHEN + err := when(client) + + // THEN + require.EqualError(t, err, wantedError.Error()) +} + +func testDeployWorkload_OnDeleteSackHelper(ctrl *gomock.Controller, stackName string) (*mocks.MockcfnClient, CloudFormation) { + mS3Client := mocks.NewMocks3Client(ctrl) + mS3Client.EXPECT().Upload(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) + mockcfnClient := mocks.NewMockcfnClient(ctrl) + mockcfnClient.EXPECT().Create(gomock.Any()).Return("1234", nil) + mockcfnClient.EXPECT().Describe(stackName).Return(&cloudformation.StackDescription{ + StackId: aws.String("myapp-myenv-mysvc"), + StackName: aws.String(stackName), + StackStatus: aws.String("CREATE_IN_PROGRESS"), + }, nil) + mockcfnClient.EXPECT().DescribeChangeSet("1234", stackName).Return(&cloudformation.ChangeSetDescription{ + Changes: []*sdkcloudformation.Change{ + { + ResourceChange: &sdkcloudformation.ResourceChange{ + LogicalResourceId: aws.String("log group"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + }, + }, + }, + }, nil) + mockcfnClient.EXPECT().TemplateBodyFromChangeSet("1234", stackName).Return(` +Resources: + LogGroup: + Metadata: + aws:copilot:description': 'A CloudWatch log group to hold your service logs' + Type: AWS::Logs::LogGroup`, nil) + + mockcfnClient.EXPECT().TemplateBody(stackName).Return(` +Resources: + LogGroup: + Metadata: + aws:copilot:description': 'A CloudWatch log group to hold your service logs' + Type: AWS::Logs::LogGroup`, nil) + mockcfnClient.EXPECT().Describe(stackName).Return(&cloudformation.StackDescription{ + StackId: aws.String("myapp-myenv-mysvc"), + StackName: aws.String(stackName), + StackStatus: aws.String("DELETE_IN_PROGRESS"), + }, nil) + client := CloudFormation{cfnClient: mockcfnClient, s3Client: mS3Client, console: os.Stderr, + notifySignals: func() chan os.Signal { + sigCh := make(chan os.Signal, 1) + sigCh <- syscall.SIGINT + return sigCh + }, + } + return mockcfnClient, client +} + +func testDeployWorkload_OnDeleteStackSuccess(t *testing.T, stackName string, when func(cf CloudFormation) error) { + // GIVEN + ctrl := gomock.NewController(t) + defer ctrl.Finish() + wantedErr := &ErrStackDeletedOnInterrupt{stackName: stackName} + deploymentTime := time.Time{} + mockcfnClient, client := testDeployWorkload_OnDeleteSackHelper(ctrl, stackName) + mockcfnClient.EXPECT().DescribeStackEvents(&sdkcloudformation.DescribeStackEventsInput{ + StackName: aws.String("myapp-myenv-mysvc"), + }).Return(&sdkcloudformation.DescribeStackEventsOutput{ + StackEvents: []*sdkcloudformation.StackEvent{ + { + EventId: aws.String("1"), + LogicalResourceId: aws.String("log group"), + PhysicalResourceId: aws.String("arn:aws:logs:us-west-2:1111:loggroup/copilot"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + ResourceStatus: aws.String("CREATE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("2"), + LogicalResourceId: aws.String(stackName), + ResourceType: aws.String("AWS::CloudFormation::Stack"), + ResourceStatus: aws.String("CREATE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("3"), + LogicalResourceId: aws.String("log group"), + PhysicalResourceId: aws.String("arn:aws:logs:us-west-2:1111:loggroup/copilot"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + ResourceStatus: aws.String("DELETE_COMPLETE"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("4"), + LogicalResourceId: aws.String(stackName), + ResourceType: aws.String("AWS::CloudFormation::Stack"), + ResourceStatus: aws.String("DELETE_COMPLETE"), + Timestamp: aws.Time(deploymentTime), + }, + }, + }, nil).AnyTimes() + mockcfnClient.EXPECT().DeleteAndWait(stackName).Return(&cloudformation.ErrStackNotFound{}) + client.cfnClient = mockcfnClient + + // WHEN + err := when(client) + + // THEN + require.EqualError(t, err, wantedErr.Error()) +} + +func testDeployWorkload_OnDeleteStackFailed(t *testing.T, stackName string, when func(cf CloudFormation) error) { + // GIVEN + ctrl := gomock.NewController(t) + defer ctrl.Finish() + wantedErr := errors.New("some error") + deploymentTime := time.Time{} + mockcfnClient, client := testDeployWorkload_OnDeleteSackHelper(ctrl, stackName) + mockcfnClient.EXPECT().DescribeStackEvents(&sdkcloudformation.DescribeStackEventsInput{ + StackName: aws.String("myapp-myenv-mysvc"), + }).Return(&sdkcloudformation.DescribeStackEventsOutput{ + StackEvents: []*sdkcloudformation.StackEvent{ + { + EventId: aws.String("1"), + LogicalResourceId: aws.String("log group"), + PhysicalResourceId: aws.String("arn:aws:logs:us-west-2:1111:loggroup/copilot"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + ResourceStatus: aws.String("CREATE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("2"), + LogicalResourceId: aws.String(stackName), + ResourceType: aws.String("AWS::CloudFormation::Stack"), + ResourceStatus: aws.String("CREATE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("3"), + LogicalResourceId: aws.String("log group"), + PhysicalResourceId: aws.String("arn:aws:logs:us-west-2:1111:loggroup/copilot"), + ResourceType: aws.String("AWS::Logs::LogGroup"), + ResourceStatus: aws.String("DELETE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + { + EventId: aws.String("4"), + LogicalResourceId: aws.String(stackName), + ResourceType: aws.String("AWS::CloudFormation::Stack"), + ResourceStatus: aws.String("DELETE_IN_PROGRESS"), + Timestamp: aws.Time(deploymentTime), + }, + }, + }, nil).AnyTimes() + mockcfnClient.EXPECT().DeleteAndWait(stackName).Return(errors.New("some error")) + client.cfnClient = mockcfnClient + // WHEN + err := when(client) + + // THEN + require.EqualError(t, err, wantedErr.Error()) +} + func TestCloudFormation_Template(t *testing.T) { inStackName := stack.NameForEnv("phonetool", "test") testCases := map[string]struct { diff --git a/internal/pkg/deploy/cloudformation/mocks/mock_cloudformation.go b/internal/pkg/deploy/cloudformation/mocks/mock_cloudformation.go index f0274105537..8dddc27ce5b 100644 --- a/internal/pkg/deploy/cloudformation/mocks/mock_cloudformation.go +++ b/internal/pkg/deploy/cloudformation/mocks/mock_cloudformation.go @@ -254,6 +254,20 @@ func (m *MockcfnClient) EXPECT() *MockcfnClientMockRecorder { return m.recorder } +// CancelUpdateStack mocks base method. +func (m *MockcfnClient) CancelUpdateStack(stackName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CancelUpdateStack", stackName) + ret0, _ := ret[0].(error) + return ret0 +} + +// CancelUpdateStack indicates an expected call of CancelUpdateStack. +func (mr *MockcfnClientMockRecorder) CancelUpdateStack(stackName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelUpdateStack", reflect.TypeOf((*MockcfnClient)(nil).CancelUpdateStack), stackName) +} + // Create mocks base method. func (m *MockcfnClient) Create(arg0 *cloudformation0.Stack) (string, error) { m.ctrl.T.Helper() diff --git a/internal/pkg/deploy/cloudformation/workload.go b/internal/pkg/deploy/cloudformation/workload.go index 5eca64d47aa..600c0e54247 100644 --- a/internal/pkg/deploy/cloudformation/workload.go +++ b/internal/pkg/deploy/cloudformation/workload.go @@ -27,7 +27,7 @@ func (cf CloudFormation) DeployService(conf StackConfiguration, bucketName strin for _, opt := range opts { opt(stack) } - return cf.executeAndRenderChangeSet(cf.newUpsertChangeSetInput(cf.console, stack)) + return cf.executeAndRenderChangeSet(cf.newUpsertChangeSetInput(cf.console, stack, withEnableInterrupt())) } type uploadableStack interface { diff --git a/internal/pkg/deploy/cloudformation/workload_test.go b/internal/pkg/deploy/cloudformation/workload_test.go index 4dc60ec7cb4..c80daa3b071 100644 --- a/internal/pkg/deploy/cloudformation/workload_test.go +++ b/internal/pkg/deploy/cloudformation/workload_test.go @@ -104,6 +104,18 @@ func TestCloudFormation_DeployService(t *testing.T) { t.Run("renders a stack with addons template if stack creation is successful", func(t *testing.T) { testDeployWorkload_RenderNewlyCreatedStackWithAddons(t, "myapp-myenv-mysvc", when) }) + t.Run("received a interrupt signal and cancel update stack is successful", func(t *testing.T) { + testDeployWorkload_OnCancelUpdateSuccess(t, "myapp-myenv-mysvc", when) + }) + t.Run("received a interrupt signal and cancel update failed", func(t *testing.T) { + testDeployWorkload_OnCancelUpdateFAILED(t, "myapp-myenv-mysvc", when) + }) + t.Run("received a interrupt signal and deletion of stack is successful", func(t *testing.T) { + testDeployWorkload_OnDeleteStackSuccess(t, "myapp-myenv-mysvc", when) + }) + t.Run("received a interrupt signal and deletion of stack is failed", func(t *testing.T) { + testDeployWorkload_OnDeleteStackFailed(t, "myapp-myenv-mysvc", when) + }) } func TestCloudFormation_DeleteWorkload(t *testing.T) { From ac4ded0019fbe2ed1ee43617187316e4134369a3 Mon Sep 17 00:00:00 2001 From: Penghao He Date: Wed, 26 Jul 2023 11:17:07 -0700 Subject: [PATCH 3/3] chore: apply all PublicAccessBlockConfiguration for all s3 buckets (#5130) Fixes https://github.com/aws/copilot-cli/issues/5089 By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License. --- e2e/multi-pipeline/s3template.yml | 2 ++ internal/pkg/addon/testdata/merge/env/second.yaml | 2 ++ internal/pkg/addon/testdata/merge/env/wanted.yaml | 2 ++ internal/pkg/addon/testdata/merge/second.yaml | 2 ++ internal/pkg/addon/testdata/merge/wanted.yaml | 2 ++ internal/pkg/addon/testdata/storage/bucket.yml | 2 ++ .../template-with-default-access-log-config.yml | 5 +++++ .../stack/testdata/workloads/static-site.stack.yml | 2 ++ internal/pkg/template/templates/addons/s3/cf.yml | 2 ++ internal/pkg/template/templates/addons/s3/env/cf.yml | 2 ++ internal/pkg/template/templates/app/cf.yml | 5 +++++ .../templates/environment/partials/elb-access-logs.yml | 7 ++++++- internal/pkg/template/templates/task/cf.yml | 5 +++++ .../templates/workloads/services/static-site/cf.yml | 2 ++ 14 files changed, 41 insertions(+), 1 deletion(-) diff --git a/e2e/multi-pipeline/s3template.yml b/e2e/multi-pipeline/s3template.yml index ea42a00fe59..1d4a74ac94d 100644 --- a/e2e/multi-pipeline/s3template.yml +++ b/e2e/multi-pipeline/s3template.yml @@ -22,6 +22,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true e2epipelineaddonBucketPolicy: Metadata: diff --git a/internal/pkg/addon/testdata/merge/env/second.yaml b/internal/pkg/addon/testdata/merge/env/second.yaml index 470722a4eef..07c30a5cd44 100644 --- a/internal/pkg/addon/testdata/merge/env/second.yaml +++ b/internal/pkg/addon/testdata/merge/env/second.yaml @@ -76,6 +76,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true MyBucketAccessPolicy: Type: AWS::IAM::ManagedPolicy diff --git a/internal/pkg/addon/testdata/merge/env/wanted.yaml b/internal/pkg/addon/testdata/merge/env/wanted.yaml index a4e6e4be4b8..fa4c17e747c 100644 --- a/internal/pkg/addon/testdata/merge/env/wanted.yaml +++ b/internal/pkg/addon/testdata/merge/env/wanted.yaml @@ -106,6 +106,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true MyBucketAccessPolicy: Type: AWS::IAM::ManagedPolicy Properties: diff --git a/internal/pkg/addon/testdata/merge/second.yaml b/internal/pkg/addon/testdata/merge/second.yaml index 21562b0ea9e..3be18d34b3b 100644 --- a/internal/pkg/addon/testdata/merge/second.yaml +++ b/internal/pkg/addon/testdata/merge/second.yaml @@ -79,6 +79,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true MyBucketAccessPolicy: Type: AWS::IAM::ManagedPolicy diff --git a/internal/pkg/addon/testdata/merge/wanted.yaml b/internal/pkg/addon/testdata/merge/wanted.yaml index 0273c8208fd..8bce79b46b5 100644 --- a/internal/pkg/addon/testdata/merge/wanted.yaml +++ b/internal/pkg/addon/testdata/merge/wanted.yaml @@ -109,6 +109,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true MyBucketAccessPolicy: Type: AWS::IAM::ManagedPolicy Properties: diff --git a/internal/pkg/addon/testdata/storage/bucket.yml b/internal/pkg/addon/testdata/storage/bucket.yml index 0a2cacbd882..c55a242772d 100644 --- a/internal/pkg/addon/testdata/storage/bucket.yml +++ b/internal/pkg/addon/testdata/storage/bucket.yml @@ -22,6 +22,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced 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 cc634b0fc84..a22aa50e8a0 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 @@ -720,6 +720,11 @@ Resources: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true ELBAccessLogsBucketPolicy: Type: AWS::S3::BucketPolicy Condition: CreateALB 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 13b9158fffd..5d5a54a6fee 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 @@ -35,6 +35,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced diff --git a/internal/pkg/template/templates/addons/s3/cf.yml b/internal/pkg/template/templates/addons/s3/cf.yml index db708d9b2c9..def196b6728 100644 --- a/internal/pkg/template/templates/addons/s3/cf.yml +++ b/internal/pkg/template/templates/addons/s3/cf.yml @@ -22,6 +22,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced diff --git a/internal/pkg/template/templates/addons/s3/env/cf.yml b/internal/pkg/template/templates/addons/s3/env/cf.yml index 2ca8033148f..f0773c9d953 100644 --- a/internal/pkg/template/templates/addons/s3/env/cf.yml +++ b/internal/pkg/template/templates/addons/s3/env/cf.yml @@ -20,6 +20,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced diff --git a/internal/pkg/template/templates/app/cf.yml b/internal/pkg/template/templates/app/cf.yml index fd7ed905292..7de62f20964 100644 --- a/internal/pkg/template/templates/app/cf.yml +++ b/internal/pkg/template/templates/app/cf.yml @@ -90,6 +90,11 @@ Resources: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced diff --git a/internal/pkg/template/templates/environment/partials/elb-access-logs.yml b/internal/pkg/template/templates/environment/partials/elb-access-logs.yml index f864f3c41bc..c542108a7d6 100644 --- a/internal/pkg/template/templates/environment/partials/elb-access-logs.yml +++ b/internal/pkg/template/templates/environment/partials/elb-access-logs.yml @@ -37,4 +37,9 @@ ELBAccessLogsBucket: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: - SSEAlgorithm: AES256 \ No newline at end of file + SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true \ No newline at end of file diff --git a/internal/pkg/template/templates/task/cf.yml b/internal/pkg/template/templates/task/cf.yml index 129722c867d..51465528d53 100644 --- a/internal/pkg/template/templates/task/cf.yml +++ b/internal/pkg/template/templates/task/cf.yml @@ -273,6 +273,11 @@ Resources: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true LifecycleConfiguration: Rules: # .env files are only needed on the initial RunTask call and are not needed after that. 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 4c79bdf3302..ce8e836c7b2 100644 --- a/internal/pkg/template/templates/workloads/services/static-site/cf.yml +++ b/internal/pkg/template/templates/workloads/services/static-site/cf.yml @@ -47,6 +47,8 @@ Resources: PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced