diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..af03999 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +/bin +/dist diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..fb86a69 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,30 @@ +#!/bin/sh +set -e + +go mod tidy -diff + +files=$(git diff --cached --name-only --diff-filter=ACM -- '*.go') +[ -n "$files" ] || exit 0 +pkgs=$(echo "$files" | xargs -n1 dirname | sort -u | sed 's|^|./|') + +unformatted=$(gofmt -l $files) +if [ -n "$unformatted" ]; then + echo "gofmt: $unformatted" >&2 + exit 1 +fi + +# The program is Linux-only (netlink), so everything is checked for Linux. +export GOOS=linux + +go vet $pkgs + +# Test binaries only run on a Linux host; elsewhere they are compiled, not run. +if [ "$(go env GOHOSTOS)" = linux ]; then + go test -race $pkgs +else + go test -exec /usr/bin/true $pkgs +fi + +if command -v golangci-lint >/dev/null; then + golangci-lint run $pkgs +fi diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..540f0d3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + groups: + go-dependencies: + patterns: ["*"] + update-types: [minor, patch] + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + actions: + patterns: ["*"] + + - package-ecosystem: docker + directory: / + schedule: + interval: weekly + groups: + base-images: + patterns: ["*"] diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..d14e430 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,83 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + # A CVE published after a merge is still our problem, so rescan daily. + schedule: + - cron: "0 4 * * *" + workflow_dispatch: + +permissions: + contents: read + +# A push supersedes the run of the commit before it, so drop that one instead of +# waiting for a verdict on code nobody has any more. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + go: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + - run: gofmt -l cmd lib | tee /dev/stderr | (! read -r) + - run: go mod tidy -diff + - run: go vet ./... + - run: go test -race ./... + - run: make build + # Read from a plain file so the upstream-sync workflow can bump it without a + # token carrying the workflow scope. + - id: lint-version + run: echo "version=$(cat hack/golangci-lint.version)" >> "$GITHUB_OUTPUT" + - uses: golangci/golangci-lint-action@v9 + with: + version: ${{ steps.lint-version.outputs.version }} + + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + # Reachability-aware, so it only fails on vulnerabilities we actually call. + # It installs its own Go, hence no setup-go step; go-version-input must be + # cleared or it collides with go-version-file. + - uses: golang/govulncheck-action@v1.1.0 + with: + repo-checkout: false + go-version-file: go.mod + go-version-input: "" + - uses: aquasecurity/trivy-action@v0.36.0 + with: + scan-type: fs + scan-ref: . + scanners: vuln,secret + format: table + exit-code: "1" + ignore-unfixed: true + severity: CRITICAL,HIGH + + image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: docker/setup-buildx-action@v4 + - uses: docker/build-push-action@v7 + with: + context: . + push: false + load: true + tags: routingtabletowg:ci + cache-from: type=gha + cache-to: type=gha,mode=max + - uses: aquasecurity/trivy-action@v0.36.0 + with: + image-ref: routingtabletowg:ci + format: table + exit-code: "1" + ignore-unfixed: true + severity: CRITICAL,HIGH diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..c6c4ff7 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,68 @@ +name: release + +# 1.2.3 is a release, anything with a suffix (1.2.3-rc.1) a prerelease. The tags +# carry no v prefix. +on: + push: + tags: ["[0-9]+.[0-9]+.[0-9]+*"] + +permissions: + contents: write + packages: write + id-token: write + +jobs: + image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + # Emulation for the RUN steps of the non-native platforms. + - uses: docker/setup-qemu-action@v4 + - uses: docker/setup-buildx-action@v4 + - uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # The tag is v-less already, so {{version}} is the tag itself. major.minor and + # major are skipped for prereleases by the action itself. + - id: meta + uses: docker/metadata-action@v6 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + - uses: docker/build-push-action@v7 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + sbom: true + provenance: mode=max + + release: + runs-on: ubuntu-latest + needs: image + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Create the GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + flags=() + case "${GITHUB_REF_NAME}" in + *-*) flags+=(--prerelease) ;; + # Notes for a stable release span back to the previous stable tag in its + # history, not to the last RC or a newer release on another branch. + *) + prev=$(git describe --tags --abbrev=0 --match '[0-9]*' --exclude '*-*' "${GITHUB_REF_NAME}^" 2>/dev/null || true) + [ -n "${prev}" ] && flags+=(--notes-start-tag "${prev}") + ;; + esac + gh release create "${GITHUB_REF_NAME}" --verify-tag --generate-notes "${flags[@]}" diff --git a/.github/workflows/upstream-sync.yaml b/.github/workflows/upstream-sync.yaml new file mode 100644 index 0000000..52be3c0 --- /dev/null +++ b/.github/workflows/upstream-sync.yaml @@ -0,0 +1,75 @@ +# The golangci-lint version lives in hack/golangci-lint.version, which Dependabot +# cannot parse. This bumps it and opens a PR. +# +# Events caused by GITHUB_TOKEN start no workflows. AUTOMATION_SSH_KEY is an optional +# repository secret holding the private half of a deploy key with write access; the +# branch is pushed over SSH with it, so pushes to an open PR run ci.yaml. Opening the PR +# is still a GITHUB_TOKEN event, so a new PR gets its checks from a dispatched run on +# its branch, as workflow_dispatch is the exception to that rule. +name: upstream-sync + +on: + schedule: + - cron: "17 4 * * *" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + actions: write + +jobs: + golangci-lint: + runs-on: ubuntu-latest + concurrency: + group: upstream-sync-golangci-lint + cancel-in-progress: true + steps: + - uses: actions/checkout@v7 + with: + ssh-key: ${{ secrets.AUTOMATION_SSH_KEY }} + + - id: detect + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + current=$(cat hack/golangci-lint.version) + latest=$(gh release view --repo golangci/golangci-lint --json tagName -q .tagName) + echo "current=${current}" >> "$GITHUB_OUTPUT" + echo "latest=${latest}" >> "$GITHUB_OUTPUT" + [ "${current}" = "${latest}" ] && echo "changed=false" >> "$GITHUB_OUTPUT" || echo "changed=true" >> "$GITHUB_OUTPUT" + + - if: steps.detect.outputs.changed == 'true' + run: echo "${{ steps.detect.outputs.latest }}" > hack/golangci-lint.version + + # A new release usually means new checks, so let the PR say whether we pass. + - if: steps.detect.outputs.changed == 'true' + id: lint + continue-on-error: true + uses: golangci/golangci-lint-action@v9 + with: + version: ${{ steps.detect.outputs.latest }} + + - if: steps.detect.outputs.changed == 'true' + id: pr + uses: peter-evans/create-pull-request@v8 + with: + branch: automation/golangci-lint-version-sync + delete-branch: true + commit-message: "Bump golangci-lint to ${{ steps.detect.outputs.latest }}" + title: "Bump golangci-lint to ${{ steps.detect.outputs.latest }}" + body: | + Automated golangci-lint upstream sync. + + - Previous: `${{ steps.detect.outputs.current }}` + - New: `${{ steps.detect.outputs.latest }}` + + Result of running the new version against the tree: `${{ steps.lint.outcome }}`. + A failure here is expected when a release adds checks; the findings are in the + job log of the run that opened this PR. + + - if: steps.pr.outputs.pull-request-operation == 'created' + env: + GH_TOKEN: ${{ github.token }} + run: gh workflow run ci.yaml --ref automation/golangci-lint-version-sync diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ccecce --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/bin +/dist +.* +!.gitignore +!.githooks +!.github +!.dockerignore +!.golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..850fcb1 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,21 @@ +version: "2" + +linters: + enable: + - bodyclose + - contextcheck + - errorlint + - gocritic + - nilerr + - rowserrcheck + - unconvert + - wastedassign + - revive + settings: + revive: + rules: + - name: unused-parameter + exclusions: + presets: + # Close and os.Remove on cleanup paths, with nothing to report the error to. + - std-error-handling diff --git a/Dockerfile b/Dockerfile index 47d2103..db255c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ FROM --platform=$BUILDPLATFORM golang:1.27-alpine AS build ARG TARGETOS ARG TARGETARCH +ARG TARGETVARIANT WORKDIR /build @@ -11,7 +12,7 @@ COPY go.mod go.sum ./ RUN go mod download COPY . . -RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOARM=${TARGETVARIANT#v} \ go build -trimpath -ldflags "-s -w" -o app ./cmd/app diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 75bfc6d..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,102 +0,0 @@ -def IMAGE_TAG = "" -pipeline { - agent { - kubernetes { - yaml """ -apiVersion: v1 -kind: Pod -metadata: - name: kaniko -spec: - containers: - - name: kaniko - image: gcr.io/kaniko-project/executor:debug - imagePullPolicy: Always - command: - - /busybox/cat - tty: true - volumeMounts: - - name: jenkins-docker-cfg - mountPath: /kaniko/.docker - volumes: - - name: jenkins-docker-cfg - projected: - sources: - - secret: - name: docker-credentials - items: - - key: data - path: config.json -""" - } - } - environment { - IMAGE_PUSH_DESTINATION="ghcr.io/ruakij/routingtabletowg" - } - stages { - stage("Pre-build") { - steps { - - script{ - //checkout scm - checkout([ - $class: 'GitSCM', - branches: scm.branches, - doGenerateSubmoduleConfigurations: scm.doGenerateSubmoduleConfigurations, - extensions: scm.extensions + [[$class: 'CloneOption', noTags: false, reference: '', shallow: true]], - submoduleCfg: [], - userRemoteConfigs: scm.userRemoteConfigs - ]) - - def version = sh (returnStdout: true, script: "git describe --tags --long --always $GIT_COMMIT").trim() - def gitCommit = sh (returnStdout: true, script: "git rev-parse --short $GIT_COMMIT").trim() - echo "Version: $version" - echo "Git Commit: $gitCommit" - - IMAGE_TAG = "--destination $IMAGE_PUSH_DESTINATION:$gitCommit " - - if (GIT_BRANCH == "main") { - IMAGE_TAG += "--destination $IMAGE_PUSH_DESTINATION:latest " - - if(version != gitCommit){ - def parts = version.split('.') - if(parts.size() > 0){ - for (int i = 0; i < parts.size(); i++) { - def versionTag = parts[0..i].join(".") - IMAGE_TAG += "--destination $IMAGE_PUSH_DESTINATION:$versionTag " - } - } - } - } else { - IMAGE_TAG += "--destination $IMAGE_PUSH_DESTINATION:$GIT_BRANCH " - - if(version != gitCommit){ - def parts = version.split('.') - if(parts.size() > 0){ - for (int i = 0; i < parts.size(); i++) { - def versionTag = parts[0..i].join(".") - IMAGE_TAG += "--destination $IMAGE_PUSH_DESTINATION:$GIT_BRANCH-$versionTag " - } - } - } - } - - echo "Image-Tags: $IMAGE_TAG" - } - } - } - - stage('Build with Kaniko') { - steps { - container(name: 'kaniko', shell: '/busybox/sh') { - withEnv(['PATH+EXTRA=/busybox', "IMAGE_TAG=$IMAGE_TAG"]) { - // Use the image tag variable as part of the image name when you build and push the image with kaniko - sh '''#!/busybox/sh - /kaniko/executor --context `pwd` --force $IMAGE_TAG - ''' - } - } - } - } - } -} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7952df9 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +LDFLAGS = -s -w + +.PHONY: build +build: + CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "$(LDFLAGS)" -o bin/routingtabletowg ./cmd/app diff --git a/README.md b/README.md index c22b112..a70a4bd 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ version: '3' services: routingtabletowg: - image: ruakij/routingtabletowg + image: ghcr.io/ruakij/routingtabletowg restart: unless-stopped network_mode: host cap_add: @@ -76,9 +76,21 @@ services: ### 1.2.3. Without Docker -Clone the Repository `git clone ` and build the Program with `go build cmd/app` +Requires Go 1.27.3 or newer and Linux. -Then you can run it with `./app` +Clone the Repository `git clone ` and build the Program with `make build` (or `go build ./cmd/app`) + +Then you can run it with `./bin/routingtabletowg` + +
+ +### 1.2.4. Development + +Enable the pre-commit hook (formatting, `go vet`, tests, `golangci-lint`) with `git config core.hooksPath .githooks`. + +The program only builds for Linux; on other systems set `GOOS=linux` for `go vet` and `golangci-lint`. + +CI runs on GitHub Actions. Pushing a tag like `1.2.3` publishes `ghcr.io/ruakij/routingtabletowg` for `linux/amd64`, `linux/arm64` and `linux/arm/v7` and creates a GitHub release.
diff --git a/build/docker-multiarch.sh b/build/docker-multiarch.sh deleted file mode 100755 index 384ba3e..0000000 --- a/build/docker-multiarch.sh +++ /dev/null @@ -1,9 +0,0 @@ -TAG="ruakij/routingtabletowg" -PLATFORM="linux/amd64,linux/arm64/v8,linux/arm/v7" -EXTRA_ARGS="$@" - -docker buildx build \ ---platform $PLATFORM \ ---tag $TAG \ -$EXTRA_ARGS \ -. diff --git a/build/docker-ownarch.sh b/build/docker-ownarch.sh deleted file mode 100755 index cee8ba8..0000000 --- a/build/docker-ownarch.sh +++ /dev/null @@ -1,7 +0,0 @@ -TAG="ruakij/routingtabletowg" -EXTRA_ARGS="$@" - -docker build \ ---tag $TAG \ -$EXTRA_ARGS \ -. diff --git a/hack/golangci-lint.version b/hack/golangci-lint.version new file mode 100644 index 0000000..f855fc0 --- /dev/null +++ b/hack/golangci-lint.version @@ -0,0 +1 @@ +v2.15.0