-
Notifications
You must be signed in to change notification settings - Fork 36
feat: add secrets fetch command to CDK CLI #494
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
aaronsteers
merged 21 commits into
aj/feat/add-standard-tests-cli
from
devin/1744945591-add-secrets-fetch-command
Apr 18, 2025
Merged
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
05ed0b3
feat: add secrets fetch command to CDK CLI
devin-ai-integration[bot] 37f5aaf
chore: update poetry.lock
devin-ai-integration[bot] 1ae9604
style: fix formatting in _secrets.py
devin-ai-integration[bot] 12f674a
fix: update dependency configuration and error message
devin-ai-integration[bot] 7a66a1c
docs: add Deptry ignore for google-cloud-secret-manager and update co…
devin-ai-integration[bot] 7ff0848
fix: add google to DEP001 ignore list for Deptry
devin-ai-integration[bot] 671ce48
chore: update poetry.lock
devin-ai-integration[bot] 18893d7
feat: rename --project to --gcp-project-id and add usage guidance
devin-ai-integration[bot] 497f73f
feat: improve error message for connector directory resolution
devin-ai-integration[bot] f4f7fae
docs: add backticks around 'secrets' in docstring
devin-ai-integration[bot] 0b653db
docs: add backticks around 'secrets' in command group docstring
devin-ai-integration[bot] 63f78e2
docs: add stateless usage examples to docstrings
devin-ai-integration[bot] 881beda
Merge branch 'aj/feat/add-standard-tests-cli' into devin/1744945591-a…
aaronsteers cfc935a
refactor: use _util.resolve_connector_name_and_directory and set help…
devin-ai-integration[bot] 8a01898
feat: add .gitignore file in secrets directory for extra protection
devin-ai-integration[bot] ac2d554
style: fix formatting issues
devin-ai-integration[bot] f88a170
formatting
aaronsteers f942e60
remove 'Optional' import
aaronsteers 6000154
fix markdown formatting
aaronsteers 07489ec
remove dev extra, re-lock
aaronsteers dc8940e
remove [dev] ref
aaronsteers File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| # Copyright (c) 2025 Airbyte, Inc., all rights reserved. | ||
| """Secret management commands. | ||
|
|
||
| This module provides commands for managing secrets for Airbyte connectors. | ||
|
|
||
| Usage: | ||
| airbyte-cdk secrets fetch --connector-name source-github | ||
| airbyte-cdk secrets fetch --connector-directory /path/to/connector | ||
| airbyte-cdk secrets fetch # Run from within a connector directory | ||
|
|
||
| Usage without pre-installing (stateless): | ||
| pipx run airbyte-cdk[dev] secrets fetch ... | ||
| uvx airbyte-cdk[dev] secrets fetch ... | ||
|
|
||
| The 'fetch' command retrieves secrets from Google Secret Manager based on connector | ||
| labels and writes them to the connector's `secrets` directory. | ||
| """ | ||
|
|
||
| import json | ||
| import os | ||
| from pathlib import Path | ||
| from typing import Optional | ||
|
|
||
| import rich_click as click | ||
|
|
||
| from airbyte_cdk.test.standard_tests.test_resources import find_connector_root_from_name | ||
|
|
||
| AIRBYTE_INTERNAL_GCP_PROJECT = "dataline-integration-testing" | ||
| CONNECTOR_LABEL = "connector" | ||
|
|
||
|
|
||
| @click.group(name="secrets") | ||
| def secrets_cli_group() -> None: | ||
| """Secret management commands. | ||
|
|
||
| This module provides commands for managing secrets for Airbyte connectors. | ||
|
|
||
| Usage: | ||
| airbyte-cdk secrets fetch --connector-name source-github | ||
| airbyte-cdk secrets fetch --connector-directory /path/to/connector | ||
| airbyte-cdk secrets fetch # Run from within a connector directory | ||
|
|
||
| Usage without pre-installing (stateless): | ||
| pipx run airbyte-cdk[dev] secrets fetch ... | ||
| uvx airbyte-cdk[dev] secrets fetch ... | ||
|
|
||
| The 'fetch' command retrieves secrets from Google Secret Manager based on connector | ||
| labels and writes them to the connector's `secrets` directory. | ||
| """ | ||
| pass | ||
|
|
||
|
|
||
| @secrets_cli_group.command() | ||
| @click.option( | ||
| "--connector-name", | ||
| type=str, | ||
| help="Name of the connector to fetch secrets for. Ignored if --connector-directory is provided.", | ||
| ) | ||
| @click.option( | ||
| "--connector-directory", | ||
| type=click.Path(exists=True, file_okay=False, path_type=Path), | ||
| help="Path to the connector directory.", | ||
| ) | ||
| @click.option( | ||
| "--gcp-project-id", | ||
| type=str, | ||
| default=AIRBYTE_INTERNAL_GCP_PROJECT, | ||
| help=f"GCP project ID. Defaults to '{AIRBYTE_INTERNAL_GCP_PROJECT}'.", | ||
| ) | ||
| def fetch( | ||
| connector_name: Optional[str] = None, | ||
| connector_directory: Optional[Path] = None, | ||
| gcp_project_id: str = AIRBYTE_INTERNAL_GCP_PROJECT, | ||
| ) -> None: | ||
| """Fetch secrets for a connector from Google Secret Manager. | ||
|
|
||
| This command fetches secrets for a connector from Google Secret Manager and writes them | ||
| to the connector's secrets directory. | ||
|
|
||
| If no connector name or directory is provided, we will look within the current working | ||
| directory. If the current working directory is not a connector directory (e.g. starting | ||
| with 'source-') and no connector name or path is provided, the process will fail. | ||
| """ | ||
| try: | ||
| from google.cloud import secretmanager_v1 as secretmanager | ||
| except ImportError: | ||
| raise ImportError( | ||
| "google-cloud-secret-manager package is required for Secret Manager integration. " | ||
| "Install it with 'pip install airbyte-cdk[dev]' or 'pip install google-cloud-secret-manager'." | ||
| ) | ||
|
|
||
| click.echo("Fetching secrets...") | ||
|
|
||
| # Resolve connector name/directory | ||
| if not connector_name and not connector_directory: | ||
| cwd = Path().resolve().absolute() | ||
| if cwd.name.startswith("source-") or cwd.name.startswith("destination-"): | ||
| connector_name = cwd.name | ||
| connector_directory = cwd | ||
| else: | ||
| raise ValueError( | ||
| "Either connector_name or connector_directory must be provided if not " | ||
| "running from a connector directory." | ||
| ) | ||
|
|
||
| if connector_directory: | ||
| connector_directory = connector_directory.resolve().absolute() | ||
| if not connector_name: | ||
| connector_name = connector_directory.name | ||
| elif connector_name: | ||
| try: | ||
| connector_directory = find_connector_root_from_name(connector_name) | ||
| except FileNotFoundError as e: | ||
| raise FileNotFoundError( | ||
| f"Could not find connector directory for '{connector_name}'. " | ||
| "Please provide the --connector-directory option with the path to the connector. " | ||
| "Note: This command requires either running from within a connector directory, " | ||
| "being in the airbyte monorepo, or explicitly providing the connector directory path." | ||
| ) from e | ||
| else: | ||
| raise ValueError("Either connector_name or connector_directory must be provided.") | ||
|
|
||
| # Create secrets directory if it doesn't exist | ||
| secrets_dir = connector_directory / "secrets" | ||
| secrets_dir.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| # Get GSM client | ||
| credentials_json = os.environ.get("GCP_GSM_CREDENTIALS") | ||
| if not credentials_json: | ||
| raise ValueError( | ||
| "No Google Cloud credentials found. Please set the GCP_GSM_CREDENTIALS environment variable." | ||
| ) | ||
|
|
||
| client = secretmanager.SecretManagerServiceClient.from_service_account_info( | ||
| json.loads(credentials_json) | ||
| ) | ||
|
|
||
| # List all secrets with the connector label | ||
| parent = f"projects/{gcp_project_id}" | ||
| filter_string = f"labels.{CONNECTOR_LABEL}={connector_name}" | ||
| secrets = client.list_secrets( | ||
| request=secretmanager.ListSecretsRequest( | ||
| parent=parent, | ||
| filter=filter_string, | ||
| ) | ||
| ) | ||
|
|
||
| # Fetch and write secrets | ||
| secret_count = 0 | ||
| for secret in secrets: | ||
| secret_name = secret.name | ||
| version_name = f"{secret_name}/versions/latest" | ||
| response = client.access_secret_version(name=version_name) | ||
| payload = response.payload.data.decode("UTF-8") | ||
|
|
||
| filename_base = "config" # Default filename | ||
| if secret.labels and "filename" in secret.labels: | ||
| filename_base = secret.labels["filename"] | ||
|
|
||
| secret_file_path = secrets_dir / f"{filename_base}.json" | ||
| secret_file_path.write_text(payload) | ||
| click.echo(f"Secret written to: {secret_file_path}") | ||
| secret_count += 1 | ||
|
|
||
| if secret_count == 0: | ||
| click.echo(f"No secrets found for connector: {connector_name}") | ||
|
|
||
|
|
||
| __all__ = [ | ||
| "secrets_cli_group", | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.