Skip to content

feat(storage): migrate ACL samples and tests#4260

Draft
angelcaamal wants to merge 1 commit intoGoogleCloudPlatform:mainfrom
angelcaamal:acl-storage-migration
Draft

feat(storage): migrate ACL samples and tests#4260
angelcaamal wants to merge 1 commit intoGoogleCloudPlatform:mainfrom
angelcaamal:acl-storage-migration

Conversation

@angelcaamal
Copy link
Contributor

@angelcaamal angelcaamal commented Mar 19, 2026

Description

Fixes #

Note: Before submitting a pull request, please open an issue for discussion if you are not associated with Google.

Checklist

  • I have followed guidelines from CONTRIBUTING.MD and Samples Style Guide
  • Tests pass: npm test (see Testing)
  • Lint pass: npm run lint (see Style)
  • Required CI tests pass (see CI testing)
  • These samples need a new API enabled in testing projects to pass (let us know which ones)
  • These samples need a new/updated env vars in testing projects set to pass (let us know which ones)
  • This pull request is from a branch created directly off of GoogleCloudPlatform/nodejs-docs-samples. Not a fork.
  • This sample adds a new sample directory, and I updated the CODEOWNERS file with the codeowners for this sample
  • This sample adds a new sample directory, and I created GitHub Actions workflow for this sample
  • This sample adds a new Product API, and I updated the Blunderbuss issue/PR auto-assigner with the codeowners for this sample
  • Please merge this PR for me once it is approved

Note: Any check with (dev), (experimental), or (legacy) can be ignored and should not block your PR from merging (see CI testing).

@product-auto-label product-auto-label bot added api: storage Issues related to the Cloud Storage API. samples Issues that are directly related to samples. labels Mar 19, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the Google Cloud Storage Node.js samples by providing a complete suite of examples for Access Control List (ACL) management. It enables developers to programmatically control permissions for buckets and files, covering operations like granting and revoking ownership, and retrieving current ACL configurations. The inclusion of dedicated system tests ensures the reliability and accuracy of these new ACL functionalities.

Highlights

  • New ACL Samples: Introduced a comprehensive set of new Node.js samples for managing Access Control Lists (ACLs) on Google Cloud Storage buckets and files. These samples cover adding, removing, and printing ACL entries for various entities.
  • Expanded Functionality: Added specific functionalities to manage default bucket owners, individual bucket owners, and file owners, allowing for fine-grained control over permissions.
  • System Test Coverage: Implemented new system tests (acl.test.js) to thoroughly validate the correctness and reliability of all newly added ACL management samples.
  • Infrastructure Updates: Updated project dependencies in package.json and added a cleanup script to manage test resources, ensuring a robust testing environment.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds a number of new Node.js samples for Google Cloud Storage ACL operations. The overall structure is good, but there is a critical race condition in all the new sample files: they call an async function without await from a synchronous main function, which can cause the script to exit prematurely. I've provided suggestions to fix this by making the main function async and simplifying the structure. I've also found some issues in the test and cleanup scripts, including redundant code and misleading test descriptions, and have suggested fixes for those as well.

