Skip to content

Conversation

@khilan-crest
Copy link

Adding cmek samples

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).

@khilan-crest khilan-crest requested review from a team as code owners January 20, 2026 12:17
@snippet-bot
Copy link

snippet-bot bot commented Jan 20, 2026

Here is the summary of changes.

You are about to add 11 region tags.

This comment is generated by snippet-bot.
If you find problems with this result, please file an issue at:
https://github.com/googleapis/repo-automation-bots/issues.
To update this comment, add snippet-bot:force-run label or use the checkbox below:

  • Refresh this comment

@product-auto-label product-auto-label bot added api: secretmanager Issues related to the Secret Manager API. samples Issues that are directly related to samples. labels Jan 20, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @khilan-crest, 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 expands the Google Cloud Secret Manager Node.js samples by introducing a suite of new examples. These additions cover advanced secret creation options, such as using Customer-Managed Encryption Keys (CMEK) and user-managed replication policies, alongside enhanced capabilities for listing secrets and their versions with filters. Furthermore, new samples for managing tag bindings on secrets have been included, providing developers with more robust tools for organizing and securing their sensitive data within Google Cloud.

Highlights

  • New Secret Creation Samples: Added samples demonstrating how to create secrets with Customer-Managed Encryption Keys (CMEK) for both global and regional secrets, and how to create secrets with user-managed replication policies.
  • Enhanced Secret Listing and Filtering: Introduced new samples for listing secrets and secret versions, including the ability to apply filters to narrow down results, covering both global and regional contexts.
  • Tag Binding Management Samples: Provided new samples for managing tag bindings on secrets, specifically for listing existing tag bindings and detaching them, applicable to both global and regional secrets.
  • Test Suite Expansion: Updated the existing test suite to include comprehensive tests for all newly added Secret Manager samples, ensuring their correct functionality and integration.
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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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 several new samples for Secret Manager, focusing on CMEK, user-managed replication, and filtering/tagging secrets. The changes are well-structured and the new functionality is covered by tests. I've identified a few areas for improvement, including a recurring pattern of redundant nested functions that can be simplified, a potential bug in handling optional arguments, and a minor error in a test assertion. Overall, this is a great addition.

const output = execSync(
`node createSecretWithUserManagedReplicationPolicy.js ${parent} ${secretId}-ummr ${locations} ${ttl}`
);
assert.match(output, new RegExp(`Created secret: ${secret.name}`));
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 assertion in this test is incorrect. It compares the output with secret.name, which refers to a secret created in the before hook with a different ID. The test creates a new secret with the ID ${secretId}-ummr, so the assertion should check for the name of this newly created secret.

Suggested change
assert.match(output, new RegExp(`Created secret: ${secret.name}`));
assert.match(
output,
new RegExp(`Created secret: ${parent}/secrets/${secretId}-ummr`)
);

Copy link
Author

Choose a reason for hiding this comment

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

we are already passing secretId

}

const args = process.argv.slice(2);
const locations = args[2] ? args[2].split(',') : [];
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 command-line argument parsing is brittle. If an empty string is passed for locations (e.g., to specify a ttl without locations), args[2].split(',') results in [''], which will likely cause an API error. A more robust way to handle this is to filter out empty strings from the locations array.

const locations = args[2] ? args[2].split(',').filter(l => l) : [];

Copy link
Author

Choose a reason for hiding this comment

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

It is passed by user and empty string should not be given into replication locations

Comment on lines +35 to +44
const secretConfig = {
replication: {
userManaged: {
replicas: locations.map(location => ({location})),
},
},
ttl: {
seconds: ttl,
},
};
Copy link
Contributor

Choose a reason for hiding this comment

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

high

When ttl is null, the secretConfig object will have ttl: { seconds: null }. This might cause an API error if the backend expects the ttl field to be absent or to contain a valid duration. It's safer to conditionally add the ttl property to the secretConfig object only when a ttl value is provided.

    const secretConfig = {
      replication: {
        userManaged: {
          replicas: locations.map(location => ({location})),
        },
      },
    };

    if (ttl) {
      secretConfig.ttl = {
        seconds: ttl,
      };
    }

Copy link
Author

Choose a reason for hiding this comment

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

Done

Comment on lines +35 to +47
async function listSecretsWithFilter() {
const [secrets] = await client.listSecrets({
parent: parent,
filter: filterStr,
});

// Print each secret
for (const secret of secrets) {
console.log(`Found secret: ${secret.name}`);
}
}

listSecretsWithFilter();
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 main function is already async, so the nested async function listSecretsWithFilter which is defined and immediately called is redundant. You can simplify the code by moving the logic from listSecretsWithFilter directly into main. This will make the code more concise and easier to read.

    const [secrets] = await client.listSecrets({
      parent: parent,
      filter: filterStr,
    });

    // Print each secret
    for (const secret of secrets) {
      console.log(`Found secret: ${secret.name}`);
    }

Copy link
Author

Choose a reason for hiding this comment

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

All samples contains inner function within main which is showing main functionality of the sample.

Comment on lines +34 to +67
async function detachTag() {
// Find the binding name for the given tag value
let bindingName = null;
const iterable = rmClient.listTagBindingsAsync(
{
parent: parent,
pageSize: 50,
},
{autoPaginate: false}
);

for await (const binding of iterable) {
if (binding.tagValue === tagValue) {
bindingName = binding.name;
break;
}
}

if (bindingName === null) {
console.log(`Tag binding for value ${tagValue} not found on ${name}.`);
return;
}

// Delete the tag binding
const [operation] = await rmClient.deleteTagBinding({
name: bindingName,
});

// Wait for the operation to complete
await operation.promise();
console.log(`Detached tag value ${tagValue} from ${name}`);
}

detachTag();
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 main function is already async, so the nested async function detachTag which is defined and immediately called is redundant. You can simplify the code by moving the logic from detachTag directly into main. This will make the code more concise and easier to read.

    // Find the binding name for the given tag value
    let bindingName = null;
    const iterable = rmClient.listTagBindingsAsync(
      {
        parent: parent,
        pageSize: 50,
      },
      {autoPaginate: false}
    );

    for await (const binding of iterable) {
      if (binding.tagValue === tagValue) {
        bindingName = binding.name;
        break;
      }
    }

    if (bindingName === null) {
      console.log(`Tag binding for value ${tagValue} not found on ${name}.`);
      return;
    }

    // Delete the tag binding
    const [operation] = await rmClient.deleteTagBinding({
      name: bindingName,
    });

    // Wait for the operation to complete
    await operation.promise();
    console.log(`Detached tag value ${tagValue} from ${name}`);

Copy link
Author

Choose a reason for hiding this comment

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

All samples contains inner function within main which is showing main functionality of the sample. For consistency with other samples this is not required.

Comment on lines +44 to +80
async function detachRegionalTag() {
// Find the binding with the specified tag value
let bindingName = null;
const iterable = tagBindingsClient.listTagBindingsAsync(
{
parent: parent,
pageSize: 50,
},
{autoPaginate: false}
);

for await (const binding of iterable) {
if (binding.tagValue === tagValue) {
bindingName = binding.name;
break;
}
}

if (bindingName === null) {
console.log(
`Tag binding for value ${tagValue} not found on ${secretName}.`
);
return;
}

// Delete the tag binding
const [operation] = await tagBindingsClient.deleteTagBinding({
name: bindingName,
});

// Wait for the operation to complete
await operation.promise();

console.log(`Detached tag value ${tagValue} from ${secretName}`);
}

return detachRegionalTag();
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 main function is already async, so the nested async function detachRegionalTag which is defined and immediately called is redundant. You can simplify the code by moving the logic from detachRegionalTag directly into main. This will make the code more concise and easier to read.

    // Find the binding with the specified tag value
    let bindingName = null;
    const iterable = tagBindingsClient.listTagBindingsAsync(
      {
        parent: parent,
        pageSize: 50,
      },
      {autoPaginate: false}
    );

    for await (const binding of iterable) {
      if (binding.tagValue === tagValue) {
        bindingName = binding.name;
        break;
      }
    }

    if (bindingName === null) {
      console.log(
        `Tag binding for value ${tagValue} not found on ${secretName}.`
      );
      return;
    }

    // Delete the tag binding
    const [operation] = await tagBindingsClient.deleteTagBinding({
      name: bindingName,
    });

    // Wait for the operation to complete
    await operation.promise();

    console.log(`Detached tag value ${tagValue} from ${secretName}`);

Copy link
Author

Choose a reason for hiding this comment

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

All samples contains inner function within main which is showing main functionality of the sample. For consistency with other samples this is not required.

Comment on lines +39 to +50
async function listRegionalSecretVersionsWithFilter() {
const [versions] = await client.listSecretVersions({
parent: parent,
filter: filterStr,
});

versions.forEach(version => {
console.log(`Found version: ${version.name}`);
});
}

listRegionalSecretVersionsWithFilter();
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 main function is already async, so the nested async function listRegionalSecretVersionsWithFilter which is defined and immediately called is redundant. You can simplify the code by moving the logic from listRegionalSecretVersionsWithFilter directly into main. This will make the code more concise and easier to read.

    const [versions] = await client.listSecretVersions({
      parent: parent,
      filter: filterStr,
    });

    versions.forEach(version => {
      console.log(`Found version: ${version.name}`);
    });

Copy link
Author

Choose a reason for hiding this comment

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

All samples contains inner function within main which is showing main functionality of the sample. For consistency with other samples this is not required.

Comment on lines +38 to +49
async function listRegionalSecretsWithFilter() {
const [secrets] = await client.listSecrets({
parent: parent,
filter: filterStr,
});

secrets.forEach(secret => {
console.log(`Found secret: ${secret.name}`);
});
}

listRegionalSecretsWithFilter();
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 main function is already async, so the nested async function listRegionalSecretsWithFilter which is defined and immediately called is redundant. You can simplify the code by moving the logic from listRegionalSecretsWithFilter directly into main. This will make the code more concise and easier to read.

    const [secrets] = await client.listSecrets({
      parent: parent,
      filter: filterStr,
    });

    secrets.forEach(secret => {
      console.log(`Found secret: ${secret.name}`);
    });

Copy link
Author

Choose a reason for hiding this comment

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

All samples contains inner function within main which is showing main functionality of the sample. For consistency with other samples this is not required.

Comment on lines +43 to +67
async function listRegionalSecretTagBindings() {
console.log(`Tag bindings for ${name}:`);
let foundBindings = false;

// List the tag bindings
const iterable = tagBindingsClient.listTagBindingsAsync(
{
parent: parent,
pageSize: 10,
},
{autoPaginate: false}
);

// Iterate through the results
for await (const binding of iterable) {
console.log(`- Tag Value: ${binding.tagValue}`);
foundBindings = true;
}

if (!foundBindings) {
console.log(`No tag bindings found for ${name}.`);
}
}

listRegionalSecretTagBindings();
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 main function is already async, so the nested async function listRegionalSecretTagBindings which is defined and immediately called is redundant. You can simplify the code by moving the logic from listRegionalSecretTagBindings directly into main. This will make the code more concise and easier to read.

    console.log(`Tag bindings for ${name}:`);
    let foundBindings = false;

    // List the tag bindings
    const iterable = tagBindingsClient.listTagBindingsAsync(
      {
        parent: parent,
        pageSize: 10,
      },
      {autoPaginate: false}
    );

    // Iterate through the results
    for await (const binding of iterable) {
      console.log(`- Tag Value: ${binding.tagValue}`);
      foundBindings = true;
    }

    if (!foundBindings) {
      console.log(`No tag bindings found for ${name}.`);
    }

Copy link
Author

Choose a reason for hiding this comment

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

All samples contains inner function within main which is showing main functionality of the sample. For consistency with other samples this is not required.

Comment on lines +33 to +56
async function createUmmrSecret() {
// Create the secret configuration
const secretConfig = {
replication: {
userManaged: {
replicas: locations.map(location => ({location})),
},
},
ttl: {
seconds: ttl,
},
};

// Create the secret
const [secret] = await client.createSecret({
parent: parent,
secretId: secretId,
secret: secretConfig,
});

console.log(`Created secret: ${secret.name}`);
}

createUmmrSecret();
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 main function is already async, so the nested async function createUmmrSecret which is defined and immediately called is redundant. You can simplify the code by moving the logic from createUmmrSecret directly into main. This will make the code more concise and easier to read.

    // Create the secret configuration
    const secretConfig = {
      replication: {
        userManaged: {
          replicas: locations.map(location => ({location})),
        },
      },
      ttl: {
        seconds: ttl,
      },
    };

    // Create the secret
    const [secret] = await client.createSecret({
      parent: parent,
      secretId: secretId,
      secret: secretConfig,
    });

    console.log(`Created secret: ${secret.name}`);

Copy link
Author

Choose a reason for hiding this comment

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

All samples contains inner function within main which is showing main functionality of the sample. For consistency with other samples this is not required.

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

Labels

api: secretmanager Issues related to the Secret Manager API. samples Issues that are directly related to samples.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant