diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index cb89f54..2da453b 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -27,6 +27,7 @@ jobs: persist-credentials: false - uses: jdx/mise-action@9149ea85001c7435d5a66bb127d6a1b6227cb0a5 # v5.0.0 - run: just lint + - run: just lint-gh env: GH_TOKEN: ${{ github.token }} # zizmor online audits - run: just validate diff --git a/.onedev-buildspec.yml b/.onedev-buildspec.yml new file mode 100644 index 0000000..765adbf --- /dev/null +++ b/.onedev-buildspec.yml @@ -0,0 +1,87 @@ +version: 52 +jobs: +- name: checks + jobExecutor: remote + steps: + - type: CheckoutStep + name: checkout + cloneCredential: + type: DefaultCredential + withLfs: false + withSubmodules: false + condition: SUCCESSFUL + optional: false + - type: CommandStep + name: lint and validate + runInContainer: true + image: docker:29.8.2-cli + interpreter: + type: PosixInterpreter + shell: sh + commands: | + set -eu + apk add --no-cache -q bash git jq python3 curl mise + git config --global --add safe.directory '*' + mise trust -q .mise.toml + mise install -q + mise exec -- just lint + mise exec -- just validate + runAs: 0:0 + useTTY: true + condition: SUCCESSFUL + optional: false + triggers: + - type: PullRequestUpdateTrigger + branches: main + - type: BranchUpdateTrigger + branches: main + userMatch: anyone + retryCondition: never + maxRetries: 3 + retryDelay: 30 + timeout: 3600 +- name: images + jobExecutor: remote + steps: + - type: CheckoutStep + name: checkout + cloneCredential: + type: DefaultCredential + withLfs: false + withSubmodules: false + condition: SUCCESSFUL + optional: false + - type: CommandStep + name: check tags, build, scan, smoke test + runInContainer: true + image: docker:29.8.2-cli + interpreter: + type: PosixInterpreter + shell: sh + commands: | + set -eu + apk add --no-cache -q bash git jq python3 curl + git config --global --add safe.directory '*' + # On a pull request the build runs on a merge commit: its first + # parent is the target branch, which check-tags compares against. + if [ "$(git rev-list --parents -n 1 HEAD | wc -w)" -gt 2 ]; then + bash scripts/check-tags.sh "$(git rev-parse HEAD^1)" + fi + bash scripts/ci-images.sh + runAs: 0:0 + envVars: + - name: COMMIT + value: '@commit_hash@' + useTTY: true + condition: SUCCESSFUL + optional: false + triggers: + - type: PullRequestUpdateTrigger + branches: main + - type: BranchUpdateTrigger + branches: main + userMatch: anyone + retryCondition: never + maxRetries: 3 + retryDelay: 30 + timeout: 7200 diff --git a/README.md b/README.md index 5beee3a..75ef047 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,22 @@ can work with OneDev issues, pull requests and builds. keeps tod below 4.3.3 (`renovate.json`). Remove that rule after the server upgrade. +## OneDev CI (migration in progress) + +The repository is moving to OneDev (`moveyourdigital/devops/coder-templates`). +Until the switch, GitHub stays the source of truth and `main` is pushed to +OneDev after each merge. `.onedev-buildspec.yml` (validate with +`tod build check-spec`), executor `remote`: + +- `checks`: `just lint` + `just validate` (Alpine `docker:29.8.2-cli`, tools + from `.mise.toml`). `just lint-gh` (actionlint, zizmor) is GitHub only. +- `images`: on a pull request `scripts/check-tags.sh `, then + `scripts/ci-images.sh`: build, trivy scan and smoke test of every image + with a new tag. **No push yet**: `main.tf` still points to GHCR. + +Still to do: push to the OneDev registry, daily scan + rebuild PRs, Renovate +(built-in step), required builds, image pulls on x1, then archive GitHub. + ## Pull request checks `.github/workflows/checks.yml` runs on each pull request to `main` diff --git a/justfile b/justfile index 74234cd..089066d 100644 --- a/justfile +++ b/justfile @@ -17,17 +17,20 @@ templates := "node-js php-wordpress ops" default: @just --list -# Check formatting, Dockerfiles, JSON, scripts, workflows, Renovate config and secrets +# Check formatting, Dockerfiles, JSON, scripts, Renovate config and secrets lint: terraform fmt -check -recursive hadolint --config .hadolint.yaml */build/Dockerfile for f in */build/*.json; do python3 -m json.tool "$f" >/dev/null || exit 1; done shellcheck tests/*.sh scripts/*.sh - actionlint check-jsonschema --builtin-schema vendor.renovate renovate.json - zizmor -q --no-progress --min-severity low .github/workflows gitleaks dir . --no-banner --redact +# Check the GitHub workflows (GitHub only; removed after the OneDev move) +lint-gh: + actionlint + zizmor -q --no-progress --min-severity low .github/workflows + # terraform validate each template (in a temp copy: no lock file left behind) validate: #!/usr/bin/env bash @@ -45,43 +48,7 @@ validate: # A change under build/ needs a new image tag. A tag already in GHCR is # accepted only if it was built from the same build/. check-tags base="origin/main": - #!/usr/bin/env bash - set -euo pipefail - err() { if [ -n "${GITHUB_ACTIONS:-}" ]; then echo "::error file=$1::$2"; else echo "error: $2" >&2; fi; } - fail=0 - for t in {{templates}}; do - new="$(just image "$t")" - old="$(git show "{{base}}:$t/main.tf" 2>/dev/null | scripts/image-ref.sh - 2>/dev/null || true)" - if [ "$new" = "$old" ]; then - if git diff --quiet "{{base}}...HEAD" -- "$t/build"; then - echo "ok: $t (unchanged: $new)" - else - err "$t/main.tf" "$t/build changed, but the image tag did not: $new"; fail=1 - fi - else - # Fail closed: only an explicit "absent" counts as a new tag. - state="$(scripts/image-state.sh "$new")" || { err "$t/main.tf" "cannot check $new in GHCR"; fail=1; continue; } - if [ "$state" = present ]; then - # An existing tag is fine only if it was built from the same build/ - # (security rebuild by scan.yml, or a rollback). Never re-pushed. - rev="$(scripts/image-revision.sh "$new")" || { err "$t/main.tf" "cannot read the revision of $new"; fail=1; continue; } - if git cat-file -e "$rev^{commit}" 2>/dev/null && git diff --quiet "$rev" HEAD -- "$t/build"; then - # The build workflow skips existing tags: scan it here, it may - # have CVEs found after it was built. - if docker pull -q "$new" >/dev/null && scripts/trivy-scan.sh "$new"; then - echo "ok: $t (existing tag, same build/ as ${rev:0:7}, trivy clean: $new)" - else - err "$t/main.tf" "existing tag has fixable CRITICAL vulnerabilities (or the scan failed): $new"; fail=1 - fi - else - err "$t/main.tf" "tag already in GHCR, built from a different build/ (${rev:0:7}): $new"; fail=1 - fi - else - echo "ok: $t (new tag: $new)" - fi - fi - done - exit "$fail" + scripts/check-tags.sh "{{base}}" # Image used by a template (from main.tf) image t: diff --git a/scripts/check-tags.sh b/scripts/check-tags.sh new file mode 100755 index 0000000..2719116 --- /dev/null +++ b/scripts/check-tags.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# A change under