Skip to content

Commit a2b6736

Browse files
committed
fix: update the method by which IAM roles are applied
* removes the previous implementation using deploymentmanager, as it does not appear to work as documented * adds support for setting IAM via cloudfunctions API * adds test cases
1 parent 0bb6cae commit a2b6736

File tree

7 files changed

+500
-101
lines changed

7 files changed

+500
-101
lines changed

deploy/googleDeploy.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const monitorDeployment = require('../shared/monitorDeployment');
1010
const uploadArtifacts = require('./lib/uploadArtifacts');
1111
const updateDeployment = require('./lib/updateDeployment');
1212
const cleanupDeploymentBucket = require('./lib/cleanupDeploymentBucket');
13+
const setIamPolicy = require('./lib/setIamPolicy');
1314

1415
class GoogleDeploy {
1516
constructor(serverless, options) {
@@ -26,7 +27,8 @@ class GoogleDeploy {
2627
monitorDeployment,
2728
uploadArtifacts,
2829
updateDeployment,
29-
cleanupDeploymentBucket
30+
cleanupDeploymentBucket,
31+
setIamPolicy
3032
);
3133

3234
this.hooks = {
@@ -37,7 +39,8 @@ class GoogleDeploy {
3739
.then(this.createDeployment)
3840
.then(this.setDeploymentBucketName)
3941
.then(this.uploadArtifacts)
40-
.then(this.updateDeployment),
42+
.then(this.updateDeployment)
43+
.then(this.setIamPolicy),
4144

4245
'after:deploy:deploy': () => BbPromise.bind(this).then(this.cleanupDeploymentBucket),
4346
};

deploy/googleDeploy.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('GoogleDeploy', () => {
4343
let uploadArtifactsStub;
4444
let updateDeploymentStub;
4545
let cleanupDeploymentBucketStub;
46+
let setIamPolicyStub;
4647

4748
beforeEach(() => {
4849
validateStub = sinon.stub(googleDeploy, 'validate').returns(BbPromise.resolve());
@@ -62,6 +63,7 @@ describe('GoogleDeploy', () => {
6263
cleanupDeploymentBucketStub = sinon
6364
.stub(googleDeploy, 'cleanupDeploymentBucket')
6465
.returns(BbPromise.resolve());
66+
setIamPolicyStub = sinon.stub(googleDeploy, 'setIamPolicy').returns(BbPromise.resolve());
6567
});
6668

6769
afterEach(() => {
@@ -72,6 +74,7 @@ describe('GoogleDeploy', () => {
7274
googleDeploy.uploadArtifacts.restore();
7375
googleDeploy.updateDeployment.restore();
7476
googleDeploy.cleanupDeploymentBucket.restore();
77+
googleDeploy.setIamPolicy.restore();
7578
});
7679

7780
it('should run "before:deploy:deploy" promise chain', () =>
@@ -86,6 +89,7 @@ describe('GoogleDeploy', () => {
8689
expect(setDeploymentBucketNameStub.calledAfter(createDeploymentStub)).toEqual(true);
8790
expect(uploadArtifactsStub.calledAfter(createDeploymentStub)).toEqual(true);
8891
expect(updateDeploymentStub.calledAfter(uploadArtifactsStub)).toEqual(true);
92+
expect(setIamPolicyStub.calledAfter(updateDeploymentStub)).toEqual(true);
8993
}));
9094

9195
it('should run "after:deploy:deploy" promise chain', () =>

deploy/lib/setIamPolicy.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const BbPromise = require('bluebird');
5+
6+
module.exports = {
7+
setIamPolicy() {
8+
return BbPromise.bind(this).then(this.getFunctions).then(this.setPolicies);
9+
},
10+
11+
getFunctions() {
12+
const project = this.serverless.service.provider.project;
13+
const region = this.options.region;
14+
15+
const params = {
16+
parent: `projects/${project}/locations/${region}`,
17+
};
18+
19+
return this.provider
20+
.request('cloudfunctions', 'projects', 'locations', 'functions', 'list', params)
21+
.then((response) => {
22+
return response.functions;
23+
});
24+
},
25+
26+
setPolicies(functions) {
27+
const policies = this.serverless.service.provider.functionIamBindings;
28+
29+
// If there are no IAM policies configured with any function, there is nothing to
30+
// do here.
31+
if (!policies || !Object.keys(policies).length) {
32+
return BbPromise.resolve();
33+
}
34+
this.serverless.cli.log('Setting IAM policies...');
35+
36+
_.forEach(policies, (value, key) => {
37+
const func = functions.find((fn) => {
38+
return fn.name === key;
39+
});
40+
if (func) {
41+
const params = {
42+
resource: func.name,
43+
requestBody: {
44+
policy: {
45+
bindings: value,
46+
},
47+
},
48+
};
49+
50+
this.provider.request(
51+
'cloudfunctions',
52+
'projects',
53+
'locations',
54+
'functions',
55+
'setIamPolicy',
56+
params
57+
);
58+
} else {
59+
const errorMessage = [
60+
`Unable to set IAM bindings (${value}) for "${key}": function not found for`,
61+
` project "${this.serverless.service.provider.project}" in region "${this.options.region}".`,
62+
].join('');
63+
throw new Error(errorMessage);
64+
}
65+
});
66+
67+
return BbPromise.resolve();
68+
},
69+
};

deploy/lib/setIamPolicy.test.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
'use strict';
2+
3+
const sinon = require('sinon');
4+
const BbPromise = require('bluebird');
5+
6+
const GoogleProvider = require('../../provider/googleProvider');
7+
const GoogleDeploy = require('../googleDeploy');
8+
const Serverless = require('../../test/serverless');
9+
10+
describe('SetIamPolicy', () => {
11+
let serverless;
12+
let googleDeploy;
13+
let requestStub;
14+
15+
beforeEach(() => {
16+
serverless = new Serverless();
17+
serverless.service.service = 'my-service';
18+
serverless.service.provider = {
19+
project: 'my-project',
20+
};
21+
serverless.config = {
22+
servicePath: 'tmp',
23+
};
24+
serverless.setProvider('google', new GoogleProvider(serverless));
25+
const options = {
26+
stage: 'dev',
27+
region: 'us-central1',
28+
};
29+
googleDeploy = new GoogleDeploy(serverless, options);
30+
requestStub = sinon.stub(googleDeploy.provider, 'request');
31+
});
32+
33+
afterEach(() => {
34+
googleDeploy.provider.request.restore();
35+
});
36+
37+
describe('#setIamPolicy', () => {
38+
let getFunctionsStub;
39+
let setPoliciesStub;
40+
41+
beforeEach(() => {
42+
getFunctionsStub = sinon.stub(googleDeploy, 'getFunctions').returns(BbPromise.resolve());
43+
setPoliciesStub = sinon.stub(googleDeploy, 'setPolicies').returns(BbPromise.resolve());
44+
});
45+
46+
afterEach(() => {
47+
googleDeploy.getFunctions.restore();
48+
googleDeploy.setPolicies.restore();
49+
});
50+
51+
it('should run the promise chain', () => {
52+
googleDeploy.setIamPolicy().then(() => {
53+
expect(getFunctionsStub.calledOnce).toEqual(true);
54+
expect(setPoliciesStub.calledAfter(getFunctionsStub));
55+
});
56+
});
57+
});
58+
59+
describe('#getFunctions', () => {
60+
it('should return "undefined" if no functions are found', () => {
61+
requestStub.returns(BbPromise.resolve([]));
62+
63+
return googleDeploy.getFunctions().then((foundFunctions) => {
64+
expect(foundFunctions).toEqual(undefined);
65+
expect(
66+
requestStub.calledWithExactly(
67+
'cloudfunctions',
68+
'projects',
69+
'locations',
70+
'functions',
71+
'list',
72+
{
73+
parent: 'projects/my-project/locations/us-central1',
74+
}
75+
)
76+
).toEqual(true);
77+
});
78+
});
79+
80+
it('should return all functions that are found', () => {
81+
const response = {
82+
functions: [{ name: 'cloud-function-1' }, { name: 'cloud-function-2' }],
83+
};
84+
requestStub.returns(BbPromise.resolve(response));
85+
86+
return googleDeploy.getFunctions().then((foundFunctions) => {
87+
expect(foundFunctions).toEqual([
88+
{ name: 'cloud-function-1' },
89+
{ name: 'cloud-function-2' },
90+
]);
91+
expect(
92+
requestStub.calledWithExactly(
93+
'cloudfunctions',
94+
'projects',
95+
'locations',
96+
'functions',
97+
'list',
98+
{
99+
parent: 'projects/my-project/locations/us-central1',
100+
}
101+
)
102+
).toEqual(true);
103+
});
104+
});
105+
});
106+
107+
describe('#setPolicies', () => {
108+
let consoleLogStub;
109+
110+
beforeEach(() => {
111+
consoleLogStub = sinon.stub(googleDeploy.serverless.cli, 'log').returns();
112+
googleDeploy.serverless.service.provider.functionIamBindings = {};
113+
});
114+
115+
afterEach(() => {
116+
googleDeploy.serverless.cli.log.restore();
117+
});
118+
119+
it('should resolve if functionIamBindings is undefined', () => {
120+
const foundFunctions = [{ name: 'cloud-function-1' }, { name: 'cloud-function-2' }];
121+
delete googleDeploy.serverless.service.provider.functionIamBindings;
122+
123+
return googleDeploy.setPolicies(foundFunctions).then(() => {
124+
expect(consoleLogStub.calledOnce).toEqual(false);
125+
});
126+
});
127+
128+
it('should resolve if there are no IAM policies configured', () => {
129+
const foundFunctions = [{ name: 'cloud-function-1' }, { name: 'cloud-function-2' }];
130+
131+
return googleDeploy.setPolicies(foundFunctions).then(() => {
132+
expect(consoleLogStub.calledOnce).toEqual(false);
133+
});
134+
});
135+
136+
it('should error if there are no existing functions to apply configured IAM to', () => {
137+
const foundFunctions = [];
138+
googleDeploy.serverless.service.provider.functionIamBindings = {
139+
'cloud-function-1': [{ role: 'roles/cloudfunctions.invoker', members: ['allUsers'] }],
140+
};
141+
142+
expect(() => googleDeploy.setPolicies(foundFunctions)).toThrow(Error);
143+
expect(consoleLogStub.calledOnce).toEqual(true);
144+
expect(requestStub.calledOnce).toEqual(false);
145+
});
146+
147+
it('should error if a configured function is not found', () => {
148+
const foundFunctions = [{ name: 'cloud-function-2' }];
149+
googleDeploy.serverless.service.provider.functionIamBindings = {
150+
'cloud-function-1': [{ role: 'roles/cloudfunctions.invoker', members: ['allUsers'] }],
151+
};
152+
153+
expect(() => googleDeploy.setPolicies(foundFunctions)).toThrow(Error);
154+
expect(consoleLogStub.calledOnce).toEqual(true);
155+
expect(requestStub.calledOnce).toEqual(false);
156+
});
157+
158+
it('should set the IAM policy for the configured functions', () => {
159+
const foundFunctions = [{ name: 'cloud-function-1' }, { name: 'cloud-function-2' }];
160+
googleDeploy.serverless.service.provider.functionIamBindings = {
161+
'cloud-function-2': [{ role: 'roles/cloudfunctions.invoker', members: ['allUsers'] }],
162+
};
163+
requestStub.returns(BbPromise.resolve());
164+
165+
return googleDeploy.setPolicies(foundFunctions).then(() => {
166+
expect(consoleLogStub.calledOnce).toEqual(true);
167+
expect(
168+
requestStub.calledWithExactly(
169+
'cloudfunctions',
170+
'projects',
171+
'locations',
172+
'functions',
173+
'setIamPolicy',
174+
{
175+
resource: 'cloud-function-2',
176+
requestBody: {
177+
policy: {
178+
bindings: [
179+
{
180+
role: 'roles/cloudfunctions.invoker',
181+
members: ['allUsers'],
182+
},
183+
],
184+
},
185+
},
186+
}
187+
)
188+
).toEqual(true);
189+
});
190+
});
191+
});
192+
});

0 commit comments

Comments
 (0)