name: Release Pipeline
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Release tag (e.g., v1.0.0)'
required: true
type: string
skip_tests:
description: 'Skip tests (not recommended)'
required: false
type: boolean
default: false
env:
GO_VERSION: '1.23'
DOCKER_REGISTRY: docker.io
DOCKER_IMAGE: keepersecurityinc/ksm-mcp-poc
jobs:
# Step 1: Run all tests
test:
name: Test Suite
runs-on: ubuntu-latest
if: ${{ !inputs.skip_tests }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install dependencies
run: |
go mod download
go mod verify
- name: Run formatting check
run: |
if [ -n "$(gofmt -l .)" ]; then
echo "Code is not properly formatted. Run 'go fmt ./...'"
gofmt -d .
exit 1
fi
- name: Run linter
uses: golangci/golangci-lint-action@v6
with:
version: latest
args: --timeout=5m
- name: Run unit tests
run: |
go test -v -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
- name: Run integration tests
run: |
go test -v -tags=integration ./internal/testing/integration/...
- name: Run security scan
run: |
go install github.com/securego/gosec/v2/cmd/gosec@latest
gosec -fmt sarif -out gosec-results.sarif ./... || true
- name: Upload test coverage
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.html
- name: Upload security results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: gosec-results.sarif
# Step 2: Build binaries for multiple platforms
build:
name: Build Binaries
needs: test
runs-on: ubuntu-latest
if: ${{ always() && (needs.test.result == 'success' || inputs.skip_tests) }}
strategy:
matrix:
include:
- os: linux
arch: amd64
- os: linux
arch: arm64
- os: darwin
arch: amd64
- os: darwin
arch: arm64
- os: windows
arch: amd64
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Determine version
id: version
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.tag }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "Building version: ${VERSION}"
- name: Extract code version
id: code_version
run: |
CODE_VERSION=$(grep -E 'const Version = "v[0-9]+\.[0-9]+\.[0-9]+"' cmd/ksm-mcp/main.go | cut -d'"' -f2)
echo "code_version=${CODE_VERSION}" >> $GITHUB_OUTPUT
- name: Validate version match (for tag pushes)
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
run: |
if [ "${{ steps.version.outputs.version }}" != "${{ steps.code_version.outputs.code_version }}" ]; then
echo "β ERROR: Git tag (${{ steps.version.outputs.version }}) does not match code version (${{ steps.code_version.outputs.code_version }})"
echo "Please update the version in cmd/ksm-mcp/main.go"
exit 1
fi
- name: Build binary
env:
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
run: |
VERSION="${{ steps.version.outputs.version }}"
BINARY_NAME="ksm-mcp-${{ matrix.os }}-${{ matrix.arch }}"
if [ "${{ matrix.os }}" = "windows" ]; then
BINARY_NAME="${BINARY_NAME}.exe"
fi
echo "Building ${BINARY_NAME}..."
go build -trimpath \
-ldflags "-X main.version=${VERSION} -w -s" \
-o "bin/${BINARY_NAME}" \
./cmd/ksm-mcp
- name: Compress binary
run: |
cd bin
if [ "${{ matrix.os }}" = "windows" ]; then
zip "ksm-mcp-${{ matrix.os }}-${{ matrix.arch }}.zip" "ksm-mcp-${{ matrix.os }}-${{ matrix.arch }}.exe"
else
tar -czf "ksm-mcp-${{ matrix.os }}-${{ matrix.arch }}.tar.gz" "ksm-mcp-${{ matrix.os }}-${{ matrix.arch }}"
fi
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}-${{ matrix.arch }}
path: |
bin/*.tar.gz
bin/*.zip
# Step 3: Create GitHub Release
release:
name: Create GitHub Release
needs: build
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Determine version
id: version
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.tag }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: binary-*
- name: Organize release assets
run: |
mkdir -p release-assets
find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" \) -exec cp {} release-assets/ \;
ls -la release-assets/
- name: Generate release notes
id: release_notes
run: |
VERSION="${{ steps.version.outputs.version }}"
cat << EOF > release-notes.md
## KSM MCP Server ${VERSION}
### π¦ Installation
#### Binary Installation
Download the appropriate binary for your platform from the assets below.
\`\`\`bash
# Linux/macOS
tar -xzf ksm-mcp-linux-amd64.tar.gz
chmod +x ksm-mcp-linux-amd64
sudo mv ksm-mcp-linux-amd64 /usr/local/bin/ksm-mcp
# Windows
# Extract the zip file and add to PATH
\`\`\`
#### Docker Installation
\`\`\`bash
docker pull keepersecurityinc/ksm-mcp-poc:${VERSION}
docker pull keepersecurityinc/ksm-mcp-poc:latest
\`\`\`
### π What's Changed
See the [full changelog](https://github.com/Keeper-Security/ksm-mcp/compare/$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "main")...${VERSION})
### π Checksums
\`\`\`
$(cd release-assets && sha256sum * || shasum -a 256 *)
\`\`\`
EOF
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
name: ${{ steps.version.outputs.version }}
body_path: release-notes.md
draft: false
prerelease: ${{ contains(steps.version.outputs.version, '-') }}
files: release-assets/*
fail_on_unmatched_files: true
generate_release_notes: true
# Step 4: Build and push Docker images
docker:
name: Build and Push Docker Images
needs: release
runs-on: ubuntu-latest
environment: poc
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ vars.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_IMAGE }}
tags: |
type=semver,pattern={{version}},value=${{ needs.release.outputs.version }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.release.outputs.version }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
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
build-args: |
VERSION=${{ needs.release.outputs.version }}
# Step 5: Summary
summary:
name: Pipeline Summary
needs: [test, build, release, docker]
runs-on: ubuntu-latest
if: always()
steps:
- name: Generate summary
run: |
echo "# Release Pipeline Summary π" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Status badges
echo "## Status" >> $GITHUB_STEP_SUMMARY
echo "| Stage | Result |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| π§ͺ Tests | ${{ needs.test.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| π¨ Build | ${{ needs.build.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| π¦ Release | ${{ needs.release.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| π³ Docker | ${{ needs.docker.result }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.docker.result }}" == "success" ]; then
echo "## π Release Complete!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Docker Images" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ env.DOCKER_IMAGE }}:${{ needs.release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ env.DOCKER_IMAGE }}:latest" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### GitHub Release" >> $GITHUB_STEP_SUMMARY
echo "https://github.com/Keeper-Security/ksm-mcp/releases/tag/${{ needs.release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
fi