diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml new file mode 100644 index 0000000..a9e2a54 --- /dev/null +++ b/.github/workflows/develop.yml @@ -0,0 +1,174 @@ +name: Develop Build + +on: + push: + branches: + - develop + +permissions: + contents: read + id-token: write # required for Workload Identity Federation (signing only) + +jobs: + build: + name: Build QC artifacts + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Check out repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + + - name: Run GoReleaser (all platforms, unsigned) + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + docker run --rm \ + -v "${PWD}:/workspace" \ + -w /workspace \ + -e GITHUB_TOKEN \ + ghcr.io/goreleaser/goreleaser-cross:v1.25-v2.12.7 \ + release --snapshot --config .goreleaser/config-qc.yaml --clean --parallelism 2 + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: qc-artifacts-${{ github.run_number }} + if-no-files-found: error + path: | + dist/*.tar.gz + dist/*.zip + dist/checksums.txt + dist/artifacts.json + dist/metadata.json + + sign-macos: + name: Sign macOS artifacts + runs-on: macos-latest + needs: build + timeout-minutes: 30 + + steps: + - name: Check out repository + uses: actions/checkout@v5 + + - name: Check for Apple signing secrets + id: secrets_check + env: + APPLE_CERT_P12: ${{ secrets.APPLE_CERT_P12 }} + run: | + if [ -n "$APPLE_CERT_P12" ]; then + echo "has_signing_cert=true" >> "$GITHUB_OUTPUT" + else + echo "has_signing_cert=false" >> "$GITHUB_OUTPUT" + echo "::warning::APPLE_CERT_P12 not configured — skipping Apple code signing." + fi + + - name: Import Apple certificate + if: ${{ steps.secrets_check.outputs.has_signing_cert == 'true' }} + env: + APPLE_CERT_P12: ${{ secrets.APPLE_CERT_P12 }} + APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + set -e + for var_name in APPLE_CERT_PASSWORD KEYCHAIN_PASSWORD; do + if [ -z "${!var_name}" ]; then + echo "::error::$var_name is required when APPLE_CERT_P12 is configured." + exit 1 + fi + done + + printf '%s' "$APPLE_CERT_P12" | base64 -D > cert.p12 + + KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db" + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security import cert.p12 -k "$KEYCHAIN_PATH" -P "$APPLE_CERT_PASSWORD" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"' | xargs) + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: qc-artifacts-${{ github.run_number }} + path: dist + + - name: Sign macOS artifacts + if: ${{ steps.secrets_check.outputs.has_signing_cert == 'true' }} + env: + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + KEYCHAIN_PATH: ${{ runner.temp }}/build.keychain-db + run: | + set -e + if [ -z "$APPLE_SIGNING_IDENTITY" ]; then + echo "::error::APPLE_SIGNING_IDENTITY is required for signing." + exit 1 + fi + if [ ! -f dist/checksums.txt ]; then + echo "::error::dist/checksums.txt not found." + exit 1 + fi + + for archive in dist/*-darwin-*.tar.gz; do + [ -f "$archive" ] || continue + tmpdir="$(mktemp -d)" + orig_members=$(tar -tzf "$archive") + tar -xzf "$archive" -C "$tmpdir" + + while IFS= read -r -d '' bin; do + codesign --force --options runtime --timestamp \ + --keychain "$KEYCHAIN_PATH" \ + --sign "$APPLE_SIGNING_IDENTITY" "$bin" + codesign --verify --deep --strict "$bin" + done < <(find "$tmpdir" -type f -perm -111 -print0) + + # shellcheck disable=SC2086 + tar -czf "${archive}.signed" -C "$tmpdir" $orig_members + mv "${archive}.signed" "$archive" + rm -rf "$tmpdir" + + filename="$(basename "$archive")" + sha="$(shasum -a 256 "$archive" | awk '{print $1}')" + grep -v " $filename\$" dist/checksums.txt > dist/checksums.new || true + printf "%s %s\n" "$sha" "$filename" >> dist/checksums.new + mv dist/checksums.new dist/checksums.txt + + PATCH_NAME="$filename" PATCH_SHA="$sha" python3 << 'PYEOF' + import json, os + data = json.load(open('dist/artifacts.json')) + name = os.environ['PATCH_NAME'] + checksum = os.environ['PATCH_SHA'] + for a in data: + if a.get('name') == name: + a.setdefault('extra', {})['Checksum'] = 'sha256:' + checksum + with open('dist/artifacts.json', 'w') as f: + json.dump(data, f, indent=2) + PYEOF + done + + - name: Upload signed macOS artifacts + if: ${{ steps.secrets_check.outputs.has_signing_cert == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: qc-artifacts-macos-signed-${{ github.run_number }} + if-no-files-found: error + path: | + dist/*-darwin-amd64.tar.gz + dist/*-darwin-arm64.tar.gz + dist/checksums.txt + + - name: Clean up keychain and cert + if: always() + run: | + security delete-keychain "$RUNNER_TEMP/build.keychain-db" 2>/dev/null || true + rm -f cert.p12 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d4a0d2..208409f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,6 +37,22 @@ jobs: go-version-file: go.mod cache: true + - name: Verify release branch + run: | + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + # Tag pushes don't populate origin/* remote-tracking refs, so fetch + # the main branches explicitly before checking containment. + git fetch --quiet origin '+refs/heads/main:refs/remotes/origin/main' '+refs/heads/main-*:refs/remotes/origin/main-*' || true + # Allow main or a main- branch (e.g. main-hotfix) for emergency releases. + if ! git branch -r --contains "$GITHUB_SHA" | grep -qE '^\s*origin/main($|-)'; then + echo "::error::Tag $GITHUB_REF_NAME must be on the main branch (or a main- branch)." + exit 1 + fi + elif [[ "$GITHUB_REF" != "refs/heads/main" && "$GITHUB_REF" != refs/heads/main-* && "${{ inputs.snapshot }}" != "true" ]]; then + echo "::error::Release workflow must be dispatched from the main branch (or a main- branch, or use snapshot=true for ad-hoc builds)." + exit 1 + fi + - name: Validate dispatch inputs if: github.event_name == 'workflow_dispatch' env: @@ -76,9 +92,12 @@ jobs: run: | set -e EXTRA="" + CONFIG=".goreleaser/config.yaml" if [ "$SNAPSHOT" = "true" ]; then + # Snapshot builds are QC builds: QC endpoints, apimetrics-qc binary. EXTRA="--snapshot --skip homebrew" - echo "Running goreleaser in snapshot mode (no publish)" + CONFIG=".goreleaser/config-qc.yaml" + echo "Running goreleaser in snapshot mode (QC build, no publish)" elif [[ "$GITHUB_REF" == refs/tags/* ]]; then EXTRA="--skip publish,homebrew" echo "Building artifacts without publishing; GitHub release and GCS upload deferred to finalization" @@ -89,7 +108,7 @@ jobs: -w /workspace \ -e GITHUB_TOKEN \ ghcr.io/goreleaser/goreleaser-cross:v1.25-v2.12.7 \ - release $EXTRA --clean --parallelism 2 + release $EXTRA --config "$CONFIG" --clean --parallelism 2 - name: Create GitHub draft release if: ${{ startsWith(github.ref, 'refs/tags/') && inputs.snapshot != true }} @@ -127,11 +146,60 @@ jobs: dist/artifacts.json dist/metadata.json + # Snapshot (QC) builds: upload each asset as its own artifact so the + # Actions run lists them individually rather than as one combined zip. + - name: Upload QC snapshot — macOS (Intel) + if: ${{ inputs.snapshot == true }} + uses: actions/upload-artifact@v4 + with: + name: apimetrics-qc-darwin-amd64 + if-no-files-found: error + path: dist/*-darwin-amd64.tar.gz + + - name: Upload QC snapshot — macOS (Apple Silicon) + if: ${{ inputs.snapshot == true }} + uses: actions/upload-artifact@v4 + with: + name: apimetrics-qc-darwin-arm64 + if-no-files-found: error + path: dist/*-darwin-arm64.tar.gz + + - name: Upload QC snapshot — Linux (amd64) + if: ${{ inputs.snapshot == true }} + uses: actions/upload-artifact@v4 + with: + name: apimetrics-qc-linux-amd64 + if-no-files-found: error + path: dist/*-linux-amd64.tar.gz + + - name: Upload QC snapshot — Linux (arm64) + if: ${{ inputs.snapshot == true }} + uses: actions/upload-artifact@v4 + with: + name: apimetrics-qc-linux-arm64 + if-no-files-found: error + path: dist/*-linux-arm64.tar.gz + + - name: Upload QC snapshot — Windows (amd64) + if: ${{ inputs.snapshot == true }} + uses: actions/upload-artifact@v4 + with: + name: apimetrics-qc-windows-amd64 + if-no-files-found: error + path: dist/*-windows-amd64.zip + + - name: Upload QC snapshot — checksums + if: ${{ inputs.snapshot == true }} + uses: actions/upload-artifact@v4 + with: + name: checksums.txt + if-no-files-found: error + path: dist/checksums.txt + release-macos: name: Sign and notarize macOS artifacts runs-on: macos-latest needs: release - if: ${{ inputs.snapshot != true }} timeout-minutes: 30 steps: @@ -176,11 +244,21 @@ jobs: security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"' | xargs) - name: Download release artifacts + if: ${{ inputs.snapshot != true }} uses: actions/download-artifact@v4 with: name: release-artifacts path: dist + # Snapshot builds upload each asset as its own artifact; pull them all + # back (flattened) so the signing step finds the darwin archives. + - name: Download QC snapshot artifacts + if: ${{ inputs.snapshot == true }} + uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: dist + - name: Sign and notarize macOS artifacts if: ${{ steps.secrets_check.outputs.has_signing_cert == 'true' }} env: @@ -237,7 +315,9 @@ jobs: printf "%s %s\n" "$sha" "$filename" >> dist/checksums.new mv dist/checksums.new dist/checksums.txt - # Keep artifacts.json in sync with the signed checksums for archival accuracy. + # Keep artifacts.json in sync with the signed checksums for archival + # accuracy. Only present for full releases, not snapshot builds. + if [ -f dist/artifacts.json ]; then PATCH_NAME="$filename" PATCH_SHA="$sha" python3 << 'PYEOF' import json, os data = json.load(open('dist/artifacts.json')) @@ -249,10 +329,11 @@ jobs: with open('dist/artifacts.json', 'w') as f: json.dump(data, f, indent=2) PYEOF + fi done - name: Upload signed macOS artifacts - if: ${{ steps.secrets_check.outputs.has_signing_cert == 'true' }} + if: ${{ steps.secrets_check.outputs.has_signing_cert == 'true' && inputs.snapshot != true }} uses: actions/upload-artifact@v4 with: name: macos-signed-artifacts @@ -262,6 +343,35 @@ jobs: dist/*-darwin-arm64.tar.gz dist/checksums.txt + # Snapshot builds: replace the unsigned darwin placeholders uploaded by + # the build job with the signed archives, listed as individual assets. + - name: Upload signed QC snapshot — macOS (Intel) + if: ${{ inputs.snapshot == true && steps.secrets_check.outputs.has_signing_cert == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: apimetrics-qc-darwin-amd64 + overwrite: true + if-no-files-found: error + path: dist/*-darwin-amd64.tar.gz + + - name: Upload signed QC snapshot — macOS (Apple Silicon) + if: ${{ inputs.snapshot == true && steps.secrets_check.outputs.has_signing_cert == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: apimetrics-qc-darwin-arm64 + overwrite: true + if-no-files-found: error + path: dist/*-darwin-arm64.tar.gz + + - name: Upload signed QC snapshot — checksums + if: ${{ inputs.snapshot == true && steps.secrets_check.outputs.has_signing_cert == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: checksums.txt + overwrite: true + if-no-files-found: error + path: dist/checksums.txt + - name: Authenticate to Google Cloud if: ${{ startsWith(github.ref, 'refs/tags/') }} uses: google-github-actions/auth@v2 diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index 21b5759..0000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,80 +0,0 @@ -version: 2 - -project_name: apimetrics - -before: - hooks: - - go mod download - # TODO: Figure out how to test with CGO on all platforms. - - go test -v ./... - -builds: - - id: apimetrics-darwin-amd64 - goos: - - darwin - goarch: - - amd64 - env: - - CGO_ENABLED=1 - - CC=o64-clang - - CXX=o64-clang++ - - - id: apimetrics-darwin-arm64 - goos: - - darwin - goarch: - - arm64 - env: - - CGO_ENABLED=1 - - CC=oa64-clang - - CXX=oa64-clang++ - - - id: apimetrics-linux-amd64 - goos: - - linux - goarch: - - amd64 - env: - - CGO_ENABLED=1 - - CC=x86_64-linux-gnu-gcc - - CXX=x86_64-linux-gnu-g++ - - - id: apimetrics-linux-arm64 - goos: - - linux - goarch: - - arm64 - env: - - CGO_ENABLED=1 - - CC=aarch64-linux-gnu-gcc - - CXX=aarch64-linux-gnu-g++ - - - id: apimetrics-windows-amd64 - goos: - - windows - goarch: - - amd64 - env: - - CGO_ENABLED=1 - - CC=x86_64-w64-mingw32-gcc - - CXX=x86_64-w64-mingw32-g++ - -archives: - - name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}" - format_overrides: - - goos: windows - formats: - - zip - -checksum: - name_template: "checksums.txt" - -snapshot: - version_template: "{{ .Tag }}-next" - -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" diff --git a/.goreleaser/config-qc.yaml b/.goreleaser/config-qc.yaml new file mode 100644 index 0000000..85a699f --- /dev/null +++ b/.goreleaser/config-qc.yaml @@ -0,0 +1,128 @@ +version: 2 +project_name: apimetrics-qc +builds: + - id: apimetrics-qc-darwin-amd64 + goos: + - darwin + goarch: + - amd64 + targets: + - darwin_amd64_v1 + dir: . + main: . + binary: apimetrics-qc + builder: go + tool: go + command: build + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=o64-clang + - CXX=o64-clang++ + - id: apimetrics-qc-darwin-arm64 + goos: + - darwin + goarch: + - arm64 + targets: + - darwin_arm64_v8.0 + dir: . + main: . + binary: apimetrics-qc + builder: go + tool: go + command: build + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=oa64-clang + - CXX=oa64-clang++ + - id: apimetrics-qc-linux-amd64 + goos: + - linux + goarch: + - amd64 + targets: + - linux_amd64_v1 + dir: . + main: . + binary: apimetrics-qc + builder: go + tool: go + command: build + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=x86_64-linux-gnu-gcc + - CXX=x86_64-linux-gnu-g++ + - id: apimetrics-qc-linux-arm64 + goos: + - linux + goarch: + - arm64 + targets: + - linux_arm64_v8.0 + dir: . + main: . + binary: apimetrics-qc + builder: go + tool: go + command: build + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=aarch64-linux-gnu-gcc + - CXX=aarch64-linux-gnu-g++ + - id: apimetrics-qc-windows-amd64 + goos: + - windows + goarch: + - amd64 + targets: + - windows_amd64_v1 + dir: . + main: . + binary: apimetrics-qc + builder: go + tool: go + command: build + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=x86_64-w64-mingw32-gcc + - CXX=x86_64-w64-mingw32-g++ +archives: + - id: default + builds_info: + mode: 493 + name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}' + formats: + - tar.gz + format_overrides: + - goos: windows + formats: + - zip + files: + - src: license* + - src: LICENSE* + - src: readme* + - src: README* +snapshot: + version_template: '{{ .ShortCommit }}' +checksum: + name_template: checksums.txt + algorithm: sha256 +dist: dist +gomod: + gobinary: go +before: + hooks: + - go mod download + - go test -v ./... +git: + tag_sort: -version:refname diff --git a/.goreleaser/config.yaml b/.goreleaser/config.yaml new file mode 100644 index 0000000..55927d7 --- /dev/null +++ b/.goreleaser/config.yaml @@ -0,0 +1,287 @@ +version: 2 +project_name: apimetrics +release: + github: + owner: APImetrics + name: APImetrics-cli + name_template: '{{.Tag}}' +builds: + - id: apimetrics-darwin-amd64 + goos: + - darwin + goarch: + - amd64 + goamd64: + - v1 + go386: + - sse2 + goarm: + - "6" + goarm64: + - v8.0 + gomips: + - hardfloat + goppc64: + - power8 + goriscv64: + - rva20u64 + targets: + - darwin_amd64_v1 + dir: . + main: . + binary: apimetrics + builder: go + tool: go + command: build + flags: + - -tags=prod + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=o64-clang + - CXX=o64-clang++ + - id: apimetrics-darwin-arm64 + goos: + - darwin + goarch: + - arm64 + goamd64: + - v1 + go386: + - sse2 + goarm: + - "6" + goarm64: + - v8.0 + gomips: + - hardfloat + goppc64: + - power8 + goriscv64: + - rva20u64 + targets: + - darwin_arm64_v8.0 + dir: . + main: . + binary: apimetrics + builder: go + tool: go + command: build + flags: + - -tags=prod + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=oa64-clang + - CXX=oa64-clang++ + - id: apimetrics-linux-amd64 + goos: + - linux + goarch: + - amd64 + goamd64: + - v1 + go386: + - sse2 + goarm: + - "6" + goarm64: + - v8.0 + gomips: + - hardfloat + goppc64: + - power8 + goriscv64: + - rva20u64 + targets: + - linux_amd64_v1 + dir: . + main: . + binary: apimetrics + builder: go + tool: go + command: build + flags: + - -tags=prod + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=x86_64-linux-gnu-gcc + - CXX=x86_64-linux-gnu-g++ + - id: apimetrics-linux-arm64 + goos: + - linux + goarch: + - arm64 + goamd64: + - v1 + go386: + - sse2 + goarm: + - "6" + goarm64: + - v8.0 + gomips: + - hardfloat + goppc64: + - power8 + goriscv64: + - rva20u64 + targets: + - linux_arm64_v8.0 + dir: . + main: . + binary: apimetrics + builder: go + tool: go + command: build + flags: + - -tags=prod + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=aarch64-linux-gnu-gcc + - CXX=aarch64-linux-gnu-g++ + - id: apimetrics-windows-amd64 + goos: + - windows + goarch: + - amd64 + goamd64: + - v1 + go386: + - sse2 + goarm: + - "6" + goarm64: + - v8.0 + gomips: + - hardfloat + goppc64: + - power8 + goriscv64: + - rva20u64 + targets: + - windows_amd64_v1 + dir: . + main: . + binary: apimetrics + builder: go + tool: go + command: build + flags: + - -tags=prod + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser + env: + - CGO_ENABLED=1 + - CC=x86_64-w64-mingw32-gcc + - CXX=x86_64-w64-mingw32-g++ +archives: + - id: default + builds_info: + mode: 493 + name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}' + formats: + - tar.gz + format_overrides: + - goos: windows + formats: + - zip + files: + - src: license* + - src: LICENSE* + - src: readme* + - src: README* + - src: changelog* + - src: CHANGELOG* +snapshot: + version_template: '{{ .Tag }}-next' +checksum: + name_template: checksums.txt + algorithm: sha256 +docker_digest: + name_template: digests.txt +blobs: + - bucket: apimetrics-cli + provider: gs + directory: '{{ .Version }}' + content_disposition: attachment;filename={{.Filename}} +changelog: + filters: + exclude: + - '^docs:' + - '^test:' + sort: asc + format: '{{ .SHA }} {{ .Message }}' +dist: dist +env_files: + github_token: ~/.config/goreleaser/github_token + gitlab_token: ~/.config/goreleaser/gitlab_token + gitea_token: ~/.config/goreleaser/gitea_token +before: + hooks: + - go mod download + - go test -v ./... +source: + name_template: '{{ .ProjectName }}-{{ .Version }}' + format: tar.gz +gomod: + gobinary: go +announce: + twitter: + message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}' + mastodon: + message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}' + server: "" + reddit: + title_template: '{{ .ProjectName }} {{ .Tag }} is out!' + url_template: '{{ .ReleaseURL }}' + slack: + message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}' + username: GoReleaser + discord: + message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}' + author: GoReleaser + color: "3888754" + icon_url: https://goreleaser.com/static/avatar.png + teams: + title_template: '{{ .ProjectName }} {{ .Tag }} is out!' + message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}' + color: '#2D313E' + icon_url: https://goreleaser.com/static/avatar.png + smtp: + subject_template: '{{ .ProjectName }} {{ .Tag }} is out!' + body_template: 'You can view details from: {{ .ReleaseURL }}' + mattermost: + message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}' + title_template: '{{ .ProjectName }} {{ .Tag }} is out!' + username: GoReleaser + linkedin: + message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}' + telegram: + message_template: '{{ mdv2escape .ProjectName }} {{ mdv2escape .Tag }} is out{{ mdv2escape "!" }} Check it out at {{ mdv2escape .ReleaseURL }}' + parse_mode: MarkdownV2 + webhook: + message_template: '{ "message": "{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}"}' + content_type: application/json; charset=utf-8 + expected_status_codes: + - 200 + - 201 + - 202 + - 204 + opencollective: + title_template: '{{ .Tag }}' + message_template: '{{ .ProjectName }} {{ .Tag }} is out!
Check it out at {{ .ReleaseURL }}' + bluesky: + message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}' +git: + tag_sort: -version:refname +github_urls: + download: https://github.com +gitlab_urls: + download: https://gitlab.com diff --git a/bench_test.go b/bench_test.go index a4ed1a0..2bf729a 100644 --- a/bench_test.go +++ b/bench_test.go @@ -6,10 +6,10 @@ import ( "net/http" "testing" - "github.com/amzn/ion-go/ion" - "github.com/fxamacker/cbor/v2" "apicontext.com/apimetrics/cli" "apicontext.com/apimetrics/openapi" + "github.com/amzn/ion-go/ion" + "github.com/fxamacker/cbor/v2" "github.com/shamaton/msgpack/v2" "github.com/spf13/cobra" ) diff --git a/config_dev.go b/config_dev.go new file mode 100644 index 0000000..a6f856d --- /dev/null +++ b/config_dev.go @@ -0,0 +1,9 @@ +//go:build dev && !prod + +package main + +var baseURL = "http://localhost:8080" +var authURL = "https://local-apimetrics.auth0.com/authorize" +var tokenURL = "https://local-apimetrics.auth0.com/oauth/token" +var authAudience = "https://apimetrics-qc.appspot.com" +var clientID = "bpplXMDCn187JipRLl6Y9KrsTVZCJTbS" diff --git a/config_prod.go b/config_prod.go new file mode 100644 index 0000000..ef370a8 --- /dev/null +++ b/config_prod.go @@ -0,0 +1,9 @@ +//go:build prod + +package main + +var baseURL = "https://client.apimetrics.io" +var authURL = "https://auth.apimetrics.io/authorize" +var tokenURL = "https://auth.apimetrics.io/oauth/token" +var authAudience = "https://client.apimetrics.io" +var clientID = "dPbV4VPvioF4nZ3oGQMn7n1vE2pFNAAI" diff --git a/config_qc.go b/config_qc.go new file mode 100644 index 0000000..2691890 --- /dev/null +++ b/config_qc.go @@ -0,0 +1,9 @@ +//go:build !prod && !dev + +package main + +var baseURL = "https://qc-client.apimetrics.io" +var authURL = "https://qc-auth.apimetrics.io/authorize" +var tokenURL = "https://qc-auth.apimetrics.io/oauth/token" +var authAudience = "https://client.apimetrics.io" +var clientID = "4fhqu4lEH5ExaRh00X1B9WJSkjTnUmuK" diff --git a/main.go b/main.go index db8bc01..caed2aa 100644 --- a/main.go +++ b/main.go @@ -11,17 +11,9 @@ import ( ) var version string = "dev" -var commit string -var date string - -// Build-time API configuration — override with -ldflags at build time: -// -// go build -ldflags "-X main.baseURL=https://client.apimetrics.io ..." -var baseURL = "https://qc-client.apimetrics.io" -var authURL = "https://qc-auth.apimetrics.io/authorize" -var tokenURL = "https://qc-auth.apimetrics.io/oauth/token" -var authAudience = "https://client.apimetrics.io" -var clientID = "bj0yh0AjBMzfeOpffmCj5UP8FbmYDwcM" + +// baseURL, authURL, tokenURL, authAudience, clientID are defined in +// config_qc.go (default) or config_prod.go (build tag: prod). func main() { if version == "dev" { diff --git a/oauth/authcode.go b/oauth/authcode.go index bde8881..5ff2eef 100644 --- a/oauth/authcode.go +++ b/oauth/authcode.go @@ -6,6 +6,7 @@ import ( "crypto/sha256" "encoding/base64" "fmt" + "html" "net/http" "net/url" "os" @@ -22,105 +23,151 @@ import ( ) var htmlSuccess = ` - + + + + + + Login Successful — APImetrics + + + + -
-
-

Login Successful!

- Please return to the terminal. You may now close this window. -

+
+ +
+

Login Successful!

+

Please return to the terminal. You may now close this window.

` var htmlError = ` - + + + + + + Login Failed — APImetrics + + + + -
-
-

Error: $ERROR

- $DETAILS -

+
+ +
+

Login Failed

+

$ERROR
$DETAILS

@@ -187,7 +234,9 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err := r.URL.Query().Get("error"); err != "" { details := r.URL.Query().Get("error_description") - rendered := strings.Replace(strings.Replace(htmlError, "$ERROR", err, 1), "$DETAILS", details, 1) + // Escape the query params before interpolating into HTML to avoid + // reflected XSS via the localhost redirect URL. + rendered := strings.Replace(strings.Replace(htmlError, "$ERROR", html.EscapeString(err), 1), "$DETAILS", html.EscapeString(details), 1) w.Write([]byte(rendered)) h.c <- "" return