Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:51:12 +08:00
commit 1878d01517
21 changed files with 8728 additions and 0 deletions

View File

@@ -0,0 +1,420 @@
# Go CI/CD Pipeline
# Optimized with caching, matrix testing, and deployment
name: Go CI
on:
push:
branches: [main, develop]
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GO_VERSION: '1.22'
jobs:
# Security: Secret Scanning
secret-scan:
name: Secret Scanning
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: TruffleHog Secret Scan
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
- name: Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Security: SAST
sast:
name: Static Analysis (CodeQL)
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: go
queries: security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
lint:
name: Lint
runs-on: ubuntu-latest
needs: [secret-scan]
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: latest
args: --timeout=5m
- name: Check formatting
run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "Please run: gofmt -s -w ."
gofmt -s -l .
exit 1
fi
- name: Check go mod tidy
run: |
go mod tidy
git diff --exit-code go.mod go.sum
test:
name: Test (Go ${{ matrix.go-version }}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
matrix:
go-version: ['1.21', '1.22']
os: [ubuntu-latest, macos-latest]
fail-fast: false
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
- name: Download dependencies
run: go mod download
- name: Run unit tests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb?sslmode=disable
REDIS_URL: redis://localhost:6379
run: |
go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Upload coverage to Codecov
if: matrix.go-version == '1.22' && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v4
with:
files: ./coverage.out
fail_ci_if_error: false
- name: Run benchmarks
if: matrix.go-version == '1.22' && matrix.os == 'ubuntu-latest'
run: go test -bench=. -benchmem ./... | tee benchmark.txt
- name: Upload benchmark results
if: matrix.go-version == '1.22' && matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: benchmark.txt
security:
name: Security Scanning
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Run Gosec
uses: securego/gosec@master
with:
args: '-fmt json -out gosec-report.json ./...'
continue-on-error: true
- name: Run govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: Upload security reports
if: always()
uses: actions/upload-artifact@v4
with:
name: security-reports
path: gosec-report.json
build:
name: Build
runs-on: ubuntu-latest
needs: [lint, test, sast, security]
timeout-minutes: 15
strategy:
matrix:
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
exclude:
- goos: windows
goarch: arm64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Build binary
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: |
OUTPUT="myapp-${{ matrix.goos }}-${{ matrix.goarch }}"
if [ "${{ matrix.goos }}" = "windows" ]; then
OUTPUT="${OUTPUT}.exe"
fi
go build \
-ldflags="-s -w -X main.version=${{ github.sha }} -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o $OUTPUT \
./cmd/myapp
ls -lh $OUTPUT
- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: myapp-${{ matrix.goos }}-${{ matrix.goarch }}
path: myapp-*
retention-days: 7
integration-test:
name: Integration Tests
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main' || github.event_name == 'pull_request'
timeout-minutes: 30
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Download Linux binary
uses: actions/download-artifact@v4
with:
name: myapp-linux-amd64
- name: Make binary executable
run: chmod +x myapp-linux-amd64
- name: Run integration tests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb?sslmode=disable
BINARY_PATH: ./myapp-linux-amd64
run: go test -v -tags=integration ./tests/integration/...
docker:
name: Build Docker Image
runs-on: ubuntu-latest
needs: [build, test]
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
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=${{ github.sha }}
BUILD_TIME=${{ github.event.head_commit.timestamp }}
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [docker, integration-test]
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://api.example.com
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Deploy to Cloud Run
run: |
gcloud run deploy myapp \
--image ghcr.io/${{ github.repository }}:${{ github.sha }} \
--region us-central1 \
--platform managed \
--allow-unauthenticated \
--memory 512Mi \
--cpu 1 \
--max-instances 10
- name: Health check
run: |
URL=$(gcloud run services describe myapp --region us-central1 --format 'value(status.url)')
for i in {1..10}; do
if curl -f $URL/health; then
echo "Health check passed"
exit 0
fi
echo "Attempt $i failed, retrying..."
sleep 10
done
exit 1
release:
name: Create Release
runs-on: ubuntu-latest
needs: [build]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Create checksums
run: |
cd artifacts
for dir in myapp-*; do
cd $dir
sha256sum * > checksums.txt
cd ..
done
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: artifacts/**/*
generate_release_notes: true
draft: false
prerelease: false