Skip to content
This repository was archived by the owner on May 31, 2024. It is now read-only.

Commit 219596e

Browse files
authored
feat: Bootstrap CDK during Account Activation (#272)
* Bootstrap CDK during Account Activation Removes the prerequisite of ensuring CDK is bootstrapped for app deployments. * Add separate bootstrap stack for AGC Fully encapsulates the bootstrap process by managing an internal Bootstrap stack coupled to the account lifecycle. This ensures other non-agc CDK applications running cannot interfere with or be interfered by AGC bootstrapping.
1 parent a6cc990 commit 219596e

File tree

19 files changed

+182
-107
lines changed

19 files changed

+182
-107
lines changed

extras/agc-minimal-permissions/cdk.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"app": "npx ts-node --prefer-ts-exts bin/permissions.ts",
33
"context": {
4+
"@aws-cdk/core:bootstrapQualifier": "agc",
45
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
56
"@aws-cdk/core:enableStackNameDuplicates": "true",
67
"aws-cdk:enableDiffNoFail": "true",

extras/agc-minimal-permissions/lib/permissions-stack.ts

-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export class AgcPermissionsStack extends cdk.Stack {
2626
...perms.s3Destroy(),
2727
...perms.s3Read(),
2828
...perms.s3Write(),
29-
...perms.s3CDK(),
3029
...perms.dynamodbCreate(),
3130
...perms.dynamodbRead(),
3231
...perms.dynamodbWrite(),
@@ -46,7 +45,6 @@ export class AgcPermissionsStack extends cdk.Stack {
4645
...perms.ec2(),
4746
...perms.s3Read(),
4847
...perms.s3Write(),
49-
...perms.s3CDK(),
5048
...perms.dynamodbRead(),
5149
...perms.dynamodbWrite(),
5250
...perms.ssmRead(),

extras/agc-minimal-permissions/lib/policy-statements.ts

+2-18
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ export class AgcPermissions {
183183
actions: actions(svc,
184184
"GetBucketPolicy",
185185
"GetBucketTagging",
186+
"GetBucketLocation",
186187
"GetEncryptionConfiguration",
187188
"ListBucket",
188189
"GetObject",
@@ -211,23 +212,6 @@ export class AgcPermissions {
211212
})]
212213
}
213214

214-
s3CDK() : PolicyStatement[] {
215-
const svc = "s3";
216-
return [new PolicyStatement({
217-
effect: Effect.ALLOW,
218-
actions: actions(svc,
219-
"ListBucket",
220-
"GetObject",
221-
"GetBucketLocation",
222-
"PutObject",
223-
"DeleteObject",
224-
),
225-
resources: [
226-
this.arn({service: svc, region: "", account: "", resource: "cdktoolkit-*"}),
227-
],
228-
})]
229-
}
230-
231215
dynamodbCreate() : PolicyStatement[] {
232216
const svc = "dynamodb";
233217
return [new PolicyStatement({
@@ -446,7 +430,7 @@ export class AgcPermissions {
446430
"UntagResource",
447431
),
448432
resources: [
449-
this.arn({service: "cloudformation", region: "*", resource: "stack", resourceName: "CDKToolkit*"}),
433+
this.arn({service: "cloudformation", region: "*", resource: "stack", resourceName: "Agc-CDKToolkit*"}),
450434
]
451435
})
452436
}

packages/cdk/apps/context/cdk.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2-
"app": "npx -p typescript -p ts-node ts-node --prefer-ts-exts app.ts"
2+
"app": "npx -p typescript -p ts-node ts-node --prefer-ts-exts app.ts",
3+
"context": {
4+
"@aws-cdk/core:bootstrapQualifier": "agc"
5+
}
36
}

packages/cdk/apps/core/cdk.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2-
"app": "npx -p typescript -p ts-node ts-node --prefer-ts-exts app.ts"
2+
"app": "npx -p typescript -p ts-node ts-node --prefer-ts-exts app.ts",
3+
"context": {
4+
"@aws-cdk/core:bootstrapQualifier": "agc"
5+
}
36
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package cdk
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/aws/amazon-genomics-cli/internal/pkg/cli/awsresources"
7+
"github.com/aws/amazon-genomics-cli/internal/pkg/cli/clierror/actionableerror"
8+
"github.com/aws/amazon-genomics-cli/internal/pkg/constants"
9+
)
10+
11+
func (client Client) Bootstrap(appDir string, context []string, executionName string) (ProgressStream, error) {
12+
cmdArgs := []string{
13+
"bootstrap",
14+
"--toolkit-stack-name", awsresources.RenderBootstrapStackName(),
15+
"--qualifier", constants.AppTagValue,
16+
"--tags", fmt.Sprintf("%s=%s", constants.AppTagKey, constants.AppTagValue),
17+
"--profile", client.profile,
18+
}
19+
cmdArgs = appendContextArguments(cmdArgs, context)
20+
progressStream, err := executeCdkCommand(appDir, cmdArgs, executionName)
21+
return progressStream, actionableerror.FindSuggestionForError(err, actionableerror.AwsErrorMessageToSuggestedActionMap)
22+
}

packages/cli/internal/pkg/aws/cdk/deploy_app.go

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cdk
33
import (
44
"os"
55

6+
"github.com/aws/amazon-genomics-cli/internal/pkg/cli/awsresources"
67
"github.com/aws/amazon-genomics-cli/internal/pkg/cli/clierror/actionableerror"
78
)
89

@@ -15,6 +16,7 @@ func (client Client) DeployApp(appDir string, context []string, executionName st
1516
"--all",
1617
"--profile", client.profile,
1718
"--require-approval", "never",
19+
"--toolkit-stack-name", awsresources.RenderBootstrapStackName(),
1820
"--output", tmpDir,
1921
}
2022
cmdArgs = appendContextArguments(cmdArgs, context)

packages/cli/internal/pkg/aws/cdk/destroy_app.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cdk
22

33
import (
4+
"github.com/aws/amazon-genomics-cli/internal/pkg/cli/awsresources"
45
"github.com/aws/amazon-genomics-cli/internal/pkg/cli/clierror/actionableerror"
56
)
67

@@ -10,6 +11,7 @@ func (client Client) DestroyApp(appDir string, context []string, executionName s
1011
"destroy",
1112
"--all",
1213
"--force",
14+
"--toolkit-stack-name", awsresources.RenderBootstrapStackName(),
1315
"--profile", client.profile,
1416
"--output", tmpDir,
1517
}

packages/cli/internal/pkg/aws/cdk/new_client.go

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cdk
22

33
type Interface interface {
4+
Bootstrap(appDir string, context []string, executionName string) (ProgressStream, error)
45
ClearContext(appDir string) error
56
DeployApp(appDir string, context []string, executionName string) (ProgressStream, error)
67
DestroyApp(appDir string, context []string, executionName string) (ProgressStream, error)

packages/cli/internal/pkg/cli/account_activate.go

+23-5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ A new VPC will be created if not specified.`
3030
cdkCoreDir = ".agc/cdk/apps/core"
3131
bucketPrefix = "agc"
3232
activateKey = "activate"
33+
bootstrapKey = "bootstrap"
3334
)
3435

3536
type accountActivateVars struct {
@@ -81,7 +82,18 @@ func (o *accountActivateOpts) Execute() error {
8182
environmentVars = append(environmentVars, fmt.Sprintf("VPC_ID=%s", o.vpcId))
8283
}
8384

84-
return o.deployCoreInfrastructure(environmentVars)
85+
homeDir, err := osutils.DetermineHomeDir()
86+
if err != nil {
87+
return err
88+
}
89+
90+
cdkAppPath := filepath.Join(homeDir, cdkCoreDir)
91+
err = o.cdkBootstrap(cdkAppPath, environmentVars)
92+
if err != nil {
93+
return err
94+
}
95+
96+
return o.deployCoreInfrastructure(cdkAppPath, environmentVars)
8597
}
8698

8799
func (o accountActivateOpts) generateDefaultBucket() (string, error) {
@@ -92,17 +104,23 @@ func (o accountActivateOpts) generateDefaultBucket() (string, error) {
92104
return generateBucketName(account, o.region), nil
93105
}
94106

95-
func (o accountActivateOpts) deployCoreInfrastructure(environmentVars []string) error {
96-
homeDir, err := osutils.DetermineHomeDir()
107+
func (o accountActivateOpts) cdkBootstrap(cdkAppPath string, environmentVars []string) error {
108+
progressStream, err := o.cdkClient.Bootstrap(cdkAppPath, environmentVars, bootstrapKey)
97109
if err != nil {
98110
return err
99111
}
112+
return displayProgress(progressStream, "Bootstrapping CDK...")
113+
}
100114

101-
cdkAppPath := filepath.Join(homeDir, cdkCoreDir)
115+
func (o accountActivateOpts) deployCoreInfrastructure(cdkAppPath string, environmentVars []string) error {
102116
progressStream, err := o.cdkClient.DeployApp(cdkAppPath, environmentVars, activateKey)
103117
if err != nil {
104118
return err
105119
}
120+
return displayProgress(progressStream, "Activating account...")
121+
}
122+
123+
func displayProgress(progressStream cdk.ProgressStream, displayMsg string) error {
106124
if logging.Verbose {
107125
var lastEvent cdk.ProgressEvent
108126
for event := range progressStream {
@@ -115,7 +133,7 @@ func (o accountActivateOpts) deployCoreInfrastructure(environmentVars []string)
115133
lastEvent = event
116134
}
117135
} else {
118-
return progressStream.DisplayProgress("Activating account...")
136+
return progressStream.DisplayProgress(displayMsg)
119137
}
120138
return nil
121139
}

packages/cli/internal/pkg/cli/account_activate_test.go

+49-36
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,13 @@ func TestAccountActivateOpts_Execute(t *testing.T) {
7070
defer close(mocks.progressStream)
7171
mocks.stsMock.EXPECT().GetAccount().Return(testAccountId, nil)
7272
mocks.s3Mock.EXPECT().BucketExists("agc-test-account-id-test-account-region").Return(false, nil)
73-
mocks.cdkMock.EXPECT().DeployApp(
74-
gomock.Any(),
75-
[]string{
76-
fmt.Sprintf("AGC_BUCKET_NAME=agc-%s-%s", testAccountId, testAccountRegion),
77-
fmt.Sprintf("CREATE_AGC_BUCKET=%t", true),
78-
fmt.Sprintf("AGC_VERSION=%s", version.Version),
79-
},
80-
"activate").Return(mocks.progressStream, nil)
73+
vars := []string{
74+
fmt.Sprintf("AGC_BUCKET_NAME=agc-%s-%s", testAccountId, testAccountRegion),
75+
fmt.Sprintf("CREATE_AGC_BUCKET=%t", true),
76+
fmt.Sprintf("AGC_VERSION=%s", version.Version),
77+
}
78+
mocks.cdkMock.EXPECT().Bootstrap(gomock.Any(), vars, "bootstrap").Return(mocks.progressStream, nil)
79+
mocks.cdkMock.EXPECT().DeployApp(gomock.Any(), vars, "activate").Return(mocks.progressStream, nil)
8180
return mocks
8281
},
8382
vpcId: "",
@@ -98,14 +97,13 @@ func TestAccountActivateOpts_Execute(t *testing.T) {
9897
mocks := createMocks(t)
9998
defer close(mocks.progressStream)
10099
mocks.s3Mock.EXPECT().BucketExists(testAccountBucketName).Return(false, nil)
101-
mocks.cdkMock.EXPECT().DeployApp(
102-
gomock.Any(),
103-
[]string{
104-
fmt.Sprintf("AGC_BUCKET_NAME=%s", testAccountBucketName),
105-
fmt.Sprintf("CREATE_AGC_BUCKET=%t", true),
106-
fmt.Sprintf("AGC_VERSION=%s", version.Version),
107-
},
108-
"activate").Return(mocks.progressStream, nil)
100+
vars := []string{
101+
fmt.Sprintf("AGC_BUCKET_NAME=%s", testAccountBucketName),
102+
fmt.Sprintf("CREATE_AGC_BUCKET=%t", true),
103+
fmt.Sprintf("AGC_VERSION=%s", version.Version),
104+
}
105+
mocks.cdkMock.EXPECT().Bootstrap(gomock.Any(), vars, "bootstrap").Return(mocks.progressStream, nil)
106+
mocks.cdkMock.EXPECT().DeployApp(gomock.Any(), vars, "activate").Return(mocks.progressStream, nil)
109107
return mocks
110108
},
111109
},
@@ -116,14 +114,13 @@ func TestAccountActivateOpts_Execute(t *testing.T) {
116114
mocks := createMocks(t)
117115
defer close(mocks.progressStream)
118116
mocks.s3Mock.EXPECT().BucketExists(testAccountBucketName).Return(true, nil)
119-
mocks.cdkMock.EXPECT().DeployApp(
120-
gomock.Any(),
121-
[]string{
122-
fmt.Sprintf("AGC_BUCKET_NAME=%s", testAccountBucketName),
123-
fmt.Sprintf("CREATE_AGC_BUCKET=%t", false),
124-
fmt.Sprintf("AGC_VERSION=%s", version.Version),
125-
},
126-
"activate").Return(mocks.progressStream, nil)
117+
vars := []string{
118+
fmt.Sprintf("AGC_BUCKET_NAME=%s", testAccountBucketName),
119+
fmt.Sprintf("CREATE_AGC_BUCKET=%t", false),
120+
fmt.Sprintf("AGC_VERSION=%s", version.Version),
121+
}
122+
mocks.cdkMock.EXPECT().Bootstrap(gomock.Any(), vars, "bootstrap").Return(mocks.progressStream, nil)
123+
mocks.cdkMock.EXPECT().DeployApp(gomock.Any(), vars, "activate").Return(mocks.progressStream, nil)
127124
return mocks
128125
},
129126
},
@@ -134,15 +131,14 @@ func TestAccountActivateOpts_Execute(t *testing.T) {
134131
mocks := createMocks(t)
135132
defer close(mocks.progressStream)
136133
mocks.s3Mock.EXPECT().BucketExists(testAccountBucketName).Return(false, nil)
137-
baseVars := []string{
134+
vars := []string{
138135
fmt.Sprintf("AGC_BUCKET_NAME=%s", testAccountBucketName),
139136
fmt.Sprintf("CREATE_AGC_BUCKET=%t", true),
140137
fmt.Sprintf("AGC_VERSION=%s", version.Version),
138+
fmt.Sprintf("VPC_ID=%s", testAccountVpcId),
141139
}
142-
mocks.cdkMock.EXPECT().DeployApp(
143-
gomock.Any(),
144-
append(baseVars, fmt.Sprintf("VPC_ID=%s", testAccountVpcId)),
145-
"activate").Return(mocks.progressStream, nil)
140+
mocks.cdkMock.EXPECT().Bootstrap(gomock.Any(), vars, "bootstrap").Return(mocks.progressStream, nil)
141+
mocks.cdkMock.EXPECT().DeployApp(gomock.Any(), vars, "activate").Return(mocks.progressStream, nil)
146142
return mocks
147143
},
148144
},
@@ -160,19 +156,36 @@ func TestAccountActivateOpts_Execute(t *testing.T) {
160156
vpcId: "",
161157
setupMocks: func(t *testing.T) mockClients {
162158
mocks := createMocks(t)
159+
defer close(mocks.progressStream)
163160
mocks.s3Mock.EXPECT().BucketExists(testAccountBucketName).Return(true, nil)
161+
vars := []string{
162+
fmt.Sprintf("AGC_BUCKET_NAME=%s", testAccountBucketName),
163+
fmt.Sprintf("CREATE_AGC_BUCKET=%t", false),
164+
fmt.Sprintf("AGC_VERSION=%s", version.Version),
165+
}
166+
mocks.cdkMock.EXPECT().Bootstrap(gomock.Any(), vars, "bootstrap").Return(mocks.progressStream, nil)
164167
mocks.cdkMock.EXPECT().DeployApp(
165-
gomock.Any(),
166-
[]string{
167-
fmt.Sprintf("AGC_BUCKET_NAME=%s", testAccountBucketName),
168-
fmt.Sprintf("CREATE_AGC_BUCKET=%t", false),
169-
fmt.Sprintf("AGC_VERSION=%s", version.Version),
170-
},
171-
"activate").Return(nil, fmt.Errorf("some deploy error"))
168+
gomock.Any(), vars, "activate").Return(nil, fmt.Errorf("some deploy error"))
172169
return mocks
173170
},
174171
expectedErr: fmt.Errorf("some deploy error"),
175172
},
173+
"bootstrap error": {
174+
bucketName: testAccountBucketName,
175+
vpcId: "",
176+
setupMocks: func(t *testing.T) mockClients {
177+
mocks := createMocks(t)
178+
vars := []string{
179+
fmt.Sprintf("AGC_BUCKET_NAME=%s", testAccountBucketName),
180+
fmt.Sprintf("CREATE_AGC_BUCKET=%t", false),
181+
fmt.Sprintf("AGC_VERSION=%s", version.Version),
182+
}
183+
mocks.s3Mock.EXPECT().BucketExists(testAccountBucketName).Return(true, nil)
184+
mocks.cdkMock.EXPECT().Bootstrap(gomock.Any(), vars, "bootstrap").Return(nil, fmt.Errorf("some bootstrap error"))
185+
return mocks
186+
},
187+
expectedErr: fmt.Errorf("some bootstrap error"),
188+
},
176189
}
177190

178191
for name, tc := range testCases {

0 commit comments

Comments
 (0)