Skip to content
Merged
47 changes: 47 additions & 0 deletions ecosystem-automation/v1-registry-sync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# V1 Registry Sync

Dry-run tool for comparing V2 ecosystem-registry data against V1 entries under
`opentelemetry.io/data/registry/` and reporting which fields would change.

## Overview

The tool reads the latest release snapshot from `ecosystem-registry/collector/` and produces a
report of proposed changes. Each entry in the report includes:

- `target_v1_file`: the expected V1 filename for the component (e.g. `collector-kafkareceiver.yml`)
- `v1_entry_exists`: whether that file is present in the V1 registry directory (when
`--v1-registry-dir` is provided)
- `proposed_v1_changes`: fields from V2 that would be written to the V1 entry

Only `description` is included in `proposed_v1_changes`. The V1 schema does not carry a `stability`
field, and `title` (mapped from `display_name`) is omitted because a small number of V1 titles
contain more information than the V2 display name and would lose fidelity on overwrite.

## Usage

From the repository root:

```bash
uv run v1-registry-sync
```

This reads `ecosystem-registry/collector/contrib/` by default and writes JSON to stdout.

### Options

```text
--inventory-dir PATH Path to ecosystem-registry/collector (default: ecosystem-registry/collector)
--distribution core or contrib (default: contrib)
--v1-registry-dir PATH Path to opentelemetry.io data/registry/ -- enables v1_entry_exists checks
--output PATH Output file path, or - for stdout (default: -)
--format json or yaml (default: json)
```

### Example with V1 registry check

```bash
uv run v1-registry-sync \
--v1-registry-dir ../opentelemetry.io/data/registry \
--format yaml \
--output sync-report.yaml
```
24 changes: 24 additions & 0 deletions ecosystem-automation/v1-registry-sync/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[project]
name = "v1-registry-sync"
version = "0.1.0"
description = "Dry-run tool for syncing description from V2 registry into V1 entries"
requires-python = ">=3.11"
dependencies = [
"collector-watcher",
]

[project.scripts]
v1-registry-sync = "v1_registry_sync.main:main"

[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-cov>=4.1.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/v1_registry_sync"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
112 changes: 112 additions & 0 deletions ecosystem-automation/v1-registry-sync/src/v1_registry_sync/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""CLI entry point for v1-registry-sync."""

import argparse
import logging
import sys

from v1_registry_sync.reader import read_latest_v2_components
from v1_registry_sync.reporter import write_report

logger = logging.getLogger(__name__)


def configure_logging() -> None:
logging.basicConfig(
level=logging.INFO,
format="%(message)s",
handlers=[logging.StreamHandler(sys.stderr)],
)


def main() -> None:
"""Generate a dry-run report of proposed V1 registry changes from V2 data."""
configure_logging()

parser = argparse.ArgumentParser(
description=(
"Read the latest V2 registry snapshot and produce a report showing "
"which description values would be synced into the matching V1 entries "
"under opentelemetry.io/data/registry/."
),
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--inventory-dir",
default="ecosystem-registry/collector",
help="Path to the ecosystem-registry/collector directory",
)
parser.add_argument(
"--distribution",
choices=["core", "contrib"],
default="contrib",
help="Distribution to read from V2",
)
parser.add_argument(
"--v1-registry-dir",
default=None,
help=(
"Optional path to the opentelemetry.io data/registry/ directory. "
"When provided, each entry includes a v1_entry_exists flag indicating "
"whether a matching V1 file is already present."
),
)
parser.add_argument(
"--output",
default="-",
help="Output file path, or - for stdout",
)
parser.add_argument(
"--format",
choices=["json", "yaml"],
default="json",
help="Output format",
)
args = parser.parse_args()

try:
logger.info("V1 Registry Sync -- dry-run report")
logger.info("Inventory directory : %s", args.inventory_dir)
logger.info("Distribution : %s", args.distribution)
if args.v1_registry_dir:
logger.info("V1 registry dir : %s", args.v1_registry_dir)
logger.info("")

report = read_latest_v2_components(
inventory_dir=args.inventory_dir,
distribution=args.distribution,
v1_registry_dir=args.v1_registry_dir,
)

logger.info("")
logger.info("Registry version : v%s", report.version)
logger.info("Total components : %d", len(report.components))
logger.info("")

if args.output == "-":
write_report(report, sys.stdout, fmt=args.format)
else:
with open(args.output, "w", encoding="utf-8") as f:
write_report(report, f, fmt=args.format)
logger.info("Report written to %s", args.output)

except Exception as e:
logger.error("Error: %s", e, exc_info=True)
sys.exit(1)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Data models for V1 registry sync."""

from dataclasses import dataclass, field
from typing import Optional

STABILITY_PRIORITY = [
"stable",
"beta",
"alpha",
"development",
"deprecated",
"unmaintained",
]


@dataclass
class ComponentSyncData:
"""Fields extracted from V2 that are candidates for syncing into a V1 entry."""

name: str
component_type: str
distribution: str
display_name: Optional[str] = None
description: Optional[str] = None
stability: Optional[str] = None
expected_go_module_path: str = ""
target_v1_file: str = ""
v1_entry_exists: bool = False

def proposed_changes(self) -> dict:
"""Return only the fields that have values and are valid V1 schema fields."""
changes: dict = {}
if self.description is not None:
changes["description"] = self.description
return changes


@dataclass
class V1SyncReport:
"""Report of proposed V1 changes derived from a single V2 registry snapshot."""

version: str
distribution: str
components: list[ComponentSyncData] = field(default_factory=list)

def to_dict(self) -> dict:
return {
"version": self.version,
"distribution": self.distribution,
"components": [
{
"name": c.name,
"component_type": c.component_type,
"expected_go_module_path": c.expected_go_module_path,
"target_v1_file": c.target_v1_file,
"v1_entry_exists": c.v1_entry_exists,
"proposed_v1_changes": c.proposed_changes(),
}
for c in self.components
],
}
Loading
Loading