diff --git a/storagebatchoperations/cancelJob.js b/storagebatchoperations/cancelJob.js new file mode 100644 index 0000000000..7e739fc076 --- /dev/null +++ b/storagebatchoperations/cancelJob.js @@ -0,0 +1,89 @@ +// 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 +// +// https://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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId, jobId) { + // [START storage_batch_cancel_job] + + /** + * Cancel a batch job instance. + * + * The operation to cancel a batch job instance in Google Cloud Storage (GCS) is used to stop + * a running or queued asynchronous task that is currently processing a large number of GCS objects. + * + * @param {string} projectId The Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function cancelJob() { + const name = client.jobPath(projectId, 'global', jobId); + + // Create the request + const request = { + name, + }; + + // Run request + try { + await client.cancelJob(request); + console.log(`Cancelled job: ${name}`); + } catch (error) { + // This might be expected if the job completed quickly or failed creation + console.error( + `Error canceling batch jobs for jobId ${jobId}:`, + error.message + ); + + if (error.code === 5) { + // NOT_FOUND (gRPC code 5) error can occur if the batch job does not exist. + console.error( + `Ensure the job '${jobId}' exists in project '${projectId}'.` + ); + } else if (error.code === 9) { + // FAILED_PRECONDITION (gRPC code 9) can occur if the job is already being cancelled + // or is not in a RUNNING state that allows the cancel operation. + console.error( + `Batch job '${jobId}' may not be in a state that allows canceling (e.g., must be RUNNING).` + ); + } + throw error; + } + } + + cancelJob(); + // [END storage_batch_cancel_job] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/createJob.js b/storagebatchoperations/createJob.js new file mode 100644 index 0000000000..0f6eba4134 --- /dev/null +++ b/storagebatchoperations/createJob.js @@ -0,0 +1,93 @@ +// 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 +// +// https://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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId, jobId, bucketName, objectPrefix) { + // [START storage_batch_create_job] + + /** + * Create a new batch job instance. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} bucketName The name of your GCS bucket. + * Example: 'your-gcp-bucket-name' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + * @param {string} objectPrefix The prefix of objects to include in the operation. + * Example: 'prefix1' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function createJob() { + const parent = await client.locationPath(projectId, 'global'); + + // Create the request + const request = { + parent, + jobId, + job: { + bucketList: { + buckets: [ + { + bucket: bucketName, + prefixList: { + includedObjectPrefixes: [objectPrefix], + }, + }, + ], + }, + deleteObject: { + permanentObjectDeletionEnabled: false, + }, + }, + }; + + try { + // Run the request, which returns an Operation object + const [operation] = await client.createJob(request); + console.log(`Waiting for operation ${operation.name} to complete...`); + + // Wait for the operation to complete and get the final resource + const [response] = await operation.promise(); + console.log(`Created job: ${response.name}`); + } catch (error) { + console.error('Failed to create batch job:', error.message); + throw error; + } + } + + createJob(); + // [END storage_batch_create_job] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/deleteJob.js b/storagebatchoperations/deleteJob.js new file mode 100644 index 0000000000..0934a2311f --- /dev/null +++ b/storagebatchoperations/deleteJob.js @@ -0,0 +1,81 @@ +// 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 +// +// https://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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId, jobId) { + // [START storage_batch_delete_job] + /** + * Delete a batch job instance. + * + * This operation is used to remove a completed, failed, or cancelled Batch Operation + * job from the system's list. It is essentially a cleanup action. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function deleteJob() { + const name = client.jobPath(projectId, 'global', jobId); + + // Create the request + const request = { + name, + }; + + try { + // Run request + await client.deleteJob(request); + console.log(`Deleted job: ${name}`); + } catch (error) { + console.error( + `Error deleting batch jobs for jobId ${jobId}:`, + error.message + ); + + if (error.code === 5) { + // NOT_FOUND (gRPC code 5) error can occur if the batch job does not exist. + console.error( + `Ensure the job '${jobId}' exists in project '${projectId}'.` + ); + } + throw error; + } + } + + deleteJob(); + // [END storage_batch_delete_job] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/getJob.js b/storagebatchoperations/getJob.js new file mode 100644 index 0000000000..b81bf7c33c --- /dev/null +++ b/storagebatchoperations/getJob.js @@ -0,0 +1,87 @@ +// 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 +// +// https://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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId, jobId) { + // [START storage_batch_get_job] + /** + * Retrieves details of a specific batch job instance. + * + * This operation is used to retrieve the detailed current state, execution status, + * and original configuration of a specific Batch Operation job that was previously + * created for a Google Cloud Storage bucket. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function getJob() { + const name = client.jobPath(projectId, 'global', jobId); + + // Create the request + const request = { + name, + }; + + try { + // Run request + const [response] = await client.getJob(request); + console.log(`Batch job details for '${jobId}':`); + console.log(`Name: ${response.name}`); + console.log(`State: ${response.state}`); + console.log( + `Create Time: ${new Date(response.createTime.seconds * 1000).toISOString()}` + ); + } catch (error) { + console.error( + `Error retrieving batch jobs for jobId ${jobId}:`, + error.message + ); + + if (error.code === 5) { + // NOT_FOUND (gRPC code 5) error can occur if the batch job does not exist. + console.error( + `Ensure the job '${jobId}' exists in project '${projectId}'.` + ); + } + throw error; + } + } + + getJob(); + // [END storage_batch_get_job] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/listJobs.js b/storagebatchoperations/listJobs.js new file mode 100644 index 0000000000..a72dbd4878 --- /dev/null +++ b/storagebatchoperations/listJobs.js @@ -0,0 +1,83 @@ +// 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 +// +// https://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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +function main(projectId) { + // [START storage_batch_list_jobs] + /** + * Lists all Jobs operation is used to query the status and configuration of all + * Storage Batch Operations jobs within a specific Google Cloud project. + * This feature is essential for tasks that affect a large number of objects, + * such as changing storage classes, deleting objects, or running custom functions + * on object metadata. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + */ + + // Imports the Control library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + // Instantiates a client + const client = new StorageBatchOperationsClient(); + + async function listJobs() { + const parent = await client.locationPath(projectId, 'global'); + + // Create the request + const request = { + parent, + }; + + try { + // Run request. The response is an array where the first element is the list of jobs. + const [response] = await client.listJobs(request); + if (response && response.length > 0) { + console.log( + `Found ${response.length} batch jobs for project: ${projectId}` + ); + for (const job of response) { + console.log(job.name); + } + } else { + // Case: Successful but empty list (No batch jobs found) + console.log(`No batch jobs found for project: ${projectId}.`); + } + } catch (error) { + console.error( + `Error listing batch jobs for project ${projectId}:`, + error.message + ); + throw error; + } + } + + listJobs(); + // [END storage_batch_list_jobs] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/storagebatchoperations/package.json b/storagebatchoperations/package.json new file mode 100644 index 0000000000..e382054dda --- /dev/null +++ b/storagebatchoperations/package.json @@ -0,0 +1,22 @@ +{ + "name": "storage-batch-operations-samples", + "version": "0.0.1", + "author": "Google Inc.", + "license": "Apache-2.0", + "description": "Examples of how to utilize the @google-cloud/storagebatchoperations library.", + "scripts": { + "test": "c8 mocha -p -j 2 system-test --timeout 600000" + }, + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "devDependencies": { + "@google-cloud/storage": "^7.17.1", + "@google-cloud/storagebatchoperations": "^0.1.0", + "c8": "^10.0.0", + "chai": "^4.5.0", + "mocha": "^10.7.0", + "uuid": "^10.0.0" + } + } diff --git a/storagebatchoperations/quickstart.js b/storagebatchoperations/quickstart.js new file mode 100644 index 0000000000..4a022041e1 --- /dev/null +++ b/storagebatchoperations/quickstart.js @@ -0,0 +1,88 @@ +// 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 +// +// https://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'; + +/** + * This application demonstrates how to perform basic operations on an Batch Operations + * instance with the Google Cloud Storage API. + * + * For more information, see the documentation at https://cloud.google.com/storage/docs/batch-operations/overview. + */ + +async function main(projectId, jobId) { + // [START storage_batch_quickstart] + + // Imports the Google Cloud client library + const {StorageBatchOperationsClient} = + require('@google-cloud/storagebatchoperations').v1; + + /** + * Retrieves details of a specific batch job instance. + * + * This operation is used to retrieve the detailed current state, execution status, + * and original configuration of a specific Batch Operation job that was previously + * created for a Google Cloud Storage bucket. + * + * @param {string} projectId Your Google Cloud project ID. + * Example: 'my-project-id' + * @param {string} jobId A unique identifier for this job. + * Example: '94d60cc1-2d95-41c5-b6e3-ff66cd3532d5' + */ + + // Creates a client + const client = new StorageBatchOperationsClient(); + + async function quickstart() { + const name = client.jobPath(projectId, 'global', jobId); + + // Create the request + const request = { + name, + }; + + try { + // Run request + const [response] = await client.getJob(request); + console.log(`Batch job details for '${jobId}':`); + console.log(` Name: ${response.name}`); + console.log(` State: ${response.state}`); + console.log( + ` Create Time: ${new Date(response.createTime.seconds * 1000).toISOString()}` + ); + } catch (error) { + console.error( + `Error retrieving batch jobs for jobId ${jobId}:`, + error.message + ); + + if (error.code === 5) { + // NOT_FOUND (gRPC code 5) error can occur if the batch job does not exist. + console.error( + `Ensure the job '${jobId}' exists in project '${projectId}'.` + ); + } + throw error; + } + } + quickstart(); + // [END storage_batch_quickstart] +} + +main(...process.argv.slice(2)); + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); diff --git a/storagebatchoperations/system-test/storagebatchoperations.test.js b/storagebatchoperations/system-test/storagebatchoperations.test.js new file mode 100644 index 0000000000..608240bcc4 --- /dev/null +++ b/storagebatchoperations/system-test/storagebatchoperations.test.js @@ -0,0 +1,99 @@ +// 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 +// +// https://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 {Storage, Bucket} = require('@google-cloud/storage'); +const cp = require('child_process'); +const {assert} = require('chai'); +const {describe, it, before, after} = require('mocha'); +const uuid = require('uuid'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const projectId = process.env.GCLOUD_PROJECT; +const bucketPrefix = 'sbo-samples'; +const bucketName = `${bucketPrefix}-${uuid.v4()}`; +const storage = new Storage({projectId: projectId}); +const bucket = new Bucket(storage, bucketName); +const jobId = uuid.v4(); +const jobName = `projects/${projectId}/locations/global/jobs/${jobId}`; + +describe('Batch Operations', () => { + before(async () => { + await storage.createBucket(bucketName, { + iamConfiguration: { + uniformBucketLevelAccess: { + enabled: true, + }, + }, + hierarchicalNamespace: {enabled: true}, + }); + }); + + after(async () => { + await bucket.delete(); + }); + + it('should create a job', async () => { + const output = execSync( + `node createJob.js ${projectId} ${jobId} ${bucketName} objectPrefix` + ); + assert.match(output, /Created job:/); + assert.match(output, new RegExp(jobName)); + }); + + it('should list jobs', async () => { + const output = execSync(`node listJobs.js ${projectId}`); + assert.match(output, new RegExp(jobName)); + }); + + it('should run quickstart', async () => { + const output = execSync(`node quickstart.js ${projectId} ${jobId}`); + const detailsHeader = `Batch job details for '${jobId}':`; + assert.match(output, new RegExp(detailsHeader)); + assert.match(output, /Name:/); + assert.match(output, new RegExp(jobName)); + assert.match(output, /State:/); + assert.match(output, /Create Time:/); + }); + + it('should get a job', async () => { + const output = execSync(`node getJob.js ${projectId} ${jobId}`); + const detailsHeader = `Batch job details for '${jobId}':`; + assert.match(output, new RegExp(detailsHeader)); + assert.match(output, /Name:/); + assert.match(output, new RegExp(jobName)); + assert.match(output, /State:/); + assert.match(output, /Create Time:/); + }); + + it('should cancel a job (or gracefully handle terminal state)', async () => { + try { + const output = execSync(`node cancelJob.js ${projectId} ${jobId}`); + assert.match(output, /Cancelled job:/); + assert.match(output, new RegExp(jobName)); + } catch (error) { + // This might be expected if the job completed quickly or failed creation + const errorMessage = error.stderr.toString(); + assert.match( + errorMessage, + /9 FAILED_PRECONDITION: Job run.* is in a terminal state and can not be changed./ + ); + } + }); + + it('should delete a job', async () => { + const output = execSync(`node deleteJob.js ${projectId} ${jobId}`); + assert.match(output, /Deleted job:/); + assert.match(output, new RegExp(jobName)); + }); +});