mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-10-13 00:47:31 +00:00
govulncheck@latest resolves to v1.8.0, which declares go 1.26.0 in its module file. Every job in this workflow pins go-version 1.25.x, so the install step fails before any scanning runs, which fails ci-success and skips the release job. v1.7.0 declares go 1.25.0 and installs on the pinned toolchain. This also matches the convention used for staticcheck, which is already pinned to an exact version. Pinning the binary does not freeze the vulnerability database, which govulncheck fetches at run time. Claude-Session: https://claude.ai/code/session_01Fu2wXqGUrfVBi5aLafKk8U
275 lines
9.5 KiB
YAML
275 lines
9.5 KiB
YAML
name: ci
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
workflow_dispatch:
|
|
inputs:
|
|
dry-run-release:
|
|
description: "Compute release version, do not tag or release"
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
contents: write
|
|
pull-requests: read
|
|
packages: write
|
|
|
|
# Cancel any in-flight CI runs for the same branch when a new push lands.
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
vet:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version: '1.25.x'
|
|
check-latest: true
|
|
- uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
~/.cache/go-build
|
|
key: ${{ runner.os }}-go-1.25-${{ hashFiles('**/go.sum') }}
|
|
- run: go vet ./...
|
|
|
|
staticcheck:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version: '1.25.x'
|
|
check-latest: true
|
|
- uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
~/.cache/go-build
|
|
key: ${{ runner.os }}-go-1.25-${{ hashFiles('**/go.sum') }}
|
|
- run: go install honnef.co/go/tools/cmd/[email protected]
|
|
- run: staticcheck ./...
|
|
|
|
govulncheck:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version: '1.25.x'
|
|
check-latest: true
|
|
- uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
~/.cache/go-build
|
|
key: ${{ runner.os }}-go-1.25-${{ hashFiles('**/go.sum') }}
|
|
# Pinned, matching staticcheck above. govulncheck@latest is v1.8.0,
|
|
# which declares go 1.26.0 and therefore cannot be installed by the
|
|
# 1.25.x toolchain this workflow pins - the install step fails before
|
|
# any scanning happens. v1.7.0 declares go 1.25.0. Pinning the binary
|
|
# does not freeze the vulnerability database, which govulncheck
|
|
# fetches at run time. Re-pin deliberately when bumping go-version.
|
|
- run: go install golang.org/x/vuln/cmd/[email protected]
|
|
- run: govulncheck ./...
|
|
|
|
gosec:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version: '1.25.x'
|
|
check-latest: true
|
|
- uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
~/.cache/go-build
|
|
key: ${{ runner.os }}-go-1.25-${{ hashFiles('**/go.sum') }}
|
|
- run: go install github.com/securego/gosec/v2/cmd/gosec@latest
|
|
# G404: math/rand/v2 jitter in transport/backoff.go — intentional (not crypto)
|
|
# G304: os.ReadFile from CLI flag variable — intentional (tool, not server)
|
|
# G306: 0o644 on generated doc artifacts in cmd/scrape — intentional
|
|
# G204: git subprocess in cmd/audit uses CLI flag path — intentional (operator tool)
|
|
# G706: log.Printf with values from Telegram/env in examples — illustrative,
|
|
# library users are expected to sanitise before logging in production
|
|
- run: gosec -quiet -exclude=G404,G304,G306,G204,G706 -exclude-dir=testdata ./...
|
|
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version: '1.25.x'
|
|
check-latest: true
|
|
- uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
~/.cache/go-build
|
|
key: ${{ runner.os }}-go-1.25-${{ hashFiles('**/go.sum') }}
|
|
- run: go test -race -coverprofile=coverage.out ./...
|
|
- name: Build all examples
|
|
run: go build ./examples/...
|
|
- uses: actions/upload-artifact@v7
|
|
with:
|
|
name: coverage
|
|
path: coverage.out
|
|
|
|
codegen-clean:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version: '1.25.x'
|
|
check-latest: true
|
|
- uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
~/.cache/go-build
|
|
key: ${{ runner.os }}-go-1.25-${{ hashFiles('**/go.sum') }}
|
|
- name: Regenerate against pinned snapshot
|
|
run: make regen-from-fixture
|
|
- name: Assert clean diff
|
|
run: git diff --exit-code internal/spec/api.json api/ docs/reference/
|
|
|
|
audit:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0 # need history for drift comparison
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version: '1.25.x'
|
|
check-latest: true
|
|
- uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/go/pkg/mod
|
|
~/.cache/go-build
|
|
key: ${{ runner.os }}-go-1.25-${{ hashFiles('**/go.sum') }}
|
|
- name: Audit fallbacks
|
|
run: make audit
|
|
- name: Audit drift vs base
|
|
# On PRs: compare against the merge base (origin/<base>).
|
|
# On push to main: compare against the parent commit.
|
|
# Drift is informational; doesn't fail CI.
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
BASE="origin/${{ github.base_ref }}"
|
|
else
|
|
BASE="HEAD~1"
|
|
fi
|
|
echo "Drift base: $BASE"
|
|
go run ./cmd/audit -ir internal/spec/api.json -drift -against "$BASE" || true
|
|
|
|
# Aggregate gate — depends on every check above. Used as a single
|
|
# required status check in branch protection AND as a dependency for the
|
|
# release job below.
|
|
ci-success:
|
|
runs-on: ubuntu-latest
|
|
needs: [vet, staticcheck, govulncheck, gosec, test, codegen-clean, audit]
|
|
if: always()
|
|
steps:
|
|
- name: All checks passed
|
|
if: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
|
|
run: echo "ci-success"
|
|
- name: At least one check failed
|
|
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
|
run: |
|
|
echo "Failed/cancelled jobs:"
|
|
echo '${{ toJSON(needs) }}'
|
|
exit 1
|
|
|
|
# Auto-release fires on every clean push to main (and on manual
|
|
# workflow_dispatch for testing). Computes next SemVer from commit
|
|
# history via lukaszraczylo/semver-generator, dual-tags
|
|
# (v<X.Y.Z> + bot-api-v<A.B>), runs GoReleaser.
|
|
release:
|
|
needs: ci-success
|
|
if: |
|
|
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
|
|
github.event_name == 'workflow_dispatch'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version: '1.25.x'
|
|
check-latest: true
|
|
|
|
# Action interface from
|
|
# https://github.com/lukaszraczylo/semver-generator/blob/main/action.yml
|
|
# Inputs: repository_local: true (use already-cloned repo)
|
|
# existing: true (respect existing tags as base)
|
|
# Output: semantic_version (bare version string, no "v" prefix)
|
|
- name: Compute next SemVer
|
|
id: semver
|
|
uses: lukaszraczylo/semver-generator@v1
|
|
with:
|
|
repository_local: true
|
|
existing: true
|
|
config_file: .semver.yaml
|
|
|
|
- name: Read Bot API version
|
|
id: api_version
|
|
run: |
|
|
VERSION=$(python3 -c 'import json; print(json.load(open("internal/spec/api.json"))["version"])')
|
|
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
|
|
echo "tag=" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "tag=bot-api-v${VERSION}" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Dry-run summary
|
|
if: github.event_name == 'workflow_dispatch' && inputs.dry-run-release == true
|
|
run: |
|
|
echo "Would release: v${{ steps.semver.outputs.semantic_version }}"
|
|
if [ -n "${{ steps.api_version.outputs.tag }}" ]; then
|
|
echo "Would also tag: ${{ steps.api_version.outputs.tag }} (Bot API ${{ steps.api_version.outputs.version }})"
|
|
fi
|
|
echo "Skipping tag + release (dry-run)."
|
|
|
|
- name: Tag library + bot-api versions
|
|
if: github.event_name != 'workflow_dispatch' || inputs.dry-run-release == false
|
|
env:
|
|
LIB_TAG: v${{ steps.semver.outputs.semantic_version }}
|
|
API_TAG: ${{ steps.api_version.outputs.tag }}
|
|
API_VER: ${{ steps.api_version.outputs.version }}
|
|
run: |
|
|
# Bot API version (currently $API_VER) is intentionally NOT
|
|
# tagged separately. semver-generator picks the most recent
|
|
# tag as the version base; a non-SemVer marker like
|
|
# bot-api-vX.Y poisons that and restarts numbering from
|
|
# v0.0.x. Bot API version stays as a comment in the lib tag
|
|
# message and in the release notes.
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
git tag -a "$LIB_TAG" -m "Release $LIB_TAG (Bot API $API_VER)"
|
|
git push origin "$LIB_TAG"
|
|
|
|
- name: Run GoReleaser
|
|
if: github.event_name != 'workflow_dispatch' || inputs.dry-run-release == false
|
|
uses: goreleaser/goreleaser-action@v7
|
|
with:
|
|
distribution: goreleaser
|
|
version: '~> v2'
|
|
args: release --clean
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
BOT_API_VERSION: ${{ steps.api_version.outputs.version }}
|