Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
12 changes: 6 additions & 6 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ trim_trailing_whitespace = true
[*.md]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
trim_trailing_whitespace = false

# MkDocs documentation (Python Markdown - 4 spaces)
[docs/**/*.md]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
trim_trailing_whitespace = false

# Configuration files
[*.{yml,yaml}]
Expand All @@ -28,7 +28,7 @@ trim_trailing_whitespace = true

[*.toml]
indent_style = space
indent_size = 4
indent_size = 2
trim_trailing_whitespace = true

[*.json]
Expand All @@ -38,7 +38,7 @@ trim_trailing_whitespace = true

# Scripts
[*.sh]
indent_style = tab
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

Expand All @@ -61,7 +61,7 @@ trim_trailing_whitespace = true

# Build files
[Dockerfile]
indent_style = tab
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

Expand All @@ -71,7 +71,7 @@ indent_size = 4
trim_trailing_whitespace = true

# Git and ignore files
[*.{gitignore,gitattributes,dockerignore}]
[{.gitignore,.gitattributes,.dockerignore}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
Expand Down
43 changes: 43 additions & 0 deletions .github/actions/clean-cache/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: "Clean GH Actions Cache"
description: "Remove GH Actions cache entries for closed branches."

inputs:
gh-token:
description: "GitHub token for API access"
required: true
gh-repo:
description: "GitHub repository in owner/repo format"
required: true
branch:
description: "Target branch"
required: true

runs:
using: "composite"
steps:
- name: Clean Cache
shell: bash
env:
# https://cli.github.com/manual/gh_help_environment
GH_TOKEN: ${{ inputs.gh-token }}
GH_REPO: ${{ inputs.gh-repo }}
BRANCH: ${{ inputs.branch }}
run: |
# Setting this to not fail the workflow while listing/deleting cache entries.
set +e

# Get cache IDs for target branch
echo "Fetching list of cache IDs for $BRANCH"
cacheIDs=$(gh cache list --ref "$BRANCH" --json id --jq '.[].id')

# Delete the cache entries
if [ -z "$cacheIDs" ]; then
echo "No cache entries found for $BRANCH"
else
echo "Deleting caches..."
fi
for cacheID in $cacheIDs
do
gh cache delete "$cacheID"
done
echo "Done"
21 changes: 0 additions & 21 deletions .github/dependabot.yml

This file was deleted.

5 changes: 4 additions & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@
"matchUpdateTypes": ["minor", "patch", "digest"],
"automerge": true
}
]
],
"vulnerabilityAlerts": {
"enabled": true
}
}
124 changes: 70 additions & 54 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
---
name: Build Release

on:
# Called by other workflows with inputs for build type and dry-run mode
workflow_call:
inputs:
build-type:
description: "Type of build (prod or dev)"
ENVIRONMENT:
description: "GitHub environment for secrets access"
required: true
type: string
dry-run:
BUILD_TYPE:
description: "Type of release build (stable or nightly)"
required: true
type: string
DRY_RUN:
description: "Run in test mode without publishing artifacts"
required: false
default: false
Expand All @@ -20,100 +24,112 @@ on:
required: true

permissions:
contents: read
contents: read # Declare default permissions as read only.

jobs:
# Build binaries, images, SBOMs, and attestations for production or development
build:
name: Build
runs-on: ubuntu-latest
environment: ${{ inputs.ENVIRONMENT }}
env:
DOCKER_CLI_EXPERIMENTAL: "enabled"
CGO_ENABLED: 0
TAG: ${{ github.ref_name }}
permissions:
contents: write # For code checkout and publishing releases
packages: write # For pushing images to registries
attestations: write # For generating provenance and SBOMs
id-token: write # For OIDC auth to Docker Hub and GHCR

steps:
- name: Harden the runner (Audit all outbound calls)
- name: Harden the Runner (Step Security)
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
with:
egress-policy: audit

- name: Validate build-type input # Ensure build-type is 'prod' or 'dev'.
- name: Validate BUILD_TYPE input
env:
BUILD_TYPE: ${{ inputs.BUILD_TYPE }}
run: |
if [[ "${{ inputs.build-type }}" != "prod" && "${{ inputs.build-type }}" != "dev" ]]; then
echo "Error: build-type must be 'prod' or 'dev', got '${{ inputs.build-type }}'"
if [[ "${BUILD_TYPE}" != "stable" && "${BUILD_TYPE}" != "nightly" ]]; then
echo "Error: BUILD_TYPE must be 'stable' or 'nightly', got '${BUILD_TYPE}'"
exit 1
fi

- name: Checkout code
uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # Full history for GoReleaser versioning.
fetch-depth: 0
persist-credentials: false

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: "go.mod"
cache: false # Disable to prevent cache poisoning
go-version-file: go.mod

- name: Set up QEMU # Enable multi-platform emulation.
uses: docker/setup-qemu-action@f4e8deed0c3c26542279f4042c0e1d059cbf012d
- name: Setup QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
with:
image: tonistiigi/binfmt@sha256:d3b963f787999e6c0219a48dba02978769286ff61a5f4d26245cb6a6e5567ea3 # latest
platforms: linux/amd64,linux/386,linux/arm/v6,linux/arm64/v8,linux/riscv64

- name: Enable containerd snapshotter # Enable containerd for multi-platform builds
run: |
sudo bash -c 'echo "{\"features\": {\"containerd-snapshotter\": true}}" > /etc/docker/daemon.json'
sudo systemctl restart docker

- name: Set up Docker Buildx # Configure multi-platform builds.
uses: docker/setup-buildx-action@21162887f0d6e25b4e8eafb0cf44cf9bf7f6acd3
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
with:
driver: docker-container
platforms: linux/amd64,linux/386,linux/arm/v6,linux/arm64/v8,linux/riscv64
use: true
driver-opts: network=host

- name: Install Syft for SBOM generation # Install Syft for GoReleaser SBOM generation
if: ${{ inputs.build-type == 'prod' }}
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- name: Install Syft for SBOM generation
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
if: ${{ inputs.BUILD_TYPE == 'stable' }}

- name: Login to Docker Hub # Authenticate to Docker Hub
if: ${{ !inputs.dry-run }}
uses: docker/login-action@ba754150c9dbbaa912ae0ac3cfba43f84195cef2
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

- name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
if: ${{ !inputs.DRY_RUN }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Login to GHCR # Authenticate to GHCR
if: ${{ !inputs.dry-run }}
uses: docker/login-action@ba754150c9dbbaa912ae0ac3cfba43f84195cef2
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
if: ${{ !inputs.DRY_RUN }}
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@01cbe076be10ba0af7b0b9319ac490a93d3d2fcd
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7.2.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRY_RUN: ${{ inputs.DRY_RUN }}
with:
distribution: goreleaser
version: "~> v2"
# prod (full release), dev (snapshot), skip publish in dry-run
# https://goreleaser.com/cmd/goreleaser_release/
# Execute GoReleaser release process
# Use prod.yml or dev.yml based on build-type
# Generate snapshot versions for dev builds
# Remove dist directory before building
# Skip publishing during dry-run
args: release --config build/goreleaser/${{ inputs.build-type }}.yml ${{ inputs.build-type == 'dev' && '--snapshot' || '' }} --clean ${{ inputs.dry-run && '--skip=publish' || '' }}
env:
GITHUB_TOKEN: ${{ github.token }}
DRY_RUN: ${{ inputs.dry-run }}
args: >-
release
--config build/goreleaser/${{ inputs.BUILD_TYPE }}.yaml
${{ inputs.BUILD_TYPE == 'nightly' && '--skip=validate,archive' || '' }}
--clean
${{ inputs.DRY_RUN && '--skip=publish' || '' }}

- name: Upload Binary SBOMs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !inputs.DRY_RUN && inputs.BUILD_TYPE == 'stable' }}
with:
name: ${{ inputs.BUILD_TYPE }}-binary-sboms
path: dist/*.sbom
if-no-files-found: ignore
Comment on lines +119 to +125

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Workflow SBOM upload configuration:"
rg -n 'Upload Binary SBOMs|path:|if-no-files-found' .github/workflows/build.yaml -C2

echo
echo "GoReleaser stable SBOM output configuration:"
rg -n 'sbom|sboms|name_template|format' build/goreleaser/stable.yaml -C3

Repository: nicholas-fedor/watchtower

Length of output: 2491


🏁 Script executed:

cat -n build/goreleaser/stable.yaml | sed -n '280,320p'

Repository: nicholas-fedor/watchtower

Length of output: 1645


🏁 Script executed:

rg -n 'sbom' build/goreleaser/stable.yaml -A 10 -B 2

Repository: nicholas-fedor/watchtower

Length of output: 733


🌐 Web query:

GoReleaser SBOM default output naming format files

💡 Result:

GoReleaser SBOMs default output filename templates (written into dist) are configured via the sboms[].documents option. 1) Default sbom id - The default SBOM config id is “default” when sboms[].id is omitted. [1] 2) Default documents (filenames) depending on sboms[].artifacts - If artifacts: "binary", default documents is: {{.Binary }}{{.Version }}{{.Os }}_{{.Arch }}.sbom.json [2][1] - If artifacts: "any", there are no default documents (empty list), so no SBOM outputs are produced unless you set sboms[].documents yourself. [2][1] - Otherwise (e.g., artifacts: "archive" or “source”), default documents is: {{.ArtifactName }}.sbom.json [2][1] 3) What the generated files look like in dist - A typical generated SBOM file corresponds to each cataloged artifact filename with a “.sbom.json” suffix. For example, an archive named my-cmd_1.2.3_linux_arm64.tar.gz results in my-cmd_1.2.3_linux_arm64.tar.gz.sbom.json. [3] 4) Note about.json - GoReleaser’s SBOM docs/defaults include the “.sbom.json” suffix (not just “.sbom”), reflecting an update that appended the “.json” to the default SBOM output naming. [2][4]

Citations:


🌐 Web query:

GoReleaser sbom artifacts default name_template

💡 Result:

GoReleaser SBOM artifacts: the default SBOM document filename is When binary: {{.Binary }}{{.Version }}{{.Os }}_{{.Arch }}.sbom.json When any: [] Otherwise (e.g., archive/source installers): {{.ArtifactName }}.sbom.json [1] So there is no single “sbom artifacts default name_template” setting; instead GoReleaser uses the SBOM configuration field documents (templates allowed) whose default value depends on whether sbom is run for binaries or for other artifact types [1]. How it works in config - In sboms section, use documents to control SBOM output names/filenames; this is where name templates are applied (e.g. ${artifact}.spdx.json in docs) [1]. - The default documents value is computed as described above, depending on sbom config (binary vs any vs other) [1]. Example override You can override document templates by setting sboms[].documents in.goreleaser.yaml (templates allowed) [1]. For example, the docs show documents: ["${artifact}.spdx.json"] [1].

Citations:


Fix SBOM upload pattern to match GoReleaser's default output format.

Line 125 uses if-no-files-found: ignore, which allows releases to proceed without SBOM artifacts. Additionally, the glob pattern dist/*.sbom will not match any files—GoReleaser generates SBOMs with .sbom.json suffix by default (e.g., watchtower_1.2.3_linux_amd64.tar.gz.sbom.json), so the upload silently fails.

Suggested patch
       - name: Upload Binary SBOMs
         uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
         if: ${{ !inputs.DRY_RUN && inputs.BUILD_TYPE == 'stable' }}
         with:
           name: ${{ inputs.BUILD_TYPE }}-binary-sboms
-          path: dist/*.sbom
-          if-no-files-found: ignore
+          path: dist/*.sbom*
+          if-no-files-found: error
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Upload Binary SBOMs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !inputs.DRY_RUN && inputs.BUILD_TYPE == 'stable' }}
with:
name: ${{ inputs.BUILD_TYPE }}-binary-sboms
path: dist/*.sbom
if-no-files-found: ignore
- name: Upload Binary SBOMs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !inputs.DRY_RUN && inputs.BUILD_TYPE == 'stable' }}
with:
name: ${{ inputs.BUILD_TYPE }}-binary-sboms
path: dist/*.sbom*
if-no-files-found: error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yaml around lines 119 - 125, The "Upload Binary
SBOMs" upload step uses the wrong glob and silent-ignore behavior: change the
artifact path from "dist/*.sbom" to "dist/*.sbom.json" to match GoReleaser
output and remove or change "if-no-files-found: ignore" so missing SBOMs fail
the job (e.g., delete that line or set it to "error"); update the step that uses
actions/upload-artifact and relies on inputs.DRY_RUN/inputs.BUILD_TYPE
accordingly so uploads don't silently skip when SBOMs are absent.


- name: Generate artifact attestation # Generate attestations for prod builds.
if: ${{ !inputs.dry-run && inputs.build-type == 'prod' }}
uses: actions/attest-build-provenance@10334b5f1e684784025c3fc0a277c88c19089275
- name: Generate Artifact Attestation
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
if: ${{ !inputs.DRY_RUN && inputs.BUILD_TYPE == 'stable' }}
with:
subject-checksums: ./dist/checksums.txt

- name: Clean up dist directory # Remove dist directory to avoid leftover artifacts.
- name: Cleanup dist Directory
if: always()
run: rm -rf dist
58 changes: 0 additions & 58 deletions .github/workflows/changelog-update.yaml

This file was deleted.

Loading
Loading