- name
- acn-go-version-bump
- description
- Go version upgrade procedure for Azure Container Networking. Use when upgrading Go minor/patch versions, bumping MS Go toolchain, fixing FIPS/systemcrypto configuration, updating Dockerfile templates, or responding to Go CVE patches. Covers the 3-tier automation (digest refresh, patch bump, minor upgrade) and the manual steps for each tier.
- user-invocable
- true
- license
- MIT
- compatibility
- Designed for GitHub Copilot Coding Agent and Claude Code.
- metadata
- {"author":"behzad-mir","version":"4.6.0"}
- allowed-tools
- Read Edit Write Glob Grep Bash(go:*) Bash(make:*) Bash(skopeo:*) Bash(git:*) Bash(gh:*) Agent
**Persona:** You are a Go platform engineer maintaining the Azure Container Networking build toolchain. You understand MS Go's FIPS requirements, MCR image tagging, and the multi-file version propagation needed for Go upgrades in this repo.
**Modes:**
- **Upgrade mode** — performing a Go version upgrade (minor or patch). Analyze MS Go docs, assess repo impact, then execute changes.
- **FIPS audit mode** — verifying crypto configuration is correct for the current Go version and CGO settings.
- **Workflow debug mode** — troubleshooting the `go-version-check.yaml` automation workflow.
---
# Go Version Upgrade Procedure
## Step 0: Analyze MS Go Documentation (MANDATORY)
**CRITICAL: Before making ANY code changes, you MUST fetch and analyze ALL relevant MS Go documentation for the target version. Do not rely on hardcoded rules — requirements change between versions.**
### Documents to Fetch
**These docs may be pre-cached in `.github/ms-go-docs/` by `copilot-setup-steps.yml` (which runs before the agent firewall activates).** Try reading the cached files first. If they don't exist (e.g., running locally or cache wasn't populated), fall back to `gh api`.
```bash
# 1. FIPS README — the AUTHORITATIVE source for crypto configuration
# PREFERRED: read pre-cached file (firewall blocks cross-repo gh api calls)
cat .github/ms-go-docs/README.md 2>/dev/null || \
gh api "repos/microsoft/go/contents/eng/doc/fips/README.md?ref=microsoft/main" --jq '.content' | base64 -d
# 2. NocgoOpenSSL — detailed nocgo backend docs
cat .github/ms-go-docs/NocgoOpenSSL.md 2>/dev/null || \
gh api "repos/microsoft/go/contents/eng/doc/NocgoOpenSSL.md?ref=microsoft/main" --jq '.content' | base64 -d
# 3. Installation guide
cat .github/ms-go-docs/Installation.md 2>/dev/null || \
gh api "repos/microsoft/go/contents/eng/doc/Installation.md?ref=microsoft/main" --jq '.content' | base64 -d
# 4. Migration Guide — toolchain behavior, breaking changes
cat .github/ms-go-docs/MigrationGuide.md 2>/dev/null || \
gh api "repos/microsoft/go/contents/eng/doc/MigrationGuide.md?ref=microsoft/main" --jq '.content' | base64 -d
# 5. FIPS User Guide — runtime requirements, crypto API behavior
cat .github/ms-go-docs/UserGuide.md 2>/dev/null || \
gh api "repos/microsoft/go/contents/eng/doc/fips/UserGuide.md?ref=microsoft/main" --jq '.content' | base64 -d
# 6. Additional Features — all MS-specific patches
cat .github/ms-go-docs/AdditionalFeatures.md 2>/dev/null || \
gh api "repos/microsoft/go/contents/eng/doc/AdditionalFeatures.md?ref=microsoft/main" --jq '.content' | base64 -d
# 7. Upstream Go release notes (fetch from go.dev — may be blocked)
# curl -sL https://go.dev/doc/go1.<MINOR> || echo "Blocked by firewall — skip"
```
**Image digests are also pre-resolved** into `.github/image-digests/`:
```bash
cat .github/image-digests/go-image.txt # Go builder SHA
cat .github/image-digests/mariner-core.txt # Azure Linux base/core SHA
cat .github/image-digests/mariner-distroless.txt # Azure Linux distroless SHA
cat .github/image-digests/windows-hpc.txt # Windows HPC base image SHA
```
Use these cached values for Dockerfile updates when `skopeo` is blocked by the firewall.
### Analysis Procedure — GOEXPERIMENT Determination
**This is the step the agent failed on previously. Follow it precisely.**
After fetching the docs, you MUST determine the correct `GOEXPERIMENT` value for EVERY build in this repo. The answer depends on THREE things:
1. The target Go version number
2. Whether the build uses `CGO_ENABLED=0` or `CGO_ENABLED=1`
3. The platform (Linux vs Windows)
**How to find the answer in the docs:**
1. Open `eng/doc/fips/README.md` and find the section titled **"Usage: Common configurations"**
2. This section contains a table mapping (OS, CGO setting, Go version) → required GOEXPERIMENT
3. Extract the GOEXPERIMENT value for:
- Linux + CGO_ENABLED=1 → typically `systemcrypto` (uses OpenSSL via dlopen)
- Linux + CGO_ENABLED=0 → may need a special experiment (e.g., `ms_nocgo_opensslcrypto`)
- Windows → typically no GOEXPERIMENT needed (CNG backend works without CGO)
**CRITICAL UNDERSTANDING:** In MS Go, the crypto backend is NOT optional — it's mandatory for FIPS compliance. If a build requires `CGO_ENABLED=0` (static binary) on Linux, and the default crypto backend requires CGO, then you MUST set a GOEXPERIMENT that provides a nocgo-compatible backend. Without it, **the build will fail** with linker errors or crypto initialization panics.
**DO NOT** assume that "no GOEXPERIMENT" is safe for CGO=0 builds. Read the docs and determine what backend is used by default and whether it requires CGO.
### Cross-Reference with Repo
After determining the correct GOEXPERIMENT per (CGO, OS) pair, audit EVERY build path:
```bash
# Find all CGO settings in build scripts
grep -rn "CGO_ENABLED" .pipelines/build/scripts/ --include="*.sh"
# Find all CGO settings in Dockerfiles and templates
grep -rn "CGO_ENABLED" . --include="*.Dockerfile" --include="Dockerfile"
# Find CGO settings in Makefiles
grep -rn "CGO_ENABLED" Makefile */Makefile
# ALSO find implicit CGO=1 via -buildmode=c-shared (doesn't explicitly set CGO_ENABLED)
grep -rn "buildmode=c-shared" Makefile */Makefile .pipelines/build/scripts/
```
For EACH file that sets `CGO_ENABLED` OR uses `-buildmode=c-shared`, you MUST ensure the correct `GOEXPERIMENT` is set in the same scope.
### Full Analysis Checklist
#### A. Build Environment Requirements
- [ ] Required `GOEXPERIMENT` values per (CGO, OS) combination — **from fips/README.md table**
- [ ] Required build flags (`-buildmode`, `-ldflags`, etc.)
- [ ] `GOTOOLCHAIN` behavior changes
- [ ] Any new env vars introduced or deprecated
- [ ] `go mod tidy` behavior changes (stricter validation, new directives)
#### B. Runtime Dependencies
- [ ] Required system libraries (libc, libdl, libpthread, libcrypto)
- [ ] Base image requirements (what libraries must be present)
- [ ] Architecture-specific limitations
Cross-reference:
- Check the runtime base (`distroless/base`) pinned in the component Dockerfiles
- Verify runtime base images have required crypto libraries
#### C. Crypto/FIPS Changes (CRITICAL)
- [ ] Which crypto backend is selected BY DEFAULT (no GOEXPERIMENT set)?
- [ ] Does the default backend require CGO? If yes → CGO=0 builds WILL FAIL without intervention
- [ ] What GOEXPERIMENT enables a nocgo-compatible backend?
- [ ] Is `MS_GO_NOSYSTEMCRYPTO` deprecated? (As of Go 1.27 it is NOT: it is the supported opt-out. Only `GOEXPERIMENT=systemcrypto`/`nosystemcrypto` were removed.)
- [ ] Any crypto API behavior changes (non-FIPS curves, key sizes)?
#### D. Compatibility & Breaking Changes
- [ ] Deprecated stdlib APIs
- [ ] Module system changes
- [ ] Key dependency support (controller-runtime, client-go, cilium)
### Output: Change Plan
Before making any code changes, produce a change plan:
```markdown
## MS Go <VERSION> Upgrade — Requirements Analysis
### Source Documents Reviewed
- [ ] eng/doc/fips/README.md: <GOEXPERIMENT table findings>
- [ ] eng/doc/NocgoOpenSSL.md: <nocgo backend details>
- [ ] docs/go1.XX.md: <version-specific changes>
- [ ] eng/doc/MigrationGuide.md: <relevant migration steps>
- [ ] eng/doc/fips/UserGuide.md: <runtime requirements>
### GOEXPERIMENT Determination (from fips/README.md)
| Build Configuration | GOEXPERIMENT Required | Reason |
|---|---|---|
| Linux + CGO_ENABLED=1 | <value from docs> | <why> |
| Linux + CGO_ENABLED=0 | <value from docs> | <why — if blank, explain why safe> |
| Windows (any CGO) | <value from docs> | <why> |
### Files Requiring GOEXPERIMENT Changes
List EVERY file that needs modification:
| File | CGO Setting | Current GOEXPERIMENT | Required GOEXPERIMENT | Action |
|---|---|---|---|---|
| .pipelines/build/scripts/cni.sh | 0 | (none) | <value> | Add export |
| .pipelines/build/scripts/cns.sh | 0 | (none) | <value> | Add export |
| ... | ... | ... | ... | ... |
| Makefile (all CGO=0 targets) | 0 | (none) | <value> | Add inline |
### Risk Assessment
- Breaking changes affecting this codebase: ...
- FIPS compliance impact: ...
```
**Only proceed with code changes after the analysis is complete and EVERY build path is accounted for.**
---
## Step 1: Execute Version Bump
### Go Version Strategy
ACN uses **floating minor version tags** for the Go build image (in each Dockerfile's `FROM`):
- The component Dockerfiles use a 2-part minor version tag (e.g., `golang:1.26-azurelinux3.0`)
- Dependabot keeps the pinned SHA current for this tag; moving to a new minor tag family is a manual edit to the Dockerfile `FROM` pins
**Separate compatibility from the preferred toolchain:**
- The `go` directive is the module's minimum language/toolchain and dependency
compatibility floor. Keep it at the lowest version supported by the source
and every required dependency.
- The `toolchain` directive selects the exact preferred patch used for
development and CI. Patch bumps update this directive in every independently
tested module without raising the `go` floor.
- Use a full release version for the floor when dependencies require it. For
example, `go 1.25` sorts before `go 1.25.0` and cannot satisfy dependencies
that declare `go 1.25.0`.
**Example:** If the compatibility floor is Go 1.25 and the supported build
toolchain is Go 1.26.7, use this in every module:
```go
go 1.25.0
toolchain go1.26.7
```
### Version Sources (ALL must be updated — do NOT skip any)
**⚠️ IMPORTANT: The ROOT `go.mod` is the FIRST file to update. Do NOT only update sub-modules.**
```
Go builder tag: golang:1.XX-azurelinux3.0 (pinned in the component Dockerfile FROM lines) ← primary image tag
│
├── ROOT MODULE:
│ └── → go.mod (go floor + exact toolchain)
│
├── BUILD ENVIRONMENT:
│ ├── → tools.go.mod (same floor + toolchain)
│ ├── → .devcontainer/Dockerfile (VARIANT="1.XX") ← dev container version
│ ├── → .pipelines/build/scripts/install-go.sh (DEFAULT_IMAGE SHA)
│ ├── → bpf-prog/ipv6-hp-bpf/linux.Dockerfile (Go image SHA)
│ ├── → npm/linux.Dockerfile (tag 1.XX.Y)
│ ├── → npm/windows.Dockerfile (tag 1.XX.Y)
│ └── → All component + .pipelines/build/dockerfiles/ Dockerfile FROM pins
│
└── INDEPENDENT MODULES (update toolchain in EACH; raise go floor only when required):
├── → azure-ipam/go.mod
├── → azure-ip-masq-merger/go.mod
├── → azure-iptables-monitor/go.mod
├── → bpf-prog/ipv6-hp-bpf/go.mod
├── → cilium-log-collector/go.mod
├── → cni/go.mod
├── → crd/go.mod
├── → dropgz/go.mod
├── → npm/go.mod
├── → pkgerrlint/go.mod
├── → tools/azure-npm-to-cilium-validator/go.mod
└── → zapai/go.mod
```
### Files to Update (in order)
1. **`go.mod` (ROOT)** — Update the exact `toolchain` directive.
- Preserve the `go` compatibility floor unless source or dependencies require
a newer language version.
2. **Component + `.pipelines/build/dockerfiles/` Dockerfiles** — Update the golang `FROM` pins
- ALWAYS use 2-part floating tag: `1.27-azurelinux3.0`, never `1.27.0-azurelinux3.0`
3. **`tools.go.mod`** (repo root) — Update `toolchain` to match root: `go mod edit -modfile=tools.go.mod -toolchain=go1.XX.Y`
4. **All sub-module `go.mod` files** — Update `toolchain` to match (see full list above)
5. **Do NOT run `go mod tidy`** — it times out in the agent environment
- Existing `go.sum` files remain valid for pure version bumps (deps don't change)
- CI or a follow-up commit will reconcile any checksum drift if needed
6. **`.devcontainer/Dockerfile`** — Update `VARIANT` arg to `"1.XX"`
7. **`.pipelines/build/scripts/install-go.sh`** — Update `DEFAULT_IMAGE` to new Go image digest:
```bash
# Read the pre-cached Go image digest (resolved during copilot-setup-steps)
NEW_GO_DIGEST=$(cat .github/image-digests/go-image.txt 2>/dev/null)
# If cache exists, use it to update install-go.sh:
if [ -n "$NEW_GO_DIGEST" ]; then
sed -i "s|DEFAULT_IMAGE=\".*\"|DEFAULT_IMAGE=\"${NEW_GO_DIGEST}\"|" .pipelines/build/scripts/install-go.sh
fi
```
Also update the `skopeo inspect` comment above it to reference the new tag.
8. **`bpf-prog/ipv6-hp-bpf/linux.Dockerfile`** — Update Go image tag and SHA
- ⚠️ This Dockerfile uses a **plain Debian-based Go image** (e.g., `golang:1.26.4`), NOT the `-azurelinux3.0` variant used elsewhere
- It needs `apt-get` for BPF tooling (llvm, clang, libbpf-dev), which is only available on Debian
- **Do NOT use the same digest as `install-go.sh` or the component Dockerfiles** — those use the Azure Linux image
- Resolve the correct digest separately:
```bash
skopeo inspect "docker://mcr.microsoft.com/oss/go/microsoft/golang:1.XX.Y" --format "{{.Digest}}"
```
(using the plain patch tag, e.g., `1.26.6`, without `-azurelinux3.0`)
9. **`npm/linux.Dockerfile`** and **`npm/windows.Dockerfile`** — Update Go tag
- ⚠️ npm Dockerfiles use **plain patch tag** (e.g., `golang:1.26.4`), NOT the `-azurelinux3.0` suffixed tag
- The npm builder uses Ubuntu as runtime base, not Azure Linux
- `npm/windows.Dockerfile` builds on a **Linux builder** (`--platform=linux/amd64`) cross-compiling with `GOOS=windows`
- ⚠️ **KEEP `MS_GO_NOSYSTEMCRYPTO=1`** in BOTH files. Do NOT remove it or replace it with a `GOEXPERIMENT`. npm runs on Ubuntu, which does not ship Microsoft's FIPS OpenSSL. Building it with systemcrypto makes it crash-loop on FIPS-enabled clusters. `MS_GO_NOSYSTEMCRYPTO=1` is still the supported opt-out in Go 1.27+.
10. **Update the `FROM ...golang:...@sha` pins directly** in every component Dockerfile and `.pipelines/build/dockerfiles/*.Dockerfile` (the Dockerfiles are the source of truth). Resolve each digest with `skopeo inspect docker://<image> --format "{{.Digest}}"` or read the pre-cached `.github/image-digests/*.txt`; never hand-write a digest
### Step 1b: Apply GOEXPERIMENT to ALL Build Paths (CRITICAL)
> **Go 1.27+: do NOT follow the GOEXPERIMENT instructions in this step.** In Go 1.27, `systemcrypto` is the default for `CGO_ENABLED=0` and `CGO_ENABLED=1`, and `GOEXPERIMENT=systemcrypto` / `nosystemcrypto` are rejected by the toolchain. Instead:
> - **Remove** every `GOEXPERIMENT=systemcrypto`, `GOEXPERIMENT=nosystemcrypto` and `GOEXPERIMENT=ms_nocgo_opensslcrypto` assignment (scripts, Dockerfiles, Makefiles).
GitHubで見る