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