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 releases get - Read release attribution info for one connector version airbyte-ops registry connector-version releases list - List release attribution info for connector versions 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 release-attribution build - Build a Git release attribution index 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.release-attribution: Connector release attribution from Git history.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 releases
Inspect which pull request and author released each connector version.
Commands:
get: Read which pull request and author released one connector version.list: List which pull request and author released each connector version.
airbyte-ops registry connector-version releases get
airbyte-ops registry connector-version releases get CONNECTOR VERSION [ARGS]
Read which pull request and author released one connector version.
Parameters:
CONNECTOR, --connector: Connector name, for examplesource-faker. [required]VERSION, --version: Connector version, for example7.2.1. [required]STORE, --store: Registry store target, for examplecoral:prod. [default: coral:prod]
airbyte-ops registry connector-version releases list
airbyte-ops registry connector-version releases list CONNECTOR [ARGS]
List which pull request and author released each connector version.
The default text output labels human and bot authors explicitly. Listing
reads only the version index unless --with-metadata-fallback is supplied.
Parameters:
CONNECTOR, --connector: Connector name, for examplesource-faker. [required]LIMIT, --limit: Maximum number of versions to return, newest first. [default: 50]WITH-METADATA-FALLBACK, --with-metadata-fallback, --no-metadata-fallback: Read metadata for entries without release attribution info. [default: False]FORMAT, --format: Output format: 'json' for machine-readable data, 'text' for a readable PR/author summary. [choices: json, text] [default: text]STORE, --store: Registry store target, for examplecoral:prod. [default: coral:prod]
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 in cloud mode (DEPLOYMENT_MODE=cloud,
AIRBYTE_EDITION=CLOUD) and oss mode (DEPLOYMENT_MODE=oss,
AIRBYTE_EDITION=COMMUNITY) 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]PR-NUMBER, --pr-number: Pull request number for prerelease attribution. When provided, it overrides the commit subject.WITH-GITHUB-LOOKUP, --with-github-lookup, --no-github-lookup: Resolve publish PR author metadata through GitHub GraphQL (default: enabled). Use --no-github-lookup for offline runs. [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 connector-version list-yanked
airbyte-ops registry connector-version list-yanked [OPTIONS] STORE
List all yanked connector versions in the registry.
Returns one entry per active version-yank.yml marker, sorted by connector
name then version. Historical version-unyanked-*.yml audit markers are
ignored.
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
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-status
airbyte-ops registry connector-version yank-status NAME VERSION STORE
Show the active yank marker for a single connector version.
Prints the marker's parsed fields (yanked_at, reason, approval_url)
when the version currently has an active version-yank.yml marker, or
reports that the version is not yanked. Historical
version-unyanked-*.yml audit markers are ignored.
Requires GCS_CREDENTIALS environment variable to be set.
Parameters:
NAME, --name: Connector name (e.g., 'source-faker'). [required]VERSION, --version: Version to inspect (e.g., '1.2.3'). [required]STORE, --store: Store target (e.g. 'coral:dev', 'coral:prod'). [required]
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.
With --with-full-restate, re-reads every version's metadata.yaml to
re-derive release attribution. This is an expensive operation at roughly
33.5k reads for the full registry.
With --release-attribution-index, seeds historical release blocks
directly into versions.json from a Phase 1 Git backfill index.
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]--output-store: Write compiled artifacts to<registry>:local:<path>; omit the path to allocate a temporary directory.--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]--with-full-restate, --no-full-restate: Re-derive every version's release attribution from metadata.yaml. Expensive for all connectors (~33.5k reads). Without --release-attribution-index, historical blocks unavailable from metadata.yaml are discarded. [default: False]--release-attribution-index: Seed missing release blocks from a Phase 1 attribution index without rewriting historical metadata.yaml files.
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, composite_registry.json). [default: True]--assert-stable, --no-assert-stable: Make the exit code reflect registry safety assertions only. [default: False]--with-volatile-fields, --no-with-volatile-fields: Include default volatile-field differences. [default: False]--html-report: Write a self-contained HTML report to this path.--text-report: Write a plain-text diff report to this path.
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]
airbyte-ops registry release-attribution
Connector release attribution from Git history.
airbyte-ops registry release-attribution build
airbyte-ops registry release-attribution build [OPTIONS] REPO-PATH OUTPUT-PATH
Build a connector release attribution index from Git history.
Parameters:
REPO-PATH, --repo-path: Path to a full airbyte monorepo checkout. [required]OUTPUT-PATH, --output-path: Local JSON path to write the attribution index. [required]--connector: Limit the scan to one connector.--with-github-enrichment, --no-github-enrichment: Enrich missing PR author metadata through GitHub GraphQL. [default: False]
airbyte-ops registry release-attribution show
airbyte-ops registry release-attribution show INDEX-PATH CONNECTOR VERSION
Show attribution for one connector version.
Parameters:
INDEX-PATH, --index-path: Attribution index JSON path. [required]CONNECTOR, --connector: Connector name, for examplesource-faker. [required]VERSION, --version: Connector version, for example7.2.1. [required]
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 releases get - Read release attribution info for one connector version 15 airbyte-ops registry connector-version releases list - List release attribution info for connector versions 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 release-attribution build - Build a Git release attribution index 21 airbyte-ops registry marketing-stubs sync --store coral:prod - Sync connector_stubs.json to GCS 22 airbyte-ops registry marketing-stubs check --store coral:prod - Compare local file with GCS 23 24## CLI reference 25 26The commands below are regenerated by `poe docs-generate` via cyclopts's 27programmatic docs API; see `docs/generate_cli.py`. 28 29.. include:: ../../../docs/generated/cli/registry.md 30 :start-line: 2 31""" 32 33from __future__ import annotations 34 35# Hide Python-level members from the pdoc page for this module; the rendered 36# docs for this CLI group come entirely from the grafted `.. include::` in 37# the module docstring above. 38__all__: list[str] = [] 39 40import sys 41from pathlib import Path 42from typing import Annotated, Any, Literal 43 44import yaml 45from cyclopts import Parameter 46from fastmcp_extensions.cli import ( 47 exit_with_error, 48 print_error, 49 print_json, 50 print_success, 51) 52from rich.console import Console 53 54from airbyte_ops_mcp.cli._base import App, app 55from airbyte_ops_mcp.github_api import ( 56 get_file_contents_at_ref, 57 resolve_default_github_token, 58) 59from airbyte_ops_mcp.mcp.connector_versions import ( 60 compute_prerelease_docker_image_tag, 61) 62from airbyte_ops_mcp.registry import ( 63 resolve_registry_store, 64) 65from airbyte_ops_mcp.registry._constants import PROD_METADATA_SERVICE_BUCKET_NAME 66from airbyte_ops_mcp.registry._enums import ( 67 ConnectorLanguage, 68 ConnectorType, 69 SupportLevel, 70) 71from airbyte_ops_mcp.registry.compare import ( 72 DEFAULT_TOLERATED_PATHS, 73 compare_stores, 74 write_html_report, 75 write_text_report, 76) 77from airbyte_ops_mcp.registry.connector_stubs import ( 78 CONNECTOR_STUBS_FILE, 79) 80from airbyte_ops_mcp.registry.generate import generate_version_artifacts 81from airbyte_ops_mcp.registry.operations import _read_cloud_registry_index 82from airbyte_ops_mcp.registry.progressive_rollout_status import ( 83 get_connector_rollout_status, 84) 85from airbyte_ops_mcp.registry.registry_store_base import ( 86 Registry, 87 get_registry, 88) 89from airbyte_ops_mcp.registry.release_attribution import ( 90 ACTOR_BOT, 91 ACTOR_USER, 92 KIND_BOT, 93 ReleaseAttributionListResult, 94 enrich_release_attribution, 95 list_release_attribution, 96 lookup_release_attribution, 97 read_release_attribution_index, 98 scan_release_attribution, 99 write_release_attribution_index, 100) 101from airbyte_ops_mcp.registry.store import RegistryStore 102 103error_console = Console(stderr=True) 104 105# Create the registry sub-app 106registry_app = App( 107 name="registry", help="Connector registry operations (GCS metadata service)." 108) 109app.command(registry_app) 110 111# Create the connector sub-app under registry 112connector_app = App(name="connector", help="Connector listing operations.") 113registry_app.command(connector_app) 114 115# Create the connector-version sub-app under registry 116connector_version_app = App( 117 name="connector-version", 118 help="Connector version operations (list, yank, unyank, artifacts).", 119) 120registry_app.command(connector_version_app) 121 122# Create the metadata sub-app under connector-version 123metadata_app = App( 124 name="metadata", 125 help="Connector metadata inspection.", 126) 127connector_version_app.command(metadata_app) 128 129# Create the releases sub-app under connector-version 130releases_app = App( 131 name="releases", 132 help="Inspect which pull request and author released each connector version.", 133) 134connector_version_app.command(releases_app) 135 136# Create the artifacts sub-app under connector-version 137artifacts_app = App( 138 name="artifacts", 139 help="Version artifact generation and publishing.", 140) 141connector_version_app.command(artifacts_app) 142 143# Create the store sub-app under registry (whole-registry operations) 144store_app = App( 145 name="store", 146 help="Whole-registry store operations (mirror, compile).", 147) 148registry_app.command(store_app) 149 150# Create the progressive-rollout sub-app under registry 151progressive_rollout_app = App( 152 name="progressive-rollout", 153 help="Progressive rollout lifecycle operations.", 154) 155registry_app.command(progressive_rollout_app) 156 157# Create the marketing-stubs sub-app under registry (for whole-file GCS operations) 158marketing_stubs_app = App( 159 name="marketing-stubs", 160 help="Marketing connector stubs GCS operations (whole-file sync).", 161) 162registry_app.command(marketing_stubs_app) 163 164# Create the release-attribution sub-app under registry 165release_attribution_app = App( 166 name="release-attribution", 167 help="Connector release attribution from Git history.", 168) 169registry_app.command(release_attribution_app) 170 171 172AIRBYTE_REPO_OWNER = "airbytehq" 173AIRBYTE_ENTERPRISE_REPO_NAME = "airbyte-enterprise" 174AIRBYTE_REPO_NAME = "airbyte" 175CONNECTOR_PATH_PREFIX = "airbyte-integrations/connectors" 176 177 178@release_attribution_app.command(name="build") 179def build_release_attribution_cmd( 180 repo_path: Annotated[ 181 Path, 182 Parameter(help="Path to a full airbyte monorepo checkout."), 183 ], 184 output_path: Annotated[ 185 Path, 186 Parameter(help="Local JSON path to write the attribution index."), 187 ], 188 *, 189 connector: Annotated[ 190 str | None, 191 Parameter(help="Limit the scan to one connector."), 192 ] = None, 193 with_github_enrichment: Annotated[ 194 bool, 195 Parameter( 196 help="Enrich missing PR author metadata through GitHub GraphQL.", 197 negative="--no-github-enrichment", 198 ), 199 ] = False, 200) -> None: 201 """Build a connector release attribution index from Git history.""" 202 try: 203 index = scan_release_attribution(repo_path, connector=connector) 204 if with_github_enrichment: 205 index = enrich_release_attribution(index) 206 write_release_attribution_index(index, output_path) 207 except (OSError, ValueError) as exc: 208 exit_with_error(str(exc)) 209 summary = index.summary 210 print_json( 211 { 212 **summary.model_dump(), 213 "pr_percentage": summary.pr_percentage, 214 "author_login_percentage": summary.author_login_percentage, 215 "output_path": str(output_path), 216 } 217 ) 218 219 220@release_attribution_app.command(name="show") 221def show_release_attribution_cmd( 222 index_path: Annotated[ 223 Path, 224 Parameter(help="Attribution index JSON path."), 225 ], 226 connector: Annotated[ 227 str, 228 Parameter(help="Connector name, for example `source-faker`."), 229 ], 230 version: Annotated[ 231 str, 232 Parameter(help="Connector version, for example `7.2.1`."), 233 ], 234) -> None: 235 """Show attribution for one connector version.""" 236 try: 237 index = read_release_attribution_index(index_path) 238 except (OSError, ValueError) as exc: 239 exit_with_error(f"Could not read attribution index {index_path}: {exc}") 240 if connector not in index.connectors: 241 exit_with_error( 242 f"Attribution not found for connector `{connector}` in `{index_path}`." 243 ) 244 if version not in index.connectors[connector]: 245 exit_with_error( 246 f"Attribution not found for `{connector}@{version}` in `{index_path}`." 247 ) 248 record = index.connectors[connector][version] 249 print_json(record.model_dump(mode="json")) 250 251 252@releases_app.command(name="get") 253def release_attribution_get_cmd( 254 connector: Annotated[ 255 str, 256 Parameter(help="Connector name, for example `source-faker`."), 257 ], 258 version: Annotated[ 259 str, 260 Parameter(help="Connector version, for example `7.2.1`."), 261 ], 262 store: Annotated[ 263 str, 264 Parameter(help="Registry store target, for example `coral:prod`."), 265 ] = "coral:prod", 266) -> None: 267 """Read which pull request and author released one connector version.""" 268 try: 269 result = lookup_release_attribution( 270 resolve_registry_store(store=store), 271 connector, 272 version, 273 ) 274 except (OSError, ValueError) as exc: 275 exit_with_error(str(exc)) 276 print_json(result.model_dump(mode="json")) 277 278 279def _print_release_attribution_list( 280 result: ReleaseAttributionListResult, 281 *, 282 format: Literal["json", "text"], 283) -> None: 284 """Render release attribution as JSON or an author-focused text table.""" 285 if format == "json": 286 print_json(result.model_dump(mode="json")) 287 return 288 289 print("VERSION\tSTATUS\tAUTHOR TYPE\tCONTACT\tPULL REQUEST\tRELEASED AT") 290 for item in result.items: 291 attribution = item.attribution 292 if attribution is None: 293 author_type = "UNKNOWN" 294 contact = "no attribution" 295 pull_request = "-" 296 released_at = "-" 297 else: 298 author_type = { 299 ACTOR_USER: "HUMAN", 300 ACTOR_BOT: "BOT", 301 }.get(attribution.pr_author_type or "", "UNKNOWN") 302 if attribution.attributed_to and attribution.attributed_to_kind != KIND_BOT: 303 contact = f"@{attribution.attributed_to}" 304 elif ( 305 attribution.attributed_to_kind == KIND_BOT 306 or attribution.pr_author_type == ACTOR_BOT 307 ): 308 contact = "none (bot-authored)" 309 elif attribution.pr_author_type == ACTOR_USER: 310 contact = "none (human login unavailable)" 311 else: 312 contact = "none (author unknown)" 313 pull_request = ( 314 f"#{attribution.pr_number}" 315 if attribution.pr_number is not None 316 else "-" 317 ) 318 released_at = ( 319 attribution.released_at.isoformat() 320 if attribution.released_at is not None 321 else "-" 322 ) 323 print( 324 f"{item.version}\t{item.status}\t{author_type}\t{contact}\t" 325 f"{pull_request}\t{released_at}" 326 ) 327 if result.error: 328 print(f"ERROR\t{result.error}") 329 330 331@releases_app.command(name="list") 332def release_attribution_list_cmd( 333 connector: Annotated[ 334 str, 335 Parameter(help="Connector name, for example `source-faker`."), 336 ], 337 limit: Annotated[ 338 int, 339 Parameter(help="Maximum number of versions to return, newest first."), 340 ] = 50, 341 with_metadata_fallback: Annotated[ 342 bool, 343 Parameter( 344 help="Read metadata for entries without release attribution info.", 345 negative="--no-metadata-fallback", 346 ), 347 ] = False, 348 format: Annotated[ 349 Literal["json", "text"], 350 Parameter( 351 help="Output format: 'json' for machine-readable data, 'text' for a readable PR/author summary." 352 ), 353 ] = "text", 354 store: Annotated[ 355 str, 356 Parameter(help="Registry store target, for example `coral:prod`."), 357 ] = "coral:prod", 358) -> None: 359 """List which pull request and author released each connector version. 360 361 The default text output labels human and bot authors explicitly. Listing 362 reads only the version index unless `--with-metadata-fallback` is supplied. 363 """ 364 try: 365 result = list_release_attribution( 366 resolve_registry_store(store=store), 367 connector, 368 limit=limit, 369 with_metadata_fallback=with_metadata_fallback, 370 ) 371 except (OSError, ValueError) as exc: 372 exit_with_error(str(exc)) 373 _print_release_attribution_list(result, format=format) 374 375 376def _resolve_store(store: str) -> Registry: 377 """Resolve `--store` to a `Registry` instance. 378 379 Wraps `resolve_registry_store` and `get_registry`, converting 380 `ValueError` into a user-friendly CLI error via `exit_with_error`. 381 """ 382 try: 383 resolved = resolve_registry_store(store=store) 384 except ValueError as e: 385 exit_with_error(str(e)) 386 raise # unreachable; satisfies type checker 387 return get_registry(resolved) 388 389 390def _get_connector_version_from_github( 391 connector_name: str, 392 ref: str, 393 token: str | None = None, 394) -> str | None: 395 """Fetch connector version from metadata.yaml via GitHub API. 396 397 Args: 398 connector_name: Connector name (e.g., "source-github") 399 ref: Git ref (commit SHA, branch name, or tag) 400 token: GitHub API token (optional for public repos) 401 402 Returns: 403 Version string from metadata.yaml, or None if not found. 404 """ 405 path = f"{CONNECTOR_PATH_PREFIX}/{connector_name}/metadata.yaml" 406 contents = get_file_contents_at_ref( 407 owner=AIRBYTE_REPO_OWNER, 408 repo=AIRBYTE_REPO_NAME, 409 path=path, 410 ref=ref, 411 token=token, 412 ) 413 if contents is None: 414 return None 415 416 metadata = yaml.safe_load(contents) 417 return metadata.get("data", {}).get("dockerImageTag") 418 419 420@connector_version_app.command(name="next") 421def compute_next_version( 422 name: Annotated[ 423 str, 424 Parameter(help="Connector name (e.g., 'source-github')."), 425 ], 426 sha: Annotated[ 427 str, 428 Parameter(help="Git commit SHA (full or at least 7 characters)."), 429 ], 430 base_version: Annotated[ 431 str | None, 432 Parameter( 433 help="Base version override. If not provided, fetched from metadata.yaml at the given SHA." 434 ), 435 ] = None, 436) -> None: 437 """Compute the next version tag for a connector. 438 439 Outputs the version tag to stdout for easy capture in shell scripts. 440 This is the single source of truth for pre-release version format. 441 442 The command fetches the connector's metadata.yaml from GitHub at the given SHA 443 to determine the base version. It also compares against the master branch and 444 prints a warning to stderr if no version bump is detected. 445 446 If --base-version is provided, it is used directly instead of fetching from GitHub. 447 448 Examples: 449 airbyte-ops registry connector-version next --name source-github --sha abcdef1234567 450 # Output: 1.2.3-preview.abcdef1 451 452 airbyte-ops registry connector-version next --name source-github --sha abcdef1234567 --base-version 1.2.3 453 # Output: 1.2.3-preview.abcdef1 (uses provided version, skips GitHub API) 454 """ 455 # Try to get a GitHub token (optional, but helps avoid rate limiting) 456 # Token resolution may fail if no token is configured, which is fine for public repos 457 token: str | None = None 458 token = resolve_default_github_token(allow_none=True) 459 460 # Determine base version 461 version: str 462 if base_version: 463 version = base_version 464 else: 465 # Fetch version from metadata.yaml at the given SHA 466 fetched_version = _get_connector_version_from_github(name, sha, token) 467 if fetched_version is None: 468 print( 469 f"Error: Could not fetch metadata.yaml for {name} at ref {sha}", 470 file=sys.stderr, 471 ) 472 sys.exit(1) 473 version = fetched_version 474 475 # Compare with master branch version and warn if no bump detected 476 master_version = _get_connector_version_from_github(name, "master", token) 477 if master_version and master_version == version: 478 print( 479 f"Warning: No version bump detected for {name}. " 480 f"Version {version} matches master branch.", 481 file=sys.stderr, 482 ) 483 484 # Compute and output the prerelease tag 485 tag = compute_prerelease_docker_image_tag(version, sha) 486 print(tag) 487 488 489@progressive_rollout_app.command(name="list") 490def progressive_rollout_list() -> None: 491 """List all connectors with active release candidates in the compiled registry.""" 492 try: 493 entries = _read_cloud_registry_index( 494 bucket_name=PROD_METADATA_SERVICE_BUCKET_NAME, 495 ) 496 except FileNotFoundError as e: 497 exit_with_error(str(e)) 498 499 rc_entries: list[dict[str, Any]] = [] 500 for entry in entries: 501 releases = entry.get("releases", {}) 502 candidates = releases.get("releaseCandidates") 503 if not candidates: 504 continue 505 docker_repo = entry.get("dockerRepository", "") 506 connector_name = ( 507 docker_repo.split("/", 1)[1] if "/" in docker_repo else docker_repo 508 ) 509 rc_entries.append( 510 { 511 "connector": connector_name, 512 "rc_versions": list(candidates.keys()), 513 } 514 ) 515 516 rc_entries.sort(key=lambda x: x["connector"]) 517 print_json(rc_entries) 518 519 520@progressive_rollout_app.command(name="status") 521def progressive_rollout_status( 522 name: Annotated[ 523 str, 524 Parameter(help="Connector technical name (e.g., source-github)."), 525 ], 526 *, 527 repo_path: Annotated[ 528 Path, 529 Parameter(help="Path to the Airbyte monorepo. Defaults to current directory."), 530 ] = Path.cwd(), 531 active_only: Annotated[ 532 bool, 533 Parameter( 534 help="Only return active non-terminal rollouts.", 535 negative="--with-terminal", 536 ), 537 ] = True, 538 limit: Annotated[ 539 int, 540 Parameter(help="Maximum number of rollout records to return."), 541 ] = 100, 542) -> None: 543 """Get progressive rollout status for a connector.""" 544 if not repo_path.exists(): 545 exit_with_error(f"Repository path not found: {repo_path}") 546 try: 547 result = get_connector_rollout_status( 548 repo_path=repo_path, 549 connector_name=name, 550 active_only=active_only, 551 limit=limit, 552 ) 553 except (FileNotFoundError, ValueError) as e: 554 exit_with_error(str(e)) 555 556 print_json(result.model_dump()) 557 558 559@progressive_rollout_app.command(name="finalize-marker") 560def progressive_rollout_finalize_marker( 561 name: Annotated[ 562 str, 563 Parameter(help="Connector technical name (e.g., source-github)."), 564 ], 565 store: Annotated[ 566 str, 567 Parameter(help="Store target (e.g. 'coral:dev', 'coral:prod')."), 568 ], 569 outcome: Annotated[ 570 Literal["promoted", "aborted"], 571 Parameter(help="Marker outcome used in the dated audit filename."), 572 ], 573 *, 574 version: Annotated[ 575 str | None, 576 Parameter( 577 help="Version to finalize. If omitted, exactly one active marker must exist." 578 ), 579 ] = None, 580 dry_run: Annotated[ 581 bool, 582 Parameter(help="Show what would be done without making changes."), 583 ] = False, 584) -> None: 585 """Rename an active `progressive-rollout.yml` marker to an audit marker.""" 586 registry = _resolve_store(store) 587 result = registry.finalize_progressive_rollout_marker( 588 connector_name=name, 589 outcome=outcome, 590 version=version, 591 dry_run=dry_run, 592 ) 593 print_json(result.to_dict()) 594 if result.success: 595 print_success(result.message) 596 else: 597 exit_with_error(result.message, code=1) 598 599 600# ============================================================================= 601# REGISTRY I/O - READ COMMANDS 602# ============================================================================= 603 604 605@metadata_app.command(name="get") 606def get_connector_version_metadata_cmd( 607 name: Annotated[ 608 str, 609 Parameter( 610 help="Connector name (e.g., 'source-faker', 'destination-postgres')." 611 ), 612 ], 613 store: Annotated[ 614 str, 615 Parameter( 616 help="Store target (e.g. 'coral:dev', 'coral:prod').", 617 ), 618 ], 619 *, 620 version: Annotated[ 621 str, 622 Parameter(help="Version to read (e.g., 'latest', '1.2.3')."), 623 ] = "latest", 624 format: Annotated[ 625 Literal["json", "raw"], 626 Parameter(help="Output format: 'json' for JSON, 'raw' for YAML."), 627 ] = "json", 628) -> None: 629 """Read a connector version's metadata from the registry. 630 631 Returns the full metadata.yaml content for a connector at the specified version. 632 633 Requires GCS_CREDENTIALS environment variable to be set. 634 635 Examples: 636 airbyte-ops registry connector-version metadata get --name source-faker --store coral:dev 637 airbyte-ops registry connector-version metadata get --name source-faker --store coral:dev --version 6.2.38 638 airbyte-ops registry connector-version metadata get --name source-faker --store coral:prod 639 """ 640 registry = _resolve_store(store) 641 642 try: 643 metadata = registry.get_connector_metadata( 644 connector_name=name, 645 version=version, 646 ) 647 except FileNotFoundError as e: 648 exit_with_error(str(e), code=1) 649 except Exception as e: 650 exit_with_error(f"Error reading metadata: {e}", code=1) 651 652 if format == "json": 653 print_json(metadata) 654 else: 655 print(yaml.dump(metadata, default_flow_style=False)) 656 657 658@connector_app.command(name="list") 659def list_connectors_cmd( 660 store: Annotated[ 661 str, 662 Parameter( 663 help="Store target (e.g. 'coral:dev', 'coral:prod').", 664 ), 665 ], 666 *, 667 certified_only: Annotated[ 668 bool, 669 Parameter(help="Include only certified connectors."), 670 ] = False, 671 support_level: Annotated[ 672 SupportLevel | None, 673 Parameter( 674 help=( 675 "Exact support level to match " 676 "(e.g. `certified`, `community`, `archived`)." 677 ) 678 ), 679 ] = None, 680 min_support_level: Annotated[ 681 SupportLevel | None, 682 Parameter( 683 help=( 684 "Minimum support level (inclusive). " 685 "Levels from lowest to highest: `archived`, `community`, `certified`." 686 ) 687 ), 688 ] = None, 689 connector_type: Annotated[ 690 ConnectorType | None, 691 Parameter(help="Filter by connector type: `source` or `destination`."), 692 ] = None, 693 language: Annotated[ 694 ConnectorLanguage | None, 695 Parameter( 696 help=( 697 "Filter by implementation language " 698 "(e.g. `python`, `java`, `manifest-only`)." 699 ) 700 ), 701 ] = None, 702 format: Annotated[ 703 Literal["json", "text"], 704 Parameter( 705 help="Output format: 'json' for JSON array, 'text' for newline-separated." 706 ), 707 ] = "json", 708) -> None: 709 """List connectors in the registry. 710 711 When filters are applied, reads the compiled `cloud_registry.json` index 712 for fast lookups. Without filters, falls back to scanning individual 713 metadata blobs (captures all connectors including OSS-only). 714 715 Requires GCS_CREDENTIALS environment variable to be set. 716 """ 717 registry = _resolve_store(store) 718 719 # `--certified-only` is sugar for `--support-level certified`. 720 effective_support_level = support_level 721 if certified_only: 722 if support_level and support_level != SupportLevel.CERTIFIED: 723 exit_with_error( 724 "`--certified-only` conflicts with `--support-level " 725 f"{support_level}`. Use one or the other.", 726 code=1, 727 ) 728 effective_support_level = SupportLevel.CERTIFIED 729 730 try: 731 connectors = registry.list_connectors( 732 support_level=effective_support_level, 733 min_support_level=min_support_level, 734 connector_type=connector_type, 735 language=language, 736 ) 737 except Exception as e: 738 exit_with_error(f"Error listing connectors: {e}", code=1) 739 740 if format == "json": 741 print_json({"connectors": connectors, "count": len(connectors)}) 742 else: 743 for connector in connectors: 744 print(connector) 745 746 747@connector_version_app.command(name="list") 748def list_connector_versions_cmd( 749 name: Annotated[ 750 str, 751 Parameter(help="Connector name (e.g., 'source-faker')."), 752 ], 753 store: Annotated[ 754 str, 755 Parameter( 756 help="Store target (e.g. 'coral:dev', 'coral:prod').", 757 ), 758 ], 759 *, 760 format: Annotated[ 761 Literal["json", "text"], 762 Parameter( 763 help="Output format: 'json' for JSON array, 'text' for newline-separated." 764 ), 765 ] = "json", 766) -> None: 767 """List all versions of a connector in the registry. 768 769 Scans the registry bucket to find all versions of a specific connector. 770 771 Requires GCS_CREDENTIALS environment variable to be set. 772 """ 773 registry = _resolve_store(store) 774 775 try: 776 versions = registry.list_connector_versions( 777 connector_name=name, 778 ) 779 except Exception as e: 780 exit_with_error(f"Error listing versions: {e}", code=1) 781 782 if format == "json": 783 print_json({"connector": name, "versions": versions, "count": len(versions)}) 784 else: 785 for v in versions: 786 print(v) 787 788 789@marketing_stubs_app.command(name="check") 790def marketing_stubs_check( 791 store: Annotated[ 792 str, 793 Parameter( 794 help="Store target (e.g. 'coral:dev', 'coral:prod').", 795 ), 796 ], 797 *, 798 repo_root: Annotated[ 799 Path, 800 Parameter( 801 help="Path to the airbyte-enterprise repository root. Defaults to current directory." 802 ), 803 ] = Path.cwd(), 804) -> None: 805 """Compare local connector_stubs.json with the version in GCS. 806 807 This command reads the entire local connector_stubs.json file and compares it 808 with the version currently published in GCS. 809 810 Exit codes: 811 0: Local file matches GCS (check passed) 812 1: Differences found (check failed) 813 814 Output: 815 STDOUT: JSON representation of the comparison result 816 STDERR: Informational messages and comparison details 817 818 Example: 819 airbyte-ops registry marketing-stubs check --store coral:prod --repo-root /path/to/airbyte-enterprise 820 airbyte-ops registry marketing-stubs check --store coral:dev 821 """ 822 registry = _resolve_store(store) 823 824 try: 825 result = registry.marketing_stubs_check(repo_root=repo_root) 826 except FileNotFoundError as e: 827 exit_with_error(str(e)) 828 except ValueError as e: 829 exit_with_error(str(e)) 830 831 error_console.print( 832 f"Comparing local {CONNECTOR_STUBS_FILE} with {result.get('bucket', '')}/{result.get('path', '')}" 833 ) 834 835 differences = result.get("differences", []) 836 if differences: 837 error_console.print( 838 f"[yellow]Warning:[/yellow] {len(differences)} difference(s) found:" 839 ) 840 for diff in differences: 841 error_console.print(f" {diff['id']}: {diff['status']}") 842 print_json(result) 843 sys.exit(1) 844 845 error_console.print( 846 f"[green]Local file is in sync with GCS ({result.get('local_count', 0)} stubs)[/green]" 847 ) 848 print_json(result) 849 850 851@marketing_stubs_app.command(name="sync") 852def marketing_stubs_sync( 853 store: Annotated[ 854 str, 855 Parameter( 856 help="Store target (e.g. 'coral:dev', 'coral:prod').", 857 ), 858 ], 859 *, 860 repo_root: Annotated[ 861 Path, 862 Parameter( 863 help="Path to the airbyte-enterprise repository root. Defaults to current directory." 864 ), 865 ] = Path.cwd(), 866 dry_run: Annotated[ 867 bool, 868 Parameter(help="Show what would be uploaded without making changes."), 869 ] = False, 870) -> None: 871 """Sync local connector_stubs.json to GCS. 872 873 This command uploads the entire local connector_stubs.json file to GCS, 874 replacing the existing file. Use this after merging changes to master 875 in the airbyte-enterprise repository. 876 877 Exit codes: 878 0: Sync successful (or dry-run completed) 879 1: Error (file not found, validation failed, etc.) 880 881 Output: 882 STDOUT: JSON representation of the sync result 883 STDERR: Informational messages and status updates 884 885 Example: 886 airbyte-ops registry marketing-stubs sync --store coral:prod --repo-root /path/to/airbyte-enterprise 887 airbyte-ops registry marketing-stubs sync --store coral:dev 888 airbyte-ops registry marketing-stubs sync --store coral:dev --dry-run 889 """ 890 registry = _resolve_store(store) 891 892 try: 893 result = registry.marketing_stubs_sync( 894 repo_root=repo_root, 895 dry_run=dry_run, 896 ) 897 except FileNotFoundError as e: 898 exit_with_error(str(e)) 899 except ValueError as e: 900 exit_with_error(str(e)) 901 902 bucket_name = result.get("bucket", "") 903 path = result.get("path", "") 904 stub_count = result.get("stub_count", 0) 905 906 if dry_run: 907 error_console.print( 908 f"[DRY RUN] Would upload {stub_count} stubs to {bucket_name}/{path}" 909 ) 910 else: 911 error_console.print( 912 f"[green]Synced {stub_count} stubs to {bucket_name}/{path}[/green]" 913 ) 914 print_json(result) 915 916 917# ============================================================================= 918# REGISTRY REBUILD COMMANDS 919# ============================================================================= 920 921 922@store_app.command(name="mirror") 923def mirror_cmd( 924 local: Annotated[ 925 bool, 926 Parameter( 927 help="Write output to a local directory. Mutually exclusive with --gcs-bucket and --s3-bucket.", 928 negative="", 929 ), 930 ] = False, 931 gcs_bucket: Annotated[ 932 str | None, 933 Parameter( 934 help="Write output to a GCS bucket. Must not be the prod bucket. " 935 "Mutually exclusive with --local and --s3-bucket.", 936 ), 937 ] = None, 938 s3_bucket: Annotated[ 939 str | None, 940 Parameter( 941 help="Write output to an S3 bucket. " 942 "Mutually exclusive with --local and --gcs-bucket.", 943 ), 944 ] = None, 945 output_path_root: Annotated[ 946 str | None, 947 Parameter( 948 help="Root path/prefix for the output. For --local, this is a directory path " 949 "(defaults to a new temp dir if omitted). For --gcs-bucket/--s3-bucket, " 950 "this prefix is prepended to all blob paths.", 951 ), 952 ] = None, 953 dry_run: Annotated[ 954 bool, 955 Parameter(help="Show what would be rebuilt without writing any files."), 956 ] = False, 957 source_store: Annotated[ 958 str, 959 Parameter( 960 help="Source store to read from (e.g. 'coral:prod', 'coral:dev').", 961 ), 962 ] = "coral:prod", 963 connector_name: Annotated[ 964 tuple[str, ...] | None, 965 Parameter( 966 help="Only rebuild these connectors (by name). " 967 "Can be specified multiple times, e.g. " 968 "--connector-name source-faker --connector-name destination-bigquery.", 969 ), 970 ] = None, 971) -> None: 972 """Create a mirror of the connector registry from the source store. 973 974 Reads all connector metadata from the source store and copies it 975 to the specified output target. Supports local filesystem, GCS, and S3 976 as output targets via fsspec. 977 978 Output targets are mutually exclusive: specify exactly one of 979 --local, --gcs-bucket, or --s3-bucket. 980 981 The production bucket is categorically disallowed as an output target. 982 983 To clean up legacy artifacts (e.g. disabled strict-encrypt connectors) 984 after mirroring, run compile with `--with-legacy-migration v1`:: 985 986 airbyte-ops registry store compile --store coral:dev/my-prefix \\ 987 --with-legacy-migration v1 988 989 Examples: 990 airbyte-ops registry store mirror --local 991 airbyte-ops registry store mirror --local --source-store coral:dev 992 airbyte-ops registry store mirror --gcs-bucket dev-airbyte-cloud-connector-metadata-service-2 \\ 993 --output-path-root test-run-123 994 airbyte-ops registry store mirror --s3-bucket my-test-bucket --dry-run 995 """ 996 # Validate mutually exclusive output targets 997 targets = [local, gcs_bucket is not None, s3_bucket is not None] 998 if sum(targets) != 1: 999 exit_with_error( 1000 "Specify exactly one output target: --local, --gcs-bucket, or --s3-bucket." 1001 ) 1002 1003 if local: 1004 output_mode = "local" 1005 elif gcs_bucket is not None: 1006 output_mode = "gcs" 1007 else: 1008 output_mode = "s3" 1009 1010 registry = _resolve_store(source_store) 1011 1012 result = registry.mirror( 1013 output_mode=output_mode, 1014 output_path_root=output_path_root, 1015 gcs_bucket=gcs_bucket, 1016 s3_bucket=s3_bucket, 1017 dry_run=dry_run, 1018 connector_name=list(connector_name) if connector_name else None, 1019 ) 1020 1021 print_json( 1022 { 1023 "status": result.status, 1024 "source_bucket": result.source_bucket, 1025 "output_mode": result.output_mode, 1026 "output_root": result.output_root, 1027 "connectors_processed": result.connectors_processed, 1028 "blobs_copied": result.blobs_copied, 1029 "blobs_skipped": result.blobs_skipped, 1030 "error_count": len(result.errors), 1031 "dry_run": result.dry_run, 1032 } 1033 ) 1034 1035 if result.errors: 1036 for err in result.errors[:10]: 1037 print_error(err) 1038 if len(result.errors) > 10: 1039 print_error(f"... and {len(result.errors) - 10} more errors") 1040 1041 if result.status == "success": 1042 print_success(result.summary()) 1043 elif result.status == "dry-run": 1044 print_success(f"[DRY RUN] {result.summary()}") 1045 else: 1046 error_console.print(f"[yellow]{result.summary()}[/yellow]") 1047 1048 1049# ============================================================================= 1050# VERSION YANK COMMANDS 1051# ============================================================================= 1052 1053 1054@connector_version_app.command(name="yank") 1055def yank_cmd( 1056 name: Annotated[ 1057 str, 1058 Parameter(help="Connector name (e.g., 'source-faker')."), 1059 ], 1060 version: Annotated[ 1061 str, 1062 Parameter(help="Version to yank (e.g., '1.2.3')."), 1063 ], 1064 store: Annotated[ 1065 str, 1066 Parameter( 1067 help="Store target (e.g. 'coral:dev', 'coral:prod').", 1068 ), 1069 ], 1070 *, 1071 reason: Annotated[ 1072 str, 1073 Parameter(help="Reason for yanking this version."), 1074 ] = "", 1075 approval_url: Annotated[ 1076 str, 1077 Parameter(help="Approval evidence URL to record in the yank marker."), 1078 ] = "", 1079 dry_run: Annotated[ 1080 bool, 1081 Parameter(help="Show what would be done without making changes."), 1082 ] = False, 1083) -> None: 1084 """Mark a connector version as yanked. 1085 1086 Writes a version-yank.yml marker file to the version's directory in GCS. 1087 Yanked versions are excluded when determining the latest version of a 1088 connector. 1089 1090 Requires GCS_CREDENTIALS environment variable to be set. 1091 1092 Examples: 1093 airbyte-ops registry connector-version yank --name source-faker --version 1.2.3 --store coral:dev 1094 airbyte-ops registry connector-version yank --name source-faker --version 1.2.3 --store coral:dev --reason "Critical bug" 1095 airbyte-ops registry connector-version yank --name source-faker --version 1.2.3 --store coral:prod 1096 """ 1097 registry = _resolve_store(store) 1098 1099 result = registry.yank( 1100 connector_name=name, 1101 version=version, 1102 reason=reason, 1103 approval_url=approval_url, 1104 dry_run=dry_run, 1105 ) 1106 1107 print_json(result.to_dict()) 1108 1109 if result.success: 1110 print_success(result.message) 1111 if not dry_run: 1112 error_console.print( 1113 "\n[yellow]Note:[/yellow] The registry indexes are now stale. " 1114 "To update them, run:\n\n" 1115 f" airbyte-ops registry store compile --store {store}\n\n" 1116 "Or wait for the next scheduled compile operation." 1117 ) 1118 else: 1119 exit_with_error(result.message, code=1) 1120 1121 1122@connector_version_app.command(name="unyank") 1123def unyank_cmd( 1124 name: Annotated[ 1125 str, 1126 Parameter(help="Connector name (e.g., 'source-faker')."), 1127 ], 1128 version: Annotated[ 1129 str, 1130 Parameter(help="Version to unyank (e.g., '1.2.3')."), 1131 ], 1132 store: Annotated[ 1133 str, 1134 Parameter( 1135 help="Store target (e.g. 'coral:dev', 'coral:prod').", 1136 ), 1137 ], 1138 *, 1139 dry_run: Annotated[ 1140 bool, 1141 Parameter(help="Show what would be done without making changes."), 1142 ] = False, 1143) -> None: 1144 """Rename the active yank marker to a dated audit marker. 1145 1146 Moves `version-yank.yml` to `version-unyanked-yyyymmdd.yml`, making the 1147 version eligible again when determining the latest version. 1148 1149 Requires GCS_CREDENTIALS environment variable to be set. 1150 1151 Examples: 1152 airbyte-ops registry connector-version unyank --name source-faker --version 1.2.3 --store coral:dev 1153 airbyte-ops registry connector-version unyank --name source-faker --version 1.2.3 --store coral:prod 1154 """ 1155 registry = _resolve_store(store) 1156 1157 result = registry.unyank( 1158 connector_name=name, 1159 version=version, 1160 dry_run=dry_run, 1161 ) 1162 1163 print_json(result.to_dict()) 1164 1165 if result.success: 1166 print_success(result.message) 1167 if not dry_run: 1168 error_console.print( 1169 "\n[yellow]Note:[/yellow] The registry indexes are now stale. " 1170 "To update them, run:\n\n" 1171 f" airbyte-ops registry store compile --store {store}\n\n" 1172 "Or wait for the next scheduled compile operation." 1173 ) 1174 else: 1175 exit_with_error(result.message, code=1) 1176 1177 1178@connector_version_app.command(name="list-yanked") 1179def list_yanked_cmd( 1180 store: Annotated[ 1181 str, 1182 Parameter( 1183 help="Store target (e.g. 'coral:dev', 'coral:prod').", 1184 ), 1185 ], 1186 *, 1187 format: Annotated[ 1188 Literal["json", "text"], 1189 Parameter( 1190 help="Output format: 'json' for JSON array, 'text' for newline-separated." 1191 ), 1192 ] = "json", 1193) -> None: 1194 """List all yanked connector versions in the registry. 1195 1196 Returns one entry per active `version-yank.yml` marker, sorted by connector 1197 name then version. Historical `version-unyanked-*.yml` audit markers are 1198 ignored. 1199 1200 Requires GCS_CREDENTIALS environment variable to be set. 1201 1202 Examples: 1203 airbyte-ops registry connector-version list-yanked --store coral:prod 1204 airbyte-ops registry connector-version list-yanked --store coral:prod --format text 1205 """ 1206 registry = _resolve_store(store) 1207 yanked = registry.list_yanked_versions() 1208 1209 if format == "json": 1210 print_json( 1211 { 1212 "yanked_versions": [item.to_dict() for item in yanked], 1213 "count": len(yanked), 1214 } 1215 ) 1216 else: 1217 for item in yanked: 1218 print(f"{item.connector_name}\t{item.version}") 1219 1220 1221@connector_version_app.command(name="yank-status") 1222def yank_status_cmd( 1223 name: Annotated[ 1224 str, 1225 Parameter(help="Connector name (e.g., 'source-faker')."), 1226 ], 1227 version: Annotated[ 1228 str, 1229 Parameter(help="Version to inspect (e.g., '1.2.3')."), 1230 ], 1231 store: Annotated[ 1232 str, 1233 Parameter( 1234 help="Store target (e.g. 'coral:dev', 'coral:prod').", 1235 ), 1236 ], 1237) -> None: 1238 """Show the active yank marker for a single connector version. 1239 1240 Prints the marker's parsed fields (`yanked_at`, `reason`, `approval_url`) 1241 when the version currently has an active `version-yank.yml` marker, or 1242 reports that the version is not yanked. Historical 1243 `version-unyanked-*.yml` audit markers are ignored. 1244 1245 Requires GCS_CREDENTIALS environment variable to be set. 1246 1247 Examples: 1248 airbyte-ops registry connector-version yank-status --name source-faker --version 1.2.3 --store coral:prod 1249 """ 1250 registry = _resolve_store(store) 1251 marker = registry.get_yank_marker(connector_name=name, version=version) 1252 1253 if marker is None: 1254 print_json( 1255 { 1256 "connector_name": name, 1257 "version": version, 1258 "yanked": False, 1259 } 1260 ) 1261 print_success(f"{name} {version} is not yanked.") 1262 return 1263 1264 print_json({"yanked": True, **marker.to_dict()}) 1265 print_success(f"{name} {version} is yanked.") 1266 1267 1268# ============================================================================= 1269# ARTIFACT GENERATION COMMANDS 1270# ============================================================================= 1271 1272 1273@artifacts_app.command(name="generate") 1274def generate_version_artifacts_cmd( 1275 metadata_file: Annotated[ 1276 Path, 1277 Parameter(help="Path to the connector's metadata.yaml file."), 1278 ], 1279 docker_image: Annotated[ 1280 str, 1281 Parameter( 1282 help="Docker image to run spec against (e.g., 'airbyte/source-faker:6.2.38')." 1283 ), 1284 ], 1285 output_dir: Annotated[ 1286 Path | None, 1287 Parameter( 1288 help="Directory to write artifacts to. If not specified, a temp directory is created." 1289 ), 1290 ] = None, 1291 repo_root: Annotated[ 1292 Path | None, 1293 Parameter( 1294 help=( 1295 "Root of the Airbyte repo checkout (for resolving doc.md). " 1296 "If not specified, inferred by walking up from metadata-file." 1297 ), 1298 ), 1299 ] = None, 1300 dry_run: Annotated[ 1301 bool, 1302 Parameter( 1303 help="Show what would be generated without running docker or writing files." 1304 ), 1305 ] = False, 1306 with_validate: Annotated[ 1307 bool, 1308 Parameter( 1309 help=( 1310 "Run metadata validators after generation (default: enabled). " 1311 "Use --no-validate to skip." 1312 ), 1313 negative="--no-validate", 1314 ), 1315 ] = True, 1316 with_sbom: Annotated[ 1317 bool, 1318 Parameter( 1319 help=( 1320 "Generate spdx.json (SBOM) for connectors " 1321 "(default: enabled). Use --no-sbom to skip." 1322 ), 1323 negative="--no-sbom", 1324 ), 1325 ] = True, 1326 with_dependency_dump: Annotated[ 1327 bool, 1328 Parameter( 1329 help=( 1330 "Generate dependencies.json for Python connectors " 1331 "(default: enabled). Use --no-dependency-dump to skip." 1332 ), 1333 negative="--no-dependency-dump", 1334 ), 1335 ] = True, 1336 pr_number: Annotated[ 1337 int | None, 1338 Parameter( 1339 help=( 1340 "Pull request number for prerelease attribution. " 1341 "When provided, it overrides the commit subject." 1342 ), 1343 ), 1344 ] = None, 1345 with_github_lookup: Annotated[ 1346 bool, 1347 Parameter( 1348 help=( 1349 "Resolve publish PR author metadata through GitHub GraphQL " 1350 "(default: enabled). Use --no-github-lookup for offline runs." 1351 ), 1352 negative="--no-github-lookup", 1353 ), 1354 ] = True, 1355) -> None: 1356 """Generate version artifacts for a connector locally. 1357 1358 Runs the connector's docker image in cloud mode (`DEPLOYMENT_MODE=cloud`, 1359 `AIRBYTE_EDITION=CLOUD`) and oss mode (`DEPLOYMENT_MODE=oss`, 1360 `AIRBYTE_EDITION=COMMUNITY`) to obtain both spec variants, then generates 1361 the registry entries (cloud.json, oss.json) by applying 1362 registryOverrides from the metadata. 1363 1364 The generated metadata.yaml is enriched with git commit info, SBOM URL, 1365 and (when applicable) components SHA before writing. Validation is run 1366 after generation by default; pass `--no-validate` to skip. 1367 1368 This is a local-only operation -- no files are uploaded to GCS. 1369 Use `artifacts publish` to upload generated artifacts to GCS. 1370 1371 Examples: 1372 airbyte-ops registry connector-version artifacts generate \\ 1373 --metadata-file path/to/metadata.yaml \\ 1374 --docker-image airbyte/source-faker:6.2.38 1375 1376 airbyte-ops registry connector-version artifacts generate \\ 1377 --metadata-file path/to/metadata.yaml \\ 1378 --docker-image airbyte/source-faker:6.2.38 \\ 1379 --output-dir ./artifacts --with-validate 1380 """ 1381 result = generate_version_artifacts( 1382 metadata_file=metadata_file, 1383 docker_image=docker_image, 1384 output_dir=output_dir, 1385 repo_root=repo_root, 1386 dry_run=dry_run, 1387 with_validate=with_validate, 1388 with_dependency_dump=with_dependency_dump, 1389 with_sbom=with_sbom, 1390 pr_number=pr_number, 1391 with_github_lookup=with_github_lookup, 1392 ) 1393 1394 print_json(result.to_dict()) 1395 1396 if result.success: 1397 print_success( 1398 f"Generated {len(result.artifacts_written)} artifacts to {result.output_dir}" 1399 ) 1400 else: 1401 all_errors = result.errors + result.validation_errors 1402 exit_with_error( 1403 f"Generation completed with {len(all_errors)} error(s): " 1404 + "; ".join(all_errors), 1405 code=1, 1406 ) 1407 1408 1409@artifacts_app.command(name="publish") 1410def publish_version_artifacts_cmd( 1411 name: Annotated[ 1412 str, 1413 Parameter(help="Connector name (e.g., 'source-faker')."), 1414 ], 1415 version: Annotated[ 1416 str, 1417 Parameter(help="Version to publish artifacts for (e.g., '1.2.3')."), 1418 ], 1419 artifacts_dir: Annotated[ 1420 Path, 1421 Parameter( 1422 help="Directory containing generated artifacts to publish (from 'artifacts generate')." 1423 ), 1424 ], 1425 store: Annotated[ 1426 str, 1427 Parameter( 1428 help="Store target (e.g. 'coral:dev', 'coral:prod', 'coral:dev/prefix').", 1429 ), 1430 ], 1431 *, 1432 dry_run: Annotated[ 1433 bool, 1434 Parameter(help="Show what would be published without writing to GCS."), 1435 ] = False, 1436 with_validate: Annotated[ 1437 bool, 1438 Parameter( 1439 help=( 1440 "Validate metadata before uploading (default: enabled). " 1441 "Use --no-validate to skip." 1442 ), 1443 negative="--no-validate", 1444 ), 1445 ] = True, 1446) -> None: 1447 """Publish version artifacts to GCS using fsspec rsync. 1448 1449 Uploads locally generated artifacts (from `artifacts generate`) to the 1450 versioned path in GCS. By default, metadata is validated before upload; 1451 pass `--no-validate` to skip. 1452 1453 Uses `--store` to select the destination store and environment: 1454 1455 * `coral:dev` → coral dev bucket at root 1456 * `coral:prod` → coral prod bucket at root 1457 * `coral:dev/aj-test100` → coral dev bucket under `aj-test100/` prefix 1458 1459 Requires GCS_CREDENTIALS environment variable to be set. 1460 1461 Examples: 1462 airbyte-ops registry connector-version artifacts publish \\ 1463 --name source-faker --version 6.2.38 \\ 1464 --artifacts-dir ./artifacts --store coral:dev --with-validate 1465 1466 airbyte-ops registry connector-version artifacts publish \\ 1467 --name source-faker --version 6.2.38 \\ 1468 --artifacts-dir ./artifacts --store coral:prod 1469 1470 airbyte-ops registry connector-version artifacts publish \\ 1471 --name source-faker --version 6.2.38 \\ 1472 --artifacts-dir ./artifacts --store coral:dev/aj-test100 1473 """ 1474 registry = _resolve_store(store) 1475 1476 result = registry.publish_version_artifacts( 1477 connector_name=name, 1478 version=version, 1479 artifacts_dir=artifacts_dir, 1480 dry_run=dry_run, 1481 with_validate=with_validate, 1482 ) 1483 1484 print_json( 1485 { 1486 "status": result.status, 1487 "connector_name": result.connector_name, 1488 "version": result.version, 1489 "target": result.target, 1490 "gcs_destination": result.gcs_destination, 1491 "files_uploaded": result.files_uploaded, 1492 "errors": result.errors, 1493 "validation_errors": result.validation_errors, 1494 "progressive_rollout_overridden_by_breaking_change": result.progressive_rollout_overridden_by_breaking_change, 1495 "progressive_rollout_overridden_by_published_ga": result.progressive_rollout_overridden_by_published_ga, 1496 "dry_run": result.dry_run, 1497 } 1498 ) 1499 1500 if result.success: 1501 print_success( 1502 f"Published {len(result.files_uploaded)} artifacts for " 1503 f"{result.connector_name}@{result.version} → {result.gcs_destination}" 1504 ) 1505 else: 1506 all_errors = result.errors + result.validation_errors 1507 exit_with_error( 1508 f"Publish completed with {len(all_errors)} error(s): " 1509 + "; ".join(all_errors), 1510 code=1, 1511 ) 1512 1513 1514# ============================================================================= 1515# COMPILE COMMAND 1516# ============================================================================= 1517 1518 1519@store_app.command(name="compile") 1520def compile_cmd( 1521 store: Annotated[ 1522 str, 1523 Parameter( 1524 help="Store target (e.g. 'coral:dev', 'coral:prod', 'coral:dev/prefix').", 1525 ), 1526 ], 1527 *, 1528 output_store: Annotated[ 1529 str | None, 1530 Parameter( 1531 help="Write compiled artifacts to `<registry>:local:<path>`; omit the path to allocate a temporary directory.", 1532 ), 1533 ] = None, 1534 connector_name: Annotated[ 1535 tuple[str, ...] | None, 1536 Parameter( 1537 help="Only compile these connectors (can be repeated).", 1538 ), 1539 ] = None, 1540 dry_run: Annotated[ 1541 bool, 1542 Parameter(help="Show what would be done without writing."), 1543 ] = False, 1544 with_secrets_mask: Annotated[ 1545 bool, 1546 Parameter( 1547 help=( 1548 "Also regenerate specs_secrets_mask.yaml by scanning all " 1549 "connector specs for airbyte_secret properties." 1550 ), 1551 ), 1552 ] = False, 1553 with_legacy_migration: Annotated[ 1554 str | None, 1555 Parameter( 1556 help=( 1557 "Run a one-time legacy migration step during compile. " 1558 "Currently supported: 'v1' — delete cloud.json / oss.json " 1559 "files for connectors whose registryOverrides.cloud.enabled " 1560 "or registryOverrides.oss.enabled is false. This cleans up " 1561 "artifacts produced by the legacy pipeline that did not " 1562 "respect the enabled flag." 1563 ), 1564 ), 1565 ] = None, 1566 with_metrics: Annotated[ 1567 bool, 1568 Parameter( 1569 help=( 1570 "Inject latest connector quality metrics from the analytics " 1571 "JSONL export into generated.metrics." 1572 ), 1573 negative="--no-metrics", 1574 ), 1575 ] = True, 1576 force: Annotated[ 1577 bool, 1578 Parameter( 1579 help=( 1580 "Force resync of latest/ directories even if version markers are current. " 1581 "Useful when metadata changes without a version bump." 1582 ), 1583 ), 1584 ] = False, 1585 with_full_restate: Annotated[ 1586 bool, 1587 Parameter( 1588 help=( 1589 "Re-derive every version's release attribution from metadata.yaml. " 1590 "Expensive for all connectors (~33.5k reads). Without " 1591 "--release-attribution-index, historical blocks unavailable " 1592 "from metadata.yaml are discarded." 1593 ), 1594 negative="--no-full-restate", 1595 ), 1596 ] = False, 1597 release_attribution_index: Annotated[ 1598 Path | None, 1599 Parameter( 1600 help=( 1601 "Seed missing release blocks from a Phase 1 attribution index " 1602 "without rewriting historical metadata.yaml files." 1603 ), 1604 ), 1605 ] = None, 1606) -> None: 1607 """Compile the registry: sync latest/ dirs, write global and per-connector indexes. 1608 1609 Scans all version directories in the target store, determines the latest GA 1610 semver per connector (excluding yanked and pre-release versions), ensures 1611 each `latest/` directory matches the computed latest, and writes: 1612 1613 * `registries/v0/cloud_registry.json` -- global cloud registry index 1614 * `registries/v0/oss_registry.json` -- global OSS registry index 1615 * `metadata/airbyte/<connector>/versions.json` -- per-connector version index 1616 1617 With `--with-secrets-mask`, also regenerates: 1618 1619 * `registries/v0/specs_secrets_mask.yaml` -- properties marked as secrets 1620 1621 With `--with-legacy-migration=v1`, deletes `cloud.json` / `oss.json` 1622 files for connectors whose `registryOverrides.cloud.enabled` or 1623 `registryOverrides.oss.enabled` is `false`. 1624 1625 By default, injects connector quality metrics from the latest analytics 1626 JSONL export into `generated.metrics`. Use `--no-metrics` for offline 1627 scenarios. 1628 1629 With `--force`, resyncs all latest/ directories even if the version marker 1630 matches the computed latest version. 1631 1632 With `--with-full-restate`, re-reads every version's metadata.yaml to 1633 re-derive release attribution. This is an expensive operation at roughly 1634 33.5k reads for the full registry. 1635 1636 With `--release-attribution-index`, seeds historical `release` blocks 1637 directly into versions.json from a Phase 1 Git backfill index. 1638 1639 Uses efficient glob patterns for scanning (no file downloads during discovery). 1640 1641 Requires GCS_CREDENTIALS environment variable to be set. 1642 1643 Examples: 1644 airbyte-ops registry store compile --store coral:dev --dry-run 1645 1646 airbyte-ops registry store compile --store coral:dev/aj-test100 \\ 1647 --connector-name source-faker --connector-name destination-bigquery 1648 1649 airbyte-ops registry store compile --store coral:prod --with-secrets-mask 1650 1651 airbyte-ops registry store compile --store coral:dev \\ 1652 --with-legacy-migration v1 1653 """ 1654 registry = _resolve_store(store) 1655 parsed_output_store = ( 1656 RegistryStore.parse(output_store) if output_store is not None else None 1657 ) 1658 if parsed_output_store is not None and parsed_output_store.env != "local": 1659 exit_with_error( 1660 "--output-store must use `<registry>:local:<path>`; an empty path allocates a temporary directory." 1661 ) 1662 1663 result = registry.compile( 1664 output_store=parsed_output_store, 1665 connector_name=list(connector_name) if connector_name else None, 1666 dry_run=dry_run, 1667 with_secrets_mask=with_secrets_mask, 1668 with_legacy_migration=with_legacy_migration, 1669 with_metrics=with_metrics, 1670 force=force, 1671 with_full_restate=with_full_restate, 1672 release_attribution_index=release_attribution_index, 1673 ) 1674 1675 print_json( 1676 { 1677 "status": result.status, 1678 "target": result.target, 1679 "connectors_scanned": result.connectors_scanned, 1680 "versions_found": result.versions_found, 1681 "yanked_versions": result.yanked_versions, 1682 "latest_updated": result.latest_updated, 1683 "latest_already_current": result.latest_already_current, 1684 "cloud_registry_entries": result.cloud_registry_entries, 1685 "oss_registry_entries": result.oss_registry_entries, 1686 "composite_registry_entries": result.composite_registry_entries, 1687 "metrics_connector_count": result.metrics_connector_count, 1688 "metrics_registry_entries": result.metrics_registry_entries, 1689 "metrics_source": result.metrics_source, 1690 "metrics_error": result.metrics_error, 1691 "version_indexes_written": result.version_indexes_written, 1692 "version_indexes_skipped": result.version_indexes_skipped, 1693 "specs_secrets_mask_properties": result.specs_secrets_mask_properties, 1694 "errors": result.errors, 1695 "dry_run": result.dry_run, 1696 } 1697 ) 1698 1699 if result.status == "success" or result.status == "dry-run": 1700 print_success(result.summary()) 1701 else: 1702 exit_with_error( 1703 f"Compile completed with {len(result.errors)} error(s): " 1704 + "; ".join(result.errors), 1705 code=1, 1706 ) 1707 1708 1709# ============================================================================= 1710# DELETE-DEV-LATEST COMMAND 1711# ============================================================================= 1712 1713 1714@store_app.command(name="delete-dev-latest") 1715def delete_dev_latest_cmd( 1716 store: Annotated[ 1717 str, 1718 Parameter( 1719 help="Store target (must begin with 'coral:dev').", 1720 ), 1721 ], 1722 *, 1723 connector_name: Annotated[ 1724 tuple[str, ...] | None, 1725 Parameter( 1726 help="Only delete latest/ for these connectors (can be repeated).", 1727 ), 1728 ] = None, 1729 dry_run: Annotated[ 1730 bool, 1731 Parameter(help="Show what would be done without deleting."), 1732 ] = False, 1733) -> None: 1734 """Delete all latest/ directories from a dev registry store. 1735 1736 Discovers every connector that has a `latest/` directory and 1737 deletes each one in parallel using a thread pool. 1738 1739 This is useful before a full re-compile to prove that latest/ 1740 directories can be correctly regenerated from versioned data. 1741 1742 Only dev stores are allowed (store must begin with 'coral:dev'). 1743 1744 Requires GCS_CREDENTIALS environment variable to be set. 1745 1746 Examples: 1747 airbyte-ops registry store delete-dev-latest --store coral:dev --dry-run 1748 1749 airbyte-ops registry store delete-dev-latest --store coral:dev/aj-test100 1750 1751 airbyte-ops registry store delete-dev-latest --store coral:dev \\ 1752 --connector-name source-faker --connector-name destination-bigquery 1753 """ 1754 if not store.startswith("coral:dev"): 1755 exit_with_error( 1756 "delete-dev-latest only supports dev stores " 1757 f"(store must begin with 'coral:dev', got '{store}').", 1758 code=1, 1759 ) 1760 1761 registry = _resolve_store(store) 1762 1763 result = registry.delete_dev_latest( 1764 connector_name=list(connector_name) if connector_name else None, 1765 dry_run=dry_run, 1766 ) 1767 1768 print_json( 1769 { 1770 "status": result.status, 1771 "target": result.target, 1772 "connectors_found": result.connectors_found, 1773 "latest_dirs_deleted": result.latest_dirs_deleted, 1774 "errors": result.errors, 1775 "dry_run": result.dry_run, 1776 } 1777 ) 1778 1779 if result.status in ("success", "dry-run"): 1780 print_success(result.summary()) 1781 else: 1782 exit_with_error( 1783 f"Delete completed with {len(result.errors)} error(s): " 1784 + "; ".join(result.errors[:5]), 1785 code=1, 1786 ) 1787 1788 1789# ============================================================================= 1790# STORE COMPARE COMMAND 1791# ============================================================================= 1792 1793 1794@store_app.command(name="compare") 1795def compare_cmd( 1796 store: Annotated[ 1797 str, 1798 Parameter( 1799 help="Store target being evaluated (e.g. 'coral:dev/20260306-mirror-compile').", 1800 ), 1801 ], 1802 reference_store: Annotated[ 1803 str, 1804 Parameter( 1805 help="Known-good reference store to compare against.", 1806 ), 1807 ], 1808 *, 1809 connector_name: Annotated[ 1810 tuple[str, ...] | None, 1811 Parameter( 1812 help="Only compare these connectors (can be repeated).", 1813 ), 1814 ] = None, 1815 with_artifacts: Annotated[ 1816 bool, 1817 Parameter( 1818 help="Compare per-connector artifact files " 1819 "(metadata.yaml, cloud.json, oss.json, spec.json).", 1820 negative="--no-artifacts", 1821 ), 1822 ] = True, 1823 with_indexes: Annotated[ 1824 bool, 1825 Parameter( 1826 help="Compare global registry index files " 1827 "(cloud_registry.json, oss_registry.json, composite_registry.json).", 1828 negative="--no-indexes", 1829 ), 1830 ] = True, 1831 assert_stable: Annotated[ 1832 bool, 1833 Parameter(help="Make the exit code reflect registry safety assertions only."), 1834 ] = False, 1835 with_volatile_fields: Annotated[ 1836 bool, 1837 Parameter(help="Include default volatile-field differences."), 1838 ] = False, 1839 html_report: Annotated[ 1840 Path | None, 1841 Parameter(help="Write a self-contained HTML report to this path."), 1842 ] = None, 1843 text_report: Annotated[ 1844 Path | None, 1845 Parameter(help="Write a plain-text diff report to this path."), 1846 ] = None, 1847) -> None: 1848 """Compare a store against a reference store and report differences. 1849 1850 Evaluates the `--store` target against `--reference-store` and reports 1851 per-connector artifact diffs and global index diffs. 1852 1853 Requires GCS_CREDENTIALS environment variable to be set. 1854 1855 Examples: 1856 airbyte-ops registry store compare --store coral:dev/20260306-mirror \\ 1857 --reference-store coral:prod 1858 1859 airbyte-ops registry store compare --store coral:dev/my-test \\ 1860 --connector-name source-faker --no-indexes 1861 1862 airbyte-ops registry store compare --store coral:dev/my-test \\ 1863 --no-artifacts 1864 """ 1865 store_target = resolve_registry_store(store=store) 1866 ref_target = resolve_registry_store(store=reference_store) 1867 either_side_local = store_target.env == "local" or ref_target.env == "local" 1868 1869 result = compare_stores( 1870 store=store_target, 1871 reference=ref_target, 1872 connector_name=list(connector_name) if connector_name else None, 1873 with_artifacts=(with_artifacts and not either_side_local), 1874 with_indexes=with_indexes, 1875 tolerated_paths=(() if with_volatile_fields else DEFAULT_TOLERATED_PATHS), 1876 ) 1877 1878 print_json(result.to_dict()) 1879 if text_report is not None: 1880 write_text_report(result, str(text_report)) 1881 if html_report is not None: 1882 write_html_report(result, str(html_report)) 1883 1884 if assert_stable and ( 1885 result.connectors_only_in_store 1886 or result.connectors_only_in_reference 1887 or result.connectors_latest_forward 1888 or result.connectors_latest_backward 1889 ): 1890 exit_with_error("Registry safety assertions failed.", code=1) 1891 1892 if result.status == "match": 1893 print_success(result.summary()) 1894 elif result.status == "differences-found": 1895 error_console.print(f"[yellow]{result.summary()}[/yellow]") 1896 1897 # Print a concise per-connector diff summary 1898 for diff in result.connector_diffs: 1899 if diff.status in ("only_in_store", "only_in_reference"): 1900 error_console.print(f" {diff.connector}: {diff.status}") 1901 else: 1902 for ad in diff.artifact_diffs: 1903 error_console.print( 1904 f" {diff.connector}/{ad.file}: {ad.status}" 1905 + (f" ({ad.details})" if ad.details else "") 1906 ) 1907 1908 for idx_diff in result.index_diffs: 1909 if idx_diff.status != "match": 1910 error_console.print( 1911 f" [index] {idx_diff.file}: {idx_diff.status}" 1912 + ( 1913 f" (store={idx_diff.entry_count_store}," 1914 f" ref={idx_diff.entry_count_reference})" 1915 if idx_diff.entry_count_store or idx_diff.entry_count_reference 1916 else "" 1917 ) 1918 ) 1919 1920 if not assert_stable: 1921 sys.exit(1) 1922 else: 1923 exit_with_error( 1924 f"Compare completed with {len(result.errors)} error(s): " 1925 + "; ".join(result.errors[:5]), 1926 code=1, 1927 )