{"id":48249997,"url":"https://github.com/rtuszik/steelman","last_synced_at":"2026-04-04T20:48:45.187Z","repository":{"id":341589617,"uuid":"1170734507","full_name":"rtuszik/steelman","owner":"rtuszik","description":"Docker Hardened Images chart migration reporter","archived":false,"fork":false,"pushed_at":"2026-03-24T23:35:56.000Z","size":82,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-26T05:11:07.252Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rtuszik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-02T13:08:06.000Z","updated_at":"2026-03-18T13:50:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rtuszik/steelman","commit_stats":null,"previous_names":["rtuszik/steelman"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/rtuszik/steelman","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtuszik%2Fsteelman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtuszik%2Fsteelman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtuszik%2Fsteelman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtuszik%2Fsteelman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rtuszik","download_url":"https://codeload.github.com/rtuszik/steelman/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtuszik%2Fsteelman/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31413284,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T20:09:54.854Z","status":"ssl_error","status_checked_at":"2026-04-04T20:09:44.350Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-04T20:48:44.548Z","updated_at":"2026-04-04T20:48:45.146Z","avatar_url":"https://github.com/rtuszik.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Steelman\n\n\u003e [!WARNING]\n\u003e This project is vibe-coded.\n\u003e Assume there are bugs, rough edges, missing validation, and incorrect assumptions.\n\u003e Review the output before relying on it.\n\n`steelman` is a reporting CLI for Flux-managed Helm releases.\n\nIt scans Flux resources from:\n\n- live Kubernetes clusters\n- Git repositories containing Flux manifests\n- or both\n\nIt compares those releases against Docker Hardened Images catalog data and reports one of:\n\n- already using a DHI chart\n- a DHI chart replacement is available\n- DHI image replacements are available inside the chart values\n- no DHI replacement was found\n\n## Scope\n\nCurrent scope:\n\n- Flux `HelmRelease`\n- Flux `HelmRepository`\n- Flux `OCIRepository`\n\nNot currently supported:\n\n- plain Helm releases that are not represented as Flux resources\n- Argo CD applications\n- direct workload or pod inspection\n- `valuesFrom` resolution\n\n## What It Checks\n\nFor each Flux release, the tool:\n\n1. identifies the current chart source\n2. checks whether a DHI chart exists\n3. if not, tries to resolve chart defaults with `helm show values`\n4. merges inline `HelmRelease.spec.values`\n5. extracts image references from the effective values\n6. checks whether matching DHI images exist\n\nChart replacement takes precedence over image replacement.\n\n## Requirements\n\n- Python 3.12+\n- `uv`\n- `helm` if you want image replacement analysis\n- kubeconfig access if you want live cluster scanning\n\n## Usage\n\nUsing the published package:\n\n```bash\nuvx steelman\n```\n\nCommon variants:\n\n```bash\nuvx steelman --mode cluster\nuvx steelman --mode git --repo /path/to/gitops-repo\nuvx steelman --contexts prod-eu,prod-us\nuvx steelman --output-dir reports\nuvx steelman --offline\nuvx steelman --skip-image-analysis\nuvx steelman --include-already-migrated\nuvx steelman --helm-bin helm\nuvx steelman --image-match-threshold 0.75\nuvx steelman --aliases ./aliases.yaml\nuvx steelman --verbose\nuvx steelman --issue\n```\n\nUse the `ci` subcommand to run with CI defaults (generates `steelman-issue.md` by default):\n\n```bash\nuvx steelman ci\nuvx steelman ci --mode git --repo . --output-dir reports\nuvx steelman ci --no-issue\n```\n\n## Defaults\n\nIf run without flags:\n\n- `--mode both`\n- scans `.` recursively for Flux manifests\n- reads kubeconfig from the default location\n- uses all contexts if kubeconfig contains 10 or fewer contexts\n- otherwise uses the current context only\n- omits the `already_dhi_chart` section from the Markdown report\n- writes:\n    - `./steelman.md`\n    - `./steelman.json`\n\n\u003e [!NOTE]\n\u003e **BREAKING CHANGE** (from previous versions): `steelman-issue.md` is no longer written by default.\n\u003e Use `--issue` to generate it, or use the `steelman ci` subcommand which generates it by default.\n\nThe `ci` subcommand uses the same defaults with one difference:\n\n- writes `./steelman-issue.md` in addition to the above (use `--no-issue` to suppress)\n\n## Output\n\nThe Markdown report contains:\n\n- summary\n- hardened chart available\n- hardened images available\n- no DHI replacement\n- scan notes\n\nUse `--include-already-migrated` if you want the Markdown report to include releases that are already using DHI charts.\n\nThe JSON report contains:\n\n- catalog metadata\n- inventory summary\n- recommendation counts\n- per-release results\n- recorded errors\n\nThe issue report contains:\n\n- a CI-managed `DHI implementation status` issue body\n- pending chart and image migrations as checklist items\n- already-migrated DHI releases as checked items\n- scan errors and a compact summary for a single long-lived issue\n\n## CI Examples\n\nThese examples assume:\n\n- the repository is a Flux v2 GitOps repository\n- the scan should use Git manifests, not live cluster access\n- the generated report should be kept as an artifact or posted into an issue\n\nFor Git-only scans, use:\n\n```bash\nuvx steelman ci --mode git --repo . --output-dir reports\n```\n\n### GitHub Actions\n\nThis example runs on a schedule and on manual dispatch, scans the current Flux repo, uploads the report artifacts, and creates or updates a single issue named `DHI implementation status`.\n\n```yaml\nname: Steelman Report\n\non:\n    schedule:\n        - cron: \"0 6 * * 1\"\n    workflow_dispatch:\n\npermissions:\n    contents: read\n    issues: write\n\njobs:\n    steelman:\n        runs-on: ubuntu-latest\n        steps:\n            - name: Checkout repository\n              uses: actions/checkout@v6\n\n            - name: Set up uv\n              uses: astral-sh/setup-uv@v7\n\n            - name: Run steelman\n              run: uvx steelman ci --mode git --repo . --output-dir reports\n\n            - name: Upload reports\n              uses: actions/upload-artifact@v6\n              with:\n                  name: steelman-report\n                  path: reports/*\n                  if-no-files-found: error\n\n            - name: Create or update issue\n              env:\n                  GH_TOKEN: ${{ github.token }}\n              run: |\n                  issue_number=\"$(gh issue list --state open --label steelman --search 'DHI implementation status in:title' --json number --jq '.[0].number')\"\n                  if [ -n \"$issue_number\" ]; then\n                    gh issue edit \"$issue_number\" --title \"DHI implementation status\" --body-file reports/steelman-issue.md\n                  else\n                    gh issue create --title \"DHI implementation status\" --label steelman --body-file reports/steelman-issue.md\n                  fi\n```\n\nNotes:\n\n- this scans desired state from Git only\n- `reports/steelman.md`, `reports/steelman.json`, and `reports/steelman-issue.md` are uploaded as artifacts\n- `steelman ci` generates `steelman-issue.md` by default; use `steelman ci --no-issue` to skip it\n- the issue update step requires `issues: write`\n\n### Woodpecker CI\n\nThis example runs the same Git-only scan and stores the generated files in the workspace. If your Woodpecker setup exposes a GitHub token, you can also open or update the same long-lived issue with `gh`.\n\n```yaml\nsteps:\n    steelman:\n        image: ghcr.io/astral-sh/uv:python3.13-bookworm\n        commands:\n            - uvx steelman ci --mode git --repo . --output-dir reports\n\n    steelman-report:\n        image: ghcr.io/astral-sh/uv:python3.13-bookworm\n        environment:\n            GITHUB_TOKEN:\n                from_secret: github_token\n        commands:\n            - apt-get update\n            - apt-get install -y gh\n            - issue_number=\"$(gh issue list --repo \"$CI_REPO\" --state open --label steelman --search 'DHI implementation status in:title' --json number --jq '.[0].number')\"\n            - |\n                if [ -n \"$issue_number\" ]; then\n                  gh issue edit \"$issue_number\" --repo \"$CI_REPO\" --title \"DHI implementation status\" --body-file reports/steelman-issue.md\n                else\n                  gh issue create --repo \"$CI_REPO\" --title \"DHI implementation status\" --label steelman --body-file reports/steelman-issue.md\n                fi\n```\n\nNotes:\n\n- the second step is optional\n- if you do not want issue creation, keep only the `steelman` step\n- artifact handling in Woodpecker depends on your runner and storage configuration, so this example leaves the report in `reports/`\n\n### GitLab CI\n\nThis example runs the same Git-only scan in a Flux repository, stores the generated reports as job artifacts, and optionally opens or updates a single GitLab issue for DHI implementation tracking.\n\n```yaml\nstages:\n    - report\n\nsteelman:\n    stage: report\n    image: ghcr.io/astral-sh/uv:python3.13-bookworm\n    script:\n        - uvx steelman ci --mode git --repo . --output-dir reports\n    artifacts:\n        when: always\n        paths:\n            - reports/steelman.md\n            - reports/steelman.json\n            - reports/steelman-issue.md\n        expire_in: 7 days\n\nsteelman_issue:\n    stage: report\n    image: debian:bookworm-slim\n    needs:\n        - job: steelman\n          artifacts: true\n    rules:\n        - if: $GITLAB_TOKEN\n    script:\n        - apt-get update\n        - apt-get install -y curl jq\n        - |\n            issue_iid=\"$(curl --silent --header \"PRIVATE-TOKEN: $GITLAB_TOKEN\" \\\n              \"$CI_API_V4_URL/projects/$CI_PROJECT_ID/issues?state=opened\u0026labels=steelman\u0026search=DHI%20implementation%20status\" | jq -r '.[0].iid // empty')\"\n        - |\n            report_body=\"$(jq -Rs . \u003c reports/steelman-issue.md)\"\n            if [ -n \"$issue_iid\" ]; then\n              curl --silent --request PUT \\\n                --header \"PRIVATE-TOKEN: $GITLAB_TOKEN\" \\\n                --header \"Content-Type: application/json\" \\\n                --data \"{\\\"title\\\":\\\"DHI implementation status\\\",\\\"description\\\":$report_body}\" \\\n                \"$CI_API_V4_URL/projects/$CI_PROJECT_ID/issues/$issue_iid\"\n            else\n              curl --silent --request POST \\\n                --header \"PRIVATE-TOKEN: $GITLAB_TOKEN\" \\\n                --header \"Content-Type: application/json\" \\\n                --data \"{\\\"title\\\":\\\"DHI implementation status\\\",\\\"description\\\":$report_body,\\\"labels\\\":\\\"steelman\\\"}\" \\\n                \"$CI_API_V4_URL/projects/$CI_PROJECT_ID/issues\"\n            fi\n```\n\nNotes:\n\n- the `steelman_issue` job is optional\n- set `GITLAB_TOKEN` as a masked CI variable if you want issue creation\n- if you only want artifacts, keep just the `steelman` job\n\n### Cluster Mode In CI\n\nIf you want to scan live clusters instead of Git manifests, switch to:\n\n```bash\nuvx steelman ci --mode cluster --contexts prod-eu,prod-us --output-dir reports\n```\n\nThat requires kubeconfig access in the CI environment. For a Flux repository, Git mode is usually the simpler starting point because it only scans desired state from the repo.\n\n## Current Limitations\n\n- `valuesFrom` is detected but not resolved\n- image analysis depends on `helm show values`\n- some OCI chart sources may fail `helm show values` depending on how the chart is published\n- matching is heuristic and may still need alias tuning for edge cases\n\n## Development\n\n```bash\nuv sync --group dev\nuv run ruff check .\nuv run ruff format --check .\nuv run ty check\nuv run pytest\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frtuszik%2Fsteelman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frtuszik%2Fsteelman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frtuszik%2Fsteelman/lists"}