Compare commits
235 changed files with 20086 additions and 28844 deletions
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
.db
|
||||||
16
.env.example
16
.env.example
|
|
@ -8,13 +8,7 @@ MCP_REGISTRY_VERSION=dev
|
||||||
MCP_REGISTRY_DATABASE_URL=postgres://username:password@localhost:5432/mcp-registry
|
MCP_REGISTRY_DATABASE_URL=postgres://username:password@localhost:5432/mcp-registry
|
||||||
|
|
||||||
# Path or URL to import seed data (supports local files and HTTP URLs)
|
# Path or URL to import seed data (supports local files and HTTP URLs)
|
||||||
# For offline development, use: data/seed.json
|
MCP_REGISTRY_SEED_FROM=data/seed.json
|
||||||
MCP_REGISTRY_SEED_FROM=https://registry.modelcontextprotocol.io/v0/servers
|
|
||||||
|
|
||||||
# Validate seed data against the registry rules on import (default: true)
|
|
||||||
# Set to false for offline development when seeding from a local file, since
|
|
||||||
# data/seed.json is not guaranteed to pass validation
|
|
||||||
MCP_REGISTRY_ENABLE_REGISTRY_VALIDATION=true
|
|
||||||
|
|
||||||
# GitHub OAuth configuration
|
# GitHub OAuth configuration
|
||||||
# These creds are for local development with the 'MCP Registry Login (Local)' GitHub App
|
# These creds are for local development with the 'MCP Registry Login (Local)' GitHub App
|
||||||
|
|
@ -32,14 +26,6 @@ MCP_REGISTRY_JWT_PRIVATE_KEY=bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb
|
||||||
# This should be disabled in prod
|
# This should be disabled in prod
|
||||||
MCP_REGISTRY_ENABLE_ANONYMOUS_AUTH=false
|
MCP_REGISTRY_ENABLE_ANONYMOUS_AUTH=false
|
||||||
|
|
||||||
# GitHub OIDC token exchange (for `mcp-publisher login github-oidc`)
|
|
||||||
# Expected `aud` claim on incoming GitHub Actions OIDC tokens. Must equal the
|
|
||||||
# scheme + host that publishers pass via `--registry` (e.g. `https://registry.example.com`).
|
|
||||||
# The publisher CLI derives the audience from `--registry` automatically; the server
|
|
||||||
# rejects all OIDC exchanges if this is unset, so set it on every deployment that
|
|
||||||
# accepts GitHub Actions publishes.
|
|
||||||
MCP_REGISTRY_GITHUB_OIDC_AUDIENCE=
|
|
||||||
|
|
||||||
# Google Cloud Identity OIDC configuration for admin access
|
# Google Cloud Identity OIDC configuration for admin access
|
||||||
# Enable OIDC authentication for @modelcontextprotocol.io admin accounts
|
# Enable OIDC authentication for @modelcontextprotocol.io admin accounts
|
||||||
MCP_REGISTRY_OIDC_ENABLED=false
|
MCP_REGISTRY_OIDC_ENABLED=false
|
||||||
|
|
|
||||||
13
.github/copilot-instructions.md
vendored
13
.github/copilot-instructions.md
vendored
|
|
@ -1,13 +0,0 @@
|
||||||
# Copilot Instructions for MCP Registry
|
|
||||||
|
|
||||||
## Important: Publishing MCP servers
|
|
||||||
|
|
||||||
The `data/seed.json` file is seed data for local development only. Do NOT create pull requests that add or modify server entries in `data/seed.json` to publish a server.
|
|
||||||
|
|
||||||
To publish an MCP server to the registry, use the `mcp-publisher` CLI tool. See `docs/modelcontextprotocol-io/quickstart.mdx` for instructions.
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
- Use `make` targets where possible (run `make help` to see available targets)
|
|
||||||
- Run `make check` to run lint, schema validation, and all tests (this also stops any running dev-compose environment)
|
|
||||||
- Run `make dev-compose` to start the local development environment
|
|
||||||
24
.github/dependabot.yml
vendored
24
.github/dependabot.yml
vendored
|
|
@ -2,29 +2,17 @@ version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: gomod
|
- package-ecosystem: gomod
|
||||||
directory: /
|
directory: /
|
||||||
groups:
|
|
||||||
go-dependencies:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: daily
|
||||||
- package-ecosystem: gomod
|
|
||||||
directory: /deploy
|
|
||||||
groups:
|
|
||||||
deploy-go-dependencies:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: /
|
directory: /
|
||||||
groups:
|
groups:
|
||||||
github-actions:
|
actions:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: daily
|
||||||
- package-ecosystem: docker
|
- package-ecosystem: "docker"
|
||||||
directory: /
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: "daily"
|
||||||
|
|
|
||||||
14
.github/workflows/cancel-pulumi-lock.yml
vendored
14
.github/workflows/cancel-pulumi-lock.yml
vendored
|
|
@ -24,10 +24,10 @@ jobs:
|
||||||
environment: ${{ inputs.environment }}
|
environment: ${{ inputs.environment }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
|
|
||||||
- name: Setup Pulumi
|
- name: Setup Pulumi
|
||||||
uses: pulumi/actions@8e5e406f4007fca908480587cb9893c07090f58d
|
uses: pulumi/actions@cc7494be991dba0978f7ffafaf995b0449a0998e
|
||||||
with:
|
with:
|
||||||
pulumi-version: ${{ env.PULUMI_VERSION }}
|
pulumi-version: ${{ env.PULUMI_VERSION }}
|
||||||
|
|
||||||
|
|
@ -58,19 +58,15 @@ jobs:
|
||||||
- name: Cancel Pulumi Lock (Staging)
|
- name: Cancel Pulumi Lock (Staging)
|
||||||
if: inputs.environment == 'staging'
|
if: inputs.environment == 'staging'
|
||||||
working-directory: ./deploy
|
working-directory: ./deploy
|
||||||
env:
|
|
||||||
PULUMI_STAGING_PASSPHRASE: ${{ secrets.PULUMI_STAGING_PASSPHRASE }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "$PULUMI_STAGING_PASSPHRASE" > passphrase.staging.txt
|
echo "${{ secrets.PULUMI_STAGING_PASSPHRASE }}" > passphrase.staging.txt
|
||||||
pulumi login gs://mcp-registry-staging-pulumi-state
|
pulumi login gs://mcp-registry-staging-pulumi-state
|
||||||
PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.staging.txt pulumi cancel --stack gcpStaging --yes
|
PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.staging.txt pulumi cancel --stack gcpStaging --yes
|
||||||
|
|
||||||
- name: Cancel Pulumi Lock (Production)
|
- name: Cancel Pulumi Lock (Production)
|
||||||
if: inputs.environment == 'production'
|
if: inputs.environment == 'production'
|
||||||
working-directory: ./deploy
|
working-directory: ./deploy
|
||||||
env:
|
|
||||||
PULUMI_PROD_PASSPHRASE: ${{ secrets.PULUMI_PROD_PASSPHRASE }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "$PULUMI_PROD_PASSPHRASE" > passphrase.prod.txt
|
echo "${{ secrets.PULUMI_PROD_PASSPHRASE }}" > passphrase.prod.txt
|
||||||
pulumi login gs://mcp-registry-prod-pulumi-state
|
pulumi login gs://mcp-registry-prod-pulumi-state
|
||||||
PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi cancel --stack gcpProd --yes
|
PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi cancel --stack gcpProd --yes
|
||||||
61
.github/workflows/ci.yml
vendored
61
.github/workflows/ci.yml
vendored
|
|
@ -16,18 +16,32 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version-file: 'go.mod'
|
||||||
cache: true
|
|
||||||
|
- name: Cache Go modules
|
||||||
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ github.sha }}-${{ hashFiles('**/go.sum', '.golangci.yml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Download dependencies
|
||||||
|
run: go mod download
|
||||||
|
|
||||||
|
- name: Install golangci-lint
|
||||||
|
run: |
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.4.0
|
||||||
|
|
||||||
- name: Run lint
|
- name: Run lint
|
||||||
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a
|
run: make lint
|
||||||
with:
|
|
||||||
version: v2.13.1
|
|
||||||
|
|
||||||
- name: Validate schemas and examples
|
- name: Validate schemas and examples
|
||||||
run: make validate
|
run: make validate
|
||||||
|
|
@ -35,12 +49,10 @@ jobs:
|
||||||
- name: Build application
|
- name: Build application
|
||||||
run: make build
|
run: make build
|
||||||
|
|
||||||
- name: Run govulncheck
|
- name: Check for vulnerabilities
|
||||||
uses: golang/govulncheck-action@v1
|
run: |
|
||||||
with:
|
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
go-version-input: 'stable'
|
govulncheck ./...
|
||||||
go-package: ./...
|
|
||||||
repo-checkout: false
|
|
||||||
|
|
||||||
# All Tests
|
# All Tests
|
||||||
tests:
|
tests:
|
||||||
|
|
@ -48,22 +60,31 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version-file: 'go.mod'
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Set up ko
|
- name: Cache Go modules
|
||||||
uses: ko-build/setup-ko@v0.10
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Download dependencies
|
||||||
|
run: go mod download
|
||||||
|
|
||||||
- name: Run all tests
|
- name: Run all tests
|
||||||
run: make test-all
|
run: make test-all
|
||||||
|
|
||||||
- name: Upload coverage artifacts
|
- name: Upload coverage artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||||
with:
|
with:
|
||||||
name: coverage-report
|
name: coverage-report
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
15
.github/workflows/claude.yml
vendored
15
.github/workflows/claude.yml
vendored
|
|
@ -26,13 +26,13 @@ jobs:
|
||||||
actions: read
|
actions: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Run Claude Code
|
- name: Run Claude Code
|
||||||
id: claude
|
id: claude
|
||||||
uses: anthropics/claude-code-action@v1
|
uses: anthropics/claude-code-action@beta
|
||||||
with:
|
with:
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
|
||||||
|
|
@ -42,7 +42,10 @@ jobs:
|
||||||
|
|
||||||
# Trigger when assigned to an issue
|
# Trigger when assigned to an issue
|
||||||
assignee_trigger: "claude"
|
assignee_trigger: "claude"
|
||||||
|
|
||||||
claude_args: |
|
# Allow Claude to run bash
|
||||||
--allowedTools Bash
|
# This should be safe given the repo is already public
|
||||||
--system-prompt "If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a <details> block."
|
allowed_tools: "Bash"
|
||||||
|
|
||||||
|
custom_instructions: |
|
||||||
|
If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a <details> block.
|
||||||
|
|
|
||||||
151
.github/workflows/close-invalid-publish-prs.yml
vendored
151
.github/workflows/close-invalid-publish-prs.yml
vendored
|
|
@ -1,151 +0,0 @@
|
||||||
name: Close invalid publish PRs
|
|
||||||
|
|
||||||
# Stage 2 of 2 (see detect-invalid-publish-prs.yml for stage 1).
|
|
||||||
#
|
|
||||||
# Runs in the trusted base-repo context (triggered by workflow_run, not by the
|
|
||||||
# PR itself), so it has a write token. It picks up the PR number flagged by
|
|
||||||
# stage 1 and, after independently re-validating the PR from the API, posts a
|
|
||||||
# comment pointing at the publishing docs, labels it `invalid`, and closes it.
|
|
||||||
#
|
|
||||||
# Trust boundary: the ONLY thing crossing from the untrusted stage is the PR
|
|
||||||
# number (sanitized to digits below). We re-derive every decision (state,
|
|
||||||
# author, changed files) here, so a wrong/stale artifact can never cause us to
|
|
||||||
# close a PR that doesn't actually match.
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["Detect invalid publish PRs"]
|
|
||||||
types: [completed]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
issues: write # labels on PRs go through the issues API
|
|
||||||
actions: read # needed to list/download the stage-1 artifact
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
close:
|
|
||||||
if: github.event.workflow_run.conclusion == 'success'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
GH_REPO: ${{ github.repository }}
|
|
||||||
RUN_ID: ${{ github.event.workflow_run.id }}
|
|
||||||
COMMENT_BODY: |
|
|
||||||
Hi @__AUTHOR__ 👋 — thanks for your interest in the MCP Registry!
|
|
||||||
|
|
||||||
It looks like this PR is trying to publish an MCP server by adding or editing server files in this repository (under `servers/`, `data/servers/`, or in `data/seed.json`). That isn't how servers get published, so I'm closing this PR automatically.
|
|
||||||
|
|
||||||
**Servers are published with the [`mcp-publisher`](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/quickstart.mdx) CLI**, not by opening a pull request against this repo. The CLI verifies that you own your namespace and submits your `server.json` directly to the live registry API.
|
|
||||||
|
|
||||||
To publish your server, follow the **[Publishing Quickstart](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/quickstart.mdx)**. In short:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build the publisher CLI
|
|
||||||
make publisher
|
|
||||||
|
|
||||||
# Authenticate (e.g. via GitHub) and publish your server.json
|
|
||||||
./bin/mcp-publisher login github
|
|
||||||
./bin/mcp-publisher publish
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: `data/seed.json` is seed data for **local development only** — adding entries there does not publish anything to the registry.
|
|
||||||
|
|
||||||
If you believe this was closed in error, please leave a comment and a maintainer will take a look. 🙇
|
|
||||||
|
|
||||||
_This is an automated message._
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check for flagged-pr artifact
|
|
||||||
id: check
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
names="$(gh api "repos/$GH_REPO/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[].name' || true)"
|
|
||||||
if printf '%s\n' "$names" | grep -qx "flagged-pr"; then
|
|
||||||
echo "found=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "No flagged-pr artifact on run $RUN_ID; nothing to do."
|
|
||||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Download flagged PR number
|
|
||||||
if: steps.check.outputs.found == 'true'
|
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
||||||
with:
|
|
||||||
name: flagged-pr
|
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
|
|
||||||
- name: Re-validate and close
|
|
||||||
if: steps.check.outputs.found == 'true'
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Sanitize: the only cross-boundary input. Keep digits only.
|
|
||||||
PR_NUMBER="$(tr -dc '0-9' < pr-number.txt)"
|
|
||||||
if [ -z "$PR_NUMBER" ]; then
|
|
||||||
echo "No valid PR number in artifact; skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "Re-validating PR #$PR_NUMBER"
|
|
||||||
|
|
||||||
state="$(gh pr view "$PR_NUMBER" --json state --jq '.state')"
|
|
||||||
if [ "$state" != "OPEN" ]; then
|
|
||||||
echo "PR #$PR_NUMBER is $state, not OPEN; skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use the REST endpoint: `gh pr view --json` does not expose an
|
|
||||||
# authorAssociation field, so calling it there exits non-zero and,
|
|
||||||
# under `set -euo pipefail`, aborts the job before we ever close.
|
|
||||||
assoc="$(gh api "repos/$GH_REPO/pulls/$PR_NUMBER" --jq '.author_association')"
|
|
||||||
case "$assoc" in
|
|
||||||
MEMBER|OWNER|COLLABORATOR)
|
|
||||||
echo "Author association '$assoc' is trusted; skipping."
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Idempotency: if a maintainer reopens after we labeled it, leave it be.
|
|
||||||
# Capture into a var first so a transient gh failure can't fail open
|
|
||||||
# (an errored fetch piped to grep would otherwise look "not labeled").
|
|
||||||
labels="$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name' || true)"
|
|
||||||
if printf '%s\n' "$labels" | grep -qx "invalid"; then
|
|
||||||
echo "PR #$PR_NUMBER already labeled 'invalid'; skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Paginated REST endpoint (gh pr view --json files caps at 100, no pagination).
|
|
||||||
mapfile -t FILES < <(gh api --paginate "repos/$GH_REPO/pulls/$PR_NUMBER/files" --jq '.[].filename')
|
|
||||||
if [ "${#FILES[@]}" -eq 0 ]; then
|
|
||||||
echo "No files for PR #$PR_NUMBER; skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
# Keep this list in sync with the copy in stage 1.
|
|
||||||
publish_attempt=0
|
|
||||||
other=0
|
|
||||||
for f in "${FILES[@]}"; do
|
|
||||||
if [[ "$f" == servers/* \
|
|
||||||
|| "$f" == data/servers/* \
|
|
||||||
|| "$f" == data/data/servers/* \
|
|
||||||
|| "$f" =~ ^data/[^/]*server\.json$ \
|
|
||||||
|| "$f" == "data/seed.json" \
|
|
||||||
|| "$f" == "server.json" \
|
|
||||||
|| "$f" == "servers.json" ]]; then
|
|
||||||
publish_attempt=1
|
|
||||||
else
|
|
||||||
other=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ "$publish_attempt" -ne 1 ] || [ "$other" -ne 0 ]; then
|
|
||||||
echo "PR #$PR_NUMBER no longer matches the publish-attempt pattern; skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
author="$(gh pr view "$PR_NUMBER" --json author --jq '.author.login')"
|
|
||||||
body="${COMMENT_BODY//__AUTHOR__/$author}"
|
|
||||||
|
|
||||||
echo "Commenting on, labeling, and closing PR #$PR_NUMBER"
|
|
||||||
gh pr comment "$PR_NUMBER" --body "$body"
|
|
||||||
gh pr edit "$PR_NUMBER" --add-label "invalid" || echo "Could not add label; continuing."
|
|
||||||
gh pr close "$PR_NUMBER"
|
|
||||||
56
.github/workflows/deploy-production.yml
vendored
56
.github/workflows/deploy-production.yml
vendored
|
|
@ -1,56 +0,0 @@
|
||||||
name: Deploy to Production
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'deploy/Pulumi.gcpProd.yaml'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
PULUMI_VERSION: "3.188.0"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy-production:
|
|
||||||
name: Deploy to Production
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: production
|
|
||||||
concurrency:
|
|
||||||
group: deploy-production
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
||||||
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
|
||||||
with:
|
|
||||||
go-version: 'stable'
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Setup Pulumi
|
|
||||||
uses: pulumi/actions@8e5e406f4007fca908480587cb9893c07090f58d
|
|
||||||
with:
|
|
||||||
pulumi-version: ${{ env.PULUMI_VERSION }}
|
|
||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
|
||||||
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
|
|
||||||
with:
|
|
||||||
credentials_json: ${{ secrets.GCP_PROD_SERVICE_ACCOUNT_KEY }}
|
|
||||||
|
|
||||||
- name: Setup Google Cloud SDK
|
|
||||||
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db
|
|
||||||
with:
|
|
||||||
project_id: mcp-registry-prod
|
|
||||||
install_components: gke-gcloud-auth-plugin
|
|
||||||
|
|
||||||
- name: Deploy to Production
|
|
||||||
working-directory: ./deploy
|
|
||||||
env:
|
|
||||||
PULUMI_PROD_PASSPHRASE: ${{ secrets.PULUMI_PROD_PASSPHRASE }}
|
|
||||||
run: |
|
|
||||||
echo "$PULUMI_PROD_PASSPHRASE" > passphrase.prod.txt
|
|
||||||
make prod-up
|
|
||||||
103
.github/workflows/deploy-staging.yml
vendored
103
.github/workflows/deploy-staging.yml
vendored
|
|
@ -1,103 +0,0 @@
|
||||||
name: Deploy to Staging
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths-ignore:
|
|
||||||
- 'deploy/Pulumi.gcpProd.yaml'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
PULUMI_VERSION: "3.188.0"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ko-push:
|
|
||||||
name: Build and Push Image with ko
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
|
||||||
with:
|
|
||||||
go-version: 'stable'
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Set up ko
|
|
||||||
uses: ko-build/setup-ko@v0.10
|
|
||||||
|
|
||||||
- name: Log in to Container Registry
|
|
||||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get build timestamp and tags
|
|
||||||
id: build-info
|
|
||||||
run: |
|
|
||||||
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
|
|
||||||
echo "date=$(date -u +%Y%m%d)" >> $GITHUB_OUTPUT
|
|
||||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Build and push with ko
|
|
||||||
env:
|
|
||||||
KO_DOCKER_REPO: ghcr.io/${{ github.repository }}
|
|
||||||
VERSION: main-${{ steps.build-info.outputs.date }}-${{ steps.build-info.outputs.short_sha }}
|
|
||||||
GIT_COMMIT: ${{ github.sha }}
|
|
||||||
BUILD_TIME: ${{ steps.build-info.outputs.timestamp }}
|
|
||||||
run: |
|
|
||||||
# Build and push multi-platform image
|
|
||||||
ko build ./cmd/registry \
|
|
||||||
--bare \
|
|
||||||
--platform=linux/amd64,linux/arm64 \
|
|
||||||
--tags=main-${{ steps.build-info.outputs.date }}-${{ steps.build-info.outputs.short_sha }},main
|
|
||||||
|
|
||||||
deploy-staging:
|
|
||||||
name: Deploy to Staging
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: staging
|
|
||||||
needs: ko-push
|
|
||||||
concurrency:
|
|
||||||
group: deploy-staging
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
|
||||||
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
|
||||||
with:
|
|
||||||
go-version: 'stable'
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Setup Pulumi
|
|
||||||
uses: pulumi/actions@8e5e406f4007fca908480587cb9893c07090f58d
|
|
||||||
with:
|
|
||||||
pulumi-version: ${{ env.PULUMI_VERSION }}
|
|
||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
|
||||||
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
|
|
||||||
with:
|
|
||||||
credentials_json: ${{ secrets.GCP_STAGING_SERVICE_ACCOUNT_KEY }}
|
|
||||||
|
|
||||||
- name: Setup Google Cloud SDK
|
|
||||||
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db
|
|
||||||
with:
|
|
||||||
project_id: mcp-registry-staging
|
|
||||||
install_components: gke-gcloud-auth-plugin
|
|
||||||
|
|
||||||
- name: Deploy to Staging
|
|
||||||
working-directory: ./deploy
|
|
||||||
env:
|
|
||||||
PULUMI_STAGING_PASSPHRASE: ${{ secrets.PULUMI_STAGING_PASSPHRASE }}
|
|
||||||
run: |
|
|
||||||
echo "$PULUMI_STAGING_PASSPHRASE" > passphrase.staging.txt
|
|
||||||
make staging-up
|
|
||||||
132
.github/workflows/deploy.yml
vendored
Normal file
132
.github/workflows/deploy.yml
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
PULUMI_VERSION: "3.188.0"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-push:
|
||||||
|
name: Build Docker Image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
|
||||||
|
|
||||||
|
- name: Log in to Container Registry
|
||||||
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
tags: |
|
||||||
|
type=sha,prefix=main-{{date 'YYYYMMDD'}}-,enable={{is_default_branch}}
|
||||||
|
type=raw,value=main,enable={{is_default_branch}}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
deploy-staging:
|
||||||
|
name: Deploy to Staging
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: staging
|
||||||
|
needs: docker-push
|
||||||
|
concurrency:
|
||||||
|
group: deploy-staging
|
||||||
|
cancel-in-progress: false
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
|
||||||
|
- name: Setup Pulumi
|
||||||
|
uses: pulumi/actions@cc7494be991dba0978f7ffafaf995b0449a0998e
|
||||||
|
with:
|
||||||
|
pulumi-version: ${{ env.PULUMI_VERSION }}
|
||||||
|
|
||||||
|
- name: Authenticate to Google Cloud
|
||||||
|
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
|
||||||
|
with:
|
||||||
|
credentials_json: ${{ secrets.GCP_STAGING_SERVICE_ACCOUNT_KEY }}
|
||||||
|
|
||||||
|
- name: Setup Google Cloud SDK
|
||||||
|
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db
|
||||||
|
with:
|
||||||
|
project_id: mcp-registry-staging
|
||||||
|
install_components: gke-gcloud-auth-plugin
|
||||||
|
|
||||||
|
- name: Deploy to Staging
|
||||||
|
working-directory: ./deploy
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.PULUMI_STAGING_PASSPHRASE }}" > passphrase.staging.txt
|
||||||
|
make staging-up
|
||||||
|
|
||||||
|
deploy-production:
|
||||||
|
name: Deploy to Production
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: production
|
||||||
|
needs: deploy-staging
|
||||||
|
concurrency:
|
||||||
|
group: deploy-production
|
||||||
|
cancel-in-progress: false
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
|
||||||
|
- name: Setup Pulumi
|
||||||
|
uses: pulumi/actions@cc7494be991dba0978f7ffafaf995b0449a0998e
|
||||||
|
with:
|
||||||
|
pulumi-version: ${{ env.PULUMI_VERSION }}
|
||||||
|
|
||||||
|
- name: Authenticate to Google Cloud
|
||||||
|
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
|
||||||
|
with:
|
||||||
|
credentials_json: ${{ secrets.GCP_PROD_SERVICE_ACCOUNT_KEY }}
|
||||||
|
|
||||||
|
- name: Setup Google Cloud SDK
|
||||||
|
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db
|
||||||
|
with:
|
||||||
|
project_id: mcp-registry-prod
|
||||||
|
install_components: gke-gcloud-auth-plugin
|
||||||
|
|
||||||
|
- name: Deploy to Production
|
||||||
|
working-directory: ./deploy
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.PULUMI_PROD_PASSPHRASE }}" > passphrase.prod.txt
|
||||||
|
make prod-up
|
||||||
96
.github/workflows/detect-invalid-publish-prs.yml
vendored
96
.github/workflows/detect-invalid-publish-prs.yml
vendored
|
|
@ -1,96 +0,0 @@
|
||||||
name: Detect invalid publish PRs
|
|
||||||
|
|
||||||
# Stage 1 of 2 (see close-invalid-publish-prs.yml for stage 2).
|
|
||||||
#
|
|
||||||
# Some contributors try to "publish" a server by opening a PR that adds files
|
|
||||||
# under servers/ or edits data/seed.json. That is not how publishing works -
|
|
||||||
# servers are published with the mcp-publisher CLI against the live registry
|
|
||||||
# API. This job only DETECTS such PRs. It runs from the (untrusted) PR context
|
|
||||||
# with a read-only token, so it cannot comment on or close anything. When it
|
|
||||||
# finds a match it records just the PR number as an artifact; the privileged
|
|
||||||
# stage-2 workflow picks that up via workflow_run and acts on it.
|
|
||||||
#
|
|
||||||
# We never check out or run the PR's code - we only read changed file paths.
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, reopened]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
detect:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
GH_REPO: ${{ github.repository }}
|
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
||||||
AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
|
|
||||||
steps:
|
|
||||||
- name: Classify PR
|
|
||||||
id: classify
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
match=false
|
|
||||||
|
|
||||||
# Maintainers may legitimately touch these files (e.g. dev seed data),
|
|
||||||
# so never flag their PRs.
|
|
||||||
case "$AUTHOR_ASSOCIATION" in
|
|
||||||
MEMBER|OWNER|COLLABORATOR)
|
|
||||||
echo "Author association '$AUTHOR_ASSOCIATION' is trusted; not flagging."
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# Use the paginated REST endpoint: `gh pr view --json files` caps
|
|
||||||
# at 100 files with no pagination, which could truncate a large PR
|
|
||||||
# and cause a false match.
|
|
||||||
mapfile -t FILES < <(gh api --paginate "repos/$GH_REPO/pulls/$PR_NUMBER/files" --jq '.[].filename')
|
|
||||||
if [ "${#FILES[@]}" -gt 0 ]; then
|
|
||||||
# Flag only if the PR touches *exclusively* publish-attempt files
|
|
||||||
# and nothing else, so that legit PRs touching seed data
|
|
||||||
# alongside real code are left alone.
|
|
||||||
#
|
|
||||||
# Keep this list in sync with the copy in stage 2. It stays an
|
|
||||||
# explicit list rather than a broad `**/server.json` glob:
|
|
||||||
# because we require *every* file to match, an over-broad
|
|
||||||
# pattern would auto-close a legitimate single-file PR.
|
|
||||||
# data/servers/** is here because #1524 put its server.json
|
|
||||||
# there - a natural guess next to data/seed.json - and slipped
|
|
||||||
# through the original servers/**-only list.
|
|
||||||
publish_attempt=0
|
|
||||||
other=0
|
|
||||||
for f in "${FILES[@]}"; do
|
|
||||||
if [[ "$f" == servers/* \
|
|
||||||
|| "$f" == data/servers/* \
|
|
||||||
|| "$f" == data/data/servers/* \
|
|
||||||
|| "$f" =~ ^data/[^/]*server\.json$ \
|
|
||||||
|| "$f" == "data/seed.json" \
|
|
||||||
|| "$f" == "server.json" \
|
|
||||||
|| "$f" == "servers.json" ]]; then
|
|
||||||
publish_attempt=1
|
|
||||||
else
|
|
||||||
echo "Non-publish file changed: $f"
|
|
||||||
other=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ "$publish_attempt" -eq 1 ] && [ "$other" -eq 0 ]; then
|
|
||||||
match=true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "PR #$PR_NUMBER match=$match"
|
|
||||||
echo "match=$match" >> "$GITHUB_OUTPUT"
|
|
||||||
if [ "$match" = "true" ]; then
|
|
||||||
echo "$PR_NUMBER" > pr-number.txt
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Record flagged PR number
|
|
||||||
if: steps.classify.outputs.match == 'true'
|
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4
|
|
||||||
with:
|
|
||||||
name: flagged-pr
|
|
||||||
path: pr-number.txt
|
|
||||||
retention-days: 1
|
|
||||||
69
.github/workflows/release.yml
vendored
69
.github/workflows/release.yml
vendored
|
|
@ -14,24 +14,23 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version-file: 'go.mod'
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62
|
||||||
|
|
||||||
- name: Install Syft
|
- name: Install Syft
|
||||||
uses: anchore/sbom-action/download-syft@v0.24.2
|
uses: anchore/sbom-action/download-syft@v0.20.6
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94
|
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser
|
distribution: goreleaser
|
||||||
version: v2.12.0
|
version: v2.12.0
|
||||||
|
|
@ -39,48 +38,40 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
ko-push:
|
docker:
|
||||||
name: Build and Push Image with ko
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: goreleaser
|
needs: goreleaser
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Docker Buildx
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
|
||||||
with:
|
|
||||||
go-version: 'stable'
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Set up ko
|
|
||||||
uses: ko-build/setup-ko@v0.10
|
|
||||||
|
|
||||||
- name: Log in to Container Registry
|
- name: Log in to Container Registry
|
||||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Get build timestamp and version
|
- name: Extract metadata
|
||||||
id: build-info
|
id: meta
|
||||||
run: |
|
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
|
||||||
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
|
with:
|
||||||
# Extract version from tag and strip 'v' prefix (e.g., refs/tags/v1.2.3 -> 1.2.3)
|
images: ghcr.io/${{ github.repository }}
|
||||||
VERSION="${GITHUB_REF#refs/tags/}"
|
tags: |
|
||||||
VERSION="${VERSION#v}"
|
type=semver,pattern={{version}}
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
type=raw,value=latest
|
||||||
|
|
||||||
- name: Build and push with ko
|
- name: Build and push Docker image
|
||||||
env:
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
|
||||||
KO_DOCKER_REPO: ghcr.io/${{ github.repository }}
|
with:
|
||||||
VERSION: ${{ steps.build-info.outputs.version }}
|
context: .
|
||||||
GIT_COMMIT: ${{ github.sha }}
|
file: ./Dockerfile
|
||||||
BUILD_TIME: ${{ steps.build-info.outputs.timestamp }}
|
platforms: linux/amd64,linux/arm64
|
||||||
run: |
|
push: true
|
||||||
# Build and push multi-platform image with version tag and latest
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
ko build ./cmd/registry \
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
--bare \
|
cache-from: type=gha
|
||||||
--platform=linux/amd64,linux/arm64 \
|
cache-to: type=gha,mode=max
|
||||||
--tags=${{ steps.build-info.outputs.version }},latest
|
|
||||||
435
.github/workflows/sync-db.yml
vendored
435
.github/workflows/sync-db.yml
vendored
|
|
@ -1,435 +0,0 @@
|
||||||
name: Sync Production DB to Staging (from backups)
|
|
||||||
|
|
||||||
on:
|
|
||||||
# schedule:
|
|
||||||
# # Run daily at 2 AM UTC (during low-traffic hours)
|
|
||||||
# - cron: '0 2 * * *'
|
|
||||||
workflow_dispatch: # Allow manual triggering
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync-database:
|
|
||||||
name: Sync Prod DB to Staging from k8up Backups
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: staging
|
|
||||||
concurrency:
|
|
||||||
group: sync-staging-database
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Authenticate to Google Cloud (Production)
|
|
||||||
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
|
|
||||||
with:
|
|
||||||
credentials_json: ${{ secrets.GCP_PROD_SERVICE_ACCOUNT_KEY }}
|
|
||||||
|
|
||||||
- name: Setup Google Cloud SDK
|
|
||||||
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db
|
|
||||||
with:
|
|
||||||
project_id: mcp-registry-prod
|
|
||||||
install_components: gke-gcloud-auth-plugin
|
|
||||||
|
|
||||||
- name: Get backup credentials from prod cluster
|
|
||||||
id: backup-creds
|
|
||||||
run: |
|
|
||||||
gcloud container clusters get-credentials mcp-registry-prod \
|
|
||||||
--zone=us-central1-b \
|
|
||||||
--project=mcp-registry-prod
|
|
||||||
|
|
||||||
# Store in outputs (GitHub Actions encrypts these automatically)
|
|
||||||
kubectl get secret k8up-backup-credentials -n default -o json | jq -r '
|
|
||||||
"access_key=" + (.data.AWS_ACCESS_KEY_ID | @base64d),
|
|
||||||
"secret_key=" + (.data.AWS_SECRET_ACCESS_KEY | @base64d)
|
|
||||||
' >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Remove all production access (SAFETY MEASURE)
|
|
||||||
run: |
|
|
||||||
# Remove production cluster from kubeconfig
|
|
||||||
kubectl config delete-context gke_mcp-registry-prod_us-central1-b_mcp-registry-prod 2>/dev/null || true
|
|
||||||
|
|
||||||
# Revoke gcloud credentials
|
|
||||||
gcloud auth revoke --all 2>/dev/null || true
|
|
||||||
|
|
||||||
# Clear gcloud configuration
|
|
||||||
gcloud config unset project 2>/dev/null || true
|
|
||||||
gcloud config unset account 2>/dev/null || true
|
|
||||||
|
|
||||||
# Verify no contexts remain
|
|
||||||
CONTEXT_COUNT=$(kubectl config get-contexts -o name 2>/dev/null | wc -l)
|
|
||||||
if [ "$CONTEXT_COUNT" -gt 0 ]; then
|
|
||||||
echo "❌ ERROR: $CONTEXT_COUNT context(s) still exist after cleanup!"
|
|
||||||
kubectl config get-contexts
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Switch to staging cluster
|
|
||||||
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
|
|
||||||
with:
|
|
||||||
credentials_json: ${{ secrets.GCP_STAGING_SERVICE_ACCOUNT_KEY }}
|
|
||||||
|
|
||||||
- name: Configure staging cluster access
|
|
||||||
run: |
|
|
||||||
gcloud config set project mcp-registry-staging
|
|
||||||
gcloud container clusters get-credentials mcp-registry-staging \
|
|
||||||
--zone=us-central1-b \
|
|
||||||
--project=mcp-registry-staging
|
|
||||||
|
|
||||||
- name: Create secret for prod backup bucket access
|
|
||||||
run: |
|
|
||||||
# Create/update secret in staging with access to prod backups
|
|
||||||
kubectl create secret generic prod-to-staging-sync-credentials \
|
|
||||||
--from-literal=AWS_ACCESS_KEY_ID="${{ steps.backup-creds.outputs.access_key }}" \
|
|
||||||
--from-literal=AWS_SECRET_ACCESS_KEY="${{ steps.backup-creds.outputs.secret_key }}" \
|
|
||||||
--dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
|
|
||||||
- name: Create restore PVC
|
|
||||||
run: |
|
|
||||||
kubectl apply -f - <<EOF
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: restore-data-pvc
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 50Gi
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Trigger k8up restore from prod backups
|
|
||||||
id: restore
|
|
||||||
run: |
|
|
||||||
RESTORE_NAME="restore-from-prod-$(date +%Y%m%d-%H%M%S)"
|
|
||||||
echo "restore_name=$RESTORE_NAME" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Create a k8up Restore resource to restore from prod backups
|
|
||||||
kubectl apply -f - <<EOF
|
|
||||||
apiVersion: k8up.io/v1
|
|
||||||
kind: Restore
|
|
||||||
metadata:
|
|
||||||
name: $RESTORE_NAME
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
snapshot: latest
|
|
||||||
restoreMethod:
|
|
||||||
folder:
|
|
||||||
claimName: restore-data-pvc
|
|
||||||
backend:
|
|
||||||
repoPasswordSecretRef:
|
|
||||||
name: k8up-repo-password
|
|
||||||
key: password
|
|
||||||
s3:
|
|
||||||
bucket: mcp-registry-prod-backups
|
|
||||||
endpoint: https://storage.googleapis.com
|
|
||||||
accessKeyIDSecretRef:
|
|
||||||
name: prod-to-staging-sync-credentials
|
|
||||||
key: AWS_ACCESS_KEY_ID
|
|
||||||
secretAccessKeySecretRef:
|
|
||||||
name: prod-to-staging-sync-credentials
|
|
||||||
key: AWS_SECRET_ACCESS_KEY
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Wait for k8up restore to complete
|
|
||||||
run: |
|
|
||||||
RESTORE_NAME="${{ steps.restore.outputs.restore_name }}"
|
|
||||||
|
|
||||||
echo "Waiting for restore job to start..."
|
|
||||||
sleep 15
|
|
||||||
|
|
||||||
# Find the job created by k8up for this restore
|
|
||||||
# k8up creates jobs with name pattern "restore-<restore-name>"
|
|
||||||
# Since our restore is named "restore-from-prod-*", the job will be "restore-restore-from-prod-*"
|
|
||||||
for i in {1..60}; do
|
|
||||||
JOB_NAME=$(kubectl get jobs -n default --no-headers 2>/dev/null | grep "^restore-$RESTORE_NAME" | awk '{print $1}' | head -1)
|
|
||||||
if [ -n "$JOB_NAME" ]; then
|
|
||||||
echo "Found restore job: $JOB_NAME"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "Waiting for job to be created... ($i/60)"
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "$JOB_NAME" ]; then
|
|
||||||
echo "ERROR: Restore job not found after 120 seconds"
|
|
||||||
echo "Checking restore resource status:"
|
|
||||||
kubectl get restore $RESTORE_NAME -n default
|
|
||||||
kubectl describe restore $RESTORE_NAME -n default
|
|
||||||
|
|
||||||
echo "Checking for any restore jobs:"
|
|
||||||
kubectl get jobs -n default | grep restore || echo "No restore jobs found"
|
|
||||||
|
|
||||||
echo "Checking k8up operator logs:"
|
|
||||||
kubectl logs deployment/k8up -n default --tail=50 | grep -i restore || echo "No restore logs found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wait for the restore job to complete (max 15 minutes)
|
|
||||||
echo "Waiting for restore job to complete..."
|
|
||||||
kubectl wait --for=condition=complete \
|
|
||||||
job/$JOB_NAME \
|
|
||||||
--timeout=900s -n default || {
|
|
||||||
echo "Restore job failed or timed out"
|
|
||||||
echo "Job status:"
|
|
||||||
kubectl get job/$JOB_NAME -n default
|
|
||||||
echo "Job details:"
|
|
||||||
kubectl describe job/$JOB_NAME -n default
|
|
||||||
echo "Job logs (if available):"
|
|
||||||
kubectl logs job/$JOB_NAME -n default --tail=100 || echo "No logs available"
|
|
||||||
|
|
||||||
# Check if it's a credential issue
|
|
||||||
echo "Checking if credentials exist:"
|
|
||||||
kubectl get secret prod-to-staging-sync-credentials -n default || echo "Credentials secret missing!"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Find staging PostgreSQL PVC
|
|
||||||
id: pgdata-pvc
|
|
||||||
run: |
|
|
||||||
# Find the PVC used by the PostgreSQL cluster
|
|
||||||
PVC_NAME=$(kubectl get pvc -n default -l cnpg.io/cluster=registry-pg -o jsonpath='{.items[0].metadata.name}')
|
|
||||||
|
|
||||||
if [ -z "$PVC_NAME" ]; then
|
|
||||||
echo "ERROR: Could not find PostgreSQL PVC"
|
|
||||||
kubectl get pvc -n default -l cnpg.io/cluster=registry-pg
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "pvc_name=$PVC_NAME" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Scale down staging PostgreSQL
|
|
||||||
run: |
|
|
||||||
echo "Scaling down PostgreSQL cluster..."
|
|
||||||
kubectl patch cluster registry-pg -n default \
|
|
||||||
--type merge \
|
|
||||||
--patch '{"spec":{"instances":0}}'
|
|
||||||
|
|
||||||
# Wait for pods to terminate
|
|
||||||
echo "Waiting for pods to terminate..."
|
|
||||||
kubectl wait --for=delete pod -l cnpg.io/cluster=registry-pg -n default --timeout=300s || true
|
|
||||||
|
|
||||||
- name: Verify we are in staging cluster (SAFETY CHECK)
|
|
||||||
run: |
|
|
||||||
# Get current cluster context
|
|
||||||
CURRENT_CONTEXT=$(kubectl config current-context)
|
|
||||||
CURRENT_PROJECT=$(gcloud config get-value project)
|
|
||||||
|
|
||||||
echo "Current kubectl context: $CURRENT_CONTEXT"
|
|
||||||
echo "Current GCP project: $CURRENT_PROJECT"
|
|
||||||
|
|
||||||
# Verify we're in staging
|
|
||||||
if ! echo "$CURRENT_CONTEXT" | grep -qi "staging"; then
|
|
||||||
echo "❌ SAFETY CHECK FAILED: Not in staging cluster"
|
|
||||||
echo "Context: $CURRENT_CONTEXT"
|
|
||||||
echo "Expected: staging cluster"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$CURRENT_PROJECT" != "mcp-registry-staging" ]; then
|
|
||||||
echo "❌ SAFETY CHECK FAILED: Not in staging project"
|
|
||||||
echo "Project: $CURRENT_PROJECT"
|
|
||||||
echo "Expected: mcp-registry-staging"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Replace staging database with restored backup
|
|
||||||
id: copy-job
|
|
||||||
run: |
|
|
||||||
JOB_NAME="copy-pgdata-$(date +%Y%m%d-%H%M%S)"
|
|
||||||
echo "job_name=$JOB_NAME" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Create a job to copy the restored backup data to the staging PVC
|
|
||||||
kubectl apply -f - <<EOF
|
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: Job
|
|
||||||
metadata:
|
|
||||||
name: $JOB_NAME
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
ttlSecondsAfterFinished: 600
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
restartPolicy: Never
|
|
||||||
containers:
|
|
||||||
- name: copy-data
|
|
||||||
image: busybox:latest
|
|
||||||
command:
|
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
set -e
|
|
||||||
echo "Finding PostgreSQL data in backup..."
|
|
||||||
echo "Restore structure:"
|
|
||||||
find /restore -maxdepth 3 -type d 2>/dev/null | head -20
|
|
||||||
|
|
||||||
# Try different possible paths for pgdata
|
|
||||||
PGDATA_SOURCE=""
|
|
||||||
for path in \$(find /restore -type d -name "pgdata" 2>/dev/null); do
|
|
||||||
if [ -f "\$path/PG_VERSION" ]; then
|
|
||||||
PGDATA_SOURCE="\$path"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "\$PGDATA_SOURCE" ]; then
|
|
||||||
echo "ERROR: Could not find valid pgdata directory with PG_VERSION"
|
|
||||||
echo "Searched paths:"
|
|
||||||
find /restore -type d -name "pgdata" 2>/dev/null
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Found pgdata at: \$PGDATA_SOURCE"
|
|
||||||
echo "Contents:"
|
|
||||||
ls -lah \$PGDATA_SOURCE/ | head -10
|
|
||||||
|
|
||||||
echo "Backing up existing staging data..."
|
|
||||||
mkdir -p /pgdata-backup
|
|
||||||
if [ "\$(ls -A /pgdata)" ]; then
|
|
||||||
cp -a /pgdata/. /pgdata-backup/ || echo "Warning: Could not backup existing data"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Clearing existing data..."
|
|
||||||
rm -rf /pgdata/*
|
|
||||||
|
|
||||||
echo "Copying backup data to staging PVC..."
|
|
||||||
cp -a \$PGDATA_SOURCE/. /pgdata/
|
|
||||||
|
|
||||||
echo "Setting correct permissions..."
|
|
||||||
chmod 700 /pgdata
|
|
||||||
|
|
||||||
ls -lah /pgdata/ | head -20
|
|
||||||
echo "PostgreSQL version: \$(cat /pgdata/PG_VERSION)"
|
|
||||||
volumeMounts:
|
|
||||||
- name: restore-data
|
|
||||||
mountPath: /restore
|
|
||||||
- name: staging-pgdata
|
|
||||||
mountPath: /pgdata
|
|
||||||
volumes:
|
|
||||||
- name: restore-data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: restore-data-pvc
|
|
||||||
- name: staging-pgdata
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: ${{ steps.pgdata-pvc.outputs.pvc_name }}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Wait for data copy to complete
|
|
||||||
run: |
|
|
||||||
JOB_NAME="${{ steps.copy-job.outputs.job_name }}"
|
|
||||||
|
|
||||||
# Wait for copy to complete
|
|
||||||
kubectl wait --for=condition=complete job/$JOB_NAME --timeout=600s -n default || {
|
|
||||||
echo "Data copy job failed"
|
|
||||||
kubectl describe job/$JOB_NAME -n default
|
|
||||||
kubectl logs job/$JOB_NAME -n default --tail=100
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Scale up staging PostgreSQL
|
|
||||||
run: |
|
|
||||||
echo "Scaling up PostgreSQL cluster..."
|
|
||||||
kubectl patch cluster registry-pg -n default \
|
|
||||||
--type merge \
|
|
||||||
--patch '{"spec":{"instances":1}}'
|
|
||||||
|
|
||||||
# Wait for PostgreSQL pod to be created
|
|
||||||
echo "Waiting for PostgreSQL pod to be created..."
|
|
||||||
for i in {1..60}; do
|
|
||||||
POD_COUNT=$(kubectl get pods -l cnpg.io/cluster=registry-pg -n default --no-headers 2>/dev/null | wc -l)
|
|
||||||
if [ "$POD_COUNT" -gt 0 ]; then
|
|
||||||
echo "Pod created"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "Waiting... ($i/60)"
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
# Wait for PostgreSQL to be ready
|
|
||||||
echo "Waiting for PostgreSQL to be ready..."
|
|
||||||
kubectl wait --for=condition=ready pod -l cnpg.io/cluster=registry-pg -n default --timeout=300s
|
|
||||||
|
|
||||||
- name: Verify staging DB is functional
|
|
||||||
run: |
|
|
||||||
# Create a verification pod
|
|
||||||
kubectl run pg-verify-$(date +%s) \
|
|
||||||
--image=postgres:15 \
|
|
||||||
--rm -i --restart=Never \
|
|
||||||
--env="PGPASSWORD=$(kubectl get secret registry-pg-superuser -n default -o jsonpath='{.data.password}' | base64 -d)" \
|
|
||||||
-- bash -c '
|
|
||||||
echo "Waiting for database to accept connections..."
|
|
||||||
for i in {1..30}; do
|
|
||||||
if pg_isready -h registry-pg-rw -U postgres 2>/dev/null; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "Waiting... ($i/30)"
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Querying database..."
|
|
||||||
TABLE_COUNT=$(psql -h registry-pg-rw -U postgres -d app -tAc "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '\''public'\'';" 2>&1)
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "ERROR: Could not query database"
|
|
||||||
echo "$TABLE_COUNT"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$TABLE_COUNT" -lt 1 ]; then
|
|
||||||
echo "ERROR: Staging DB has no tables!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Staging DB has $TABLE_COUNT tables"
|
|
||||||
echo "Top 10 tables by row count:"
|
|
||||||
psql -h registry-pg-rw -U postgres -d app \
|
|
||||||
-c "SELECT schemaname, tablename, n_live_tup FROM pg_stat_user_tables ORDER BY n_live_tup DESC LIMIT 10;" || true
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Cleanup
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
# Wait for any restore jobs to finish before cleanup (max 5 minutes for large database restores)
|
|
||||||
echo "Checking for running restore jobs..."
|
|
||||||
RESTORE_NAME="${{ steps.restore.outputs.restore_name }}"
|
|
||||||
|
|
||||||
for i in {1..60}; do
|
|
||||||
# Check specifically for our restore job in Running or Pending state
|
|
||||||
RUNNING_JOBS=$(kubectl get jobs -n default --no-headers 2>/dev/null | grep "^restore-${RESTORE_NAME}" | grep -E "Running|Pending" | wc -l)
|
|
||||||
if [ "$RUNNING_JOBS" -eq 0 ]; then
|
|
||||||
echo "No running restore jobs found"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "Waiting for $RUNNING_JOBS restore job(s) to finish... ($i/60)"
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clean up jobs first
|
|
||||||
if [ -n "${{ steps.copy-job.outputs.job_name }}" ]; then
|
|
||||||
kubectl delete job ${{ steps.copy-job.outputs.job_name }} -n default || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up the restore job if it exists (with proper error handling)
|
|
||||||
if [ -n "$RESTORE_NAME" ]; then
|
|
||||||
if kubectl get job "restore-$RESTORE_NAME" -n default >/dev/null 2>&1; then
|
|
||||||
echo "Deleting restore job: restore-$RESTORE_NAME"
|
|
||||||
kubectl delete job "restore-$RESTORE_NAME" -n default || echo "Failed to delete job, may have already been cleaned up"
|
|
||||||
else
|
|
||||||
echo "Restore job restore-$RESTORE_NAME not found, skipping deletion"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove restore PVC (will wait for jobs to finish)
|
|
||||||
kubectl delete pvc restore-data-pvc -n default || true
|
|
||||||
|
|
||||||
# Remove prod backup credentials (for security)
|
|
||||||
kubectl delete secret prod-to-staging-sync-credentials -n default || true
|
|
||||||
|
|
||||||
# Clean up old restore resources (keep last 3)
|
|
||||||
kubectl get restore -n default --sort-by=.metadata.creationTimestamp -o name | head -n -3 | xargs -r kubectl delete || true
|
|
||||||
|
|
||||||
# Clean up old restore jobs (keep last 3)
|
|
||||||
kubectl get jobs -n default --sort-by=.metadata.creationTimestamp -o name | grep '^job.batch/restore-' | head -n -3 | xargs -r kubectl delete -n default || true
|
|
||||||
|
|
||||||
# Clean up old copy jobs (keep last 3)
|
|
||||||
kubectl get jobs -n default --sort-by=.metadata.creationTimestamp -o name | grep 'copy-pgdata-' | head -n -3 | xargs -r kubectl delete -n default || true
|
|
||||||
69
.github/workflows/sync-schema.yml
vendored
69
.github/workflows/sync-schema.yml
vendored
|
|
@ -1,69 +0,0 @@
|
||||||
name: Sync Schema
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch: # Manual trigger
|
|
||||||
# TODO: Add daily schedule later
|
|
||||||
# schedule:
|
|
||||||
# - cron: '0 2 * * *' # Run daily at 2 AM UTC
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync-schema:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
|
|
||||||
- name: Checkout static repo
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
repository: modelcontextprotocol/static
|
|
||||||
path: static-repo
|
|
||||||
|
|
||||||
- name: Sync schemas from static repo
|
|
||||||
run: |
|
|
||||||
echo "🔍 Syncing schemas from modelcontextprotocol/static..."
|
|
||||||
mkdir -p internal/validators/schemas
|
|
||||||
|
|
||||||
# Copy all versioned schema files
|
|
||||||
for dir in static-repo/schemas/*/; do
|
|
||||||
if [ -f "$dir/server.schema.json" ]; then
|
|
||||||
version=$(basename "$dir")
|
|
||||||
# Skip draft directory if it exists
|
|
||||||
if [ "$version" != "draft" ]; then
|
|
||||||
output_file="internal/validators/schemas/${version}.json"
|
|
||||||
if [ ! -f "$output_file" ] || ! cmp -s "$dir/server.schema.json" "$output_file"; then
|
|
||||||
echo "⬇ Adding/updating ${version}/server.schema.json -> ${version}.json"
|
|
||||||
cp "$dir/server.schema.json" "$output_file"
|
|
||||||
else
|
|
||||||
echo "✓ ${version} is already up to date"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "✅ Schema sync complete"
|
|
||||||
|
|
||||||
- name: Check for changes
|
|
||||||
id: changes
|
|
||||||
run: |
|
|
||||||
# Check for both modified and untracked files
|
|
||||||
if [ -n "$(git status --porcelain internal/validators/schemas/)" ]; then
|
|
||||||
echo "changed=true" >> $GITHUB_OUTPUT
|
|
||||||
git status --porcelain internal/validators/schemas/
|
|
||||||
else
|
|
||||||
echo "changed=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "No changes to schemas"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Commit and push changes
|
|
||||||
if: steps.changes.outputs.changed == 'true'
|
|
||||||
run: |
|
|
||||||
git config --local user.email "action@github.com"
|
|
||||||
git config --local user.name "GitHub Action"
|
|
||||||
git add internal/validators/schemas/
|
|
||||||
git commit -m "Sync schemas from modelcontextprotocol/static [skip ci]"
|
|
||||||
git push
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -12,4 +12,4 @@ validate-schemas
|
||||||
coverage.out
|
coverage.out
|
||||||
coverage.html
|
coverage.html
|
||||||
deploy/infra/infra
|
deploy/infra/infra
|
||||||
registry
|
registry
|
||||||
|
|
@ -22,7 +22,7 @@ linters:
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- godox
|
- godox
|
||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- gomodguard_v2
|
- gomodguard
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
- grouper
|
- grouper
|
||||||
|
|
@ -55,10 +55,6 @@ linters:
|
||||||
max-complexity: 20
|
max-complexity: 20
|
||||||
dupl:
|
dupl:
|
||||||
threshold: 300
|
threshold: 300
|
||||||
goconst:
|
|
||||||
# Repeated literals in tests are fixture data ("1.0.0", "Test server", ...);
|
|
||||||
# hoisting them into constants hurts readability more than it helps.
|
|
||||||
ignore-tests: true
|
|
||||||
gocognit:
|
gocognit:
|
||||||
min-complexity: 51
|
min-complexity: 51
|
||||||
nestif:
|
nestif:
|
||||||
|
|
@ -70,18 +66,11 @@ linters:
|
||||||
- common-false-positives
|
- common-false-positives
|
||||||
- legacy
|
- legacy
|
||||||
- std-error-handling
|
- std-error-handling
|
||||||
rules:
|
|
||||||
# httptest.NewRequest is idiomatic in tests; noctx is not useful there.
|
|
||||||
- path: _test\.go
|
|
||||||
linters:
|
|
||||||
- noctx
|
|
||||||
paths:
|
paths:
|
||||||
- third_party$
|
- third_party$
|
||||||
- builtin$
|
- builtin$
|
||||||
- examples$
|
- examples$
|
||||||
formatters:
|
formatters:
|
||||||
enable:
|
|
||||||
- gofmt
|
|
||||||
exclusions:
|
exclusions:
|
||||||
generated: lax
|
generated: lax
|
||||||
paths:
|
paths:
|
||||||
|
|
|
||||||
|
|
@ -95,35 +95,37 @@ signs:
|
||||||
cmd: cosign
|
cmd: cosign
|
||||||
args:
|
args:
|
||||||
- "sign-blob"
|
- "sign-blob"
|
||||||
- "--bundle=${signature}" # cosign v3+: bundles signature and certificate together
|
- "--output-signature=${signature}"
|
||||||
|
- "--output-certificate=${certificate}"
|
||||||
- "${artifact}"
|
- "${artifact}"
|
||||||
- "--yes" # needed on cosign 2.0.0+
|
- "--yes" # needed on cosign 2.0.0+
|
||||||
artifacts: archive
|
artifacts: archive
|
||||||
output: true
|
output: true
|
||||||
signature: "${artifact}.sigstore.json"
|
certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem'
|
||||||
|
|
||||||
# Also sign checksums file for additional verification
|
# Also sign checksums file for additional verification
|
||||||
- id: checksums
|
- id: checksums
|
||||||
cmd: cosign
|
cmd: cosign
|
||||||
args:
|
args:
|
||||||
- "sign-blob"
|
- "sign-blob"
|
||||||
- "--bundle=${signature}" # cosign v3+: bundles signature and certificate together
|
- "--output-signature=${signature}"
|
||||||
|
- "--output-certificate=${certificate}"
|
||||||
- "${artifact}"
|
- "${artifact}"
|
||||||
- "--yes"
|
- "--yes"
|
||||||
artifacts: checksum
|
artifacts: checksum
|
||||||
output: true
|
output: true
|
||||||
signature: "${artifact}.sigstore.json"
|
certificate: '{{ trimsuffix .Env.artifact ".txt" }}.pem'
|
||||||
|
|
||||||
# This section defines the release format.
|
# This section defines the release format.
|
||||||
archives:
|
archives:
|
||||||
# Registry server archive
|
# Registry server archive
|
||||||
- id: registry
|
- id: registry
|
||||||
name_template: "registry_{{ .Os }}_{{ .Arch }}"
|
name_template: "registry_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
ids:
|
ids:
|
||||||
- registry
|
- registry
|
||||||
|
|
||||||
# Publisher CLI archive
|
# Publisher CLI archive
|
||||||
- id: publisher
|
- id: publisher
|
||||||
name_template: "mcp-publisher_{{ .Os }}_{{ .Arch }}"
|
name_template: "mcp-publisher_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
ids:
|
ids:
|
||||||
- publisher
|
- publisher
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
# Build artifacts
|
|
||||||
bin/
|
|
||||||
*.exe
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test and coverage files
|
|
||||||
coverage.out
|
|
||||||
coverage.html
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Database files
|
|
||||||
*.db
|
|
||||||
.db/
|
|
||||||
|
|
||||||
# Deployment files
|
|
||||||
deploy/
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
docs/
|
|
||||||
|
|
||||||
# Tests
|
|
||||||
tests/
|
|
||||||
|
|
||||||
# Git
|
|
||||||
.git/
|
|
||||||
.gitignore
|
|
||||||
|
|
||||||
# GitHub Actions
|
|
||||||
.github/
|
|
||||||
|
|
||||||
# Docker files (no longer used)
|
|
||||||
Dockerfile
|
|
||||||
.dockerignore
|
|
||||||
docker-compose.yml
|
|
||||||
|
|
||||||
# IDE and editor files
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
|
|
||||||
# Temporary files
|
|
||||||
tmp/
|
|
||||||
temp/
|
|
||||||
*.tmp
|
|
||||||
|
|
||||||
# OS files
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
32
.ko.yaml
32
.ko.yaml
|
|
@ -1,32 +0,0 @@
|
||||||
# ko configuration for MCP Registry
|
|
||||||
# Documentation: https://ko.build/configuration/
|
|
||||||
|
|
||||||
# Default base image for all builds
|
|
||||||
# Using Chainguard's static image for minimal, secure containers (~2MB)
|
|
||||||
defaultBaseImage: cgr.dev/chainguard/static:latest
|
|
||||||
|
|
||||||
# Default platforms for multi-architecture builds
|
|
||||||
defaultPlatforms:
|
|
||||||
- linux/amd64
|
|
||||||
- linux/arm64
|
|
||||||
|
|
||||||
# Build configuration
|
|
||||||
builds:
|
|
||||||
- id: registry
|
|
||||||
# Main package to build
|
|
||||||
main: ./cmd/registry
|
|
||||||
# Inject version information at build time via ldflags
|
|
||||||
ldflags:
|
|
||||||
- -s -w
|
|
||||||
- -X main.Version={{ .Env.VERSION }}
|
|
||||||
- -X main.GitCommit={{ .Env.GIT_COMMIT }}
|
|
||||||
- -X main.BuildTime={{ .Env.BUILD_TIME }}
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
|
|
||||||
# SBOM (Software Bill of Materials) configuration
|
|
||||||
sbom: spdx
|
|
||||||
|
|
||||||
# Base import path handling
|
|
||||||
# Set to false to preserve full import paths in image names
|
|
||||||
baseImportPaths: false
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue