diff --git a/bigquery/cloud-client/grantAccessToDataset.js b/bigquery/cloud-client/grantAccessToDataset.js new file mode 100644 index 0000000000..3c56b1c9af --- /dev/null +++ b/bigquery/cloud-client/grantAccessToDataset.js @@ -0,0 +1,79 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +async function main(datasetId, entityId, role) { + // [START bigquery_grant_access_to_dataset] + + /** + * TODO(developer): Update and un-comment below lines. + */ + + // const datasetId = "my_project_id.my_dataset_name"; + + // ID of the user or group from whom you are adding access. + // const entityId = "user-or-group-to-add@example.com"; + + // One of the "Basic roles for datasets" described here: + // https://cloud.google.com/bigquery/docs/access-control-basic-roles#dataset-basic-roles + // const role = "READER"; + + const {BigQuery} = require('@google-cloud/bigquery'); + + // Instantiate a client. + const client = new BigQuery(); + + // Type of entity you are granting access to. + // Find allowed allowed entity type names here: + // https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#resource:-dataset + const entityType = 'groupByEmail'; + + async function grantAccessToDataset() { + const [dataset] = await client.dataset(datasetId).get(); + + // The 'access entries' array is immutable. Create a copy for modifications. + const entries = [...dataset.metadata.access]; + + // Append an AccessEntry to grant the role to a dataset. + // Find more details about the AccessEntry object in the BigQuery documentation: + // https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.dataset.AccessEntry + entries.push({ + role, + [entityType]: entityId, + }); + + // Assign the array of AccessEntries back to the dataset. + const metadata = { + access: entries, + }; + + // Update will only succeed if the dataset + // has not been modified externally since retrieval. + // + // See the BigQuery client library documentation for more details on metadata updates: + // https://cloud.google.com/nodejs/docs/reference/bigquery/latest + + // Update just the 'access entries' property of the dataset. + await client.dataset(datasetId).setMetadata(metadata); + + console.log( + `Role '${role}' granted for entity '${entityId}' in '${datasetId}'.` + ); + } + // [END bigquery_grant_access_to_dataset] + await grantAccessToDataset(); +} + +exports.grantAccessToDataset = main; diff --git a/bigquery/cloud-client/grantAccessToTableOrView.js b/bigquery/cloud-client/grantAccessToTableOrView.js new file mode 100644 index 0000000000..bbd566c411 --- /dev/null +++ b/bigquery/cloud-client/grantAccessToTableOrView.js @@ -0,0 +1,71 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +async function main(projectId, datasetId, tableId, principalId, role) { + // [START bigquery_grant_access_to_table_or_view] + + /** + * TODO(developer): Update and un-comment below lines + */ + // const projectId = "YOUR_PROJECT_ID"; + // const datasetId = "YOUR_DATASET_ID"; + // const tableId = "YOUR_TABLE_ID"; + // const principalId = "YOUR_PRINCIPAL_ID"; + // const role = "YOUR_ROLE"; + + const {BigQuery} = require('@google-cloud/bigquery'); + + // Instantiate a client. + const client = new BigQuery(); + + async function grantAccessToTableOrView() { + const dataset = client.dataset(datasetId); + const table = dataset.table(tableId); + + // Get the IAM access policy for the table or view. + const [policy] = await table.getIamPolicy(); + + // Initialize bindings array. + if (!policy.bindings) { + policy.bindings = []; + } + + // To grant access to a table or view + // add bindings to the Table or View policy. + // + // Find more details about Policy and Binding objects here: + // https://cloud.google.com/security-command-center/docs/reference/rest/Shared.Types/Policy + // https://cloud.google.com/security-command-center/docs/reference/rest/Shared.Types/Binding + const binding = { + role, + members: [principalId], + }; + policy.bindings.push(binding); + + // Set the IAM access policy with updated bindings. + await table.setIamPolicy(policy); + + // Show a success message. + console.log( + `Role '${role}' granted for principal '${principalId}' on resource '${datasetId}.${tableId}'.` + ); + } + + await grantAccessToTableOrView(); + // [END bigquery_grant_access_to_table_or_view] +} + +exports.grantAccessToTableOrView = main; diff --git a/bigquery/cloud-client/package.json b/bigquery/cloud-client/package.json new file mode 100644 index 0000000000..fb97277d3a --- /dev/null +++ b/bigquery/cloud-client/package.json @@ -0,0 +1,26 @@ +{ + "name": "bigquery-cloud-client", + "description": "Big Query Cloud Client Node.js samples", + "version": "0.0.1", + "private": true, + "license": "Apache Version 2.0", + "author": "Google LLC", + "engines": { + "node": "20.x" + }, + "scripts": { + "deploy": "gcloud app deploy", + "start": "node app.js", + "unit-test": "c8 mocha -p -j 2 test/ --timeout=10000 --exit", + "test": "npm run unit-test" + }, + "dependencies": { + "@google-cloud/bigquery": "7.9.2" + }, + "devDependencies": { + "c8": "^10.0.0", + "chai": "^4.5.0", + "mocha": "^10.0.0", + "sinon": "^18.0.0" + } +} diff --git a/bigquery/cloud-client/test/config.js b/bigquery/cloud-client/test/config.js new file mode 100644 index 0000000000..00d4fd0b90 --- /dev/null +++ b/bigquery/cloud-client/test/config.js @@ -0,0 +1,52 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const uuid = require('uuid'); +const {BigQuery} = require('@google-cloud/bigquery'); + +// Setup and teardown functions for test suites +const setupBeforeAll = async () => { + const prefix = `nodejs_test_${uuid.v4().replace(/-/g, '').substring(0, 8)}`; + const entityId = 'example-analyst-group@google.com'; // Group account + const datasetId = `${prefix}_cloud_client`; + const tableName = `${prefix}_table`; + const viewName = `${prefix}_view`; + + const client = new BigQuery(); + await client + .createDataset(datasetId) + .then(() => { + return client.dataset(datasetId).createTable(tableName); + }) + .catch(err => { + console.error(`Error creating table: ${err.message}`); + }); + + return { + datasetId: datasetId, + tableId: tableName, + viewId: viewName, + entityId: entityId, + }; +}; + +const cleanupResources = async datasetId => { + const client = new BigQuery(); + await client.dataset(datasetId).delete({deleteContents: true, force: true}); +}; + +module.exports = { + setupBeforeAll, + cleanupResources, +}; diff --git a/bigquery/cloud-client/test/grantAccessToDataset.test.js b/bigquery/cloud-client/test/grantAccessToDataset.test.js new file mode 100644 index 0000000000..2fff5bfc6a --- /dev/null +++ b/bigquery/cloud-client/test/grantAccessToDataset.test.js @@ -0,0 +1,58 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const {beforeEach, afterEach, it, describe} = require('mocha'); +const assert = require('assert'); +const sinon = require('sinon'); + +const {setupBeforeAll, cleanupResources} = require('./config'); + +const {grantAccessToDataset} = require('../grantAccessToDataset'); + +describe('grantAccessToDataset', () => { + let datasetId = null; + let entityId = null; + const role = 'READER'; + + beforeEach(async () => { + const response = await setupBeforeAll(); + datasetId = response.datasetId; + entityId = response.entityId; + + sinon.stub(console, 'log'); + sinon.stub(console, 'error'); + }); + + // Clean up after all tests. + afterEach(async () => { + await cleanupResources(datasetId); + console.log.restore(); + console.error.restore(); + }); + + it('should add entity to access entries', async () => { + // Act: Grant access to the dataset. + await grantAccessToDataset(datasetId, entityId, role); + + // Check if our entity ID is in the updated access entries. + assert.strictEqual( + console.log.calledWith( + `Role '${role}' granted for entity '${entityId}' in '${datasetId}'.` + ), + true + ); + }); +}); diff --git a/bigquery/cloud-client/test/grantAccessToTableOrView.test.js b/bigquery/cloud-client/test/grantAccessToTableOrView.test.js new file mode 100644 index 0000000000..b4dc6ac39c --- /dev/null +++ b/bigquery/cloud-client/test/grantAccessToTableOrView.test.js @@ -0,0 +1,65 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const {describe, it, beforeEach, afterEach} = require('mocha'); +const assert = require('assert'); +const sinon = require('sinon'); + +const {grantAccessToTableOrView} = require('../grantAccessToTableOrView'); +const {setupBeforeAll, cleanupResources} = require('./config'); + +describe('grantAccessToTableOrView', () => { + let datasetId = null; + let entityId = null; + let tableId = null; + const projectId = process.env.GCLOUD_PROJECT; + + beforeEach(async () => { + const response = await setupBeforeAll(); + datasetId = response.datasetId; + entityId = response.entityId; + tableId = response.tableId; + + sinon.stub(console, 'log'); + sinon.stub(console, 'error'); + }); + + afterEach(async () => { + await cleanupResources(datasetId); + console.log.restore(); + console.error.restore(); + }); + + it('should grant access to a table', async () => { + const roleId = 'roles/bigquery.dataViewer'; + const principalId = `group:${entityId}`; + + await grantAccessToTableOrView( + projectId, + datasetId, + tableId, + principalId, + roleId + ); + + assert.strictEqual( + console.log.calledWith( + `Role '${roleId}' granted for principal '${principalId}' on resource '${datasetId}.${tableId}'.` + ), + true + ); + }); +});