Skip to content
Closed
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
32 changes: 32 additions & 0 deletions .github/scripts/generate-versions
Original file line number Diff line number Diff line change
Expand Up @@ -484,3 +484,35 @@ HEADER
} >"${out}"

echo "wrote ${out}" >&2

# --- Taskfile.yml

# The shared Taskfile bakes the pins into its vars block, so a repository that
# includes it needs no second download. A key becomes a var name by dropping
# any go: prefix and the module path, keeping the last segment upper-cased with
# dashes as underscores: go:golang.org/x/tools/cmd/goimports is GOIMPORTS,
# golangci-lint is GOLANGCI_LINT. Only the block between the markers is
# rewritten; the rest of the file is hand-written.
taskfile="${TASKFILE:-${root}/Taskfile.yml}"

jq -r '
to_entries
| sort_by(.key)
| .[]
| (.key | sub("^go:"; "") | split("/") | .[-1] | ascii_upcase | gsub("-"; "_")) as $name
| " \($name): \(.value.version)"
' "${releases}" >"${work}/taskvars"

awk -v vars="${work}/taskvars" '
/# BEGIN generated/ {
print
while ((getline line < vars) > 0) print line
skipping = 1
next
}
/# END generated/ { skipping = 0 }
!skipping { print }
' "${taskfile}" >"${work}/Taskfile.yml"
mv -- "${work}/Taskfile.yml" "${taskfile}"

echo "wrote ${taskfile}" >&2
15 changes: 14 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,37 @@ jobs:
go-version: ${{ steps.pins.outputs.go }}
check-latest: false

- name: Check .versions agrees with .versions.json
- name: Check .versions and Taskfile.yml agree with .versions.json
shell: bash
run: |
set -euo pipefail

cp .versions "${RUNNER_TEMP}/versions.committed"
cp Taskfile.yml "${RUNNER_TEMP}/Taskfile.committed"
.github/scripts/generate-versions --no-refresh

if ! diff -u "${RUNNER_TEMP}/versions.committed" .versions; then
echo "::error::.versions is not what .versions.json flattens to; run .github/scripts/generate-versions"
exit 1
fi
if ! diff -u "${RUNNER_TEMP}/Taskfile.committed" Taskfile.yml; then
echo "::error::Taskfile.yml does not carry the pins in .versions.json; run .github/scripts/generate-versions"
exit 1
fi

- name: Setup mise
uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
with:
install: false

- name: Check the shared Taskfile parses and lists its tasks
shell: bash
run: |
set -euo pipefail
mise install task@3.53.1
mise exec task@3.53.1 -- task --list-all
mise exec task@3.53.1 -- task pins

# NOTE(@azazeal): .versions doubles as a .tool-versions, so we check
# whether mise can resolve
- name: Check mise resolves every pin
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ jobs:
run: |
set -euo pipefail

if git diff --quiet -- .versions .versions.json; then
if git diff --quiet -- .versions .versions.json Taskfile.yml; then
echo "found=false" >> "${GITHUB_OUTPUT}"
echo "already up to date"
exit 0
fi

echo "found=true" >> "${GITHUB_OUTPUT}"
git --no-pager diff -- .versions .versions.json
git --no-pager diff -- .versions .versions.json Taskfile.yml

- name: Setup the SSH signing key
if: steps.changes.outputs.found == 'true'
Expand Down Expand Up @@ -74,7 +74,7 @@ jobs:
exit 1
fi

if git diff --quiet FETCH_HEAD -- .versions .versions.json; then
if git diff --quiet FETCH_HEAD -- .versions .versions.json Taskfile.yml; then
echo "${BRANCH} already carries these versions"
exit 0
fi
Expand Down Expand Up @@ -138,7 +138,7 @@ jobs:
git config gpg.format ssh

git switch --create "${BRANCH}"
git add .versions .versions.json
git add .versions .versions.json Taskfile.yml
git commit --message "${title}"
git push --force "${remote}" "${BRANCH}"

Expand Down
166 changes: 166 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# The tasks every Go repository at Smallstep shares. A repository includes this
# file from its own Taskfile.yml under a namespace of its choosing:
#
# includes:
# common:
# taskfile: https://raw.githubusercontent.com/smallstep/workflows/main/Taskfile.yml
#
# There are two layers. The runners (goimports, gotestsum, govulncheck and
# golangci-lint) run one tool at the version .versions pins, with whatever
# arguments the caller passes. The stock tasks (fmt, lint, test and vuln) are
# the defaults most repositories want, built on the runners. A repository's own
# Taskfile.yml delegates to a stock task where the default fits and writes its
# own on top of a runner where it does not; nothing here owns a repository's
# fmt, lint or test.
#
# goimports, gotestsum and govulncheck run through `go run <module>@<version>`,
# so they are the pinned version by construction. golangci-lint is the
# exception: it breaks when built from source, so it is expected on PATH at
# exactly the pinned version, and the runner stops when it is not. Go itself is
# not pinned or enforced here: `check` reports the version found against the
# pin, and a repository's go.mod governs its toolchain.
#
# Every task body runs through Task's own POSIX shell on every platform, so
# nothing here may assume bash, Unix-only tools or /-rooted paths.
version: "3"

vars:
# BEGIN generated by .github/scripts/generate-versions; DO NOT EDIT
GO: 1.27.0
GOIMPORTS: 0.49.0
GOPLS: 0.23.0
GOVULNCHECK: 1.7.0
GOLANGCI_LINT: 2.13.1
GOLANGCI_LINT_LANGSERVER: 0.0.12
GOTESTSUM: 1.13.0
# END generated

# The defaults a repository may override through its include's vars.
GOIMPORTS_LOCAL: go.step.sm/,github.com/smallstep/
TEST_FLAGS: -race -coverpkg=./... -coverprofile=coverage.out -covermode=atomic ./...
LINT_FLAGS: --timeout=30m
GOLANGCI_CONFIG_URL: https://raw.githubusercontent.com/smallstep/workflows/main/.golangci.yml

tasks:
pins:
desc: print the tool versions this Taskfile pins
silent: true
cmds:
- echo "go {{.GO}}"
- echo "golangci-lint {{.GOLANGCI_LINT}}"
- echo "goimports {{.GOIMPORTS}}"
- echo "gotestsum {{.GOTESTSUM}}"
- echo "govulncheck {{.GOVULNCHECK}}"

check:
desc: say which tools on PATH are not the versions smallstep pins
silent: true
cmds:
- |
found="$(go env GOVERSION)"
[ "$found" = "go{{.GO}}" ] ||
echo "go: $found on PATH; smallstep builds with go{{.GO}}"
- |
if command -v golangci-lint >/dev/null 2>&1; then
found="$(golangci-lint version --short 2>/dev/null)"
[ "$found" = "{{.GOLANGCI_LINT}}" ] ||
echo "golangci-lint: $found on PATH; smallstep pins {{.GOLANGCI_LINT}}"
fi
- |
if command -v gotestsum >/dev/null 2>&1; then
found="$(gotestsum --version 2>/dev/null)"
found="${found##* }"
found="${found#v}"
[ "$found" = "{{.GOTESTSUM}}" ] ||
echo "gotestsum: $found on PATH; smallstep pins {{.GOTESTSUM}} (tasks run the pinned one)"
fi

# --- runners: one tool each, at the pinned version, with the caller's args

goimports:
desc: run the pinned goimports with the given arguments
cmds:
- go run golang.org/x/tools/cmd/goimports@v{{.GOIMPORTS}} {{.ARGS}} {{.CLI_ARGS}}

gotestsum:
desc: run the pinned gotestsum with the given arguments
cmds:
- go run gotest.tools/gotestsum@v{{.GOTESTSUM}} {{.ARGS}} {{.CLI_ARGS}}

govulncheck:
desc: run the pinned govulncheck with the given arguments
cmds:
- go run golang.org/x/vuln/cmd/govulncheck@v{{.GOVULNCHECK}} {{.ARGS}} {{.CLI_ARGS}}

golangci-lint:
desc: run the golangci-lint on PATH, which must be the pinned version
cmds:
- cmd: |
if ! command -v golangci-lint >/dev/null 2>&1; then
echo "golangci-lint is not on PATH; smallstep pins {{.GOLANGCI_LINT}}:" >&2
echo " mise use -g golangci-lint@{{.GOLANGCI_LINT}}" >&2
echo " https://github.com/golangci/golangci-lint/releases/tag/v{{.GOLANGCI_LINT}}" >&2
exit 1
fi
found="$(golangci-lint version --short 2>/dev/null)"
if [ "$found" != "{{.GOLANGCI_LINT}}" ]; then
echo "golangci-lint $found is on PATH; smallstep pins {{.GOLANGCI_LINT}}:" >&2
echo " mise use -g golangci-lint@{{.GOLANGCI_LINT}}" >&2
echo " https://github.com/golangci/golangci-lint/releases/tag/v{{.GOLANGCI_LINT}}" >&2
exit 1
fi
silent: true
- golangci-lint {{.ARGS}} {{.CLI_ARGS}}

# --- stock tasks: the defaults, built on the runners

fmt:
desc: format the Go sources with the pinned goimports and smallstep's import groups
cmds:
- task: goimports
vars:
ARGS: -local {{.GOIMPORTS_LOCAL}} -l -w .

lint:
desc: lint with the pinned golangci-lint, using the repository's own config or smallstep's shared one
deps: [check]
vars:
OWN_CONFIG:
sh: |
for f in .golangci.yml .golangci.yaml .golangci.toml .golangci.json; do
if [ -f "$f" ]; then
echo "$f"
break
fi
done
cmds:
# The shared config is fetched fresh, and the last copy serves when the
# network is away; a repository with a config of its own uses that, the
# same rule the goLint workflow applies.
- cmd: |
if [ -n "{{.OWN_CONFIG}}" ]; then
exit 0
fi
mkdir -p .task
curl -fsSL -o .task/golangci.yml.tmp {{.GOLANGCI_CONFIG_URL}} &&
mv .task/golangci.yml.tmp .task/golangci.yml ||
[ -f .task/golangci.yml ]
silent: true
- task: golangci-lint
vars:
ARGS: run {{if .OWN_CONFIG}}{{else}}--config .task/golangci.yml{{end}} {{.LINT_FLAGS}}

test:
desc: run the tests with the pinned gotestsum
deps: [check]
cmds:
- task: gotestsum
vars:
ARGS: -- {{.TEST_FLAGS}}

vuln:
desc: scan the module with the pinned govulncheck
cmds:
- task: govulncheck
vars:
ARGS: ./...
Loading