Skip to main content

Zero-config Flux to Docker Hardened Images chart migration reporter

Project description

Steelman

[!WARNING] This project is vibe-coded. Assume there are bugs, rough edges, missing validation, and incorrect assumptions. Review the output before relying on it.

steelman is a reporting CLI for Flux-managed Helm releases.

It scans Flux resources from:

  • live Kubernetes clusters
  • Git repositories containing Flux manifests
  • or both

It compares those releases against Docker Hardened Images catalog data and reports one of:

  • already using a DHI chart
  • a DHI chart replacement is available
  • DHI image replacements are available inside the chart values
  • no DHI replacement was found

Scope

Current scope:

  • Flux HelmRelease
  • Flux HelmRepository
  • Flux OCIRepository

Not currently supported:

  • plain Helm releases that are not represented as Flux resources
  • Argo CD applications
  • direct workload or pod inspection
  • valuesFrom resolution

What It Checks

For each Flux release, the tool:

  1. identifies the current chart source
  2. checks whether a DHI chart exists
  3. if not, tries to resolve chart defaults with helm show values
  4. merges inline HelmRelease.spec.values
  5. extracts image references from the effective values
  6. checks whether matching DHI images exist

Chart replacement takes precedence over image replacement.

Requirements

  • Python 3.12+
  • uv
  • helm if you want image replacement analysis
  • kubeconfig access if you want live cluster scanning

Usage

Using the published package:

uvx steelman

Common variants:

uvx steelman --mode cluster
uvx steelman --mode git --repo /path/to/gitops-repo
uvx steelman --contexts prod-eu,prod-us
uvx steelman --output-dir reports
uvx steelman --offline
uvx steelman --skip-image-analysis
uvx steelman --include-already-migrated
uvx steelman --helm-bin helm
uvx steelman --image-match-threshold 0.75
uvx steelman --aliases ./aliases.yaml
uvx steelman --verbose

Defaults

If run without flags:

  • --mode both
  • scans . recursively for Flux manifests
  • reads kubeconfig from the default location
  • uses all contexts if kubeconfig contains 10 or fewer contexts
  • otherwise uses the current context only
  • omits the already_dhi_chart section from the Markdown report
  • writes:
    • ./steelman.md
    • ./steelman.json
    • ./steelman-issue.md

Output

The Markdown report contains:

  • summary
  • hardened chart available
  • hardened images available
  • no DHI replacement
  • scan notes

Use --include-already-migrated if you want the Markdown report to include releases that are already using DHI charts.

The JSON report contains:

  • catalog metadata
  • inventory summary
  • recommendation counts
  • per-release results
  • recorded errors

The issue report contains:

  • a CI-managed DHI implementation status issue body
  • pending chart and image migrations as checklist items
  • already-migrated DHI releases as checked items
  • scan errors and a compact summary for a single long-lived issue

CI Examples

These examples assume:

  • the repository is a Flux v2 GitOps repository
  • the scan should use Git manifests, not live cluster access
  • the generated report should be kept as an artifact or posted into an issue

For Git-only scans, use:

uvx steelman --mode git --repo . --output-dir reports

GitHub Actions

This 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.

name: Steelman Report

on:
    schedule:
        - cron: "0 6 * * 1"
    workflow_dispatch:

permissions:
    contents: read
    issues: write

jobs:
    steelman:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout repository
              uses: actions/checkout@v6

            - name: Set up uv
              uses: astral-sh/setup-uv@v7

            - name: Run steelman
              run: uvx steelman --mode git --repo . --output-dir reports

            - name: Upload reports
              uses: actions/upload-artifact@v6
              with:
                  name: steelman-report
                  path: reports/*
                  if-no-files-found: error

            - name: Create or update issue
              env:
                  GH_TOKEN: ${{ github.token }}
              run: |
                  issue_number="$(gh issue list --state open --label steelman --search 'DHI implementation status in:title' --json number --jq '.[0].number')"
                  if [ -n "$issue_number" ]; then
                    gh issue edit "$issue_number" --title "DHI implementation status" --body-file reports/steelman-issue.md
                  else
                    gh issue create --title "DHI implementation status" --label steelman --body-file reports/steelman-issue.md
                  fi

Notes:

  • this scans desired state from Git only
  • reports/steelman.md, reports/steelman.json, and reports/steelman-issue.md are uploaded as artifacts
  • the issue update step requires issues: write

Woodpecker CI

This 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.

steps:
    steelman:
        image: ghcr.io/astral-sh/uv:python3.13-bookworm
        commands:
            - uvx steelman --mode git --repo . --output-dir reports

    steelman-report:
        image: ghcr.io/astral-sh/uv:python3.13-bookworm
        environment:
            GITHUB_TOKEN:
                from_secret: github_token
        commands:
            - apt-get update
            - apt-get install -y gh
            - issue_number="$(gh issue list --repo "$CI_REPO" --state open --label steelman --search 'DHI implementation status in:title' --json number --jq '.[0].number')"
            - |
                if [ -n "$issue_number" ]; then
                  gh issue edit "$issue_number" --repo "$CI_REPO" --title "DHI implementation status" --body-file reports/steelman-issue.md
                else
                  gh issue create --repo "$CI_REPO" --title "DHI implementation status" --label steelman --body-file reports/steelman-issue.md
                fi

Notes:

  • the second step is optional
  • if you do not want issue creation, keep only the steelman step
  • artifact handling in Woodpecker depends on your runner and storage configuration, so this example leaves the report in reports/

GitLab CI

This 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.

stages:
    - report

steelman:
    stage: report
    image: ghcr.io/astral-sh/uv:python3.13-bookworm
    script:
        - uvx steelman --mode git --repo . --output-dir reports
    artifacts:
        when: always
        paths:
            - reports/steelman.md
            - reports/steelman.json
            - reports/steelman-issue.md
        expire_in: 7 days

steelman_issue:
    stage: report
    image: debian:bookworm-slim
    needs:
        - job: steelman
          artifacts: true
    rules:
        - if: $GITLAB_TOKEN
    script:
        - apt-get update
        - apt-get install -y curl jq
        - |
            issue_iid="$(curl --silent --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
              "$CI_API_V4_URL/projects/$CI_PROJECT_ID/issues?state=opened&labels=steelman&search=DHI%20implementation%20status" | jq -r '.[0].iid // empty')"
        - |
            report_body="$(jq -Rs . < reports/steelman-issue.md)"
            if [ -n "$issue_iid" ]; then
              curl --silent --request PUT \
                --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
                --header "Content-Type: application/json" \
                --data "{\"title\":\"DHI implementation status\",\"description\":$report_body}" \
                "$CI_API_V4_URL/projects/$CI_PROJECT_ID/issues/$issue_iid"
            else
              curl --silent --request POST \
                --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
                --header "Content-Type: application/json" \
                --data "{\"title\":\"DHI implementation status\",\"description\":$report_body,\"labels\":\"steelman\"}" \
                "$CI_API_V4_URL/projects/$CI_PROJECT_ID/issues"
            fi

Notes:

  • the steelman_issue job is optional
  • set GITLAB_TOKEN as a masked CI variable if you want issue creation
  • if you only want artifacts, keep just the steelman job

Cluster Mode In CI

If you want to scan live clusters instead of Git manifests, switch to:

uvx steelman --mode cluster --contexts prod-eu,prod-us --output-dir reports

That 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.

Current Limitations

  • valuesFrom is detected but not resolved
  • image analysis depends on helm show values
  • some OCI chart sources may fail helm show values depending on how the chart is published
  • matching is heuristic and may still need alias tuning for edge cases

Development

uv sync --group dev
uv run ruff check .
uv run ruff format --check .
uv run ty check
uv run pytest

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

steelman-0.3.0.tar.gz (20.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

steelman-0.3.0-py3-none-any.whl (26.7 kB view details)

Uploaded Python 3

File details

Details for the file steelman-0.3.0.tar.gz.

File metadata

  • Download URL: steelman-0.3.0.tar.gz
  • Upload date:
  • Size: 20.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for steelman-0.3.0.tar.gz
Algorithm Hash digest
SHA256 332158619217ffdfc2c18648063f7c1860e82b90a01adddb4dec3031dcc01596
MD5 d8d1f4b249aa8d1527f88c3a4002d7f4
BLAKE2b-256 8fca7a4613f088e0b0cda336ae37e539ebe90d028d59192334a9c52fae1b0692

See more details on using hashes here.

File details

Details for the file steelman-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: steelman-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 26.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for steelman-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6a1690dac37bd8089e09e3521e610dc4a5d6b60f786fecb4ddcc2f38bd7c5a53
MD5 a3b7b5ead013c3b2212bfee802a1b34f
BLAKE2b-256 be69885bf0ea00e3762b36e6527e0b21be90fd91c71148e8cc866f5925d5bef5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page