Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add backup level IAM policy controls #799

Merged
merged 11 commits into from
Oct 26, 2020
121 changes: 120 additions & 1 deletion src/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,19 @@ import {PreciseDate} from '@google-cloud/precise-date';
import {promisifyAll} from '@google-cloud/promisify';
import snakeCase = require('lodash.snakecase');
import {google} from '../protos/protos';
import {Bigtable, Cluster, Table} from './';
import {
Bigtable,
Cluster,
GetIamPolicyCallback,
GetIamPolicyOptions,
GetIamPolicyResponse,
Policy,
SetIamPolicyCallback,
SetIamPolicyResponse,
TestIamPermissionsCallback,
TestIamPermissionsResponse,
} from './';
import {Table} from '../src/table';
crwilcox marked this conversation as resolved.
Show resolved Hide resolved
import {
CreateBackupConfig,
CreateBackupCallback,
Expand Down Expand Up @@ -353,6 +365,38 @@ Please use the format 'my-backup' or '${cluster.name}/backups/my-backup'.`);
);
}

getIamPolicy(options?: GetIamPolicyOptions): Promise<GetIamPolicyResponse>;
getIamPolicy(
options: GetIamPolicyOptions,
callback: GetIamPolicyCallback
): void;
/**
* @param {object} [options] Configuration object.
* @param {object} [options.gaxOptions] Request configuration options, outlined
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.
* @param {number} [options.requestedPolicyVersion] The policy format version
* to be returned. Valid values are 0, 1, and 3. Requests specifying an
* invalid value will be rejected. Requests for policies with any
* conditional bindings must specify version 3. Policies without any
* conditional bindings may specify any valid value or leave the field unset.
* @param {function} [cb] The callback function.
* @param {?error} callback.error An error returned while making this request.
* @param {Policy} policy The policy.
*
* @example <caption>include:samples/document-snippets/instance.js</caption>
* region_tag:bigtable_get_table_Iam_policy
*/
getIamPolicy(
optionsOrCallback?: GetIamPolicyOptions | GetIamPolicyCallback,
cb?: GetIamPolicyCallback
): void | Promise<GetIamPolicyResponse> {
const options =
typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;
Table.prototype.getIamPolicy.call(this, options, callback);
}

getMetadata(gaxOptions?: CallOptions): Promise<BackupGetMetadataResponse>;
getMetadata(callback: BackupGetMetadataCallback): void;
getMetadata(
Expand Down Expand Up @@ -462,6 +506,38 @@ Please use the format 'my-backup' or '${cluster.name}/backups/my-backup'.`);
);
}

setIamPolicy(
policy: Policy,
gaxOptions?: CallOptions
): Promise<SetIamPolicyResponse>;
setIamPolicy(
policy: Policy,
gaxOptions: CallOptions,
callback: SetIamPolicyCallback
): void;
setIamPolicy(policy: Policy, callback: SetIamPolicyCallback): void;
/**
* @param {object} [gaxOptions] Request configuration options, outlined
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.
* @param {function} [callback] The callback function.
* @param {?error} callback.error An error returned while making this request.
* @param {Policy} policy The policy.
*
* @example <caption>include:samples/document-snippets/instance.js</caption>
* region_tag:bigtable_set_instance_Iam_policy
*/
setIamPolicy(
policy: Policy,
gaxOptionsOrCallback?: CallOptions | SetIamPolicyCallback,
cb?: SetIamPolicyCallback
): void | Promise<SetIamPolicyResponse> {
const gaxOptions =
typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
const callback =
typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : cb!;
Table.prototype.setIamPolicy.call(this, policy, gaxOptions, callback);
}

setMetadata(
metadata: ModifiableBackupFields,
gaxOptions?: CallOptions
Expand Down Expand Up @@ -526,6 +602,49 @@ Please use the format 'my-backup' or '${cluster.name}/backups/my-backup'.`);
}
);
}

testIamPermissions(
permissions: string | string[],
gaxOptions?: CallOptions
): Promise<TestIamPermissionsResponse>;
testIamPermissions(
permissions: string | string[],
callback: TestIamPermissionsCallback
): void;
testIamPermissions(
permissions: string | string[],
gaxOptions: CallOptions,
callback: TestIamPermissionsCallback
): void;
/**
*
* @param {string | string[]} permissions The permission(s) to test for.
* @param {object} [gaxOptions] Request configuration options, outlined
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.
* @param {function} [callback] The callback function.
* @param {?error} callback.error An error returned while making this request.
* @param {string[]} permissions A subset of permissions that the caller is
* allowed.
*
* @example <caption>include:samples/document-snippets/instance.js</caption>
* region_tag:bigtable_test_table_Iam_permissions
*/
testIamPermissions(
permissions: string | string[],
gaxOptionsOrCallback?: CallOptions | TestIamPermissionsCallback,
cb?: TestIamPermissionsCallback
): void | Promise<TestIamPermissionsResponse> {
const gaxOptions =
typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
const callback =
typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : cb!;
Table.prototype.testIamPermissions.call(
this,
permissions,
gaxOptions,
callback
);
}
}

/*! Developer Documentation
Expand Down
2 changes: 1 addition & 1 deletion src/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1678,12 +1678,12 @@ Please use the format 'prezzy' or '${instance.name}/tables/prezzy'.`);
policy: Policy,
gaxOptions?: CallOptions
): Promise<SetIamPolicyResponse>;
setIamPolicy(policy: Policy, callback: SetIamPolicyCallback): void;
setIamPolicy(
policy: Policy,
gaxOptions: CallOptions,
callback: SetIamPolicyCallback
): void;
setIamPolicy(policy: Policy, callback: SetIamPolicyCallback): void;
/**
* @param {object} [gaxOptions] Request configuration options, outlined
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.
Expand Down
35 changes: 31 additions & 4 deletions system-test/bigtable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ describe('Bigtable', () => {
const policyProperties = ['version', 'bindings', 'etag'];
const [policy] = await INSTANCE.getIamPolicy();
policyProperties.forEach(property => {
assert.strictEqual(Object.keys(policy).includes(property), true);
assert(property in policy);
});
});

Expand Down Expand Up @@ -170,7 +170,7 @@ describe('Bigtable', () => {

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

await instance.delete();
});
Expand Down Expand Up @@ -326,7 +326,7 @@ describe('Bigtable', () => {
const policyProperties = ['version', 'bindings', 'etag'];
const [policy] = await TABLE.getIamPolicy();
policyProperties.forEach(property => {
assert.strictEqual(Object.keys(policy).includes(property), true);
assert(property in policy);
});
});

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

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

await table.delete();
});
Expand Down Expand Up @@ -1259,6 +1259,33 @@ describe('Bigtable', () => {
assert.strictEqual(metadata.name, backupNameFromCluster);
assert.deepStrictEqual(backup.expireDate, updateExpireTime);
});

it('should get an Iam Policy for the backup', async () => {
const policyProperties = ['version', 'bindings', 'etag'];
const [policy] = await BACKUP.getIamPolicy();

policyProperties.forEach(property => {
assert(property in policy);
});
});

it('should test Iam permissions for the backup', async () => {
const permissions = ['bigtable.backups.get', 'bigtable.backups.delete'];
const [grantedPermissions] = await BACKUP.testIamPermissions(permissions);
assert.strictEqual(grantedPermissions.length, permissions.length);
permissions.forEach(permission => {
assert.strictEqual(grantedPermissions.includes(permission), true);
});
});

it('should set Iam Policy on the backup', async () => {
const backup = CLUSTER.backup(backupIdFromCluster);

const [policy] = await backup.getIamPolicy();
const [updatedPolicy] = await backup.setIamPolicy(policy);

Object.keys(policy).forEach(key => assert(key in updatedPolicy));
});
});
});

Expand Down
112 changes: 111 additions & 1 deletion test/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@
import {PreciseDate} from '@google-cloud/precise-date';
import * as promisify from '@google-cloud/promisify';
import * as assert from 'assert';
import {before, beforeEach, describe, it} from 'mocha';
import {before, beforeEach, describe, it, afterEach} from 'mocha';
import * as proxyquire from 'proxyquire';
import * as pumpify from 'pumpify';
import {ServiceError} from 'google-gax';

import * as clusterTypes from '../src/cluster';
import * as backupTypes from '../src/backup';
import * as instanceTypes from '../src/instance';
import * as sinon from 'sinon';

import {Bigtable} from '../src';
import {Table, Policy, GetIamPolicyOptions} from '../src/table';
import {callbackify} from 'util';

let promisified = false;
const fakePromisify = Object.assign({}, promisify, {
Expand All @@ -40,6 +44,14 @@ const fakePromisify = Object.assign({}, promisify, {
},
});

class FakeTable extends Table {
calledWith_: Array<{}>;
constructor(...args: [instanceTypes.Instance, string]) {
super(args[0], args[1]);
this.calledWith_ = args;
}
}

describe('Bigtable/Backup', () => {
const BACKUP_ID = 'my-backup';
let CLUSTER: clusterTypes.Cluster;
Expand All @@ -52,6 +64,7 @@ describe('Bigtable/Backup', () => {
before(() => {
Backup = proxyquire('../src/backup.js', {
'@google-cloud/promisify': fakePromisify,
'./table.js': {Table: FakeTable},
pumpify,
}).Backup;
});
Expand Down Expand Up @@ -328,6 +341,30 @@ describe('Bigtable/Backup', () => {
});
});

describe('getIamPolicy', () => {
afterEach(() => {
sinon.restore();
});

it('should correctly call Table#getIamPolicy()', done => {
sinon.stub(Table.prototype, 'getIamPolicy').callsFake((opt, callback) => {
assert.deepStrictEqual(opt, {});
callback(); // done()
});
backup.getIamPolicy(done);
});

it('should accept options', done => {
const options = {gaxOptions: {}, requestedPolicyVersion: 1};

sinon.stub(Table.prototype, 'getIamPolicy').callsFake((opt, callback) => {
assert.strictEqual(opt, options);
callback(); // done()
});
backup.getIamPolicy(options, done);
});
});

describe('getMetadata', () => {
it('should make the correct request', done => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -529,4 +566,77 @@ describe('Bigtable/Backup', () => {
);
});
});

describe('setIamPolicy', () => {
afterEach(() => {
sinon.restore();
});
const policy = {};
it('should correctly call Table#setIamPolicy()', done => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backup#setIamPolicy()

Copy link
Contributor Author

@AVaksman AVaksman Oct 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backup IAM policy controls functions delegate to/reuse Table IAM policy controls functions.
Please refer below
setIamPolicy
getIamPolicy
testIamPermissions

The unit tests verify that corresponding Table functions are properly called.

sinon
.stub(Table.prototype, 'setIamPolicy')
.callsFake((_policy, gaxOpts, callback) => {
assert.strictEqual(_policy, policy);
assert.deepStrictEqual(gaxOpts, {});
callback(); // done()
});
backup.setIamPolicy(policy, done);
});

it('should accept gaxOptions', done => {
const gaxOptions = {};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
AVaksman marked this conversation as resolved.
Show resolved Hide resolved
sinon
.stub(Table.prototype, 'setIamPolicy')
.callsFake((_policy, gaxOpts, callback) => {
assert.strictEqual(_policy, policy);
assert.strictEqual(gaxOpts, gaxOptions);
callback(); // done()
});
backup.setIamPolicy(policy, gaxOptions, done);
});
});

describe('testIamPermissions', () => {
afterEach(() => {
sinon.restore();
});

const permissions = 'bigtable.tables.get';
it('should properly call Table#testIamPermissions', done => {
sinon
.stub(Table.prototype, 'testIamPermissions')
.callsFake((_permissions, gaxOpts, callback) => {
assert.strictEqual(_permissions, permissions);
assert.deepStrictEqual(gaxOpts, {});
callback(); // done()
});
backup.testIamPermissions(permissions, done);
});

it('should accept permissions as array', done => {
const permissions = ['bigtable.tables.get', 'bigtable.tables.list'];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and elsewhere in these tests)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done
Thanks

sinon
.stub(Table.prototype, 'testIamPermissions')
.callsFake((_permissions, gaxOpts, callback) => {
assert.strictEqual(_permissions, permissions);
assert.deepStrictEqual(gaxOpts, {});
callback(); // done()
});
backup.testIamPermissions(permissions, done);
});

it('should accept gaxOptions', done => {
const gaxOptions = {};
sinon
.stub(Table.prototype, 'testIamPermissions')
.callsFake((_permissions, gaxOpts, callback) => {
assert.strictEqual(_permissions, permissions);
assert.strictEqual(gaxOpts, gaxOptions);
callback(); // done()
});
backup.testIamPermissions(permissions, gaxOptions, done);
});
});
});