Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion internal/pkg/cli/deploy/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

awscfn "github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/copilot-cli/internal/pkg/aws/cloudformation"
awscloudformation "github.com/aws/copilot-cli/internal/pkg/aws/cloudformation"
"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"
Expand Down Expand Up @@ -321,6 +322,7 @@ type DeployEnvironmentInput struct {
ForceNewUpdate bool
RawManifest []byte
PermissionsBoundary string
DisableRollback bool
}

// GenerateCloudFormationTemplate returns the environment stack's template and parameter configuration.
Expand Down Expand Up @@ -366,8 +368,14 @@ func (d *envDeployer) DeployEnvironment(in *DeployEnvironmentInput) error {
if err != nil {
return fmt.Errorf("retrieve environment stack force update ID: %w", err)
}
opts := []awscloudformation.StackOption{
awscloudformation.WithRoleARN(d.env.ExecutionRoleARN),
}
if in.DisableRollback {
opts = append(opts, awscloudformation.WithDisableRollback())
}
conf := cfnstack.NewEnvConfigFromExistingStack(stackInput, lastForceUpdateID, oldParams)
return d.envDeployer.UpdateAndRenderEnvironment(conf, stackInput.ArtifactBucketARN, cloudformation.WithRoleARN(d.env.ExecutionRoleARN))
return d.envDeployer.UpdateAndRenderEnvironment(conf, stackInput.ArtifactBucketARN, opts...)
}

func (d *envDeployer) getAppRegionalResources() (*cfnstack.AppRegionalResources, error) {
Expand Down
22 changes: 18 additions & 4 deletions internal/pkg/cli/deploy/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,10 @@ func TestEnvDeployer_DeployEnvironment(t *testing.T) {
Name: mockAppName,
}
testCases := map[string]struct {
setUpMocks func(m *envDeployerMocks)
inManifest *manifest.Environment
wantedError error
setUpMocks func(m *envDeployerMocks)
inManifest *manifest.Environment
inDisableRollback bool
wantedError error
}{
"fail to get app resources by region": {
setUpMocks: func(m *envDeployerMocks) {
Expand Down Expand Up @@ -360,6 +361,18 @@ func TestEnvDeployer_DeployEnvironment(t *testing.T) {
m.envDeployer.EXPECT().UpdateAndRenderEnvironment(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
},
},
"successful environment deployment, no rollback": {
inDisableRollback: true,
setUpMocks: func(m *envDeployerMocks) {
m.appCFN.EXPECT().GetAppResourcesByRegion(mockApp, mockEnvRegion).Return(&cfnstack.AppRegionalResources{
S3Bucket: "mockS3Bucket",
}, nil)
m.prefixListGetter.EXPECT().CloudFrontManagedPrefixListID().Return("mockPrefixListID", nil).Times(0)
m.envDeployer.EXPECT().DeployedEnvironmentParameters(gomock.Any(), gomock.Any()).Return(nil, nil)
m.envDeployer.EXPECT().ForceUpdateOutputID(gomock.Any(), gomock.Any()).Return("", nil)
m.envDeployer.EXPECT().UpdateAndRenderEnvironment(gomock.Any(), gomock.Any(), gomock.Len(2)).Return(nil)
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -388,7 +401,8 @@ func TestEnvDeployer_DeployEnvironment(t *testing.T) {
CustomResourcesURLs: map[string]string{
"mockResource": "mockURL",
},
Manifest: tc.inManifest,
Manifest: tc.inManifest,
DisableRollback: tc.inDisableRollback,
}
gotErr := d.DeployEnvironment(mockIn)
if tc.wantedError != nil {
Expand Down
20 changes: 17 additions & 3 deletions internal/pkg/cli/env_deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/aws/copilot-cli/internal/pkg/aws/sessions"
"github.com/aws/copilot-cli/internal/pkg/cli/deploy"
"github.com/aws/copilot-cli/internal/pkg/config"
"github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation/stack"
"github.com/aws/copilot-cli/internal/pkg/manifest"
"github.com/aws/copilot-cli/internal/pkg/term/color"
"github.com/aws/copilot-cli/internal/pkg/term/log"
Expand All @@ -27,9 +28,10 @@ import (
)

type deployEnvVars struct {
appName string
name string
forceNewUpdate bool
appName string
name string
forceNewUpdate bool
disableRollback bool
}

type deployEnvOpts struct {
Expand Down Expand Up @@ -147,6 +149,7 @@ func (o *deployEnvOpts) Execute() error {
ForceNewUpdate: o.forceNewUpdate,
RawManifest: rawMft,
PermissionsBoundary: o.targetApp.PermissionsBoundary,
DisableRollback: o.disableRollback,
}); err != nil {
var errEmptyChangeSet *awscfn.ErrChangeSetEmpty
if errors.As(err, &errEmptyChangeSet) {
Expand All @@ -156,6 +159,16 @@ necessary by a service deployment.

In this case, you can run %s to push a modified template, even if there are no immediate changes.
`, color.HighlightCode("copilot env deploy --force"))
}
if o.disableRollback {
stackName := stack.NameForEnv(o.targetApp.Name, o.targetEnv.Name)
rollbackCmd := fmt.Sprintf("aws cloudformation rollback-stack --stack-name %s --role-arn %s", stackName, o.targetEnv.ExecutionRoleARN)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice to include the role ARN! 💖

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This is nice so you don't have to change it, I'm just thinking out loud here.

I'd have expected this log to be in either the (1) RecommendedActions method, or, (2) we implement a custom error type that implements the RecommendActions method.

I don't think (1) fits since we are not exiting Execute successfully, so I think (2) is the better route 💭

So perhaps something like:

err := fmt.Errorf("deploy environment %s: %w", o.name, err)
if o.disableRollback{ 
  return &errWithDisabledRollback{
     parentErr: err,
     stackName: stack.NameForEnv(o.targetApp.Name, o.targetEnv.Name),
     cmd: fmt.Sprintf("aws cloudformation rollback-stack --stack-name %s --role-arn %s", stackName, o.targetEnv.ExecutionRoleARN)
  }
}
return err

log.Infof(`It seems like you have disabled automatic stack rollback for this deployment.
To debug, you can visit the AWS console to inspect the errors.
After fixing the deployment, you can:
1. Run %s to rollback the deployment.
2. Run %s to make a new deployment.
`, color.HighlightCode(rollbackCmd), color.HighlightCode("copilot env deploy"))
}
return fmt.Errorf("deploy environment %s: %w", o.name, err)
}
Expand Down Expand Up @@ -271,5 +284,6 @@ Deploy an environment named "test".
cmd.Flags().StringVarP(&vars.appName, appFlag, appFlagShort, tryReadingAppName(), appFlagDescription)
cmd.Flags().StringVarP(&vars.name, nameFlag, nameFlagShort, "", envFlagDescription)
cmd.Flags().BoolVar(&vars.forceNewUpdate, forceFlag, false, forceEnvDeployFlagDescription)
cmd.Flags().BoolVar(&vars.disableRollback, noRollbackFlag, false, noRollbackFlagDescription)
return cmd
}