Skip to content

Commit b15f137

Browse files
authored
Add ACL support to CodeDeploy task (#459)
* Add ACL support to CodeDeploy task * Update test code
1 parent 8d16c1a commit b15f137

File tree

5 files changed

+121
-58
lines changed

5 files changed

+121
-58
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Added \"filesAcl\" parameter to CodeDeploy task when uploading to S3"
4+
}

src/tasks/CodeDeployDeployApplication/TaskOperations.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,17 @@ export class TaskOperations {
122122
console.log(tl.loc('UploadingBundle', archiveName, key, this.taskParameters.bucketName))
123123
const fileBuffer = fs.createReadStream(archiveName)
124124
try {
125-
const response: S3.ManagedUpload.SendData = await this.s3Client
126-
.upload({
127-
Bucket: this.taskParameters.bucketName,
128-
Key: key,
129-
Body: fileBuffer
130-
})
131-
.promise()
125+
const request: S3.PutObjectRequest = {
126+
Bucket: this.taskParameters.bucketName,
127+
Key: key,
128+
Body: fileBuffer
129+
}
130+
131+
if (this.taskParameters.filesAcl && this.taskParameters.filesAcl !== 'none') {
132+
request.ACL = this.taskParameters.filesAcl
133+
}
134+
135+
await this.s3Client.upload(request).promise()
132136
console.log(tl.loc('BundleUploadCompleted'))
133137

134138
// clean up the archive if we created one

src/tasks/CodeDeployDeployApplication/TaskParameters.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface TaskParameters {
2020
bucketName: string
2121
bundlePrefix: string
2222
bundleKey: string
23+
filesAcl: string
2324
description: string
2425
fileExistsBehavior: string | undefined
2526
updateOutdatedInstancesOnly: boolean
@@ -38,6 +39,7 @@ export function buildTaskParameters(): TaskParameters {
3839
bucketName: getInputRequired('bucketName'),
3940
bundlePrefix: '',
4041
bundleKey: '',
42+
filesAcl: '',
4143
description: getInputOrEmpty('description'),
4244
fileExistsBehavior: tl.getInput('fileExistsBehavior', false),
4345
updateOutdatedInstancesOnly: tl.getBoolInput('updateOutdatedInstancesOnly', false),
@@ -50,6 +52,7 @@ export function buildTaskParameters(): TaskParameters {
5052
case revisionSourceFromWorkspace:
5153
parameters.revisionBundle = getPathInputRequiredCheck('revisionBundle')
5254
parameters.bundlePrefix = getInputOrEmpty('bundlePrefix')
55+
parameters.filesAcl = getInputOrEmpty('filesAcl')
5356
break
5457

5558
case revisionSourceFromS3:

src/tasks/CodeDeployDeployApplication/task.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,25 @@
111111
"visibleRule": "deploymentRevisionSource = s3",
112112
"helpMarkDown": "The Amazon S3 object key of the previously uploaded archive file containing the deployment revision artifacts."
113113
},
114+
{
115+
"name": "filesAcl",
116+
"type": "pickList",
117+
"label": "Access Control (ACL)",
118+
"defaultValue": "none",
119+
"required": false,
120+
"visibleRule": "deploymentRevisionSource = workspace",
121+
"helpMarkDown": "The canned Access Control List (ACL) to apply to the uploaded content. See [Canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) for an explanation of the possible values. By default, no ACL is applied.",
122+
"options": {
123+
"none": "none",
124+
"private": "private",
125+
"public-read": "public read",
126+
"public-read-write": "public read write",
127+
"authenticated-read": "authenticated read",
128+
"aws-exec-read": "aws-exec-read",
129+
"bucket-owner-read": "bucket-owner-read",
130+
"bucket-owner-full-control": "bucket-owner-full-control"
131+
}
132+
},
114133
{
115134
"name": "description",
116135
"label": "Description",

tests/taskTests/codeDeployDeployApplication/codeDeployDeployApplication-test.ts

Lines changed: 84 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const defaultTaskParameters: TaskParameters = {
2828
bundlePrefix: '',
2929
bundleKey: 'undefined',
3030
description: '',
31+
filesAcl: '',
3132
fileExistsBehavior: '',
3233
updateOutdatedInstancesOnly: false,
3334
ignoreApplicationStopFailures: false,
@@ -44,6 +45,21 @@ const codeDeployDeploymentId = {
4445
}
4546

4647
describe('CodeDeploy Deploy Application', () => {
48+
// Creates a simple mock that always succeeds (at least for these tests)
49+
function createSuccessfulCodeDeploy(): CodeDeploy {
50+
const codeDeploy = new CodeDeploy() as any
51+
codeDeploy.getApplication = jest.fn(() => emptyPromise)
52+
codeDeploy.getDeploymentGroup = jest.fn(() => emptyPromise)
53+
codeDeploy.createDeployment = jest.fn(() => codeDeployDeploymentId)
54+
codeDeploy.waitFor = jest.fn((thing, thing2, cb) => {
55+
cb()
56+
57+
return { promise: () => undefined }
58+
})
59+
60+
return codeDeploy
61+
}
62+
4763
// TODO https://github.com/aws/aws-vsts-tools/issues/167
4864
beforeAll(() => {
4965
SdkUtils.readResourcesFromRelativePath('../../build/src/tasks/CodeDeployDeployApplication/task.json')
@@ -75,10 +91,7 @@ describe('CodeDeploy Deploy Application', () => {
7591
expect.assertions(1)
7692
const taskParameters = { ...defaultTaskParameters }
7793
taskParameters.deploymentRevisionSource = revisionSourceFromS3
78-
const codeDeploy = new CodeDeploy() as any
79-
codeDeploy.getApplication = jest.fn(() => emptyPromise)
80-
codeDeploy.getDeploymentGroup = jest.fn(() => emptyPromise)
81-
const taskOperations = new TaskOperations(codeDeploy, new S3(), taskParameters)
94+
const taskOperations = new TaskOperations(createSuccessfulCodeDeploy(), new S3(), taskParameters)
8295
await taskOperations.execute().catch(err => {
8396
expect(`${err}`).toContain('Archive with key undefined does not exist')
8497
})
@@ -89,10 +102,7 @@ describe('CodeDeploy Deploy Application', () => {
89102
const taskParameters = { ...defaultTaskParameters }
90103
taskParameters.deploymentRevisionSource = revisionSourceFromS3
91104
taskParameters.bundleKey = '/whatever/wahtever'
92-
const codeDeploy = new CodeDeploy() as any
93-
codeDeploy.getApplication = jest.fn(() => emptyPromise)
94-
codeDeploy.getDeploymentGroup = jest.fn(() => emptyPromise)
95-
codeDeploy.createDeployment = jest.fn(() => codeDeployDeploymentId)
105+
const codeDeploy = createSuccessfulCodeDeploy() as any
96106
// the first argument of the callback is error so pass in an "error"
97107
codeDeploy.waitFor = jest.fn((arr1, arr2, cb) => cb(new Error('22'), undefined))
98108
const s3 = new S3() as any
@@ -103,44 +113,76 @@ describe('CodeDeploy Deploy Application', () => {
103113
})
104114
})
105115

106-
test('Upload needed, packages properly, succeeds', async () => {
107-
expect.assertions(6)
108-
process.env.TEMP = __dirname
109-
const taskParameters = { ...defaultTaskParameters }
110-
taskParameters.deploymentRevisionSource = revisionSourceFromWorkspace
111-
taskParameters.revisionBundle = path.join(__dirname, '../../resources/codeDeployCode')
112-
taskParameters.applicationName = 'test'
113-
const s3 = new S3() as any
114-
s3.upload = jest.fn(args => {
115-
expect(args.Bucket).toBe('')
116-
expect(args.Key).toContain('test.v')
117-
const dir = fs.readdirSync(__dirname)
118-
for (const file of dir) {
119-
if (path.extname(file) === '.zip') {
120-
const f = path.join(__dirname, file)
121-
const zip = new AdmZip(f)
122-
const entries = zip.getEntries().map(it => it.entryName)
123-
expect(entries.length).toBe(3)
124-
expect(entries).toContain('test.txt')
125-
expect(entries).toContain('subpath/')
126-
expect(entries).toContain('subpath/abc.txt')
127-
break
116+
describe('S3 upload needed', () => {
117+
let parameters: TaskParameters
118+
119+
beforeEach(() => {
120+
process.env.TEMP = __dirname
121+
122+
parameters = { ...defaultTaskParameters }
123+
parameters.deploymentRevisionSource = revisionSourceFromWorkspace
124+
parameters.revisionBundle = path.join(__dirname, '../../resources/codeDeployCode')
125+
parameters.applicationName = 'test'
126+
})
127+
128+
test('Upload needed, packages properly, succeeds', async () => {
129+
expect.assertions(7)
130+
131+
const s3 = new S3() as any
132+
s3.upload = jest.fn(args => {
133+
expect(args.Bucket).toBe('')
134+
expect(args.Key).toContain('test.v')
135+
expect(args.ACL).toBeUndefined()
136+
const dir = fs.readdirSync(__dirname)
137+
for (const file of dir) {
138+
if (path.extname(file) === '.zip') {
139+
const f = path.join(__dirname, file)
140+
const zip = new AdmZip(f)
141+
const entries = zip.getEntries().map(it => it.entryName)
142+
expect(entries.length).toBe(3)
143+
expect(entries).toContain('test.txt')
144+
expect(entries).toContain('subpath/')
145+
expect(entries).toContain('subpath/abc.txt')
146+
break
147+
}
128148
}
129-
}
130149

131-
return emptyPromise
150+
return emptyPromise
151+
})
152+
153+
const taskOperations = new TaskOperations(createSuccessfulCodeDeploy(), s3, parameters)
154+
await taskOperations.execute()
132155
})
133-
const codeDeploy = new CodeDeploy() as any
134-
codeDeploy.getApplication = jest.fn(() => emptyPromise)
135-
codeDeploy.getDeploymentGroup = jest.fn(() => emptyPromise)
136-
codeDeploy.createDeployment = jest.fn(() => codeDeployDeploymentId)
137-
codeDeploy.waitFor = jest.fn((thing, thing2, cb) => {
138-
cb()
139156

140-
return { promise: () => undefined }
157+
test('Uses ACL provided by task parameters', async () => {
158+
expect.assertions(1)
159+
parameters.filesAcl = 'bucket-owner-full-control'
160+
161+
const s3 = new S3() as any
162+
s3.upload = jest.fn(args => {
163+
expect(args.ACL).toBe('bucket-owner-full-control')
164+
165+
return emptyPromise
166+
})
167+
168+
const taskOperations = new TaskOperations(createSuccessfulCodeDeploy(), s3, parameters)
169+
await taskOperations.execute()
170+
})
171+
172+
test('Does not set ACL when using "none"', async () => {
173+
expect.assertions(1)
174+
parameters.filesAcl = 'none'
175+
176+
const s3 = new S3() as any
177+
s3.upload = jest.fn(args => {
178+
expect(args.ACL).toBeUndefined()
179+
180+
return emptyPromise
181+
})
182+
183+
const taskOperations = new TaskOperations(createSuccessfulCodeDeploy(), s3, parameters)
184+
await taskOperations.execute()
141185
})
142-
const taskOperations = new TaskOperations(codeDeploy, s3, taskParameters)
143-
await taskOperations.execute()
144186
})
145187

146188
test('Upload not needed, succeeds', async () => {
@@ -150,16 +192,7 @@ describe('CodeDeploy Deploy Application', () => {
150192
taskParameters.bundleKey = path.join(__dirname, '../../resources/codeDeployCode.zip')
151193
const s3 = new S3() as any
152194
s3.headObject = jest.fn(() => emptyPromise)
153-
const codeDeploy = new CodeDeploy() as any
154-
codeDeploy.getApplication = jest.fn(() => emptyPromise)
155-
codeDeploy.getDeploymentGroup = jest.fn(() => emptyPromise)
156-
codeDeploy.createDeployment = jest.fn(() => codeDeployDeploymentId)
157-
codeDeploy.waitFor = jest.fn((thing, thing2, cb) => {
158-
cb()
159-
160-
return { promise: () => undefined }
161-
})
162-
const taskOperations = new TaskOperations(codeDeploy, s3, taskParameters)
195+
const taskOperations = new TaskOperations(createSuccessfulCodeDeploy(), s3, taskParameters)
163196
await taskOperations.execute()
164197
})
165198
})

0 commit comments

Comments
 (0)