Skip to content

CLI tool binary, no deps & Generate manifest from existing config.yaml#50

Open
adnanrahic wants to merge 16 commits intomainfrom
feature/cli
Open

CLI tool binary, no deps & Generate manifest from existing config.yaml#50
adnanrahic wants to merge 16 commits intomainfrom
feature/cli

Conversation

@adnanrahic
Copy link
Collaborator

@adnanrahic adnanrahic commented Feb 7, 2026

CLI tool binary, no deps & Generate manifest from existing config.yaml

Context

Summary

Implements the "Package Python app as CLI" plan: the builder is now an installable CLI (otel-distro-builder) that runs on the host with no Docker required. For full distribution builds, Go is the only required dependency; OCB is downloaded by the CLI. Manifest generation (--from-config, --generate-only) has no runtime dependencies when using the standalone binary or pip install.

Changes

Packaging & CLI

  • pyproject.toml at repo root: installable package otel-distro-builder with console_scripts entry, dependencies from builder/requirements.txt, and package data (templates, versions.yaml, components.yaml, bindplane_components.yaml).
  • Package-relative paths: New builder/src/resources.py uses importlib.resources and sys._MEIPASS (PyInstaller) so templates and YAML resolve correctly when installed or frozen. All references in version.py, main.py, build.py, component_registry.py, manifest_generator.py use these helpers.
  • Host-first build: Removed hardcoded BUILD_DIR = "/build". Build and artifact directories are host paths (e.g. --artifacts ./artifacts); BuildContext takes a build_dir argument. Default artifacts dir is ./artifacts (cwd-based).

Standalone binary & release

  • PyInstaller: otel-distro-builder.spec produces a one-file binary; make build-cli builds it locally. Optional dependency: pip install pyinstaller.
  • CI: .github/workflows/build-cli-binaries.yml on tag v* builds binaries for linux/amd64, linux/arm64 (ubuntu-24.04-arm), and darwin/arm64, runs in-job tests and a no-Python job to validate the binary, then uploads tarballs to the release.
  • Release: release.yml builds the Python package and attaches dist/* to the release; binary workflow attaches platform tarballs.

Homebrew & docs

  • Formula: Formula/otel-distro-builder.rb with version and OS/arch-specific URLs (sha256 placeholders; update on first release).
  • README: Install section (pip, Homebrew, standalone binary), dependencies note (Go only for full builds; none for generate-only), and updated Prerequisites.

Scripts & tests

  • Scripts: generate_manifest.sh and run_local_build.sh prefer otel-distro-builder on PATH, then python -m builder.src.main, with host paths. Docker remains optional.
  • CI: Base tests install the package and run otel-distro-builder --help and a generate-only smoke test.
  • Make: script-test, build-cli, test-standalone-binary (validates binary with no Python at runtime).

Config-to-manifest (from existing branch)

  • Config-to-manifest support: --from-config, --output-manifest, --generate-only, --no-bindplane, etc., with components.yaml / bindplane_components.yaml, and scripts/docs aligned.

Testing

  • make unit-test — 108 unit tests passed
  • make script-test — script smoke tests (generate_manifest, run_local_build, build_from_config) passed
  • make test-standalone-binary — run after make build-cli to confirm the binary works with no Python at runtime
  • CI: base-tests (unit + install package + CLI smoke), build-cli-binaries (build + per-platform test + no-Python test)

Notes

  • ARM runners: ubuntu-24.04-arm is used for linux/arm64. If your GitHub plan doesn't include ARM runners, remove that matrix entry from build-cli-binaries.yml.
  • Homebrew: Update Formula/otel-distro-builder.rb with real sha256 values after the first release that publishes binary tarballs.
  • Pylint: Minor style warnings (e.g. global-statement in resources, import-outside-toplevel in main); score 9.94/10. Can be cleaned in a follow-up if desired.

Checklist

  • Unit tests pass
  • Script smoke tests pass
  • README and docs updated (Install, Dependencies, Prerequisites)
  • CI: package install + CLI smoke in base-tests
  • CI: build-cli-binaries (multi-platform + standalone binary test)
  • Go documented as the only dependency for full builds; OCB downloaded by CLI

Made with Cursor

adnanrahic and others added 8 commits January 30, 2026 21:13
Add the ability to generate an OCB manifest from an existing OpenTelemetry
Collector config.yaml file. This feature automatically detects components
(receivers, processors, exporters, extensions, connectors) and generates
a minimal manifest with the correct Go module paths.

Key features:
- Parse collector config.yaml and extract component names
- Map components to Go modules via a comprehensive registry
- Include Bindplane/observIQ components by default (--no-bindplane to exclude)
- Support for named instances (e.g., otlp/traces -> otlp)
- Generate-only mode or generate-and-build in one step
- Shell scripts for easy CLI usage
- Makefile targets for convenience

New files:
- builder/src/config_parser.py: Parse collector config files
- builder/src/component_registry.py: Component name to gomod mapping
- builder/src/components.yaml: Registry of all known components
- builder/src/manifest_generator.py: Generate OCB manifests
- builder/src/bindplane_components.yaml: Bindplane components config
- scripts/generate_manifest.sh: Generate manifest from config
- scripts/build_from_config.sh: Generate and build in one step
- docs/config-to-manifest.md: Feature documentation

Closes #33
Add pyproject.toml, resources.py for package-relative paths,
PyInstaller spec, Homebrew formula, CI workflows for multi-platform
binary builds, and host-first build support. Update scripts to prefer
CLI on PATH, fix test artifact path, and align docs with --artifacts
usage.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment on lines 15 to 66
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
os: linux
arch: amd64
- runner: ubuntu-24.04-arm
os: linux
arch: arm64
- runner: macos-14
os: darwin
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .
pip install pyinstaller

- name: Build binary
run: |
pyinstaller --clean --noconfirm otel-distro-builder.spec
VERSION=${GITHUB_REF#refs/tags/}
mkdir -p release
cp dist/otel-distro-builder release/otel-distro-builder-${{ matrix.os }}-${{ matrix.arch }}
chmod +x release/otel-distro-builder-${{ matrix.os }}-${{ matrix.arch }}
tar -czvf "otel-distro-builder-${VERSION}-${{ matrix.os }}-${{ matrix.arch }}.tar.gz" -C release "otel-distro-builder-${{ matrix.os }}-${{ matrix.arch }}"

- name: Test standalone binary (no Python required)
run: |
BIN="release/otel-distro-builder-${{ matrix.os }}-${{ matrix.arch }}"
"$BIN" --help
"$BIN" --version
"$BIN" --from-config builder/tests/configs/otelcol/simple.yaml --generate-only -o /tmp/gen.yaml
test -s /tmp/gen.yaml

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}-${{ matrix.arch }}
path: "otel-distro-builder-*.tar.gz"

test-standalone-no-python:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 23 hours ago

  • In general, to fix this class of problem you add an explicit permissions block either at the workflow root (applies to all jobs) or per job, giving only the scopes and access levels needed (e.g., contents: read). Jobs that need broader permissions (like publishing a release) can override with their own permissions blocks.
  • For this workflow, the upload-to-release job already has permissions: contents: write, which is appropriate. The other jobs (build-binary and test-standalone-no-python) only need to read the repository contents and use artifacts; they do not push commits or modify issues, so they can safely run with contents: read. The least intrusive and clearest fix is to add a workflow-level permissions: block setting contents: read. This will apply to all jobs by default, while the existing permissions block in upload-to-release will override it for that job, preserving its necessary write access.
  • Concretely, in .github/workflows/build-cli-binaries.yml, insert a permissions: section near the top of the file, after the name: declaration and before on:. No imports or additional methods are needed; this is purely a YAML configuration change for the workflow.
Suggested changeset 1
.github/workflows/build-cli-binaries.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/build-cli-binaries.yml b/.github/workflows/build-cli-binaries.yml
--- a/.github/workflows/build-cli-binaries.yml
+++ b/.github/workflows/build-cli-binaries.yml
@@ -5,6 +5,9 @@
 
 name: Build CLI Binaries
 
+permissions:
+  contents: read
+
 on:
   push:
     tags:
EOF
@@ -5,6 +5,9 @@

name: Build CLI Binaries

permissions:
contents: read

on:
push:
tags:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 68 to 89
runs-on: ubuntu-latest
needs: build-binary
steps:
- uses: actions/checkout@v4

- name: Download linux/amd64 binary
uses: actions/download-artifact@v4
with:
name: binary-linux-amd64

- name: Extract and run binary (no Python in this job)
run: |
tar -xzf binary-linux-amd64/otel-distro-builder-*.tar.gz
BIN=$(find . -maxdepth 1 -type f -name 'otel-distro-builder-*' | head -1)
chmod +x "$BIN"
"$BIN" --help
"$BIN" --version
"$BIN" --from-config builder/tests/configs/otelcol/simple.yaml --generate-only -o /tmp/out.yaml
test -s /tmp/out.yaml
echo "Standalone binary works with no Python dependency."

upload-to-release:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 23 hours ago

To fix the problem, explicitly declare a minimal permissions block for the test-standalone-no-python job so it does not inherit broader default permissions. This job only needs to download an artifact and read repository contents at most, so contents: read is sufficient.

Concretely, in .github/workflows/build-cli-binaries.yml, under the test-standalone-no-python job (around line 91), add a permissions: section at the same indentation level as runs-on and needs. The block should specify contents: read. No additional imports or methods are needed; this is a pure YAML configuration change and does not alter existing functional behavior of the workflow.

Suggested changeset 1
.github/workflows/build-cli-binaries.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/build-cli-binaries.yml b/.github/workflows/build-cli-binaries.yml
--- a/.github/workflows/build-cli-binaries.yml
+++ b/.github/workflows/build-cli-binaries.yml
@@ -92,6 +92,8 @@
     # Prove the binary works with no Python on PATH (Go only needed for full builds).
     runs-on: ubuntu-latest
     needs: build-binary
+    permissions:
+      contents: read
     steps:
       - uses: actions/checkout@v4
 
EOF
@@ -92,6 +92,8 @@
# Prove the binary works with no Python on PATH (Go only needed for full builds).
runs-on: ubuntu-latest
needs: build-binary
permissions:
contents: read
steps:
- uses: actions/checkout@v4

Copilot is powered by AI and may make mistakes. Always verify output.
@adnanrahic adnanrahic added the enhancement New feature or request label Feb 7, 2026
@adnanrahic adnanrahic self-assigned this Feb 9, 2026
@adnanrahic adnanrahic changed the title Package Python app as CLI – installable binary, no Docker required feat: Package Python app as CLI – installable binary, no Docker required Feb 9, 2026
@adnanrahic adnanrahic changed the title feat: Package Python app as CLI – installable binary, no Docker required feat: package Python app as CLI – installable binary, no Docker required Feb 9, 2026
adnanrahic and others added 7 commits February 13, 2026 10:12
…rmula

Add test_resources.py covering path resolution in dev, frozen (PyInstaller),
and frozen-fallback contexts, plus lru_cache behavior. Add --version/-V and
_get_version tests to test_main.py. Fix Homebrew formula test expecting wrong
exit code (1) for --help (argparse exits 0).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CLI defines --output-manifest but all CI workflows and the Makefile
test-standalone-binary target used the non-existent -o shorthand, causing
argparse to exit with code 2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
test_simple_build and test_contrib_build hardcoded LINUX_ARM64_ARTIFACTS,
but the Docker container builds for the host architecture. On CI
(ubuntu-latest = amd64) this produced amd64 artifacts, causing the
assertion to fail. Now dynamically selects the expected artifact list
based on the host arch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add cli_entry.py launcher so frozen binary runs builder.src.main as a
  package, fixing 'attempted relative import with no known parent package'
- Update otel-distro-builder.spec to use cli_entry.py and repo root pathex
- build.py: guard psutil Process.io_counters() (not available on macOS)
- build.py: on darwin, use resource.getrusage() ru_inblock/ru_oublock
  (512-byte blocks) for approximate disk read/write in build metrics

Co-authored-by: Cursor <cursoragent@cursor.com>
- goreleaser_downloader: download goreleaser OSS and syft when not on PATH;
  use tools_dir and prepend to PATH so goreleaser finds syft for SBOMs
- build.py: resolve goreleaser and syft, inject tools_dir into subprocess PATH
- build-cli-binaries.yml: add windows/amd64 matrix; Unix/Windows build and
  test steps; upload .zip for Windows, .tar.gz for Unix; release globs both
- ocb_downloader, goreleaser_downloader: use platform.system/machine for
  Windows compatibility (os.uname not available on Windows)
- build.py: show 'N/A (unavailable on macOS)' for disk I/O when on darwin
  (kernel does not populate getrusage block I/O)
- otel-distro-builder.spec: add goreleaser_downloader to hiddenimports

Co-authored-by: Cursor <cursoragent@cursor.com>
- goreleaser_downloader: use _ for unused os.walk() variable (fix W0612)
- build.py, tests: black/isort formatting

Co-authored-by: Cursor <cursoragent@cursor.com>
@adnanrahic adnanrahic changed the title feat: package Python app as CLI – installable binary, no Docker required CLI tool binary, no deps & Generate manifest from existing config.yaml Feb 13, 2026
@adnanrahic adnanrahic linked an issue Feb 13, 2026 that may be closed by this pull request
- README.md: update docker run/pull examples and references
- docs/config-to-manifest.md: update image tag references

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

1 participant