Comment on lines +25 to +63
function main(bucketName = 'my-bucket', userEmail = 'jdobry@google.com') {
// [START storage_add_bucket_default_owner]
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';

// The email address of the user to add
// const userEmail = 'user-email-to-add';

// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

async function addBucketDefaultOwner() {
try {
// Makes the user an owner in the default ACL of the bucket. You can use
// addAllUsers(), addDomain(), addProject(), addGroup(), and
// addAllAuthenticatedUsers() to grant access to different types of entities.
// You can also use "readers" and "writers" to grant different roles.
await storage.bucket(bucketName).acl.default.owners.addUser(userEmail);

console.log(
`Added user ${userEmail} as an owner on bucket ${bucketName}.`
);
} catch (error) {
console.error(
'Error executing add bucket default owner ACL:',
error.message || error
);
}
}

addBucketDefaultOwner();
// [END storage_add_bucket_default_owner]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The main function is synchronous and calls the async function addBucketDefaultOwner without await. This creates a race condition where the script can terminate before the asynchronous operation completes. To fix this, main should be async and the logic can be simplified by removing the unnecessary inner function. Setting process.exitCode = 1 on error will also correctly signal failure to calling scripts.

async function main(bucketName = 'my-bucket', userEmail = 'jdobry@google.com') {
  // [START storage_add_bucket_default_owner]
  /**
   * TODO(developer): Uncomment the following lines before running the sample.
   */
  // The ID of your GCS bucket
  // const bucketName = 'your-unique-bucket-name';

  // The email address of the user to add
  // const userEmail = 'user-email-to-add';

  // Imports the Google Cloud client library
  const {Storage} = require('@google-cloud/storage');

  // Creates a client
  const storage = new Storage();

  try {
    // Makes the user an owner in the default ACL of the bucket. You can use
    // addAllUsers(), addDomain(), addProject(), addGroup(), and
    // addAllAuthenticatedUsers() to grant access to different types of entities.
    // You can also use "readers" and "writers" to grant different roles.
    await storage.bucket(bucketName).acl.default.owners.addUser(userEmail);

    console.log(
      `Added user ${userEmail} as an owner on bucket ${bucketName}.`
    );
  } catch (error) {
    console.error(
      'Error executing add bucket default owner ACL:',
      error.message || error
    );
    process.exitCode = 1;
  }
  // [END storage_add_bucket_default_owner]
}

Comment on lines +25 to +63
function main(bucketName = 'my-bucket', userEmail = 'jdobry@google.com') {
// [START storage_add_bucket_owner]
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';

// The email address of the user to add
// const userEmail = 'user-email-to-add';

// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

async function addBucketOwner() {
try {
// Makes the user an owner of the bucket. You can use addAllUsers(),
// addDomain(), addProject(), addGroup(), and addAllAuthenticatedUsers()
// to grant access to different types of entities. You can also use "readers"
// and "writers" to grant different roles.
await storage.bucket(bucketName).acl.owners.addUser(userEmail);

console.log(
`Added user ${userEmail} as an owner on bucket ${bucketName}.`
);
} catch (error) {
console.error(
'Error executing add bucket owner ACL:',
error.message || error
);
}
}

addBucketOwner();
// [END storage_add_bucket_owner]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The main function is synchronous and calls the async function addBucketOwner without await. This creates a race condition where the script can terminate before the asynchronous operation completes. To fix this, main should be async and the logic can be simplified by removing the unnecessary inner function. Setting process.exitCode = 1 on error will also correctly signal failure to calling scripts.

async function main(bucketName = 'my-bucket', userEmail = 'jdobry@google.com') {
  // [START storage_add_bucket_owner]
  /**
   * TODO(developer): Uncomment the following lines before running the sample.
   */
  // The ID of your GCS bucket
  // const bucketName = 'your-unique-bucket-name';

  // The email address of the user to add
  // const userEmail = 'user-email-to-add';

  // Imports the Google Cloud client library
  const {Storage} = require('@google-cloud/storage');

  // Creates a client
  const storage = new Storage();

  try {
    // Makes the user an owner of the bucket. You can use addAllUsers(),
    // addDomain(), addProject(), addGroup(), and addAllAuthenticatedUsers()
    // to grant access to different types of entities. You can also use "readers"
    // and "writers" to grant different roles.
    await storage.bucket(bucketName).acl.owners.addUser(userEmail);

    console.log(
      `Added user ${userEmail} as an owner on bucket ${bucketName}.`
    );
  } catch (error) {
    console.error(
      'Error executing add bucket owner ACL:',
      error.message || error
    );
    process.exitCode = 1;
  }
  // [END storage_add_bucket_owner]
}

Comment on lines +25 to +67
function main(
bucketName = 'my-bucket',
fileName = 'test.txt',
userEmail = 'jdobry@google.com'
) {
// [START storage_add_file_owner]
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';

// The name of the file to access
// const fileName = 'file.txt';

// The email address of the user to add
// const userEmail = 'user-email-to-add';

// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

async function addFileOwner() {
try {
await storage
.bucket(bucketName)
.file(fileName)
.acl.owners.addUser(userEmail);

console.log(`Added user ${userEmail} as an owner on file ${fileName}.`);
} catch (error) {
console.error(
'Error executing add file owner ACL:',
error.message || error
);
}
}

addFileOwner();
// [END storage_add_file_owner]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The main function is synchronous and calls the async function addFileOwner without await. This creates a race condition where the script can terminate before the asynchronous operation completes. To fix this, main should be async and the logic can be simplified by removing the unnecessary inner function. Setting process.exitCode = 1 on error will also correctly signal failure to calling scripts.

async function main(
  bucketName = 'my-bucket',
  fileName = 'test.txt',
  userEmail = 'jdobry@google.com'
) {
  // [START storage_add_file_owner]
  /**
   * TODO(developer): Uncomment the following lines before running the sample.
   */
  // The ID of your GCS bucket
  // const bucketName = 'your-unique-bucket-name';

  // The name of the file to access
  // const fileName = 'file.txt';

  // The email address of the user to add
  // const userEmail = 'user-email-to-add';

  // Imports the Google Cloud client library
  const {Storage} = require('@google-cloud/storage');

  // Creates a client
  const storage = new Storage();

  try {
    await storage
      .bucket(bucketName)
      .file(fileName)
      .acl.owners.addUser(userEmail);

    console.log(`Added user ${userEmail} as an owner on file ${fileName}.`);
  } catch (error) {
    console.error(
      'Error executing add file owner ACL:',
      error.message || error
    );
    process.exitCode = 1;
  }
  // [END storage_add_file_owner]
}

Comment on lines +25 to +56
function main(bucketName = 'my-bucket') {
// [START storage_print_bucket_acl]
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';

// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

async function printBucketAcl() {
try {
// Gets the ACL for the bucket
const [acls] = await storage.bucket(bucketName).acl.get();

acls.forEach(acl => {
console.log(`${acl.role}: ${acl.entity}`);
});
} catch (error) {
console.error(
'Error executing print bucket ACL:',
error.message || error
);
}
}
printBucketAcl();
// [END storage_print_bucket_acl]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The main function is synchronous and calls the async function printBucketAcl without await. This creates a race condition where the script can terminate before the asynchronous operation completes. To fix this, main should be async and the logic can be simplified by removing the unnecessary inner function. Setting process.exitCode = 1 on error will also correctly signal failure to calling scripts.

async function main(bucketName = 'my-bucket') {
  // [START storage_print_bucket_acl]
  /**
   * TODO(developer): Uncomment the following lines before running the sample.
   */
  // The ID of your GCS bucket
  // const bucketName = 'your-unique-bucket-name';

  // Imports the Google Cloud client library
  const {Storage} = require('@google-cloud/storage');

  // Creates a client
  const storage = new Storage();

  try {
    // Gets the ACL for the bucket
    const [acls] = await storage.bucket(bucketName).acl.get();

    acls.forEach(acl => {
      console.log(`${acl.role}: ${acl.entity}`);
    });
  } catch (error) {
    console.error(
      'Error executing print bucket ACL:',
      error.message || error
    );
    process.exitCode = 1;
  }
  // [END storage_print_bucket_acl]
}

Comment on lines +25 to +63
function main(bucketName = 'my-bucket', userEmail = 'jdobry@google.com') {
// [START storage_print_bucket_acl_for_user]
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';

// The email address of the user to check
// const userEmail = 'user-email-to-check';

// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

async function printBucketAclForUser() {
try {
const options = {
// Specify the user
entity: `user-${userEmail}`,
};

// Gets the user's ACL for the bucket
const [aclObject] = await storage.bucket(bucketName).acl.get(options);

console.log(`${aclObject.role}: ${aclObject.entity}`);
} catch (error) {
console.error(
'Error executing print bucket ACL for user:',
error.message || error
);
}
}

printBucketAclForUser();
// [END storage_print_bucket_acl_for_user]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The main function is synchronous and calls the async function printBucketAclForUser without await. This creates a race condition where the script can terminate before the asynchronous operation completes. To fix this, main should be async and the logic can be simplified by removing the unnecessary inner function. Setting process.exitCode = 1 on error will also correctly signal failure to calling scripts.

async function main(bucketName = 'my-bucket', userEmail = 'jdobry@google.com') {
  // [START storage_print_bucket_acl_for_user]
  /**
   * TODO(developer): Uncomment the following lines before running the sample.
   */
  // The ID of your GCS bucket
  // const bucketName = 'your-unique-bucket-name';

  // The email address of the user to check
  // const userEmail = 'user-email-to-check';

  // Imports the Google Cloud client library
  const {Storage} = require('@google-cloud/storage');

  // Creates a client
  const storage = new Storage();

  try {
    const options = {
      // Specify the user
      entity: `user-${userEmail}`,
    };

    // Gets the user's ACL for the bucket
    const [aclObject] = await storage.bucket(bucketName).acl.get(options);

    console.log(`${aclObject.role}: ${aclObject.entity}`);
  } catch (error) {
    console.error(
      'Error executing print bucket ACL for user:',
      error.message || error
    );
    process.exitCode = 1;
  }
  // [END storage_print_bucket_acl_for_user]
}

Comment on lines +25 to +70
function main(
bucketName = 'my-bucket',
fileName = 'test.txt',
userEmail = 'jdobry@google.com'
) {
// [START storage_remove_file_owner]
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// The ID of your GCS bucket
// const bucketName = 'your-unique-bucket-name';

// The ID of your GCS file
// const fileName = 'your-file-name';

// The email address of the user to remove
// const userEmail = 'user-email-to-remove';

// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

async function removeFileOwner() {
try {
// Removes the user from the access control list of the file. You can use
// deleteAllUsers(), deleteDomain(), deleteProject(), deleteGroup(), and
// deleteAllAuthenticatedUsers() to remove access for different types of entities.
await storage
.bucket(bucketName)
.file(fileName)
.acl.owners.deleteUser(userEmail);

console.log(`Removed user ${userEmail} from file ${fileName}.`);
} catch (error) {
console.error(
'Error executing remove file owner ACL:',
error.message || error
);
}
}

removeFileOwner();
// [END storage_remove_file_owner]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The main function is synchronous and calls the async function removeFileOwner without await. This creates a race condition where the script can terminate before the asynchronous operation completes. To fix this, main should be async and the logic can be simplified by removing the unnecessary inner function. Setting process.exitCode = 1 on error will also correctly signal failure to calling scripts.

async function main(
  bucketName = 'my-bucket',
  fileName = 'test.txt',
  userEmail = 'jdobry@google.com'
) {
  // [START storage_remove_file_owner]
  /**
   * TODO(developer): Uncomment the following lines before running the sample.
   */
  // The ID of your GCS bucket
  // const bucketName = 'your-unique-bucket-name';

  // The ID of your GCS file
  // const fileName = 'your-file-name';

  // The email address of the user to remove
  // const userEmail = 'user-email-to-remove';

  // Imports the Google Cloud client library
  const {Storage} = require('@google-cloud/storage');

  // Creates a client
  const storage = new Storage();

  try {
    // Removes the user from the access control list of the file. You can use
    // deleteAllUsers(), deleteDomain(), deleteProject(), deleteGroup(), and
    // deleteAllAuthenticatedUsers() to remove access for different types of entities.
    await storage
      .bucket(bucketName)
      .file(fileName)
      .acl.owners.deleteUser(userEmail);

    console.log(`Removed user ${userEmail} from file ${fileName}.`);
  } catch (error) {
    console.error(
      'Error executing remove file owner ACL:',
      error.message || error
    );
    process.exitCode = 1;
  }
  // [END storage_remove_file_owner]
}

Comment on lines +32 to +38
return bucket.deleteFiles()
.then(() => bucket.deleteFiles(), console.error)
.then(() => {
console.log(`Deleting ${bucket.name}`);
return bucket.delete();
}, console.error)
.catch(console.error);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The bucket deletion logic has a few issues: the deleteFiles() call is duplicated, and the error handling could lead to subsequent errors. If the first deleteFiles() fails, the error is logged, but the promise chain continues, which will cause the bucket.delete() call to fail on a non-empty bucket. The logic can be simplified and made more robust.

          return bucket.deleteFiles({force: true})
            .then(() => {
              console.log(`Deleting ${bucket.name}`);
              return bucket.delete();
            })
            .catch(err => {
              console.error(`Failed to delete bucket ${bucket.name}:`, err);
            });

Comment on lines +44 to +48
try {
await bucket.deleteFiles({force: true});
} catch (err) {
// ignore error
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This try-catch block containing bucket.deleteFiles({force: true}) is a duplicate of the one just above it. One call is sufficient to delete all files and their versions.

await bucket.file(fileName).acl.readers.deleteUser(userEmail);
});

it('should add a user as an owner on a bucket', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The test description is misleading. This test case adds a user as an owner to a file, not a bucket.

Suggested change
it('should add a user as an owner on a bucket', () => {
it('should add a user as an owner on a file', () => {

);
});

it('should remove a user from a bucket', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The test description is misleading. This test case removes a user from a file, not a bucket.

Suggested change
it('should remove a user from a bucket', () => {
it('should remove a user from a file', () => {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: storage Issues related to the Cloud Storage API. samples Issues that are directly related to samples.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant