diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..359eaf3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,42 @@ +# Build artifacts +bin/ +dist/ +linux/ +site/ +coverage.* +*.coverprofile +*.test +*.out + +# Node + UI build outputs (UI is built inside the Dockerfile when needed, +# or pre-embedded into internal/ui/dist by GoReleaser). +**/node_modules/ +ui/dist/ +internal/ui/dist/ + +# Docs (not needed in the runtime image; deployed separately to GH Pages) +docs/ +mkdocs.yml + +# Repo metadata +.git/ +.github/ +.gitignore +.gitattributes +.golangci.yml +.goreleaser.yml +.editorconfig + +# Local env files (never bake into an image) +.env +.env.* +!.env.example + +# IDE / OS noise +.idea/ +.vscode/ +.DS_Store +Thumbs.db + +# Local screenshot tooling +scripts/screenshots/node_modules/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..39dbf92 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,66 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: {} + +jobs: + release: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: write + packages: write + id-token: write + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: "1.26.3" + cache: true + + - name: Set up Node (for SPA build) + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + + - name: Set up pnpm + uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + with: + version: 10 + run_install: false + + - name: Verify Go modules + run: | + go mod download + go mod verify + + - name: Set up QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 + with: + distribution: goreleaser + version: "~> v2" + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 5fc9905..ffe16e2 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,10 @@ go.work.sum # Build artifacts /bin/ /site/ +# GoReleaser snapshot/release output (matches mcp-test convention) +/dist/ +# Local docker buildx scratch dir for `make docker` +/linux/ # UI build outputs (regenerated by `make ui`; only .gitkeep is tracked # under internal/ui/dist so the //go:embed directive has something to diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..36313f1 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,157 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj +# +# GoReleaser config for api-test. Publishes binaries to GitHub Releases +# and multi-arch container images to GHCR. Cosign keyless signing and +# SBOM generation are wired through via the release.yml workflow's +# id-token: write permission. + +version: 2 + +project_name: api-test + +env: + - GO111MODULE=on + +before: + hooks: + - go mod download + - go mod verify + # Build the React SPA into internal/ui/dist so the embedded UI is in + # every shipped binary. Skipped if ui/ is missing (pure-binary builds). + - bash -c "if [ -f ui/package.json ]; then cd ui && (corepack enable && pnpm install --frozen-lockfile && pnpm build || npm install && npm run build) && rm -rf ../internal/ui/dist && cp -R dist ../internal/ui/dist; fi" + +builds: + - id: api-test + main: ./cmd/api-test + binary: api-test + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + mod_timestamp: '{{ .CommitTimestamp }}' + env: + - CGO_ENABLED=0 + flags: + - -trimpath + - -a + ldflags: + - -s -w + - -X github.com/plexara/api-test/pkg/build.Version={{.Version}} + - -X github.com/plexara/api-test/pkg/build.Commit={{.ShortCommit}} + - -X github.com/plexara/api-test/pkg/build.Date={{.Date}} + +archives: + - id: default + name_template: >- + {{ .ProjectName }}_ + {{- .Version }}_ + {{- .Os }}_ + {{- .Arch }} + format_overrides: + - goos: windows + formats: [zip] + files: + - LICENSE + - README.md + +checksum: + name_template: 'checksums.txt' + algorithm: sha256 + +snapshot: + version_template: "{{ incpatch .Version }}-next" + +changelog: + sort: asc + use: github + filters: + exclude: + - '^docs:' + - '^test:' + - '^chore:' + - '^build:' + - Merge pull request + - Merge branch + groups: + - title: Features + regexp: '^feat' + order: 0 + - title: Bug Fixes + regexp: '^fix' + order: 1 + - title: Security + regexp: '^security' + order: 2 + - title: Others + order: 999 + +# Multi-arch container image to GHCR. +# +# `extra_files` augments GoReleaser's per-platform Docker build context. +# Without it the context only contains the linux//api-test binaries +# GoReleaser stages. We add LICENSE for OCI compliance; no example config +# is shipped in the image (operators mount their own). +dockers_v2: + - id: scratch + dockerfile: Dockerfile + ids: + - api-test + images: + - ghcr.io/plexara/api-test + tags: + - latest + - "{{ .Tag }}" + - "v{{ .Major }}" + platforms: + - linux/amd64 + - linux/arm64 + extra_files: + - LICENSE + labels: + org.opencontainers.image.created: "{{ .Date }}" + org.opencontainers.image.title: "{{ .ProjectName }}" + org.opencontainers.image.revision: "{{ .FullCommit }}" + org.opencontainers.image.version: "{{ .Version }}" + org.opencontainers.image.source: "{{ .GitURL }}" + org.opencontainers.image.description: "A controllable HTTP REST fixture for testing API gateways" + org.opencontainers.image.licenses: "Apache-2.0" + org.opencontainers.image.vendor: "Plexara" + org.opencontainers.image.url: "https://api-test.plexara.io" + +release: + github: + owner: plexara + name: api-test + name_template: "{{.ProjectName}}-v{{.Version}}" + prerelease: auto + mode: append + footer: | + ## Installation + + ### Container + + ```bash + docker pull ghcr.io/plexara/api-test:{{ .Tag }} + ``` + + ### Binary (macOS / Linux) + + ```bash + curl -L -o api-test.tar.gz \ + https://github.com/plexara/api-test/releases/download/{{ .Tag }}/api-test_{{ .Version }}_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz + tar -xzf api-test.tar.gz + ./api-test --version + ``` + + ### Documentation + + Full docs at . + + Open source by [Plexara](https://plexara.io), the commercial MCP + API gateway with configurable enrichment built in. diff --git a/Dockerfile b/Dockerfile index f1ddbee..cf545c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,42 +1,39 @@ -# Multi-stage build for the api-test fixture binary. +# syntax=docker/dockerfile:1 # -# Stage 1: build the static linux binary with version metadata stamped in. -# Stage 2: distroless base; the binary doubles as its own healthcheck via -# `--healthcheck` so we don't need curl/wget in the runtime image. +# api-test runtime image. Goreleaser supplies the pre-built binary in +# the build context (one per linux/); we just bundle it with CA +# certs and run as a non-root user. -FROM golang:1.26 AS build +FROM alpine:3.23 AS certs +RUN apk add --no-cache ca-certificates -ARG TARGETARCH=amd64 -ARG VERSION=dev -ARG COMMIT=none -ARG BUILD_DATE=unknown +FROM scratch -WORKDIR /src -COPY go.mod go.sum* ./ -RUN go mod download +# TLS root certs so OIDC discovery (HTTPS to the IdP) works. +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -COPY . . +# Goreleaser sets TARGETARCH for the platform-specific binary path. +ARG TARGETARCH +COPY linux/${TARGETARCH}/api-test /usr/local/bin/api-test -RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \ - go build \ - -trimpath \ - -ldflags "-s -w \ - -X github.com/plexara/api-test/pkg/build.Version=${VERSION} \ - -X github.com/plexara/api-test/pkg/build.Commit=${COMMIT} \ - -X github.com/plexara/api-test/pkg/build.Date=${BUILD_DATE}" \ - -o /out/api-test \ - ./cmd/api-test - -FROM gcr.io/distroless/static-debian12:nonroot +# No config is baked in. Operators mount one (or use env vars): +# +# docker run --rm \ +# -v $(pwd)/api-test.yaml:/app/configs/api-test.yaml:ro \ +# ghcr.io/plexara/api-test:latest +# +# A starter config to copy from lives in the source tree at +# configs/api-test.example.yaml on the GitHub repo. -COPY --from=build /out/api-test /usr/local/bin/api-test -COPY --chown=nonroot:nonroot configs/api-test.dev.yaml /etc/api-test/api-test.yaml +# Non-root (scratch has no /etc/passwd; numeric IDs only). +USER 1000:1000 EXPOSE 8080 -USER nonroot:nonroot +# The binary doubles as its own healthcheck via `--healthcheck`, which +# probes 127.0.0.1:8080/healthz. No curl/wget needed in the runtime image. HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ CMD ["/usr/local/bin/api-test", "--healthcheck"] ENTRYPOINT ["/usr/local/bin/api-test"] -CMD ["--config", "/etc/api-test/api-test.yaml"] +CMD ["--config", "/app/configs/api-test.yaml"]