Skip to content
111 changes: 111 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: CI

# Public repo → GitHub-hosted Actions are free. Kept fast: Build and Vet share
# one job so `go vet` reuses the compile cache `go build` just populated
# (standalone `go vet ./...` recompiles everything and is slow). Test runs in
# parallel; the quick checks (Tidy/Format) give sub-minute feedback. Every job
# restores the Go build+module cache. One run per PR (+ push to master);
# superseded runs cancel.
on:
pull_request:
push:
branches: [master]

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-vet:
name: Build & Vet
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
# Manual cache with restore-keys so a changed go.sum still restores the
# previous build+module cache (only the changed deps recompile) instead
# of a full cold compile of the whole cloud-SDK dependency tree.
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-${{ runner.os }}-
- name: go build
run: go build ./...
- name: go vet (reuses the build cache)
run: go vet ./...

test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: false
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-${{ runner.os }}-
- run: go test ./...

tidy:
name: Tidy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
- name: go mod tidy is clean
run: |
go mod tidy
git diff --exit-code go.mod go.sum

format:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
- name: gofmt (report-only)
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "::warning::gofmt needed on the following files (advisory):"
echo "$unformatted"
else
echo "all files gofmt-clean"
fi

lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
- name: install golangci-lint v2
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b "$(go env GOPATH)/bin"
- name: golangci-lint run (report-only)
run: "$(go env GOPATH)/bin/golangci-lint run --timeout=9m ./... || echo '::warning::golangci-lint reported findings (advisory)'"
83 changes: 83 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Security

# Security scanning, separate from CI so it runs in parallel and is easy to
# read. Each scanner is its own cached job. Runs on PRs, on push to master, and
# weekly (so newly-disclosed CVEs are caught even without a push).
on:
pull_request:
push:
branches: [master]
schedule:
- cron: "0 6 * * 1" # Mondays 06:00 UTC

permissions:
contents: read

concurrency:
group: security-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
govulncheck:
name: Vulnerabilities (govulncheck)
runs-on: ubuntu-latest
# Advisory to start: flags known CVEs in dependencies the code reaches.
# Flip to blocking once the baseline is confirmed clean.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
- name: install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: govulncheck (report-only)
run: "$(go env GOPATH)/bin/govulncheck ./... || echo '::warning::govulncheck reported findings (advisory)'"

gosec:
name: SAST (gosec)
runs-on: ubuntu-latest
# Advisory to start: static security analysis (hardcoded creds, weak crypto,
# unsafe patterns). Reports without blocking until the baseline is triaged.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
- name: install gosec
run: go install github.com/securego/gosec/v2/cmd/gosec@latest
- name: gosec (report-only)
run: "$(go env GOPATH)/bin/gosec -exclude-generated ./... || echo '::warning::gosec reported findings (advisory)'"

codeql:
name: CodeQL (SAST)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache: true
- uses: github/codeql-action/init@v3
with:
languages: go
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3

dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Review dependency changes for known vulnerabilities
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
20 changes: 16 additions & 4 deletions kubernetes/handlers_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,10 @@ func TestConfigMap_PatchBadContentTypeReturns400(t *testing.T) {
base+"/api/v1/namespaces/default/configmaps/p", bytes.NewReader([]byte(`[]`)))
req.Header.Set("Content-Type", "application/strategic-merge-patch+json")

resp, _ := http.DefaultClient.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusBadRequest {
Expand Down Expand Up @@ -595,7 +598,10 @@ func TestConfigMap_PatchBadJSONReturns400(t *testing.T) {
)
req.Header.Set("Content-Type", "application/merge-patch+json")

resp, _ := http.DefaultClient.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -618,7 +624,10 @@ func TestConfigMap_PatchTypeIncompatibleReturns400(t *testing.T) {
)
req.Header.Set("Content-Type", "application/merge-patch+json")

resp, _ := http.DefaultClient.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -636,7 +645,10 @@ func TestNamespace_PatchOnMissingReturns404(t *testing.T) {
)
req.Header.Set("Content-Type", "application/merge-patch+json")

resp, _ := http.DefaultClient.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusNotFound {
Expand Down
Loading