Skip to content

Commit 8a59f53

Browse files
feat(bigquery): add samples for control access 1/3 (#4023)
* feat(bigquery): add samples for control access * testing, stylistic update --------- Co-authored-by: Eric Schmidt <[email protected]>
1 parent 2035417 commit 8a59f53

File tree

6 files changed

+351
-0
lines changed

6 files changed

+351
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
async function main(datasetId, entityId, role) {
18+
// [START bigquery_grant_access_to_dataset]
19+
20+
/**
21+
* TODO(developer): Update and un-comment below lines.
22+
*/
23+
24+
// const datasetId = "my_project_id.my_dataset_name";
25+
26+
// ID of the user or group from whom you are adding access.
27+
// const entityId = "[email protected]";
28+
29+
// One of the "Basic roles for datasets" described here:
30+
// https://cloud.google.com/bigquery/docs/access-control-basic-roles#dataset-basic-roles
31+
// const role = "READER";
32+
33+
const {BigQuery} = require('@google-cloud/bigquery');
34+
35+
// Instantiate a client.
36+
const client = new BigQuery();
37+
38+
// Type of entity you are granting access to.
39+
// Find allowed allowed entity type names here:
40+
// https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#resource:-dataset
41+
const entityType = 'groupByEmail';
42+
43+
async function grantAccessToDataset() {
44+
const [dataset] = await client.dataset(datasetId).get();
45+
46+
// The 'access entries' array is immutable. Create a copy for modifications.
47+
const entries = [...dataset.metadata.access];
48+
49+
// Append an AccessEntry to grant the role to a dataset.
50+
// Find more details about the AccessEntry object in the BigQuery documentation:
51+
// https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.dataset.AccessEntry
52+
entries.push({
53+
role,
54+
[entityType]: entityId,
55+
});
56+
57+
// Assign the array of AccessEntries back to the dataset.
58+
const metadata = {
59+
access: entries,
60+
};
61+
62+
// Update will only succeed if the dataset
63+
// has not been modified externally since retrieval.
64+
//
65+
// See the BigQuery client library documentation for more details on metadata updates:
66+
// https://cloud.google.com/nodejs/docs/reference/bigquery/latest
67+
68+
// Update just the 'access entries' property of the dataset.
69+
await client.dataset(datasetId).setMetadata(metadata);
70+
71+
console.log(
72+
`Role '${role}' granted for entity '${entityId}' in '${datasetId}'.`
73+
);
74+
}
75+
// [END bigquery_grant_access_to_dataset]
76+
await grantAccessToDataset();
77+
}
78+
79+
exports.grantAccessToDataset = main;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
async function main(projectId, datasetId, tableId, principalId, role) {
18+
// [START bigquery_grant_access_to_table_or_view]
19+
20+
/**
21+
* TODO(developer): Update and un-comment below lines
22+
*/
23+
// const projectId = "YOUR_PROJECT_ID";
24+
// const datasetId = "YOUR_DATASET_ID";
25+
// const tableId = "YOUR_TABLE_ID";
26+
// const principalId = "YOUR_PRINCIPAL_ID";
27+
// const role = "YOUR_ROLE";
28+
29+
const {BigQuery} = require('@google-cloud/bigquery');
30+
31+
// Instantiate a client.
32+
const client = new BigQuery();
33+
34+
async function grantAccessToTableOrView() {
35+
const dataset = client.dataset(datasetId);
36+
const table = dataset.table(tableId);
37+
38+
// Get the IAM access policy for the table or view.
39+
const [policy] = await table.getIamPolicy();
40+
41+
// Initialize bindings array.
42+
if (!policy.bindings) {
43+
policy.bindings = [];
44+
}
45+
46+
// To grant access to a table or view
47+
// add bindings to the Table or View policy.
48+
//
49+
// Find more details about Policy and Binding objects here:
50+
// https://cloud.google.com/security-command-center/docs/reference/rest/Shared.Types/Policy
51+
// https://cloud.google.com/security-command-center/docs/reference/rest/Shared.Types/Binding
52+
const binding = {
53+
role,
54+
members: [principalId],
55+
};
56+
policy.bindings.push(binding);
57+
58+
// Set the IAM access policy with updated bindings.
59+
await table.setIamPolicy(policy);
60+
61+
// Show a success message.
62+
console.log(
63+
`Role '${role}' granted for principal '${principalId}' on resource '${datasetId}.${tableId}'.`
64+
);
65+
}
66+
67+
await grantAccessToTableOrView();
68+
// [END bigquery_grant_access_to_table_or_view]
69+
}
70+
71+
exports.grantAccessToTableOrView = main;

bigquery/cloud-client/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "bigquery-cloud-client",
3+
"description": "Big Query Cloud Client Node.js samples",
4+
"version": "0.0.1",
5+
"private": true,
6+
"license": "Apache Version 2.0",
7+
"author": "Google LLC",
8+
"engines": {
9+
"node": "20.x"
10+
},
11+
"scripts": {
12+
"deploy": "gcloud app deploy",
13+
"start": "node app.js",
14+
"unit-test": "c8 mocha -p -j 2 test/ --timeout=10000 --exit",
15+
"test": "npm run unit-test"
16+
},
17+
"dependencies": {
18+
"@google-cloud/bigquery": "7.9.2"
19+
},
20+
"devDependencies": {
21+
"c8": "^10.0.0",
22+
"chai": "^4.5.0",
23+
"mocha": "^10.0.0",
24+
"sinon": "^18.0.0"
25+
}
26+
}

bigquery/cloud-client/test/config.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
const uuid = require('uuid');
16+
const {BigQuery} = require('@google-cloud/bigquery');
17+
18+
// Setup and teardown functions for test suites
19+
const setupBeforeAll = async () => {
20+
const prefix = `nodejs_test_${uuid.v4().replace(/-/g, '').substring(0, 8)}`;
21+
const entityId = '[email protected]'; // Group account
22+
const datasetId = `${prefix}_cloud_client`;
23+
const tableName = `${prefix}_table`;
24+
const viewName = `${prefix}_view`;
25+
26+
const client = new BigQuery();
27+
await client
28+
.createDataset(datasetId)
29+
.then(() => {
30+
return client.dataset(datasetId).createTable(tableName);
31+
})
32+
.catch(err => {
33+
console.error(`Error creating table: ${err.message}`);
34+
});
35+
36+
return {
37+
datasetId: datasetId,
38+
tableId: tableName,
39+
viewId: viewName,
40+
entityId: entityId,
41+
};
42+
};
43+
44+
const cleanupResources = async datasetId => {
45+
const client = new BigQuery();
46+
await client.dataset(datasetId).delete({deleteContents: true, force: true});
47+
};
48+
49+
module.exports = {
50+
setupBeforeAll,
51+
cleanupResources,
52+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
const {beforeEach, afterEach, it, describe} = require('mocha');
18+
const assert = require('assert');
19+
const sinon = require('sinon');
20+
21+
const {setupBeforeAll, cleanupResources} = require('./config');
22+
23+
const {grantAccessToDataset} = require('../grantAccessToDataset');
24+
25+
describe('grantAccessToDataset', () => {
26+
let datasetId = null;
27+
let entityId = null;
28+
const role = 'READER';
29+
30+
beforeEach(async () => {
31+
const response = await setupBeforeAll();
32+
datasetId = response.datasetId;
33+
entityId = response.entityId;
34+
35+
sinon.stub(console, 'log');
36+
sinon.stub(console, 'error');
37+
});
38+
39+
// Clean up after all tests.
40+
afterEach(async () => {
41+
await cleanupResources(datasetId);
42+
console.log.restore();
43+
console.error.restore();
44+
});
45+
46+
it('should add entity to access entries', async () => {
47+
// Act: Grant access to the dataset.
48+
await grantAccessToDataset(datasetId, entityId, role);
49+
50+
// Check if our entity ID is in the updated access entries.
51+
assert.strictEqual(
52+
console.log.calledWith(
53+
`Role '${role}' granted for entity '${entityId}' in '${datasetId}'.`
54+
),
55+
true
56+
);
57+
});
58+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
const {describe, it, beforeEach, afterEach} = require('mocha');
18+
const assert = require('assert');
19+
const sinon = require('sinon');
20+
21+
const {grantAccessToTableOrView} = require('../grantAccessToTableOrView');
22+
const {setupBeforeAll, cleanupResources} = require('./config');
23+
24+
describe('grantAccessToTableOrView', () => {
25+
let datasetId = null;
26+
let entityId = null;
27+
let tableId = null;
28+
const projectId = process.env.GCLOUD_PROJECT;
29+
30+
beforeEach(async () => {
31+
const response = await setupBeforeAll();
32+
datasetId = response.datasetId;
33+
entityId = response.entityId;
34+
tableId = response.tableId;
35+
36+
sinon.stub(console, 'log');
37+
sinon.stub(console, 'error');
38+
});
39+
40+
afterEach(async () => {
41+
await cleanupResources(datasetId);
42+
console.log.restore();
43+
console.error.restore();
44+
});
45+
46+
it('should grant access to a table', async () => {
47+
const roleId = 'roles/bigquery.dataViewer';
48+
const principalId = `group:${entityId}`;
49+
50+
await grantAccessToTableOrView(
51+
projectId,
52+
datasetId,
53+
tableId,
54+
principalId,
55+
roleId
56+
);
57+
58+
assert.strictEqual(
59+
console.log.calledWith(
60+
`Role '${roleId}' granted for principal '${principalId}' on resource '${datasetId}.${tableId}'.`
61+
),
62+
true
63+
);
64+
});
65+
});

0 commit comments

Comments
 (0)