diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ac69f5c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,96 @@ +name: CI + +on: + pull_request: + branches: + - 'main' + +permissions: + contents: read + pull-requests: write # Required for posting comments (if using a commenting action) or checks + security-events: write # Required for uploading SARIF results + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: v2.7.2 + + sast: + name: SAST (Gosec) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run Gosec Security Scanner + uses: securego/gosec@v2.22.0 + with: + args: '-no-fail -fmt sarif -out results.sarif ./...' + + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif + + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: Run tests + run: go test ./... + + build-image: + name: Build Image + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build Docker image + run: docker build -f Containerfile . + + container-scan: + name: Container Scan + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build Docker image + run: docker build -f Containerfile -t local/app:latest . + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.28.0 + with: + image-ref: 'local/app:latest' + format: 'sarif' + output: 'trivy-results.sarif' + ignore-unfixed: true + vuln-type: 'os,library' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..3520038 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,43 @@ +name: Publish + +on: + release: + types: [published] + +permissions: + packages: write + contents: read + +jobs: + push-image: + name: Build & Push Image + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: Containerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1a153dd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: Release + +on: + push: + branches: + - 'main' + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for commit history + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + + - name: Install dependencies + run: npm install --no-save semantic-release@^24.2.0 @semantic-release/commit-analyzer@^13.0.0 @semantic-release/release-notes-generator@^14.0.1 @semantic-release/github@^11.0.1 + + - name: Semantic Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npx semantic-release@^24.2.0 diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..cb38e9c --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,10 @@ +{ + "branches": [ + "main" + ], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/github" + ] +} \ No newline at end of file diff --git a/Dockerfile b/Containerfile similarity index 100% rename from Dockerfile rename to Containerfile diff --git a/Dockerfile.dev b/Containerfile.dev similarity index 100% rename from Dockerfile.dev rename to Containerfile.dev diff --git a/Tiltfile b/Tiltfile index 4e8878b..c351c56 100644 --- a/Tiltfile +++ b/Tiltfile @@ -14,9 +14,9 @@ k8s_yaml(secret_yaml_tls( # 3. Build and Deploy Webhook docker_build_with_restart( - 'archy-webhook', + 'webhook', '.', - dockerfile='Dockerfile.dev', + dockerfile='Containerfile.dev', # Live update for fast iteration live_update=[ sync('.', '/app'), diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 9b2db53..24d7e15 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -46,7 +46,9 @@ func main() { mux.Handle("/mutate", handler) mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) + if _, err := w.Write([]byte("ok")); err != nil { + log.Printf("Failed to write health check response: %v", err) + } }) server := &http.Server{ diff --git a/pkg/webhook/handler.go b/pkg/webhook/handler.go index f2e1d52..cf00d40 100644 --- a/pkg/webhook/handler.go +++ b/pkg/webhook/handler.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "log" "net/http" "github.com/google/go-containerregistry/pkg/v1/remote" @@ -23,8 +24,12 @@ var ( ) func init() { - admissionv1.AddToScheme(scheme) - corev1.AddToScheme(scheme) + if err := admissionv1.AddToScheme(scheme); err != nil { + panic(err) + } + if err := corev1.AddToScheme(scheme); err != nil { + panic(err) + } } type Handler struct { @@ -84,7 +89,9 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - w.Write(respBytes) + if _, err := w.Write(respBytes); err != nil { + log.Printf("Failed to write response: %v", err) + } } func (h *Handler) mutate(ctx context.Context, ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {