-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathdeploy-action.ts
134 lines (121 loc) · 4.72 KB
/
deploy-action.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { Construct } from 'constructs';
import * as codepipeline from '../../../aws-codepipeline';
import * as ecs from '../../../aws-ecs';
import * as iam from '../../../aws-iam';
import { Duration } from '../../../core';
import { Action } from '../action';
import { deployArtifactBounds } from '../common';
/**
* Construction properties of `EcsDeployAction`.
*/
export interface EcsDeployActionProps extends codepipeline.CommonAwsActionProps {
/**
* The input artifact that contains the JSON image definitions file to use for deployments.
* The JSON file is a list of objects,
* each with 2 keys: `name` is the name of the container in the Task Definition,
* and `imageUri` is the Docker image URI you want to update your service with.
* If you use this property, it's assumed the file is called 'imagedefinitions.json'.
* If your build uses a different file, leave this property empty,
* and use the `imageFile` property instead.
*
* @default - one of this property, or `imageFile`, is required
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create.html#pipelines-create-image-definitions
*/
readonly input?: codepipeline.Artifact;
/**
* The name of the JSON image definitions file to use for deployments.
* The JSON file is a list of objects,
* each with 2 keys: `name` is the name of the container in the Task Definition,
* and `imageUri` is the Docker image URI you want to update your service with.
* Use this property if you want to use a different name for this file than the default 'imagedefinitions.json'.
* If you use this property, you don't need to specify the `input` property.
*
* @default - one of this property, or `input`, is required
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create.html#pipelines-create-image-definitions
*/
readonly imageFile?: codepipeline.ArtifactPath;
/**
* The ECS Service to deploy.
*/
readonly service: ecs.IBaseService;
/**
* Timeout for the ECS deployment in minutes. Value must be between 1-60.
*
* @default - 60 minutes
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-ECS.html
*/
readonly deploymentTimeout?: Duration;
}
/**
* CodePipeline Action to deploy an ECS Service.
*/
export class EcsDeployAction extends Action {
private readonly props: EcsDeployActionProps;
private readonly deploymentTimeout?: number;
constructor(props: EcsDeployActionProps) {
super({
...props,
category: codepipeline.ActionCategory.DEPLOY,
provider: 'ECS',
artifactBounds: deployArtifactBounds(),
inputs: [determineInputArtifact(props)],
resource: props.service,
});
const deploymentTimeout = props.deploymentTimeout?.toMinutes({ integral: true });
if (deploymentTimeout !== undefined && (deploymentTimeout < 1 || deploymentTimeout > 60)) {
throw new Error(`Deployment timeout must be between 1 and 60 minutes, got: ${deploymentTimeout}`);
}
this.props = props;
this.deploymentTimeout = deploymentTimeout;
}
protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig {
// permissions based on CodePipeline documentation:
// https://docs.aws.amazon.com/codepipeline/latest/userguide/security-iam.html#how-to-custom-role
options.role.addToPolicy(new iam.PolicyStatement({
actions: [
'ecs:DescribeServices',
'ecs:DescribeTaskDefinition',
'ecs:DescribeTasks',
'ecs:ListTasks',
'ecs:RegisterTaskDefinition',
'ecs:TagResource',
'ecs:UpdateService',
],
resources: ['*'],
}));
options.role.addToPolicy(new iam.PolicyStatement({
actions: ['iam:PassRole'],
resources: ['*'],
conditions: {
StringEqualsIfExists: {
'iam:PassedToService': [
'ec2.amazonaws.com',
'ecs-tasks.amazonaws.com',
],
},
},
}));
options.bucket.grantRead(options.role);
return {
configuration: {
ClusterName: this.props.service.cluster.clusterName,
ServiceName: this.props.service.serviceName,
FileName: this.props.imageFile?.fileName,
DeploymentTimeout: this.deploymentTimeout,
},
};
}
}
function determineInputArtifact(props: EcsDeployActionProps): codepipeline.Artifact {
if (props.imageFile && props.input) {
throw new Error("Exactly one of 'input' or 'imageFile' can be provided in the ECS deploy Action");
}
if (props.imageFile) {
return props.imageFile.artifact;
}
if (props.input) {
return props.input;
}
throw new Error("Specifying one of 'input' or 'imageFile' is required for the ECS deploy Action");
}