Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
*.md
*.sarif
*.spec
.git
.github
*.md
__pycache__
build/
dist/
pytest.ini
requirements-dev.txt
test/
tests/
tmp/
virtenv
*.spec
*.sarif
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
groups:
python-deps:
patterns: ["*"]
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
actions:
patterns: ["*"]
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
62 changes: 47 additions & 15 deletions .github/workflows/binary-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,44 @@ on:
tags: ["v*"]
workflow_dispatch:

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
contents: read
security-events: write
id-token: none

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.12"
python-version: "3.13"
cache: pip
cache-dependency-path: requirements*.txt

- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
run: |
pip install -r requirements-dev.txt
python -m pytest -q

- name: Build binary with PyInstaller
run: pyinstaller --onefile -n helm-mirror src/main.py
run: |
pip install -r requirements.txt -r requirements-build.txt
pyinstaller --onefile -n helm-mirror src/main.py
./dist/helm-mirror --help > /dev/null
(cd dist && sha256sum helm-mirror > helm-mirror.sha256)

- name: Scan binary with Trivy
uses: aquasecurity/trivy-action@v0.35.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: fs
scan-ref: dist/helm-mirror
Expand All @@ -39,22 +52,41 @@ jobs:
exit-code: "0"

- name: Upload Trivy binary scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
if: always() && hashFiles('trivy-binary.sarif') != ''
with:
sarif_file: trivy-binary.sarif
category: trivy-binary

- name: Upload binary artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: helm-mirror-linux-amd64
path: dist/helm-mirror
path: |
dist/helm-mirror
dist/helm-mirror.sha256
if-no-files-found: error

# Only this job can write to the repository, and only for tag builds.
release:
needs: build
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write

steps:
- name: Download binary
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: helm-mirror-linux-amd64
path: dist

- name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
files: dist/helm-mirror
files: |
dist/helm-mirror
dist/helm-mirror.sha256
generate_release_notes: true
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
test:
name: Tests (Python ${{ matrix.python }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
python: ["3.10", "3.13"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: ${{ matrix.python }}
cache: pip
cache-dependency-path: requirements*.txt
- run: pip install -r requirements-dev.txt
- run: python -m pytest -q

security:
name: Bandit + dependency audit
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.13"
cache: pip
cache-dependency-path: requirements*.txt
- run: pip install -r requirements-dev.txt
# Medium severity and above fails the build; the Low findings are the
# expected "subprocess without shell" notes for a CLI wrapper.
- run: bandit -ll -r src
- run: pip-audit -r requirements.txt -r requirements-build.txt -r requirements-dev.txt

binary-smoke:
name: Build binary (smoke test)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.13"
cache: pip
cache-dependency-path: requirements*.txt
- run: pip install -r requirements.txt -r requirements-build.txt
- run: pyinstaller --onefile -n helm-mirror src/main.py
- run: ./dist/helm-mirror --help
60 changes: 48 additions & 12 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,39 @@ on:
pull_request:
branches: [main]

concurrency:
group: docker-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
security-events: write
id-token: none

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1

- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ghcr.io/${{ github.repository }}
tags: |
Expand All @@ -43,20 +49,29 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push
id: build
uses: docker/build-push-action@v6
# Build locally first; nothing is pushed until the image passed the checks below.
- name: Build image
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: APK_REFRESH=${{ github.run_id }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Scan Docker image with Trivy
uses: aquasecurity/trivy-action@v0.35.0
- name: Smoke test image
env:
IMAGE: ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
run: |
docker run --rm "$IMAGE" --help > /dev/null
docker run --rm --entrypoint sh "$IMAGE" -c \
'test "$(id -u)" != 0 && helm version --short && crane version && yq --version'

- name: Scan Docker image with Trivy (SARIF report)
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
format: sarif
Expand All @@ -65,8 +80,29 @@ jobs:
exit-code: "0"

- name: Upload Trivy image scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
if: always() && hashFiles('trivy-image.sarif') != ''
with:
sarif_file: trivy-image.sarif
category: trivy-docker-image

# Gate: fail only on vulnerabilities that already have a fix available,
# and print them to the log so the failure is actionable.
- name: Fail on fixable HIGH/CRITICAL vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
format: table
severity: CRITICAL,HIGH
ignore-unfixed: true
exit-code: "1"

- name: Push image
if: github.event_name != 'pull_request'
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
Loading
Loading