airbyte_ops_mcp.cli.registry
CLI commands for connector registry operations.
This module provides CLI wrappers for registry operations. The core logic
lives in the airbyte_ops_mcp.registry capability module.
Command groups:
airbyte-ops registry rc create - Create the release_candidate/ marker in GCS airbyte-ops registry rc cleanup - Delete the release_candidate/ marker from GCS airbyte-ops registry connector list - List all connectors in registry airbyte-ops registry connector-version list - List versions for a connector airbyte-ops registry connector-version next - Compute next version tag (prerelease/RC) airbyte-ops registry connector-version yank - Mark a connector version as yanked airbyte-ops registry connector-version unyank - Remove yank marker from a connector version airbyte-ops registry connector-version metadata get - Read connector metadata from GCS airbyte-ops registry connector-version artifacts generate - Generate version artifacts locally via docker airbyte-ops registry connector-version artifacts publish - Publish version artifacts to GCS airbyte-ops registry store mirror --local|--gcs-bucket|--s3-bucket - Mirror entire registry for testing airbyte-ops registry store compile --store coral:dev|coral:prod - Compile registry indexes and sync latest/ dirs airbyte-ops registry marketing-stubs sync --store coral:prod - Sync connector_stubs.json to GCS airbyte-ops registry marketing-stubs check --store coral:prod - Compare local file with GCS
CLI reference
The commands below are regenerated by poe docs-generate via cyclopts's
programmatic docs API; see docs/generate_cli.py.
airbyte-ops registry COMMAND
Connector registry operations (GCS metadata service).
Commands:
connector: Connector listing operations.connector-version: Connector version operations (list, yank, unyank, artifacts).marketing-stubs: Marketing connector stubs GCS operations (whole-file sync).progressive-rollout: Progressive rollout lifecycle operations (create/cleanup rollout marker in GCS).store: Whole-registry store operations (mirror, compile).
airbyte-ops registry connector
Connector listing operations.
airbyte-ops registry connector list
airbyte-ops registry connector list [OPTIONS] STORE
List connectors in the registry.
When filters are applied, reads the compiled cloud_registry.json index
for fast lookups. Without filters, falls back to scanning individual
metadata blobs (captures all connectors including OSS-only).
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]--certified-only, --no-certified-only: Include only certified connectors. [default: False]--support-level: Exact support level to match (e.g.certified,community,archived). [choices: archived, community, certified]--min-support-level: Minimum support level (inclusive). Levels from lowest to highest:archived,community,certified. [choices: archived, community, certified]--connector-type: Filter by connector type:sourceordestination. [choices: source, destination]--language: Filter by implementation language (e.g.python,java,manifest-only). [choices: python, java, low-code, manifest-only]--format: Output format: 'json' for JSON array, 'text' for newline-separated. [choices: json, text] [default: json]
airbyte-ops registry connector-version
Connector version operations (list, yank, unyank, artifacts).
airbyte-ops registry connector-version metadata
Connector metadata inspection.
Commands:
get: Read a connector version's metadata from the registry.
airbyte-ops registry connector-version metadata get
airbyte-ops registry connector-version metadata get [OPTIONS] NAME STORE
Read a connector version's metadata from the registry.
Returns the full metadata.yaml content for a connector at the specified version.
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
NAME, --name: Connector name (e.g., 'source-faker', 'destination-postgres'). [required]STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]--version: Version to read (e.g., 'latest', '1.2.3'). [default: latest]--format: Output format: 'json' for JSON, 'raw' for YAML. [choices: json, raw] [default: json]
airbyte-ops registry connector-version artifacts
Version artifact generation and publishing.
Commands:
generate: Generate version artifacts for a connector locally.publish: Publish version artifacts to GCS using fsspec rsync.
airbyte-ops registry connector-version artifacts generate
airbyte-ops registry connector-version artifacts generate METADATA-FILE DOCKER-IMAGE [ARGS]
Generate version artifacts for a connector locally.
Runs the connector's docker image with DEPLOYMENT_MODE=cloud and DEPLOYMENT_MODE=oss to obtain both spec variants, then generates the registry entries (cloud.json, oss.json) by applying registryOverrides from the metadata.
The generated metadata.yaml is enriched with git commit info, SBOM URL,
and (when applicable) components SHA before writing. Validation is run
after generation by default; pass --no-validate to skip.
This is a local-only operation -- no files are uploaded to GCS.
Use artifacts publish to upload generated artifacts to GCS.
Parameters:
METADATA-FILE, --metadata-file: Path to the connector's metadata.yaml file. [required]DOCKER-IMAGE, --docker-image: Docker image to run spec against (e.g., 'airbyte/source-faker:6.2.38'). [required]OUTPUT-DIR, --output-dir: Directory to write artifacts to. If not specified, a temp directory is created.REPO-ROOT, --repo-root: Root of the Airbyte repo checkout (for resolving doc.md). If not specified, inferred by walking up from metadata-file.DRY-RUN, --dry-run, --no-dry-run: Show what would be generated without running docker or writing files. [default: False]WITH-VALIDATE, --with-validate, --no-validate: Run metadata validators after generation (default: enabled). Use --no-validate to skip. [default: True]WITH-SBOM, --with-sbom, --no-sbom: Generate spdx.json (SBOM) for connectors (default: enabled). Use --no-sbom to skip. [default: True]WITH-DEPENDENCY-DUMP, --with-dependency-dump, --no-dependency-dump: Generate dependencies.json for Python connectors (default: enabled). Use --no-dependency-dump to skip. [default: True]
airbyte-ops registry connector-version artifacts publish
airbyte-ops registry connector-version artifacts publish [OPTIONS] NAME VERSION ARTIFACTS-DIR STORE
Publish version artifacts to GCS using fsspec rsync.
Uploads locally generated artifacts (from artifacts generate) to the
versioned path in GCS. By default, metadata is validated before upload;
pass --no-validate to skip.
Uses --store to select the destination store and environment:
coral:dev→ coral dev bucket at rootcoral:prod→ coral prod bucket at rootcoral:dev/aj-test100→ coral dev bucket underaj-test100/prefix
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
NAME, --name: Connector name (e.g., 'source-faker'). [required]VERSION, --version: Version to publish artifacts for (e.g., '1.2.3'). [required]ARTIFACTS-DIR, --artifacts-dir: Directory containing generated artifacts to publish (from 'artifacts generate'). [required]STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod', 'coral:dev/prefix'). [required]--dry-run, --no-dry-run: Show what would be published without writing to GCS. [default: False]--with-validate, --no-validate: Validate metadata before uploading (default: enabled). Use --no-validate to skip. [default: True]
airbyte-ops registry connector-version next
airbyte-ops registry connector-version next NAME SHA [ARGS]
Compute the next version tag for a connector.
Outputs the version tag to stdout for easy capture in shell scripts. This is the single source of truth for pre-release version format.
The command fetches the connector's metadata.yaml from GitHub at the given SHA to determine the base version. It also compares against the master branch and prints a warning to stderr if no version bump is detected.
If --base-version is provided, it is used directly instead of fetching from GitHub.
Parameters:
NAME, --name: Connector name (e.g., 'source-github'). [required]SHA, --sha: Git commit SHA (full or at least 7 characters). [required]BASE-VERSION, --base-version: Base version override. If not provided, fetched from metadata.yaml at the given SHA.
airbyte-ops registry connector-version list
airbyte-ops registry connector-version list [OPTIONS] NAME STORE
List all versions of a connector in the registry.
Scans the registry bucket to find all versions of a specific connector.
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
NAME, --name: Connector name (e.g., 'source-faker'). [required]STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]--format: Output format: 'json' for JSON array, 'text' for newline-separated. [choices: json, text] [default: json]
airbyte-ops registry connector-version yank
airbyte-ops registry connector-version yank [OPTIONS] NAME VERSION STORE
Mark a connector version as yanked.
Writes a version-yank.yml marker file to the version's directory in GCS. Yanked versions are excluded when determining the latest version of a connector.
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
NAME, --name: Connector name (e.g., 'source-faker'). [required]VERSION, --version: Version to yank (e.g., '1.2.3'). [required]STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]--reason: Reason for yanking this version. [default: ""]--dry-run, --no-dry-run: Show what would be done without making changes. [default: False]
airbyte-ops registry connector-version unyank
airbyte-ops registry connector-version unyank [OPTIONS] NAME VERSION STORE
Remove the yank marker from a connector version.
Deletes the version-yank.yml marker file from the version's directory in GCS, making the version eligible again when determining the latest version.
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
NAME, --name: Connector name (e.g., 'source-faker'). [required]VERSION, --version: Version to unyank (e.g., '1.2.3'). [required]STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]--dry-run, --no-dry-run: Show what would be done without making changes. [default: False]
airbyte-ops registry store
Whole-registry store operations (mirror, compile).
airbyte-ops registry store mirror
airbyte-ops registry store mirror [ARGS]
Create a mirror of the connector registry from the source store.
Reads all connector metadata from the source store and copies it to the specified output target. Supports local filesystem, GCS, and S3 as output targets via fsspec.
Output targets are mutually exclusive: specify exactly one of --local, --gcs-bucket, or --s3-bucket.
The production bucket is categorically disallowed as an output target.
To clean up legacy artifacts (e.g. disabled strict-encrypt connectors)
after mirroring, run compile with --with-legacy-migration v1::
airbyte-ops registry store compile --store coral:dev/my-prefix \
--with-legacy-migration v1
Parameters:
LOCAL, --local: Write output to a local directory. Mutually exclusive with --gcs-bucket and --s3-bucket. [default: False]GCS-BUCKET, --gcs-bucket: Write output to a GCS bucket. Must not be the prod bucket. Mutually exclusive with --local and --s3-bucket.S3-BUCKET, --s3-bucket: Write output to an S3 bucket. Mutually exclusive with --local and --gcs-bucket.OUTPUT-PATH-ROOT, --output-path-root: Root path/prefix for the output. For --local, this is a directory path (defaults to a new temp dir if omitted). For --gcs-bucket/--s3-bucket, this prefix is prepended to all blob paths.DRY-RUN, --dry-run, --no-dry-run: Show what would be rebuilt without writing any files. [default: False]SOURCE-STORE, --source-store: Source store to read from (e.g. 'coral:prod', 'coral:dev'). [default: coral:prod]CONNECTOR-NAME, --connector-name, --empty-connector-name: Only rebuild these connectors (by name). Can be specified multiple times, e.g. --connector-name source-faker --connector-name destination-bigquery.
airbyte-ops registry store compile
airbyte-ops registry store compile [OPTIONS] STORE
Compile the registry: sync latest/ dirs, write global and per-connector indexes.
Scans all version directories in the target store, determines the latest GA
semver per connector (excluding yanked and pre-release versions), ensures
each latest/ directory matches the computed latest, and writes:
registries/v0/cloud_registry.json-- global cloud registry indexregistries/v0/oss_registry.json-- global OSS registry indexmetadata/airbyte/<connector>/versions.json-- per-connector version index
With --with-secrets-mask, also regenerates:
registries/v0/specs_secrets_mask.yaml-- properties marked as secrets
With --with-legacy-migration=v1, deletes cloud.json / oss.json
files for connectors whose registryOverrides.cloud.enabled or
registryOverrides.oss.enabled is false.
By default, injects connector quality metrics from the latest analytics
JSONL export into generated.metrics. Use --no-metrics for offline
scenarios.
With --force, resyncs all latest/ directories even if the version marker
matches the computed latest version.
Uses efficient glob patterns for scanning (no file downloads during discovery).
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod', 'coral:dev/prefix'). [required]--connector-name, --empty-connector-name: Only compile these connectors (can be repeated).--dry-run, --no-dry-run: Show what would be done without writing. [default: False]--with-secrets-mask, --no-with-secrets-mask: Also regenerate specs_secrets_mask.yaml by scanning all connector specs for airbyte_secret properties. [default: False]--with-legacy-migration: Run a one-time legacy migration step during compile. Currently supported: 'v1' — delete cloud.json / oss.json files for connectors whose registryOverrides.cloud.enabled or registryOverrides.oss.enabled is false. This cleans up artifacts produced by the legacy pipeline that did not respect the enabled flag.--with-metrics, --no-metrics: Inject latest connector quality metrics from the analytics JSONL export into generated.metrics. [default: True]--force, --no-force: Force resync of latest/ directories even if version markers are current. Useful when metadata changes without a version bump. [default: False]
airbyte-ops registry store delete-dev-latest
airbyte-ops registry store delete-dev-latest [OPTIONS] STORE
Delete all latest/ directories from a dev registry store.
Discovers every connector that has a latest/ directory and
deletes each one in parallel using a thread pool.
This is useful before a full re-compile to prove that latest/ directories can be correctly regenerated from versioned data.
Only dev stores are allowed (store must begin with 'coral:dev').
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
STORE, --store: Store target (must begin with 'coral:dev'). [required]--connector-name, --empty-connector-name: Only delete latest/ for these connectors (can be repeated).--dry-run, --no-dry-run: Show what would be done without deleting. [default: False]
airbyte-ops registry store compare
airbyte-ops registry store compare [OPTIONS] STORE REFERENCE-STORE
Compare a store against a reference store and report differences.
Evaluates the --store target against --reference-store and reports
per-connector artifact diffs and global index diffs.
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
STORE, --store: Store target being evaluated (e.g. 'coral:dev/20260306-mirror-compile'). [required]REFERENCE-STORE, --reference-store: Known-good reference store to compare against. [required]--connector-name, --empty-connector-name: Only compare these connectors (can be repeated).--with-artifacts, --no-artifacts: Compare per-connector artifact files (metadata.yaml, cloud.json, oss.json, spec.json). [default: True]--with-indexes, --no-indexes: Compare global registry index files (cloud_registry.json, oss_registry.json). [default: True]
airbyte-ops registry progressive-rollout
Progressive rollout lifecycle operations (create/cleanup rollout marker in GCS).
airbyte-ops registry progressive-rollout create
airbyte-ops registry progressive-rollout create [OPTIONS] NAME STORE
Create the release_candidate/ metadata marker in GCS.
Copies the versioned metadata (e.g. 1.2.3-rc.1/metadata.yaml or
2.1.0/metadata.yaml) to release_candidate/metadata.yaml so the
platform knows a progressive rollout is active for this connector.
The connector's metadata.yaml on disk must declare a version that is valid for progressive rollout (i.e. not a -preview build) and the versioned blob must already exist in GCS.
Uses --store to select the registry store and environment.
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
NAME, --name: Connector technical name (e.g., source-github). [required]STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]--repo-path: Path to the Airbyte monorepo. Defaults to current directory. [default: /home/runner/work/airbyte-ops-mcp/airbyte-ops-mcp]--dry-run, --no-dry-run: Show what would be done without making changes. [default: False]
airbyte-ops registry progressive-rollout cleanup
airbyte-ops registry progressive-rollout cleanup [OPTIONS] NAME STORE
Delete the release_candidate/ metadata marker from GCS.
This command deletes only the release_candidate/metadata.yaml file
for the given connector. The versioned directory (e.g. 1.2.3-rc.1/)
is intentionally preserved as an audit trail of what was actually deployed
during the progressive rollout.
Both promote and rollback workflows call this same command -- the only
difference between the two is in the git-side steps (version bump vs.
not), which are handled by the finalize_rollout GitHub Actions
workflow.
Uses --store to select the registry store and environment.
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
NAME, --name: Connector technical name (e.g., source-github). [required]STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]--repo-path: Path to the Airbyte monorepo. Defaults to current directory. [default: /home/runner/work/airbyte-ops-mcp/airbyte-ops-mcp]--dry-run, --no-dry-run: Show what would be done without making changes. [default: False]
airbyte-ops registry progressive-rollout status
airbyte-ops registry progressive-rollout status [OPTIONS] NAME
Get progressive rollout status for a connector.
Parameters:
NAME, --name: Connector technical name (e.g., source-github). [required]--repo-path: Path to the Airbyte monorepo. Defaults to current directory. [default: /home/runner/work/airbyte-ops-mcp/airbyte-ops-mcp]--active-only, --with-terminal: Only return active non-terminal rollouts. [default: True]--limit: Maximum number of rollout records to return. [default: 100]
airbyte-ops registry marketing-stubs
Marketing connector stubs GCS operations (whole-file sync).
airbyte-ops registry marketing-stubs check
airbyte-ops registry marketing-stubs check [OPTIONS] STORE
Compare local connector_stubs.json with the version in GCS.
This command reads the entire local connector_stubs.json file and compares it with the version currently published in GCS.
Exit codes:
0: Local file matches GCS (check passed) 1: Differences found (check failed)
Output:
STDOUT: JSON representation of the comparison result STDERR: Informational messages and comparison details
Parameters:
STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]--repo-root: Path to the airbyte-enterprise repository root. Defaults to current directory. [default: /home/runner/work/airbyte-ops-mcp/airbyte-ops-mcp]
airbyte-ops registry marketing-stubs sync
airbyte-ops registry marketing-stubs sync [OPTIONS] STORE
Sync local connector_stubs.json to GCS.
This command uploads the entire local connector_stubs.json file to GCS, replacing the existing file. Use this after merging changes to master in the airbyte-enterprise repository.
Exit codes:
0: Sync successful (or dry-run completed) 1: Error (file not found, validation failed, etc.)
Output:
STDOUT: JSON representation of the sync result STDERR: Informational messages and status updates
Parameters:
STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]--repo-root: Path to the airbyte-enterprise repository root. Defaults to current directory. [default: /home/runner/work/airbyte-ops-mcp/airbyte-ops-mcp]--dry-run, --no-dry-run: Show what would be uploaded without making changes. [default: False]
1# Copyright (c) 2025 Airbyte, Inc., all rights reserved. 2"""CLI commands for connector registry operations. 3 4This module provides CLI wrappers for registry operations. The core logic 5lives in the `airbyte_ops_mcp.registry` capability module. 6 7Command groups: 8 airbyte-ops registry rc create - Create the release_candidate/ marker in GCS 9 airbyte-ops registry rc cleanup - Delete the release_candidate/ marker from GCS 10 airbyte-ops registry connector list - List all connectors in registry 11 airbyte-ops registry connector-version list - List versions for a connector 12 airbyte-ops registry connector-version next - Compute next version tag (prerelease/RC) 13 airbyte-ops registry connector-version yank - Mark a connector version as yanked 14 airbyte-ops registry connector-version unyank - Remove yank marker from a connector version 15 airbyte-ops registry connector-version metadata get - Read connector metadata from GCS 16 airbyte-ops registry connector-version artifacts generate - Generate version artifacts locally via docker 17 airbyte-ops registry connector-version artifacts publish - Publish version artifacts to GCS 18 airbyte-ops registry store mirror --local|--gcs-bucket|--s3-bucket - Mirror entire registry for testing 19 airbyte-ops registry store compile --store coral:dev|coral:prod - Compile registry indexes and sync latest/ dirs 20 airbyte-ops registry marketing-stubs sync --store coral:prod - Sync connector_stubs.json to GCS 21 airbyte-ops registry marketing-stubs check --store coral:prod - Compare local file with GCS 22 23## CLI reference 24 25The commands below are regenerated by `poe docs-generate` via cyclopts's 26programmatic docs API; see `docs/generate_cli.py`. 27 28.. include:: ../../../docs/generated/cli/registry.md 29 :start-line: 2 30""" 31 32from __future__ import annotations 33 34# Hide Python-level members from the pdoc page for this module; the rendered 35# docs for this CLI group come entirely from the grafted `.. include::` in 36# the module docstring above. 37__all__: list[str] = [] 38 39import sys 40from pathlib import Path 41from typing import Annotated, Literal 42 43import yaml 44from cyclopts import Parameter 45 46from airbyte_ops_mcp.cli._base import App, app 47from airbyte_ops_mcp.cli._shared import ( 48 error_console, 49 exit_with_error, 50 print_error, 51 print_json, 52 print_success, 53) 54from airbyte_ops_mcp.github_api import ( 55 get_file_contents_at_ref, 56 resolve_default_github_token, 57) 58from airbyte_ops_mcp.mcp.prerelease import ( 59 compute_prerelease_docker_image_tag, 60) 61from airbyte_ops_mcp.registry import ( 62 resolve_registry_store, 63) 64from airbyte_ops_mcp.registry._enums import ( 65 ConnectorLanguage, 66 ConnectorType, 67 SupportLevel, 68) 69from airbyte_ops_mcp.registry.compare import compare_stores 70from airbyte_ops_mcp.registry.connector_stubs import ( 71 CONNECTOR_STUBS_FILE, 72) 73from airbyte_ops_mcp.registry.generate import generate_version_artifacts 74from airbyte_ops_mcp.registry.progressive_rollout_status import ( 75 get_connector_rollout_status, 76) 77from airbyte_ops_mcp.registry.registry_store_base import ( 78 Registry, 79 get_registry, 80) 81 82# Create the registry sub-app 83registry_app = App( 84 name="registry", help="Connector registry operations (GCS metadata service)." 85) 86app.command(registry_app) 87 88# Create the connector sub-app under registry 89connector_app = App(name="connector", help="Connector listing operations.") 90registry_app.command(connector_app) 91 92# Create the connector-version sub-app under registry 93connector_version_app = App( 94 name="connector-version", 95 help="Connector version operations (list, yank, unyank, artifacts).", 96) 97registry_app.command(connector_version_app) 98 99# Create the metadata sub-app under connector-version 100metadata_app = App( 101 name="metadata", 102 help="Connector metadata inspection.", 103) 104connector_version_app.command(metadata_app) 105 106# Create the artifacts sub-app under connector-version 107artifacts_app = App( 108 name="artifacts", 109 help="Version artifact generation and publishing.", 110) 111connector_version_app.command(artifacts_app) 112 113# Create the store sub-app under registry (whole-registry operations) 114store_app = App( 115 name="store", 116 help="Whole-registry store operations (mirror, compile).", 117) 118registry_app.command(store_app) 119 120# Create the progressive-rollout sub-app under registry 121progressive_rollout_app = App( 122 name="progressive-rollout", 123 help="Progressive rollout lifecycle operations (create/cleanup rollout marker in GCS).", 124) 125registry_app.command(progressive_rollout_app) 126 127# Create the marketing-stubs sub-app under registry (for whole-file GCS operations) 128marketing_stubs_app = App( 129 name="marketing-stubs", 130 help="Marketing connector stubs GCS operations (whole-file sync).", 131) 132registry_app.command(marketing_stubs_app) 133 134 135AIRBYTE_REPO_OWNER = "airbytehq" 136AIRBYTE_ENTERPRISE_REPO_NAME = "airbyte-enterprise" 137AIRBYTE_REPO_NAME = "airbyte" 138CONNECTOR_PATH_PREFIX = "airbyte-integrations/connectors" 139 140 141def _resolve_store(store: str) -> Registry: 142 """Resolve `--store` to a `Registry` instance. 143 144 Wraps `resolve_registry_store` and `get_registry`, converting 145 `ValueError` into a user-friendly CLI error via `exit_with_error`. 146 """ 147 try: 148 resolved = resolve_registry_store(store=store) 149 except ValueError as e: 150 exit_with_error(str(e)) 151 raise # unreachable; satisfies type checker 152 return get_registry(resolved) 153 154 155def _get_connector_version_from_github( 156 connector_name: str, 157 ref: str, 158 token: str | None = None, 159) -> str | None: 160 """Fetch connector version from metadata.yaml via GitHub API. 161 162 Args: 163 connector_name: Connector name (e.g., "source-github") 164 ref: Git ref (commit SHA, branch name, or tag) 165 token: GitHub API token (optional for public repos) 166 167 Returns: 168 Version string from metadata.yaml, or None if not found. 169 """ 170 path = f"{CONNECTOR_PATH_PREFIX}/{connector_name}/metadata.yaml" 171 contents = get_file_contents_at_ref( 172 owner=AIRBYTE_REPO_OWNER, 173 repo=AIRBYTE_REPO_NAME, 174 path=path, 175 ref=ref, 176 token=token, 177 ) 178 if contents is None: 179 return None 180 181 metadata = yaml.safe_load(contents) 182 return metadata.get("data", {}).get("dockerImageTag") 183 184 185@connector_version_app.command(name="next") 186def compute_next_version( 187 name: Annotated[ 188 str, 189 Parameter(help="Connector name (e.g., 'source-github')."), 190 ], 191 sha: Annotated[ 192 str, 193 Parameter(help="Git commit SHA (full or at least 7 characters)."), 194 ], 195 base_version: Annotated[ 196 str | None, 197 Parameter( 198 help="Base version override. If not provided, fetched from metadata.yaml at the given SHA." 199 ), 200 ] = None, 201) -> None: 202 """Compute the next version tag for a connector. 203 204 Outputs the version tag to stdout for easy capture in shell scripts. 205 This is the single source of truth for pre-release version format. 206 207 The command fetches the connector's metadata.yaml from GitHub at the given SHA 208 to determine the base version. It also compares against the master branch and 209 prints a warning to stderr if no version bump is detected. 210 211 If --base-version is provided, it is used directly instead of fetching from GitHub. 212 213 Examples: 214 airbyte-ops registry connector-version next --name source-github --sha abcdef1234567 215 # Output: 1.2.3-preview.abcdef1 216 217 airbyte-ops registry connector-version next --name source-github --sha abcdef1234567 --base-version 1.2.3 218 # Output: 1.2.3-preview.abcdef1 (uses provided version, skips GitHub API) 219 """ 220 # Try to get a GitHub token (optional, but helps avoid rate limiting) 221 # Token resolution may fail if no token is configured, which is fine for public repos 222 token: str | None = None 223 token = resolve_default_github_token(allow_none=True) 224 225 # Determine base version 226 version: str 227 if base_version: 228 version = base_version 229 else: 230 # Fetch version from metadata.yaml at the given SHA 231 fetched_version = _get_connector_version_from_github(name, sha, token) 232 if fetched_version is None: 233 print( 234 f"Error: Could not fetch metadata.yaml for {name} at ref {sha}", 235 file=sys.stderr, 236 ) 237 sys.exit(1) 238 version = fetched_version 239 240 # Compare with master branch version and warn if no bump detected 241 master_version = _get_connector_version_from_github(name, "master", token) 242 if master_version and master_version == version: 243 print( 244 f"Warning: No version bump detected for {name}. " 245 f"Version {version} matches master branch.", 246 file=sys.stderr, 247 ) 248 249 # Compute and output the prerelease tag 250 tag = compute_prerelease_docker_image_tag(version, sha) 251 print(tag) 252 253 254# ============================================================================= 255# PROGRESSIVE ROLLOUT COMMANDS 256# ============================================================================= 257 258 259@progressive_rollout_app.command(name="create") 260def progressive_rollout_create( 261 name: Annotated[ 262 str, 263 Parameter(help="Connector technical name (e.g., source-github)."), 264 ], 265 store: Annotated[ 266 str, 267 Parameter( 268 help="Store target (e.g. 'coral:dev', 'coral:prod').", 269 ), 270 ], 271 *, 272 repo_path: Annotated[ 273 Path, 274 Parameter(help="Path to the Airbyte monorepo. Defaults to current directory."), 275 ] = Path.cwd(), 276 dry_run: Annotated[ 277 bool, 278 Parameter(help="Show what would be done without making changes."), 279 ] = False, 280) -> None: 281 """Create the release_candidate/ metadata marker in GCS. 282 283 Copies the versioned metadata (e.g. `1.2.3-rc.1/metadata.yaml` or 284 `2.1.0/metadata.yaml`) to `release_candidate/metadata.yaml` so the 285 platform knows a progressive rollout is active for this connector. 286 287 The connector's metadata.yaml on disk must declare a version that 288 is valid for progressive rollout (i.e. not a -preview build) and the 289 versioned blob must already exist in GCS. 290 291 Uses `--store` to select the registry store and environment. 292 293 Requires `GCS_CREDENTIALS` environment variable to be set. 294 """ 295 if not repo_path.exists(): 296 exit_with_error(f"Repository path not found: {repo_path}") 297 298 registry = _resolve_store(store) 299 300 result = registry.progressive_rollout_create( 301 repo_path=repo_path, 302 connector_name=name, 303 dry_run=dry_run, 304 ) 305 306 print_json(result.model_dump()) 307 308 if result.status == "failure": 309 exit_with_error(result.message or "Operation failed", code=1) 310 311 312@progressive_rollout_app.command(name="cleanup") 313def progressive_rollout_cleanup( 314 name: Annotated[ 315 str, 316 Parameter(help="Connector technical name (e.g., source-github)."), 317 ], 318 store: Annotated[ 319 str, 320 Parameter( 321 help="Store target (e.g. 'coral:dev', 'coral:prod').", 322 ), 323 ], 324 *, 325 repo_path: Annotated[ 326 Path, 327 Parameter(help="Path to the Airbyte monorepo. Defaults to current directory."), 328 ] = Path.cwd(), 329 dry_run: Annotated[ 330 bool, 331 Parameter(help="Show what would be done without making changes."), 332 ] = False, 333) -> None: 334 """Delete the release_candidate/ metadata marker from GCS. 335 336 This command deletes *only* the `release_candidate/metadata.yaml` file 337 for the given connector. The versioned directory (e.g. `1.2.3-rc.1/`) 338 is intentionally preserved as an audit trail of what was actually deployed 339 during the progressive rollout. 340 341 Both promote and rollback workflows call this same command -- the only 342 difference between the two is in the git-side steps (version bump vs. 343 not), which are handled by the `finalize_rollout` GitHub Actions 344 workflow. 345 346 Uses `--store` to select the registry store and environment. 347 348 Requires `GCS_CREDENTIALS` environment variable to be set. 349 """ 350 if not repo_path.exists(): 351 exit_with_error(f"Repository path not found: {repo_path}") 352 353 registry = _resolve_store(store) 354 355 result = registry.progressive_rollout_cleanup( 356 repo_path=repo_path, 357 connector_name=name, 358 dry_run=dry_run, 359 ) 360 361 print_json(result.model_dump()) 362 363 if result.status == "failure": 364 exit_with_error(result.message or "Operation failed", code=1) 365 366 367@progressive_rollout_app.command(name="status") 368def progressive_rollout_status( 369 name: Annotated[ 370 str, 371 Parameter(help="Connector technical name (e.g., source-github)."), 372 ], 373 *, 374 repo_path: Annotated[ 375 Path, 376 Parameter(help="Path to the Airbyte monorepo. Defaults to current directory."), 377 ] = Path.cwd(), 378 active_only: Annotated[ 379 bool, 380 Parameter( 381 help="Only return active non-terminal rollouts.", 382 negative="--with-terminal", 383 ), 384 ] = True, 385 limit: Annotated[ 386 int, 387 Parameter(help="Maximum number of rollout records to return."), 388 ] = 100, 389) -> None: 390 """Get progressive rollout status for a connector.""" 391 if not repo_path.exists(): 392 exit_with_error(f"Repository path not found: {repo_path}") 393 try: 394 result = get_connector_rollout_status( 395 repo_path=repo_path, 396 connector_name=name, 397 active_only=active_only, 398 limit=limit, 399 ) 400 except (FileNotFoundError, ValueError) as e: 401 exit_with_error(str(e)) 402 403 print_json(result.model_dump()) 404 405 406# ============================================================================= 407# REGISTRY I/O - READ COMMANDS 408# ============================================================================= 409 410 411@metadata_app.command(name="get") 412def get_connector_version_metadata_cmd( 413 name: Annotated[ 414 str, 415 Parameter( 416 help="Connector name (e.g., 'source-faker', 'destination-postgres')." 417 ), 418 ], 419 store: Annotated[ 420 str, 421 Parameter( 422 help="Store target (e.g. 'coral:dev', 'coral:prod').", 423 ), 424 ], 425 *, 426 version: Annotated[ 427 str, 428 Parameter(help="Version to read (e.g., 'latest', '1.2.3')."), 429 ] = "latest", 430 format: Annotated[ 431 Literal["json", "raw"], 432 Parameter(help="Output format: 'json' for JSON, 'raw' for YAML."), 433 ] = "json", 434) -> None: 435 """Read a connector version's metadata from the registry. 436 437 Returns the full metadata.yaml content for a connector at the specified version. 438 439 Requires GCS_CREDENTIALS environment variable to be set. 440 441 Examples: 442 airbyte-ops registry connector-version metadata get --name source-faker --store coral:dev 443 airbyte-ops registry connector-version metadata get --name source-faker --store coral:dev --version 6.2.38 444 airbyte-ops registry connector-version metadata get --name source-faker --store coral:prod 445 """ 446 registry = _resolve_store(store) 447 448 try: 449 metadata = registry.get_connector_metadata( 450 connector_name=name, 451 version=version, 452 ) 453 except FileNotFoundError as e: 454 exit_with_error(str(e), code=1) 455 except Exception as e: 456 exit_with_error(f"Error reading metadata: {e}", code=1) 457 458 if format == "json": 459 print_json(metadata) 460 else: 461 print(yaml.dump(metadata, default_flow_style=False)) 462 463 464@connector_app.command(name="list") 465def list_connectors_cmd( 466 store: Annotated[ 467 str, 468 Parameter( 469 help="Store target (e.g. 'coral:dev', 'coral:prod').", 470 ), 471 ], 472 *, 473 certified_only: Annotated[ 474 bool, 475 Parameter(help="Include only certified connectors."), 476 ] = False, 477 support_level: Annotated[ 478 SupportLevel | None, 479 Parameter( 480 help=( 481 "Exact support level to match " 482 "(e.g. `certified`, `community`, `archived`)." 483 ) 484 ), 485 ] = None, 486 min_support_level: Annotated[ 487 SupportLevel | None, 488 Parameter( 489 help=( 490 "Minimum support level (inclusive). " 491 "Levels from lowest to highest: `archived`, `community`, `certified`." 492 ) 493 ), 494 ] = None, 495 connector_type: Annotated[ 496 ConnectorType | None, 497 Parameter(help="Filter by connector type: `source` or `destination`."), 498 ] = None, 499 language: Annotated[ 500 ConnectorLanguage | None, 501 Parameter( 502 help=( 503 "Filter by implementation language " 504 "(e.g. `python`, `java`, `manifest-only`)." 505 ) 506 ), 507 ] = None, 508 format: Annotated[ 509 Literal["json", "text"], 510 Parameter( 511 help="Output format: 'json' for JSON array, 'text' for newline-separated." 512 ), 513 ] = "json", 514) -> None: 515 """List connectors in the registry. 516 517 When filters are applied, reads the compiled `cloud_registry.json` index 518 for fast lookups. Without filters, falls back to scanning individual 519 metadata blobs (captures all connectors including OSS-only). 520 521 Requires GCS_CREDENTIALS environment variable to be set. 522 """ 523 registry = _resolve_store(store) 524 525 # `--certified-only` is sugar for `--support-level certified`. 526 effective_support_level = support_level 527 if certified_only: 528 if support_level and support_level != SupportLevel.CERTIFIED: 529 exit_with_error( 530 "`--certified-only` conflicts with `--support-level " 531 f"{support_level}`. Use one or the other.", 532 code=1, 533 ) 534 effective_support_level = SupportLevel.CERTIFIED 535 536 try: 537 connectors = registry.list_connectors( 538 support_level=effective_support_level, 539 min_support_level=min_support_level, 540 connector_type=connector_type, 541 language=language, 542 ) 543 except Exception as e: 544 exit_with_error(f"Error listing connectors: {e}", code=1) 545 546 if format == "json": 547 print_json({"connectors": connectors, "count": len(connectors)}) 548 else: 549 for connector in connectors: 550 print(connector) 551 552 553@connector_version_app.command(name="list") 554def list_connector_versions_cmd( 555 name: Annotated[ 556 str, 557 Parameter(help="Connector name (e.g., 'source-faker')."), 558 ], 559 store: Annotated[ 560 str, 561 Parameter( 562 help="Store target (e.g. 'coral:dev', 'coral:prod').", 563 ), 564 ], 565 *, 566 format: Annotated[ 567 Literal["json", "text"], 568 Parameter( 569 help="Output format: 'json' for JSON array, 'text' for newline-separated." 570 ), 571 ] = "json", 572) -> None: 573 """List all versions of a connector in the registry. 574 575 Scans the registry bucket to find all versions of a specific connector. 576 577 Requires GCS_CREDENTIALS environment variable to be set. 578 """ 579 registry = _resolve_store(store) 580 581 try: 582 versions = registry.list_connector_versions( 583 connector_name=name, 584 ) 585 except Exception as e: 586 exit_with_error(f"Error listing versions: {e}", code=1) 587 588 if format == "json": 589 print_json({"connector": name, "versions": versions, "count": len(versions)}) 590 else: 591 for v in versions: 592 print(v) 593 594 595@marketing_stubs_app.command(name="check") 596def marketing_stubs_check( 597 store: Annotated[ 598 str, 599 Parameter( 600 help="Store target (e.g. 'coral:dev', 'coral:prod').", 601 ), 602 ], 603 *, 604 repo_root: Annotated[ 605 Path, 606 Parameter( 607 help="Path to the airbyte-enterprise repository root. Defaults to current directory." 608 ), 609 ] = Path.cwd(), 610) -> None: 611 """Compare local connector_stubs.json with the version in GCS. 612 613 This command reads the entire local connector_stubs.json file and compares it 614 with the version currently published in GCS. 615 616 Exit codes: 617 0: Local file matches GCS (check passed) 618 1: Differences found (check failed) 619 620 Output: 621 STDOUT: JSON representation of the comparison result 622 STDERR: Informational messages and comparison details 623 624 Example: 625 airbyte-ops registry marketing-stubs check --store coral:prod --repo-root /path/to/airbyte-enterprise 626 airbyte-ops registry marketing-stubs check --store coral:dev 627 """ 628 registry = _resolve_store(store) 629 630 try: 631 result = registry.marketing_stubs_check(repo_root=repo_root) 632 except FileNotFoundError as e: 633 exit_with_error(str(e)) 634 except ValueError as e: 635 exit_with_error(str(e)) 636 637 error_console.print( 638 f"Comparing local {CONNECTOR_STUBS_FILE} with {result.get('bucket', '')}/{result.get('path', '')}" 639 ) 640 641 differences = result.get("differences", []) 642 if differences: 643 error_console.print( 644 f"[yellow]Warning:[/yellow] {len(differences)} difference(s) found:" 645 ) 646 for diff in differences: 647 error_console.print(f" {diff['id']}: {diff['status']}") 648 print_json(result) 649 sys.exit(1) 650 651 error_console.print( 652 f"[green]Local file is in sync with GCS ({result.get('local_count', 0)} stubs)[/green]" 653 ) 654 print_json(result) 655 656 657@marketing_stubs_app.command(name="sync") 658def marketing_stubs_sync( 659 store: Annotated[ 660 str, 661 Parameter( 662 help="Store target (e.g. 'coral:dev', 'coral:prod').", 663 ), 664 ], 665 *, 666 repo_root: Annotated[ 667 Path, 668 Parameter( 669 help="Path to the airbyte-enterprise repository root. Defaults to current directory." 670 ), 671 ] = Path.cwd(), 672 dry_run: Annotated[ 673 bool, 674 Parameter(help="Show what would be uploaded without making changes."), 675 ] = False, 676) -> None: 677 """Sync local connector_stubs.json to GCS. 678 679 This command uploads the entire local connector_stubs.json file to GCS, 680 replacing the existing file. Use this after merging changes to master 681 in the airbyte-enterprise repository. 682 683 Exit codes: 684 0: Sync successful (or dry-run completed) 685 1: Error (file not found, validation failed, etc.) 686 687 Output: 688 STDOUT: JSON representation of the sync result 689 STDERR: Informational messages and status updates 690 691 Example: 692 airbyte-ops registry marketing-stubs sync --store coral:prod --repo-root /path/to/airbyte-enterprise 693 airbyte-ops registry marketing-stubs sync --store coral:dev 694 airbyte-ops registry marketing-stubs sync --store coral:dev --dry-run 695 """ 696 registry = _resolve_store(store) 697 698 try: 699 result = registry.marketing_stubs_sync( 700 repo_root=repo_root, 701 dry_run=dry_run, 702 ) 703 except FileNotFoundError as e: 704 exit_with_error(str(e)) 705 except ValueError as e: 706 exit_with_error(str(e)) 707 708 bucket_name = result.get("bucket", "") 709 path = result.get("path", "") 710 stub_count = result.get("stub_count", 0) 711 712 if dry_run: 713 error_console.print( 714 f"[DRY RUN] Would upload {stub_count} stubs to {bucket_name}/{path}" 715 ) 716 else: 717 error_console.print( 718 f"[green]Synced {stub_count} stubs to {bucket_name}/{path}[/green]" 719 ) 720 print_json(result) 721 722 723# ============================================================================= 724# REGISTRY REBUILD COMMANDS 725# ============================================================================= 726 727 728@store_app.command(name="mirror") 729def mirror_cmd( 730 local: Annotated[ 731 bool, 732 Parameter( 733 help="Write output to a local directory. Mutually exclusive with --gcs-bucket and --s3-bucket.", 734 negative="", 735 ), 736 ] = False, 737 gcs_bucket: Annotated[ 738 str | None, 739 Parameter( 740 help="Write output to a GCS bucket. Must not be the prod bucket. " 741 "Mutually exclusive with --local and --s3-bucket.", 742 ), 743 ] = None, 744 s3_bucket: Annotated[ 745 str | None, 746 Parameter( 747 help="Write output to an S3 bucket. " 748 "Mutually exclusive with --local and --gcs-bucket.", 749 ), 750 ] = None, 751 output_path_root: Annotated[ 752 str | None, 753 Parameter( 754 help="Root path/prefix for the output. For --local, this is a directory path " 755 "(defaults to a new temp dir if omitted). For --gcs-bucket/--s3-bucket, " 756 "this prefix is prepended to all blob paths.", 757 ), 758 ] = None, 759 dry_run: Annotated[ 760 bool, 761 Parameter(help="Show what would be rebuilt without writing any files."), 762 ] = False, 763 source_store: Annotated[ 764 str, 765 Parameter( 766 help="Source store to read from (e.g. 'coral:prod', 'coral:dev').", 767 ), 768 ] = "coral:prod", 769 connector_name: Annotated[ 770 tuple[str, ...] | None, 771 Parameter( 772 help="Only rebuild these connectors (by name). " 773 "Can be specified multiple times, e.g. " 774 "--connector-name source-faker --connector-name destination-bigquery.", 775 ), 776 ] = None, 777) -> None: 778 """Create a mirror of the connector registry from the source store. 779 780 Reads all connector metadata from the source store and copies it 781 to the specified output target. Supports local filesystem, GCS, and S3 782 as output targets via fsspec. 783 784 Output targets are mutually exclusive: specify exactly one of 785 --local, --gcs-bucket, or --s3-bucket. 786 787 The production bucket is categorically disallowed as an output target. 788 789 To clean up legacy artifacts (e.g. disabled strict-encrypt connectors) 790 after mirroring, run compile with `--with-legacy-migration v1`:: 791 792 airbyte-ops registry store compile --store coral:dev/my-prefix \\ 793 --with-legacy-migration v1 794 795 Examples: 796 airbyte-ops registry store mirror --local 797 airbyte-ops registry store mirror --local --source-store coral:dev 798 airbyte-ops registry store mirror --gcs-bucket dev-airbyte-cloud-connector-metadata-service-2 \\ 799 --output-path-root test-run-123 800 airbyte-ops registry store mirror --s3-bucket my-test-bucket --dry-run 801 """ 802 # Validate mutually exclusive output targets 803 targets = [local, gcs_bucket is not None, s3_bucket is not None] 804 if sum(targets) != 1: 805 exit_with_error( 806 "Specify exactly one output target: --local, --gcs-bucket, or --s3-bucket." 807 ) 808 809 if local: 810 output_mode = "local" 811 elif gcs_bucket is not None: 812 output_mode = "gcs" 813 else: 814 output_mode = "s3" 815 816 registry = _resolve_store(source_store) 817 818 result = registry.mirror( 819 output_mode=output_mode, 820 output_path_root=output_path_root, 821 gcs_bucket=gcs_bucket, 822 s3_bucket=s3_bucket, 823 dry_run=dry_run, 824 connector_name=list(connector_name) if connector_name else None, 825 ) 826 827 print_json( 828 { 829 "status": result.status, 830 "source_bucket": result.source_bucket, 831 "output_mode": result.output_mode, 832 "output_root": result.output_root, 833 "connectors_processed": result.connectors_processed, 834 "blobs_copied": result.blobs_copied, 835 "blobs_skipped": result.blobs_skipped, 836 "error_count": len(result.errors), 837 "dry_run": result.dry_run, 838 } 839 ) 840 841 if result.errors: 842 for err in result.errors[:10]: 843 print_error(err) 844 if len(result.errors) > 10: 845 print_error(f"... and {len(result.errors) - 10} more errors") 846 847 if result.status == "success": 848 print_success(result.summary()) 849 elif result.status == "dry-run": 850 print_success(f"[DRY RUN] {result.summary()}") 851 else: 852 error_console.print(f"[yellow]{result.summary()}[/yellow]") 853 854 855# ============================================================================= 856# VERSION YANK COMMANDS 857# ============================================================================= 858 859 860@connector_version_app.command(name="yank") 861def yank_cmd( 862 name: Annotated[ 863 str, 864 Parameter(help="Connector name (e.g., 'source-faker')."), 865 ], 866 version: Annotated[ 867 str, 868 Parameter(help="Version to yank (e.g., '1.2.3')."), 869 ], 870 store: Annotated[ 871 str, 872 Parameter( 873 help="Store target (e.g. 'coral:dev', 'coral:prod').", 874 ), 875 ], 876 *, 877 reason: Annotated[ 878 str, 879 Parameter(help="Reason for yanking this version."), 880 ] = "", 881 dry_run: Annotated[ 882 bool, 883 Parameter(help="Show what would be done without making changes."), 884 ] = False, 885) -> None: 886 """Mark a connector version as yanked. 887 888 Writes a version-yank.yml marker file to the version's directory in GCS. 889 Yanked versions are excluded when determining the latest version of a 890 connector. 891 892 Requires GCS_CREDENTIALS environment variable to be set. 893 894 Examples: 895 airbyte-ops registry connector-version yank --name source-faker --version 1.2.3 --store coral:dev 896 airbyte-ops registry connector-version yank --name source-faker --version 1.2.3 --store coral:dev --reason "Critical bug" 897 airbyte-ops registry connector-version yank --name source-faker --version 1.2.3 --store coral:prod 898 """ 899 registry = _resolve_store(store) 900 901 result = registry.yank( 902 connector_name=name, 903 version=version, 904 reason=reason, 905 dry_run=dry_run, 906 ) 907 908 print_json(result.to_dict()) 909 910 if result.success: 911 print_success(result.message) 912 if not dry_run: 913 error_console.print( 914 "\n[yellow]Note:[/yellow] The registry indexes are now stale. " 915 "To update them, run:\n\n" 916 f" airbyte-ops registry store compile --store {store}\n\n" 917 "Or wait for the next scheduled compile operation." 918 ) 919 else: 920 exit_with_error(result.message, code=1) 921 922 923@connector_version_app.command(name="unyank") 924def unyank_cmd( 925 name: Annotated[ 926 str, 927 Parameter(help="Connector name (e.g., 'source-faker')."), 928 ], 929 version: Annotated[ 930 str, 931 Parameter(help="Version to unyank (e.g., '1.2.3')."), 932 ], 933 store: Annotated[ 934 str, 935 Parameter( 936 help="Store target (e.g. 'coral:dev', 'coral:prod').", 937 ), 938 ], 939 *, 940 dry_run: Annotated[ 941 bool, 942 Parameter(help="Show what would be done without making changes."), 943 ] = False, 944) -> None: 945 """Remove the yank marker from a connector version. 946 947 Deletes the version-yank.yml marker file from the version's directory in GCS, 948 making the version eligible again when determining the latest version. 949 950 Requires GCS_CREDENTIALS environment variable to be set. 951 952 Examples: 953 airbyte-ops registry connector-version unyank --name source-faker --version 1.2.3 --store coral:dev 954 airbyte-ops registry connector-version unyank --name source-faker --version 1.2.3 --store coral:prod 955 """ 956 registry = _resolve_store(store) 957 958 result = registry.unyank( 959 connector_name=name, 960 version=version, 961 dry_run=dry_run, 962 ) 963 964 print_json(result.to_dict()) 965 966 if result.success: 967 print_success(result.message) 968 if not dry_run: 969 error_console.print( 970 "\n[yellow]Note:[/yellow] The registry indexes are now stale. " 971 "To update them, run:\n\n" 972 f" airbyte-ops registry store compile --store {store}\n\n" 973 "Or wait for the next scheduled compile operation." 974 ) 975 else: 976 exit_with_error(result.message, code=1) 977 978 979# ============================================================================= 980# ARTIFACT GENERATION COMMANDS 981# ============================================================================= 982 983 984@artifacts_app.command(name="generate") 985def generate_version_artifacts_cmd( 986 metadata_file: Annotated[ 987 Path, 988 Parameter(help="Path to the connector's metadata.yaml file."), 989 ], 990 docker_image: Annotated[ 991 str, 992 Parameter( 993 help="Docker image to run spec against (e.g., 'airbyte/source-faker:6.2.38')." 994 ), 995 ], 996 output_dir: Annotated[ 997 Path | None, 998 Parameter( 999 help="Directory to write artifacts to. If not specified, a temp directory is created." 1000 ), 1001 ] = None, 1002 repo_root: Annotated[ 1003 Path | None, 1004 Parameter( 1005 help=( 1006 "Root of the Airbyte repo checkout (for resolving doc.md). " 1007 "If not specified, inferred by walking up from metadata-file." 1008 ), 1009 ), 1010 ] = None, 1011 dry_run: Annotated[ 1012 bool, 1013 Parameter( 1014 help="Show what would be generated without running docker or writing files." 1015 ), 1016 ] = False, 1017 with_validate: Annotated[ 1018 bool, 1019 Parameter( 1020 help=( 1021 "Run metadata validators after generation (default: enabled). " 1022 "Use --no-validate to skip." 1023 ), 1024 negative="--no-validate", 1025 ), 1026 ] = True, 1027 with_sbom: Annotated[ 1028 bool, 1029 Parameter( 1030 help=( 1031 "Generate spdx.json (SBOM) for connectors " 1032 "(default: enabled). Use --no-sbom to skip." 1033 ), 1034 negative="--no-sbom", 1035 ), 1036 ] = True, 1037 with_dependency_dump: Annotated[ 1038 bool, 1039 Parameter( 1040 help=( 1041 "Generate dependencies.json for Python connectors " 1042 "(default: enabled). Use --no-dependency-dump to skip." 1043 ), 1044 negative="--no-dependency-dump", 1045 ), 1046 ] = True, 1047) -> None: 1048 """Generate version artifacts for a connector locally. 1049 1050 Runs the connector's docker image with DEPLOYMENT_MODE=cloud and 1051 DEPLOYMENT_MODE=oss to obtain both spec variants, then generates 1052 the registry entries (cloud.json, oss.json) by applying 1053 registryOverrides from the metadata. 1054 1055 The generated metadata.yaml is enriched with git commit info, SBOM URL, 1056 and (when applicable) components SHA before writing. Validation is run 1057 after generation by default; pass `--no-validate` to skip. 1058 1059 This is a local-only operation -- no files are uploaded to GCS. 1060 Use `artifacts publish` to upload generated artifacts to GCS. 1061 1062 Examples: 1063 airbyte-ops registry connector-version artifacts generate \\ 1064 --metadata-file path/to/metadata.yaml \\ 1065 --docker-image airbyte/source-faker:6.2.38 1066 1067 airbyte-ops registry connector-version artifacts generate \\ 1068 --metadata-file path/to/metadata.yaml \\ 1069 --docker-image airbyte/source-faker:6.2.38 \\ 1070 --output-dir ./artifacts --with-validate 1071 """ 1072 result = generate_version_artifacts( 1073 metadata_file=metadata_file, 1074 docker_image=docker_image, 1075 output_dir=output_dir, 1076 repo_root=repo_root, 1077 dry_run=dry_run, 1078 with_validate=with_validate, 1079 with_dependency_dump=with_dependency_dump, 1080 with_sbom=with_sbom, 1081 ) 1082 1083 print_json(result.to_dict()) 1084 1085 if result.success: 1086 print_success( 1087 f"Generated {len(result.artifacts_written)} artifacts to {result.output_dir}" 1088 ) 1089 else: 1090 all_errors = result.errors + result.validation_errors 1091 exit_with_error( 1092 f"Generation completed with {len(all_errors)} error(s): " 1093 + "; ".join(all_errors), 1094 code=1, 1095 ) 1096 1097 1098@artifacts_app.command(name="publish") 1099def publish_version_artifacts_cmd( 1100 name: Annotated[ 1101 str, 1102 Parameter(help="Connector name (e.g., 'source-faker')."), 1103 ], 1104 version: Annotated[ 1105 str, 1106 Parameter(help="Version to publish artifacts for (e.g., '1.2.3')."), 1107 ], 1108 artifacts_dir: Annotated[ 1109 Path, 1110 Parameter( 1111 help="Directory containing generated artifacts to publish (from 'artifacts generate')." 1112 ), 1113 ], 1114 store: Annotated[ 1115 str, 1116 Parameter( 1117 help="Store target (e.g. 'coral:dev', 'coral:prod', 'coral:dev/prefix').", 1118 ), 1119 ], 1120 *, 1121 dry_run: Annotated[ 1122 bool, 1123 Parameter(help="Show what would be published without writing to GCS."), 1124 ] = False, 1125 with_validate: Annotated[ 1126 bool, 1127 Parameter( 1128 help=( 1129 "Validate metadata before uploading (default: enabled). " 1130 "Use --no-validate to skip." 1131 ), 1132 negative="--no-validate", 1133 ), 1134 ] = True, 1135) -> None: 1136 """Publish version artifacts to GCS using fsspec rsync. 1137 1138 Uploads locally generated artifacts (from `artifacts generate`) to the 1139 versioned path in GCS. By default, metadata is validated before upload; 1140 pass `--no-validate` to skip. 1141 1142 Uses `--store` to select the destination store and environment: 1143 1144 * `coral:dev` → coral dev bucket at root 1145 * `coral:prod` → coral prod bucket at root 1146 * `coral:dev/aj-test100` → coral dev bucket under `aj-test100/` prefix 1147 1148 Requires GCS_CREDENTIALS environment variable to be set. 1149 1150 Examples: 1151 airbyte-ops registry connector-version artifacts publish \\ 1152 --name source-faker --version 6.2.38 \\ 1153 --artifacts-dir ./artifacts --store coral:dev --with-validate 1154 1155 airbyte-ops registry connector-version artifacts publish \\ 1156 --name source-faker --version 6.2.38 \\ 1157 --artifacts-dir ./artifacts --store coral:prod 1158 1159 airbyte-ops registry connector-version artifacts publish \\ 1160 --name source-faker --version 6.2.38 \\ 1161 --artifacts-dir ./artifacts --store coral:dev/aj-test100 1162 """ 1163 registry = _resolve_store(store) 1164 1165 result = registry.publish_version_artifacts( 1166 connector_name=name, 1167 version=version, 1168 artifacts_dir=artifacts_dir, 1169 dry_run=dry_run, 1170 with_validate=with_validate, 1171 ) 1172 1173 print_json( 1174 { 1175 "status": result.status, 1176 "connector_name": result.connector_name, 1177 "version": result.version, 1178 "target": result.target, 1179 "gcs_destination": result.gcs_destination, 1180 "files_uploaded": result.files_uploaded, 1181 "errors": result.errors, 1182 "validation_errors": result.validation_errors, 1183 "dry_run": result.dry_run, 1184 } 1185 ) 1186 1187 if result.success: 1188 print_success( 1189 f"Published {len(result.files_uploaded)} artifacts for " 1190 f"{result.connector_name}@{result.version} → {result.gcs_destination}" 1191 ) 1192 else: 1193 all_errors = result.errors + result.validation_errors 1194 exit_with_error( 1195 f"Publish completed with {len(all_errors)} error(s): " 1196 + "; ".join(all_errors), 1197 code=1, 1198 ) 1199 1200 1201# ============================================================================= 1202# COMPILE COMMAND 1203# ============================================================================= 1204 1205 1206@store_app.command(name="compile") 1207def compile_cmd( 1208 store: Annotated[ 1209 str, 1210 Parameter( 1211 help="Store target (e.g. 'coral:dev', 'coral:prod', 'coral:dev/prefix').", 1212 ), 1213 ], 1214 *, 1215 connector_name: Annotated[ 1216 tuple[str, ...] | None, 1217 Parameter( 1218 help="Only compile these connectors (can be repeated).", 1219 ), 1220 ] = None, 1221 dry_run: Annotated[ 1222 bool, 1223 Parameter(help="Show what would be done without writing."), 1224 ] = False, 1225 with_secrets_mask: Annotated[ 1226 bool, 1227 Parameter( 1228 help=( 1229 "Also regenerate specs_secrets_mask.yaml by scanning all " 1230 "connector specs for airbyte_secret properties." 1231 ), 1232 ), 1233 ] = False, 1234 with_legacy_migration: Annotated[ 1235 str | None, 1236 Parameter( 1237 help=( 1238 "Run a one-time legacy migration step during compile. " 1239 "Currently supported: 'v1' — delete cloud.json / oss.json " 1240 "files for connectors whose registryOverrides.cloud.enabled " 1241 "or registryOverrides.oss.enabled is false. This cleans up " 1242 "artifacts produced by the legacy pipeline that did not " 1243 "respect the enabled flag." 1244 ), 1245 ), 1246 ] = None, 1247 with_metrics: Annotated[ 1248 bool, 1249 Parameter( 1250 help=( 1251 "Inject latest connector quality metrics from the analytics " 1252 "JSONL export into generated.metrics." 1253 ), 1254 negative="--no-metrics", 1255 ), 1256 ] = True, 1257 force: Annotated[ 1258 bool, 1259 Parameter( 1260 help=( 1261 "Force resync of latest/ directories even if version markers are current. " 1262 "Useful when metadata changes without a version bump." 1263 ), 1264 ), 1265 ] = False, 1266) -> None: 1267 """Compile the registry: sync latest/ dirs, write global and per-connector indexes. 1268 1269 Scans all version directories in the target store, determines the latest GA 1270 semver per connector (excluding yanked and pre-release versions), ensures 1271 each `latest/` directory matches the computed latest, and writes: 1272 1273 * `registries/v0/cloud_registry.json` -- global cloud registry index 1274 * `registries/v0/oss_registry.json` -- global OSS registry index 1275 * `metadata/airbyte/<connector>/versions.json` -- per-connector version index 1276 1277 With `--with-secrets-mask`, also regenerates: 1278 1279 * `registries/v0/specs_secrets_mask.yaml` -- properties marked as secrets 1280 1281 With `--with-legacy-migration=v1`, deletes `cloud.json` / `oss.json` 1282 files for connectors whose `registryOverrides.cloud.enabled` or 1283 `registryOverrides.oss.enabled` is `false`. 1284 1285 By default, injects connector quality metrics from the latest analytics 1286 JSONL export into `generated.metrics`. Use `--no-metrics` for offline 1287 scenarios. 1288 1289 With `--force`, resyncs all latest/ directories even if the version marker 1290 matches the computed latest version. 1291 1292 Uses efficient glob patterns for scanning (no file downloads during discovery). 1293 1294 Requires GCS_CREDENTIALS environment variable to be set. 1295 1296 Examples: 1297 airbyte-ops registry store compile --store coral:dev --dry-run 1298 1299 airbyte-ops registry store compile --store coral:dev/aj-test100 \\ 1300 --connector-name source-faker --connector-name destination-bigquery 1301 1302 airbyte-ops registry store compile --store coral:prod --with-secrets-mask 1303 1304 airbyte-ops registry store compile --store coral:dev \\ 1305 --with-legacy-migration v1 1306 """ 1307 registry = _resolve_store(store) 1308 1309 result = registry.compile( 1310 connector_name=list(connector_name) if connector_name else None, 1311 dry_run=dry_run, 1312 with_secrets_mask=with_secrets_mask, 1313 with_legacy_migration=with_legacy_migration, 1314 with_metrics=with_metrics, 1315 force=force, 1316 ) 1317 1318 print_json( 1319 { 1320 "status": result.status, 1321 "target": result.target, 1322 "connectors_scanned": result.connectors_scanned, 1323 "versions_found": result.versions_found, 1324 "yanked_versions": result.yanked_versions, 1325 "latest_updated": result.latest_updated, 1326 "latest_already_current": result.latest_already_current, 1327 "cloud_registry_entries": result.cloud_registry_entries, 1328 "oss_registry_entries": result.oss_registry_entries, 1329 "composite_registry_entries": result.composite_registry_entries, 1330 "metrics_connector_count": result.metrics_connector_count, 1331 "metrics_registry_entries": result.metrics_registry_entries, 1332 "metrics_source": result.metrics_source, 1333 "metrics_error": result.metrics_error, 1334 "version_indexes_written": result.version_indexes_written, 1335 "specs_secrets_mask_properties": result.specs_secrets_mask_properties, 1336 "errors": result.errors, 1337 "dry_run": result.dry_run, 1338 } 1339 ) 1340 1341 if result.status == "success" or result.status == "dry-run": 1342 print_success(result.summary()) 1343 else: 1344 exit_with_error( 1345 f"Compile completed with {len(result.errors)} error(s): " 1346 + "; ".join(result.errors), 1347 code=1, 1348 ) 1349 1350 1351# ============================================================================= 1352# DELETE-DEV-LATEST COMMAND 1353# ============================================================================= 1354 1355 1356@store_app.command(name="delete-dev-latest") 1357def delete_dev_latest_cmd( 1358 store: Annotated[ 1359 str, 1360 Parameter( 1361 help="Store target (must begin with 'coral:dev').", 1362 ), 1363 ], 1364 *, 1365 connector_name: Annotated[ 1366 tuple[str, ...] | None, 1367 Parameter( 1368 help="Only delete latest/ for these connectors (can be repeated).", 1369 ), 1370 ] = None, 1371 dry_run: Annotated[ 1372 bool, 1373 Parameter(help="Show what would be done without deleting."), 1374 ] = False, 1375) -> None: 1376 """Delete all latest/ directories from a dev registry store. 1377 1378 Discovers every connector that has a `latest/` directory and 1379 deletes each one in parallel using a thread pool. 1380 1381 This is useful before a full re-compile to prove that latest/ 1382 directories can be correctly regenerated from versioned data. 1383 1384 Only dev stores are allowed (store must begin with 'coral:dev'). 1385 1386 Requires GCS_CREDENTIALS environment variable to be set. 1387 1388 Examples: 1389 airbyte-ops registry store delete-dev-latest --store coral:dev --dry-run 1390 1391 airbyte-ops registry store delete-dev-latest --store coral:dev/aj-test100 1392 1393 airbyte-ops registry store delete-dev-latest --store coral:dev \\ 1394 --connector-name source-faker --connector-name destination-bigquery 1395 """ 1396 if not store.startswith("coral:dev"): 1397 exit_with_error( 1398 "delete-dev-latest only supports dev stores " 1399 f"(store must begin with 'coral:dev', got '{store}').", 1400 code=1, 1401 ) 1402 1403 registry = _resolve_store(store) 1404 1405 result = registry.delete_dev_latest( 1406 connector_name=list(connector_name) if connector_name else None, 1407 dry_run=dry_run, 1408 ) 1409 1410 print_json( 1411 { 1412 "status": result.status, 1413 "target": result.target, 1414 "connectors_found": result.connectors_found, 1415 "latest_dirs_deleted": result.latest_dirs_deleted, 1416 "errors": result.errors, 1417 "dry_run": result.dry_run, 1418 } 1419 ) 1420 1421 if result.status in ("success", "dry-run"): 1422 print_success(result.summary()) 1423 else: 1424 exit_with_error( 1425 f"Delete completed with {len(result.errors)} error(s): " 1426 + "; ".join(result.errors[:5]), 1427 code=1, 1428 ) 1429 1430 1431# ============================================================================= 1432# STORE COMPARE COMMAND 1433# ============================================================================= 1434 1435 1436@store_app.command(name="compare") 1437def compare_cmd( 1438 store: Annotated[ 1439 str, 1440 Parameter( 1441 help="Store target being evaluated (e.g. 'coral:dev/20260306-mirror-compile').", 1442 ), 1443 ], 1444 reference_store: Annotated[ 1445 str, 1446 Parameter( 1447 help="Known-good reference store to compare against.", 1448 ), 1449 ], 1450 *, 1451 connector_name: Annotated[ 1452 tuple[str, ...] | None, 1453 Parameter( 1454 help="Only compare these connectors (can be repeated).", 1455 ), 1456 ] = None, 1457 with_artifacts: Annotated[ 1458 bool, 1459 Parameter( 1460 help="Compare per-connector artifact files " 1461 "(metadata.yaml, cloud.json, oss.json, spec.json).", 1462 negative="--no-artifacts", 1463 ), 1464 ] = True, 1465 with_indexes: Annotated[ 1466 bool, 1467 Parameter( 1468 help="Compare global registry index files " 1469 "(cloud_registry.json, oss_registry.json).", 1470 negative="--no-indexes", 1471 ), 1472 ] = True, 1473) -> None: 1474 """Compare a store against a reference store and report differences. 1475 1476 Evaluates the `--store` target against `--reference-store` and reports 1477 per-connector artifact diffs and global index diffs. 1478 1479 Requires GCS_CREDENTIALS environment variable to be set. 1480 1481 Examples: 1482 airbyte-ops registry store compare --store coral:dev/20260306-mirror \\ 1483 --reference-store coral:prod 1484 1485 airbyte-ops registry store compare --store coral:dev/my-test \\ 1486 --connector-name source-faker --no-indexes 1487 1488 airbyte-ops registry store compare --store coral:dev/my-test \\ 1489 --no-artifacts 1490 """ 1491 store_target = _resolve_store(store) 1492 ref_target = _resolve_store(reference_store) 1493 1494 store_prefix = f"{store_target.prefix}/" if store_target.prefix else "" 1495 ref_prefix = f"{ref_target.prefix}/" if ref_target.prefix else "" 1496 1497 result = compare_stores( 1498 store_bucket=store_target.bucket_name, 1499 store_prefix=store_prefix, 1500 reference_bucket=ref_target.bucket_name, 1501 reference_prefix=ref_prefix, 1502 connector_name=list(connector_name) if connector_name else None, 1503 with_artifacts=with_artifacts, 1504 with_indexes=with_indexes, 1505 ) 1506 1507 print_json(result.to_dict()) 1508 1509 if result.status == "match": 1510 print_success(result.summary()) 1511 elif result.status == "differences-found": 1512 error_console.print(f"[yellow]{result.summary()}[/yellow]") 1513 1514 # Print a concise per-connector diff summary 1515 for diff in result.connector_diffs: 1516 if diff.status in ("only_in_store", "only_in_reference"): 1517 error_console.print(f" {diff.connector}: {diff.status}") 1518 else: 1519 for ad in diff.artifact_diffs: 1520 error_console.print( 1521 f" {diff.connector}/{ad.file}: {ad.status}" 1522 + (f" ({ad.details})" if ad.details else "") 1523 ) 1524 1525 for idx_diff in result.index_diffs: 1526 if idx_diff.status != "match": 1527 error_console.print( 1528 f" [index] {idx_diff.file}: {idx_diff.status}" 1529 + ( 1530 f" (store={idx_diff.entry_count_store}," 1531 f" ref={idx_diff.entry_count_reference})" 1532 if idx_diff.entry_count_store or idx_diff.entry_count_reference 1533 else "" 1534 ) 1535 ) 1536 1537 sys.exit(1) 1538 else: 1539 exit_with_error( 1540 f"Compare completed with {len(result.errors)} error(s): " 1541 + "; ".join(result.errors[:5]), 1542 code=1, 1543 )