@flit/cdk-pipeline 1.4.4 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,20 @@
1
1
  import { Stack } from "aws-cdk-lib";
2
2
  import {
3
- BuildEnvironmentVariable,
4
- BuildSpec,
5
- mergeBuildSpecs,
6
- Project,
7
- ProjectProps,
3
+ BuildEnvironmentVariable,
4
+ BuildEnvironmentVariableType,
5
+ BuildSpec,
6
+ mergeBuildSpecs,
7
+ Project,
8
+ ProjectProps,
8
9
  } from "aws-cdk-lib/aws-codebuild";
9
10
  import { IAction } from "aws-cdk-lib/aws-codepipeline";
10
11
  import {
11
- CloudFormationCreateReplaceChangeSetAction,
12
- CloudFormationExecuteChangeSetAction,
13
- CodeBuildAction,
14
- ManualApprovalAction,
12
+ CloudFormationCreateReplaceChangeSetAction,
13
+ CloudFormationExecuteChangeSetAction,
14
+ CodeBuildAction,
15
+ ManualApprovalAction,
15
16
  } from "aws-cdk-lib/aws-codepipeline-actions";
16
- import { IRole } from "aws-cdk-lib/aws-iam";
17
+ import { IRole, PolicyStatement } from "aws-cdk-lib/aws-iam";
17
18
  import * as path from "path";
18
19
 
19
20
  import { Artifact } from "./artifact";
@@ -22,185 +23,215 @@ import { Pipeline } from "./pipeline";
22
23
  import { PublishAssetsAction } from "./publish-assets-action";
23
24
 
24
25
  export interface StackSegmentProps {
25
- /**
26
- * The stack to be deployed in this segment.
27
- */
28
- readonly stack: Stack;
29
- /**
30
- * The input arfifact for the build stage.
31
- */
32
- readonly input: Artifact | Artifact[];
33
- /**
34
- * The command(s) to build the stack.
35
- * @example "cdk synth StackName --strict --exclusively"
36
- */
37
- readonly project?: ProjectProps;
38
- /**
39
- * The role for the build stage.
40
- */
41
- readonly role?: IRole;
42
- /**
43
- * The environmental variables for the build stage.
44
- */
45
- readonly environmentVariables?: { [key: string]: BuildEnvironmentVariable };
46
- /**
47
- * The name of the stack to deploy the changes to.
48
- * @default The name of the given stack.
49
- */
50
- readonly stackName?: string;
51
- /**
52
- * The artifact to hold the output of the build if this stack includes a build.
53
- */
54
- readonly buildOutput?: Artifact;
55
- /**
56
- * The artifact to hold the stack deployment output file.
57
- */
58
- readonly stackOutput?: Artifact;
59
- /**
60
- * The filename for the file in the output artifact
61
- * @default "artifact.json"
62
- */
63
- readonly outputFileName?: string;
64
- /**
65
- * Does this stage require manual approval of the change set?
66
- * @default false
67
- */
68
- readonly manualApproval?: Boolean;
26
+ /**
27
+ * The stack to be deployed in this segment.
28
+ */
29
+ readonly stack: Stack;
30
+ /**
31
+ * The input arfifact for the build stage.
32
+ */
33
+ readonly input: Artifact | Artifact[];
34
+ /**
35
+ * The command(s) to build the stack.
36
+ * @example "cdk synth StackName --strict --exclusively"
37
+ */
38
+ readonly project?: ProjectProps;
39
+ /**
40
+ * The role for the build stage.
41
+ */
42
+ readonly role?: IRole;
43
+ /**
44
+ * The environmental variables for the build stage.
45
+ */
46
+ readonly environmentVariables?: { [key: string]: BuildEnvironmentVariable };
47
+ /**
48
+ * The name of the stack to deploy the changes to.
49
+ * @default The name of the given stack.
50
+ */
51
+ readonly stackName?: string;
52
+ /**
53
+ * The artifact to hold the output of the build if this stack includes a build.
54
+ */
55
+ readonly buildOutput?: Artifact;
56
+ /**
57
+ * The artifact to hold the stack deployment output file.
58
+ */
59
+ readonly stackOutput?: Artifact;
60
+ /**
61
+ * The filename for the file in the output artifact
62
+ * @default "artifact.json"
63
+ */
64
+ readonly outputFileName?: string;
65
+ /**
66
+ * Does this stage require manual approval of the change set?
67
+ * @default false
68
+ */
69
+ readonly manualApproval?: Boolean;
69
70
  }
70
71
 
71
72
  /**
72
73
  * @category Segments
73
74
  */
74
75
  export class StackSegment extends Segment {
75
- readonly props: StackSegmentProps;
76
+ readonly props: StackSegmentProps;
76
77
 
77
- constructor(props: StackSegmentProps) {
78
- super({
79
- ...props,
80
- output: [props.buildOutput, props.stackOutput].filter((a) => !!a),
81
- });
82
- this.props = props;
83
- }
78
+ constructor(props: StackSegmentProps) {
79
+ super({
80
+ ...props,
81
+ output: [props.buildOutput, props.stackOutput].filter((a) => !!a),
82
+ });
83
+ this.props = props;
84
+ }
84
85
 
85
- construct(scope: Pipeline): SegmentConstructed {
86
- return new StackSegmentConstructed(
87
- scope,
88
- `Deploy${this.props.stack.stackName}`,
89
- {
90
- ...this.props,
91
- input: this.inputs[0],
92
- extraInputs: this.inputs.slice(1),
93
- },
94
- );
95
- }
86
+ construct(scope: Pipeline): SegmentConstructed {
87
+ return new StackSegmentConstructed(
88
+ scope,
89
+ `Deploy${this.props.stack.stackName}`,
90
+ {
91
+ ...this.props,
92
+ input: this.inputs[0],
93
+ extraInputs: this.inputs.slice(1),
94
+ },
95
+ );
96
+ }
96
97
  }
97
98
 
98
99
  export interface StackSegmentConstructedProps {
99
- readonly stack: Stack;
100
- readonly project?: ProjectProps;
101
- readonly environmentVariables?: { [key: string]: BuildEnvironmentVariable };
102
- readonly stackName?: string;
103
- readonly input: Artifact;
104
- readonly extraInputs?: Artifact[];
105
- readonly buildOutput?: Artifact;
106
- readonly stackOutput?: Artifact;
107
- readonly outputFileName?: string;
108
- readonly manualApproval?: Boolean;
100
+ readonly stack: Stack;
101
+ readonly project?: ProjectProps;
102
+ readonly environmentVariables?: { [key: string]: BuildEnvironmentVariable };
103
+ readonly stackName?: string;
104
+ readonly input: Artifact;
105
+ readonly extraInputs?: Artifact[];
106
+ readonly buildOutput?: Artifact;
107
+ readonly stackOutput?: Artifact;
108
+ readonly outputFileName?: string;
109
+ readonly manualApproval?: Boolean;
109
110
  }
110
111
 
111
112
  export class StackSegmentConstructed extends SegmentConstructed {
112
- readonly name: string;
113
- readonly actions: IAction[];
113
+ readonly name: string;
114
+ readonly actions: IAction[];
114
115
 
115
- constructor(
116
- scope: Pipeline,
117
- id: string,
118
- props: StackSegmentConstructedProps,
119
- ) {
120
- super(scope, id);
116
+ constructor(
117
+ scope: Pipeline,
118
+ id: string,
119
+ props: StackSegmentConstructedProps,
120
+ ) {
121
+ super(scope, id);
121
122
 
122
- this.name = props.stack.stackName;
123
+ this.name = props.stack.stackName;
123
124
 
124
- const buildArtifact = props.project
125
- ? props.buildOutput || new Artifact()
126
- : undefined;
125
+ const buildArtifact = props.project
126
+ ? props.buildOutput || new Artifact()
127
+ : undefined;
127
128
 
128
- this.actions = [
129
- ...(buildArtifact
130
- ? [
131
- new CodeBuildAction({
132
- actionName: `${this.name}Build`,
133
- runOrder: 1,
134
- input: props.input,
135
- extraInputs: props.extraInputs,
136
- outputs: [buildArtifact],
137
- environmentVariables: props.environmentVariables,
138
- project: new Project(this, "UpdateCodeBuild", {
139
- ...props.project,
140
- buildSpec: props.project?.buildSpec
141
- ? mergeBuildSpecs(
142
- props.project.buildSpec,
143
- BuildSpec.fromObject({
144
- artifacts: {
145
- files: [path.join(scope.buildDir, "**/*")],
146
- },
147
- }),
148
- )
149
- : BuildSpec.fromObject({
150
- artifacts: {
151
- files: [path.join(scope.buildDir, "**/*")],
152
- },
153
- }),
154
- }),
155
- }),
156
- new PublishAssetsAction(this, "PublishAssets", {
157
- actionName: `${this.name}PublishAssets`,
158
- runOrder: 2,
159
- input: buildArtifact,
160
- manifestPath: scope.buildDir,
161
- }),
162
- ]
163
- : []),
164
- new CloudFormationCreateReplaceChangeSetAction({
165
- actionName: `${this.name}PrepareChanges`,
166
- runOrder: buildArtifact ? 3 : 1,
167
- stackName: props.stackName ? props.stackName : props.stack.stackName,
168
- account: props.stack.account,
169
- region: props.stack.region,
170
- changeSetName: `${props.stack.stackName}Changes`,
171
- adminPermissions: true,
172
- templatePath: (buildArtifact ? buildArtifact : props.input).atPath(
173
- path.join(scope.buildDir, props.stack.templateFile),
174
- ),
175
- }),
176
- ...(props.manualApproval
177
- ? [
178
- new ManualApprovalAction({
179
- actionName: `${this.name}ApproveChanges`,
180
- runOrder: buildArtifact ? 4 : 2,
181
- }),
182
- ]
183
- : []),
184
- new CloudFormationExecuteChangeSetAction({
185
- actionName: `${this.name}ExecuteChanges`,
186
- runOrder: props.manualApproval
187
- ? buildArtifact
188
- ? 5
189
- : 3
190
- : buildArtifact
191
- ? 4
192
- : 2,
193
- stackName: props.stackName ? props.stackName : props.stack.stackName,
194
- account: props.stack.account,
195
- region: props.stack.region,
196
- changeSetName: `${props.stack.stackName}Changes`,
197
- output: props.stackOutput,
198
- outputFileName: props.outputFileName
199
- ? props.outputFileName
200
- : props.stackOutput
201
- ? "artifact.json"
202
- : undefined,
203
- }),
204
- ];
205
- }
129
+ const codeBuildProject = new Project(this, "UpdateCodeBuild", {
130
+ ...props.project,
131
+ buildSpec: props.project?.buildSpec
132
+ ? mergeBuildSpecs(
133
+ props.project.buildSpec,
134
+ BuildSpec.fromObject({
135
+ artifacts: {
136
+ files: [path.join(scope.buildDir, "**/*")],
137
+ },
138
+ }),
139
+ )
140
+ : BuildSpec.fromObject({
141
+ artifacts: {
142
+ files: [path.join(scope.buildDir, "**/*")],
143
+ },
144
+ }),
145
+ })
146
+
147
+ Object.entries(props.project?.environment?.environmentVariables || []).forEach(([, v]) => {
148
+ switch (v.type) {
149
+ case BuildEnvironmentVariableType.PARAMETER_STORE:
150
+ codeBuildProject.addToRolePolicy(new PolicyStatement({
151
+ actions: [
152
+ "ssm:GetParameter",
153
+ "ssm:GetParameters",
154
+ "ssm:GetParametersByPath",
155
+ ],
156
+ resources: [
157
+ `arn:aws:ssm:*:${Stack.of(this).account}:parameter/${v.value}`
158
+ ],
159
+ }));
160
+ break;
161
+ case BuildEnvironmentVariableType.SECRETS_MANAGER:
162
+ codeBuildProject.addToRolePolicy(new PolicyStatement({
163
+ actions: [
164
+ "secretsmanager:GetSecretValue",
165
+ "secretsmanager:DescribeSecret",
166
+ ],
167
+ resources: [(v.value as string).startsWith("arn:") ?
168
+ v.value.split(":").slice(0, 7).join(":") :
169
+ `arn:aws:secretsmanager:*:${Stack.of(this).account}:secret:${v.value}-*`],
170
+ }));
171
+ break;
172
+ }
173
+ })
174
+
175
+ this.actions = [
176
+ ...(buildArtifact
177
+ ? [
178
+ new CodeBuildAction({
179
+ actionName: `${this.name}Build`,
180
+ runOrder: 1,
181
+ input: props.input,
182
+ extraInputs: props.extraInputs,
183
+ outputs: [buildArtifact],
184
+ environmentVariables: props.environmentVariables,
185
+ project: codeBuildProject,
186
+ }),
187
+ new PublishAssetsAction(this, "PublishAssets", {
188
+ actionName: `${this.name}PublishAssets`,
189
+ runOrder: 2,
190
+ input: buildArtifact,
191
+ manifestPath: scope.buildDir,
192
+ }),
193
+ ]
194
+ : []),
195
+ new CloudFormationCreateReplaceChangeSetAction({
196
+ actionName: `${this.name}PrepareChanges`,
197
+ runOrder: buildArtifact ? 3 : 1,
198
+ stackName: props.stackName ? props.stackName : props.stack.stackName,
199
+ account: props.stack.account,
200
+ region: props.stack.region,
201
+ changeSetName: `${props.stack.stackName}Changes`,
202
+ adminPermissions: true,
203
+ templatePath: (buildArtifact ? buildArtifact : props.input).atPath(
204
+ path.join(scope.buildDir, props.stack.templateFile),
205
+ ),
206
+ }),
207
+ ...(props.manualApproval
208
+ ? [
209
+ new ManualApprovalAction({
210
+ actionName: `${this.name}ApproveChanges`,
211
+ runOrder: buildArtifact ? 4 : 2,
212
+ }),
213
+ ]
214
+ : []),
215
+ new CloudFormationExecuteChangeSetAction({
216
+ actionName: `${this.name}ExecuteChanges`,
217
+ runOrder: props.manualApproval
218
+ ? buildArtifact
219
+ ? 5
220
+ : 3
221
+ : buildArtifact
222
+ ? 4
223
+ : 2,
224
+ stackName: props.stackName ? props.stackName : props.stack.stackName,
225
+ account: props.stack.account,
226
+ region: props.stack.region,
227
+ changeSetName: `${props.stack.stackName}Changes`,
228
+ output: props.stackOutput,
229
+ outputFileName: props.outputFileName
230
+ ? props.outputFileName
231
+ : props.stackOutput
232
+ ? "artifact.json"
233
+ : undefined,
234
+ }),
235
+ ];
236
+ }
206
237
  }