Skip to content

Commit 3f42335

Browse files
AVaksmanGautamSharda
authored andcommitted
feat: add backup level IAM policy controls (#799)
The new features allow user to check and tune [IAM policy](https://cloud.google.com/iam/docs/reference/rest/v1/Policy) for the backup level: - `getIamPolicy` - allows a user obtain the current resource IAM policy. - `setIamPolicy` - allows a user to set resource level IAM policy. - `testIamPermissions` - allows a user to pass a list of [`permissions`](https://cloud.google.com/bigtable/docs/access-control#permissions) and get back a sub-list of granted permissions. - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary)
1 parent 6af0dcb commit 3f42335

4 files changed

Lines changed: 266 additions & 7 deletions

File tree

handwritten/bigtable/src/backup.ts

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,19 @@ import {PreciseDate} from '@google-cloud/precise-date';
1616
import {promisifyAll} from '@google-cloud/promisify';
1717
import snakeCase = require('lodash.snakecase');
1818
import {google} from '../protos/protos';
19-
import {Bigtable, Cluster, Table} from './';
19+
import {
20+
Bigtable,
21+
Cluster,
22+
GetIamPolicyCallback,
23+
GetIamPolicyOptions,
24+
GetIamPolicyResponse,
25+
Policy,
26+
SetIamPolicyCallback,
27+
SetIamPolicyResponse,
28+
TestIamPermissionsCallback,
29+
TestIamPermissionsResponse,
30+
} from './';
31+
import {Table} from '../src/table';
2032
import {
2133
CreateBackupConfig,
2234
CreateBackupCallback,
@@ -362,6 +374,38 @@ Please use the format 'my-backup' or '${cluster.name}/backups/my-backup'.`);
362374
);
363375
}
364376

377+
getIamPolicy(options?: GetIamPolicyOptions): Promise<GetIamPolicyResponse>;
378+
getIamPolicy(
379+
options: GetIamPolicyOptions,
380+
callback: GetIamPolicyCallback
381+
): void;
382+
/**
383+
* @param {object} [options] Configuration object.
384+
* @param {object} [options.gaxOptions] Request configuration options, outlined
385+
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.
386+
* @param {number} [options.requestedPolicyVersion] The policy format version
387+
* to be returned. Valid values are 0, 1, and 3. Requests specifying an
388+
* invalid value will be rejected. Requests for policies with any
389+
* conditional bindings must specify version 3. Policies without any
390+
* conditional bindings may specify any valid value or leave the field unset.
391+
* @param {function} [cb] The callback function.
392+
* @param {?error} callback.error An error returned while making this request.
393+
* @param {Policy} policy The policy.
394+
*
395+
* @example <caption>include:samples/document-snippets/instance.js</caption>
396+
* region_tag:bigtable_get_table_Iam_policy
397+
*/
398+
getIamPolicy(
399+
optionsOrCallback?: GetIamPolicyOptions | GetIamPolicyCallback,
400+
cb?: GetIamPolicyCallback
401+
): void | Promise<GetIamPolicyResponse> {
402+
const options =
403+
typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
404+
const callback =
405+
typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
406+
Table.prototype.getIamPolicy.call(this, options, callback);
407+
}
408+
365409
getMetadata(gaxOptions?: CallOptions): Promise<BackupGetMetadataResponse>;
366410
getMetadata(callback: BackupGetMetadataCallback): void;
367411
getMetadata(
@@ -477,6 +521,38 @@ Please use the format 'my-backup' or '${cluster.name}/backups/my-backup'.`);
477521
);
478522
}
479523

524+
setIamPolicy(
525+
policy: Policy,
526+
gaxOptions?: CallOptions
527+
): Promise<SetIamPolicyResponse>;
528+
setIamPolicy(
529+
policy: Policy,
530+
gaxOptions: CallOptions,
531+
callback: SetIamPolicyCallback
532+
): void;
533+
setIamPolicy(policy: Policy, callback: SetIamPolicyCallback): void;
534+
/**
535+
* @param {object} [gaxOptions] Request configuration options, outlined
536+
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.
537+
* @param {function} [callback] The callback function.
538+
* @param {?error} callback.error An error returned while making this request.
539+
* @param {Policy} policy The policy.
540+
*
541+
* @example <caption>include:samples/document-snippets/instance.js</caption>
542+
* region_tag:bigtable_set_instance_Iam_policy
543+
*/
544+
setIamPolicy(
545+
policy: Policy,
546+
gaxOptionsOrCallback?: CallOptions | SetIamPolicyCallback,
547+
cb?: SetIamPolicyCallback
548+
): void | Promise<SetIamPolicyResponse> {
549+
const gaxOptions =
550+
typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
551+
const callback =
552+
typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : cb!;
553+
Table.prototype.setIamPolicy.call(this, policy, gaxOptions, callback);
554+
}
555+
480556
setMetadata(
481557
metadata: ModifiableBackupFields,
482558
gaxOptions?: CallOptions
@@ -544,6 +620,49 @@ Please use the format 'my-backup' or '${cluster.name}/backups/my-backup'.`);
544620
}
545621
);
546622
}
623+
624+
testIamPermissions(
625+
permissions: string | string[],
626+
gaxOptions?: CallOptions
627+
): Promise<TestIamPermissionsResponse>;
628+
testIamPermissions(
629+
permissions: string | string[],
630+
callback: TestIamPermissionsCallback
631+
): void;
632+
testIamPermissions(
633+
permissions: string | string[],
634+
gaxOptions: CallOptions,
635+
callback: TestIamPermissionsCallback
636+
): void;
637+
/**
638+
*
639+
* @param {string | string[]} permissions The permission(s) to test for.
640+
* @param {object} [gaxOptions] Request configuration options, outlined
641+
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.
642+
* @param {function} [callback] The callback function.
643+
* @param {?error} callback.error An error returned while making this request.
644+
* @param {string[]} permissions A subset of permissions that the caller is
645+
* allowed.
646+
*
647+
* @example <caption>include:samples/document-snippets/instance.js</caption>
648+
* region_tag:bigtable_test_table_Iam_permissions
649+
*/
650+
testIamPermissions(
651+
permissions: string | string[],
652+
gaxOptionsOrCallback?: CallOptions | TestIamPermissionsCallback,
653+
cb?: TestIamPermissionsCallback
654+
): void | Promise<TestIamPermissionsResponse> {
655+
const gaxOptions =
656+
typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
657+
const callback =
658+
typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : cb!;
659+
Table.prototype.testIamPermissions.call(
660+
this,
661+
permissions,
662+
gaxOptions,
663+
callback
664+
);
665+
}
547666
}
548667

549668
/*! Developer Documentation

handwritten/bigtable/src/table.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1678,12 +1678,12 @@ Please use the format 'prezzy' or '${instance.name}/tables/prezzy'.`);
16781678
policy: Policy,
16791679
gaxOptions?: CallOptions
16801680
): Promise<SetIamPolicyResponse>;
1681+
setIamPolicy(policy: Policy, callback: SetIamPolicyCallback): void;
16811682
setIamPolicy(
16821683
policy: Policy,
16831684
gaxOptions: CallOptions,
16841685
callback: SetIamPolicyCallback
16851686
): void;
1686-
setIamPolicy(policy: Policy, callback: SetIamPolicyCallback): void;
16871687
/**
16881688
* @param {object} [gaxOptions] Request configuration options, outlined
16891689
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.

handwritten/bigtable/system-test/bigtable.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ describe('Bigtable', () => {
136136
const policyProperties = ['version', 'bindings', 'etag'];
137137
const [policy] = await INSTANCE.getIamPolicy();
138138
policyProperties.forEach(property => {
139-
assert.strictEqual(Object.keys(policy).includes(property), true);
139+
assert(property in policy);
140140
});
141141
});
142142

@@ -170,7 +170,7 @@ describe('Bigtable', () => {
170170

171171
const [policy] = await instance.getIamPolicy();
172172
const [updatedPolicy] = await instance.setIamPolicy(policy);
173-
assert.notStrictEqual(updatedPolicy, null);
173+
Object.keys(policy).forEach(key => assert(key in updatedPolicy));
174174

175175
await instance.delete();
176176
});
@@ -326,7 +326,7 @@ describe('Bigtable', () => {
326326
const policyProperties = ['version', 'bindings', 'etag'];
327327
const [policy] = await TABLE.getIamPolicy();
328328
policyProperties.forEach(property => {
329-
assert.strictEqual(Object.keys(policy).includes(property), true);
329+
assert(property in policy);
330330
});
331331
});
332332

@@ -345,7 +345,7 @@ describe('Bigtable', () => {
345345

346346
const [policy] = await table.getIamPolicy();
347347
const [updatedPolicy] = await table.setIamPolicy(policy);
348-
assert.notStrictEqual(updatedPolicy, null);
348+
Object.keys(policy).forEach(key => assert(key in updatedPolicy));
349349

350350
await table.delete();
351351
});
@@ -1259,6 +1259,33 @@ describe('Bigtable', () => {
12591259
assert.strictEqual(metadata.name, backupNameFromCluster);
12601260
assert.deepStrictEqual(backup.expireDate, updateExpireTime);
12611261
});
1262+
1263+
it('should get an Iam Policy for the backup', async () => {
1264+
const policyProperties = ['version', 'bindings', 'etag'];
1265+
const [policy] = await BACKUP.getIamPolicy();
1266+
1267+
policyProperties.forEach(property => {
1268+
assert(property in policy);
1269+
});
1270+
});
1271+
1272+
it('should test Iam permissions for the backup', async () => {
1273+
const permissions = ['bigtable.backups.get', 'bigtable.backups.delete'];
1274+
const [grantedPermissions] = await BACKUP.testIamPermissions(permissions);
1275+
assert.strictEqual(grantedPermissions.length, permissions.length);
1276+
permissions.forEach(permission => {
1277+
assert.strictEqual(grantedPermissions.includes(permission), true);
1278+
});
1279+
});
1280+
1281+
it('should set Iam Policy on the backup', async () => {
1282+
const backup = CLUSTER.backup(backupIdFromCluster);
1283+
1284+
const [policy] = await backup.getIamPolicy();
1285+
const [updatedPolicy] = await backup.setIamPolicy(policy);
1286+
1287+
Object.keys(policy).forEach(key => assert(key in updatedPolicy));
1288+
});
12621289
});
12631290
});
12641291

handwritten/bigtable/test/backup.ts

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@
1515
import {PreciseDate} from '@google-cloud/precise-date';
1616
import * as promisify from '@google-cloud/promisify';
1717
import * as assert from 'assert';
18-
import {before, beforeEach, describe, it} from 'mocha';
18+
import {before, beforeEach, describe, it, afterEach} from 'mocha';
1919
import * as proxyquire from 'proxyquire';
2020
import * as pumpify from 'pumpify';
2121
import {ServiceError} from 'google-gax';
2222

2323
import * as clusterTypes from '../src/cluster';
2424
import * as backupTypes from '../src/backup';
25+
import * as instanceTypes from '../src/instance';
26+
import * as sinon from 'sinon';
2527

2628
import {Bigtable} from '../src';
29+
import {Table} from '../src/table';
2730

2831
let promisified = false;
2932
const fakePromisify = Object.assign({}, promisify, {
@@ -40,6 +43,14 @@ const fakePromisify = Object.assign({}, promisify, {
4043
},
4144
});
4245

46+
class FakeTable extends Table {
47+
calledWith_: Array<{}>;
48+
constructor(...args: [instanceTypes.Instance, string]) {
49+
super(args[0], args[1]);
50+
this.calledWith_ = args;
51+
}
52+
}
53+
4354
describe('Bigtable/Backup', () => {
4455
const BACKUP_ID = 'my-backup';
4556
let CLUSTER: clusterTypes.Cluster;
@@ -52,6 +63,7 @@ describe('Bigtable/Backup', () => {
5263
before(() => {
5364
Backup = proxyquire('../src/backup.js', {
5465
'@google-cloud/promisify': fakePromisify,
66+
'./table.js': {Table: FakeTable},
5567
pumpify,
5668
}).Backup;
5769
});
@@ -328,6 +340,30 @@ describe('Bigtable/Backup', () => {
328340
});
329341
});
330342

343+
describe('getIamPolicy', () => {
344+
afterEach(() => {
345+
sinon.restore();
346+
});
347+
348+
it('should correctly call Table#getIamPolicy()', done => {
349+
sinon.stub(Table.prototype, 'getIamPolicy').callsFake((opt, callback) => {
350+
assert.deepStrictEqual(opt, {});
351+
callback(); // done()
352+
});
353+
backup.getIamPolicy(done);
354+
});
355+
356+
it('should accept options', done => {
357+
const options = {gaxOptions: {}, requestedPolicyVersion: 1};
358+
359+
sinon.stub(Table.prototype, 'getIamPolicy').callsFake((opt, callback) => {
360+
assert.strictEqual(opt, options);
361+
callback(); // done()
362+
});
363+
backup.getIamPolicy(options, done);
364+
});
365+
});
366+
331367
describe('getMetadata', () => {
332368
it('should make the correct request', done => {
333369
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -529,4 +565,81 @@ describe('Bigtable/Backup', () => {
529565
);
530566
});
531567
});
568+
569+
describe('setIamPolicy', () => {
570+
afterEach(() => {
571+
sinon.restore();
572+
});
573+
const policy = {};
574+
it('should correctly call Table#setIamPolicy()', done => {
575+
sinon
576+
.stub(Table.prototype, 'setIamPolicy')
577+
.callsFake((_policy, gaxOpts, callback) => {
578+
assert.strictEqual(_policy, policy);
579+
assert.deepStrictEqual(gaxOpts, {});
580+
callback(); // done()
581+
});
582+
backup.setIamPolicy(policy, done);
583+
});
584+
585+
it('should accept gaxOptions', done => {
586+
const gaxOptions = {};
587+
588+
sinon
589+
.stub(Table.prototype, 'setIamPolicy')
590+
.callsFake((_policy, gaxOpts, callback) => {
591+
assert.strictEqual(_policy, policy);
592+
assert.strictEqual(gaxOpts, gaxOptions);
593+
callback(); // done()
594+
});
595+
backup.setIamPolicy(policy, gaxOptions, done);
596+
});
597+
});
598+
599+
describe('testIamPermissions', () => {
600+
afterEach(() => {
601+
sinon.restore();
602+
});
603+
604+
const permissions = 'bigtable.backups.get';
605+
it('should properly call Table#testIamPermissions', done => {
606+
sinon
607+
.stub(Table.prototype, 'testIamPermissions')
608+
.callsFake((_permissions, gaxOpts, callback) => {
609+
assert.strictEqual(_permissions, permissions);
610+
assert.deepStrictEqual(gaxOpts, {});
611+
callback(); // done()
612+
});
613+
backup.testIamPermissions(permissions, done);
614+
});
615+
616+
it('should accept permissions as array', done => {
617+
const permissions = [
618+
'bigtable.backups.get',
619+
'bigtable.backups.delete',
620+
'bigtable.backups.update',
621+
'bigtable.backups.restore',
622+
];
623+
sinon
624+
.stub(Table.prototype, 'testIamPermissions')
625+
.callsFake((_permissions, gaxOpts, callback) => {
626+
assert.strictEqual(_permissions, permissions);
627+
assert.deepStrictEqual(gaxOpts, {});
628+
callback(); // done()
629+
});
630+
backup.testIamPermissions(permissions, done);
631+
});
632+
633+
it('should accept gaxOptions', done => {
634+
const gaxOptions = {};
635+
sinon
636+
.stub(Table.prototype, 'testIamPermissions')
637+
.callsFake((_permissions, gaxOpts, callback) => {
638+
assert.strictEqual(_permissions, permissions);
639+
assert.strictEqual(gaxOpts, gaxOptions);
640+
callback(); // done()
641+
});
642+
backup.testIamPermissions(permissions, gaxOptions, done);
643+
});
644+
});
532645
});

0 commit comments

Comments
 (0)