Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3123426
fix(anc): remove unsafe direct hotfix download
abigailliang-aks-sig-node Sep 2, 2026
b398995
feat(anc): verify and extract repository hotfixes
abigailliang-aks-sig-node Sep 3, 2026
75390e9
style(anc): satisfy golangci-lint on repository hotfix path
abigailliang-aks-sig-node Sep 3, 2026
80ccb6d
ci: stop verifying golangci config against remote schema
abigailliang-aks-sig-node Sep 3, 2026
8ea673f
fix(anc): disarm stale hotfix binary when removal fails
abigailliang-aks-sig-node Sep 3, 2026
c161cb1
fix(anc): look up Packages checksum by suite-relative path
abigailliang-aks-sig-node Sep 3, 2026
5a3b17f
perf(anc): cancel the peer branch when a repository fetch fails
abigailliang-aks-sig-node Sep 3, 2026
e0fe991
fix(anc): recognize Mariner's Microsoft repository in RPM discovery
abigailliang-aks-sig-node Sep 8, 2026
9b7618c
perf(anc): fetch the compressed Packages index on the fast path
abigailliang-aks-sig-node Sep 8, 2026
6fd893b
test(anc): assert the fast path fetches an exact set of URLs
abigailliang-aks-sig-node Sep 8, 2026
345db74
test(e2e): measure hotfix install timing during bootstrap
abigailliang-aks-sig-node Sep 9, 2026
91c7e4d
test(e2e): add Azure Linux 3 hotfix bootstrap timing
abigailliang-aks-sig-node Sep 9, 2026
3a9bf9b
fix(anc): enhance error handling for APT repository parsing
abigailliang-aks-sig-node Sep 9, 2026
3275863
Merge remote-tracking branch 'origin/main' into abigailliang/remove-b…
abigailliang-aks-sig-node Sep 9, 2026
caa609a
test(e2e): port hotfix bootstrap timing to the scenario registry
abigailliang-aks-sig-node Sep 9, 2026
400143d
fix(anc): improve error handling for missing hotfix completion line
abigailliang-aks-sig-node Sep 9, 2026
9ed04f5
Potential fix for pull request finding
abigailliang-aks-sig-node Sep 9, 2026
da3c208
fix(anc): improve error handling for repository downloads and metadat…
abigailliang-aks-sig-node Sep 9, 2026
e51e856
feat(anc): implement extraction of ANCBinary from deb packages and ad…
abigailliang-aks-sig-node Sep 9, 2026
2bdbe0b
feat(anc): add pkgBinaryPath to App struct and update hotfix download…
abigailliang-aks-sig-node Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ jobs:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: v2.8.0
working-directory: ${{ matrix.dirs }}
# `config verify` fetches the JSON schema from golangci-lint.run on every run, with no
# retry or cache, and each matrix leg fetches it independently. Outages there fail the
# whole job before any linting happens (three consecutive failures on 2026-09-02/03).
# The check only validates .golangci.yaml syntax, which changes rarely and surfaces
# immediately when it does, so we trade it away for a lint gate that does not depend on
# third-party availability.
verify: false

# Optional: working directory, useful for monorepos
# working-directory: somedir
Expand Down
22 changes: 15 additions & 7 deletions aks-node-controller/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,23 @@ type App struct {
hotfixVersionPath string
// aptSourcesDir overrides the default APT sources directory for testing.
aptSourcesDir string
// aptTrustedKeyringsDir overrides the default APT trusted keyrings directory for testing.
aptTrustedKeyringsDir string
// yumReposDir overrides the default RPM repositories directory for testing.
yumReposDir string
// osReleasePath overrides the default /etc/os-release path for testing.
osReleasePath string
// goArch overrides runtime.GOARCH for repository-path tests.
goArch string
// repositoryTempDir overrides where repository downloads and extraction are staged.
repositoryTempDir string
// vhdBinaryPath and hotfixBinaryPath override ANC binary paths for testing.
vhdBinaryPath string
hotfixBinaryPath string
// verifyRepositorySignature overrides gpgv-backed repository signature verification.
verifyRepositorySignature func(ctx context.Context, signedPath, signaturePath string, keyrings []string) error
// extractRepositoryPackage overrides package extraction for deterministic unit tests.
extractRepositoryPackage func(ctx context.Context, format, packagePath, destination string) error
// nodeCustomDataPath overrides the default nodecustomdata path for testing.
nodeCustomDataPath string
// nodeConfigPath overrides the default AKSNodeConfig path for testing. It is the
Expand All @@ -74,13 +89,6 @@ type App struct {
// grpcDialContext overrides how the gRPC LPS client dials, letting tests point the client at
// an in-process (bufconn) server. When nil, the real TLS dial to the apiserver front is used.
grpcDialContext func(ctx context.Context, target string) (net.Conn, error)
// httpDownload overrides the real HTTP GET for download-hotfix artifact fetching, letting
// unit tests inject canned binary content or errors without real networking. When nil, the
// real HTTP download is used.
httpDownload func(ctx context.Context, url string) ([]byte, error)
// downloadDir overrides the directory where artifact downloads are staged. When empty,
// defaults to filepath.Dir(hotfixBinaryPath). Used for testing.
downloadDir string
}

// provision.json values are emitted as strings by the shell jq invocation.
Expand Down
21 changes: 6 additions & 15 deletions aks-node-controller/checkhotfix.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func (a *App) checkHotfix(ctx context.Context) (checkHotfixOutcome, error) {
// value keeps the reported outcome consistent with what download-hotfix will actually read:
// a pointer with no entry for this node's base stages nothing resolvable, so it must report
// noHotfixForBase, not LPSRead.
staged := hotfixConfig{Hotfixes: cfg.Hotfixes, Artifacts: cfg.Artifacts}
staged := hotfixConfig{Hotfixes: cfg.Hotfixes}

if err := writeHotfixConfig(hotfixPath, staged); err != nil {
return outcomeFailed, fmt.Errorf("writing hotfix config: %w", err)
Expand Down Expand Up @@ -444,16 +444,15 @@ func (a *App) coldStartHotfixConfig() (hotfixConfig, bool, error) {
// Lenient parse: the AKSNodeConfig is protojson, but the cold-start pointer is an
// out-of-contract top-level object, so parse it permissively with encoding/json.
var lenient struct {
Hotfixes map[string]string `json:"hotfixes"`
Artifacts map[string]map[string]artifactInfo `json:"artifacts"`
Hotfixes map[string]string `json:"hotfixes"`
}
if err := json.Unmarshal(raw, &lenient); err != nil {
return hotfixConfig{}, false, fmt.Errorf("parsing cold-start hotfixes from node config: %w", err)
}
if len(lenient.Hotfixes) == 0 {
return hotfixConfig{}, false, nil
}
return hotfixConfig{Hotfixes: lenient.Hotfixes, Artifacts: lenient.Artifacts}, true, nil
return hotfixConfig{Hotfixes: lenient.Hotfixes}, true, nil
}

// writeHotfixConfig stages the LPS-served hotfixes map to the path download-hotfix reads.
Expand Down Expand Up @@ -482,21 +481,13 @@ func writeHotfixConfig(path string, cfg hotfixConfig) error {
hotfixes = map[string]string{}
}
out := struct {
Version string `json:"version,omitempty"`
ScriptsVersion string `json:"scripts_version,omitempty"`
Hotfixes map[string]string `json:"hotfixes"`
Artifacts map[string]map[string]artifactInfo `json:"artifacts,omitempty"`
Version string `json:"version,omitempty"`
ScriptsVersion string `json:"scripts_version,omitempty"`
Hotfixes map[string]string `json:"hotfixes"`
}{
Version: existing.Version,
ScriptsVersion: existing.ScriptsVersion,
Hotfixes: hotfixes,
Artifacts: cfg.Artifacts,
}
// Preserve existing artifacts when the incoming config has none (e.g. LPS response
// doesn't include artifacts yet). This mirrors the Version/ScriptsVersion preservation
// and avoids erasing artifacts that cloud-init originally wrote.
if out.Artifacts == nil {
out.Artifacts = existing.Artifacts
}
data, err := json.Marshal(out)
if err != nil {
Expand Down
14 changes: 8 additions & 6 deletions aks-node-controller/checkhotfix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,13 +682,15 @@ func TestWriteHotfixConfig_EmptyMapKeepsStableKey(t *testing.T) {
}
}

// TestWriteHotfixConfig_PreservesExistingVersionAndScriptsVersion is the unit-level guard for
// the read-modify-write: given a pre-existing file (as cloud-init writes) carrying version and
// scripts_version, writeHotfixConfig must keep those fields and only replace the hotfixes map.
func TestWriteHotfixConfig_PreservesExistingVersionAndScriptsVersion(t *testing.T) {
// TestWriteHotfixConfig_PreservesVersionsAndDropsArtifacts guards the read-modify-write:
// version fields remain compatible with cloud-init, while the retired artifacts contract is removed.
func TestWriteHotfixConfig_PreservesVersionsAndDropsArtifacts(t *testing.T) {
path := filepath.Join(t.TempDir(), "hotfix.json")
require.NoError(t, os.WriteFile(path, []byte(
`{"version":"202604.01.5","scripts_version":"202604.01.7","hotfixes":{"202604.01":"202604.01.5"}}`), 0644))
existing := `{"version":"202604.01.5","scripts_version":"202604.01.7",` +
`"hotfixes":{"202604.01":"202604.01.5"},` +
`"artifacts":{"202604.01.5":{"linux-ubuntu-22.04-amd64":` +
`{"url":"https://packages.microsoft.com/fake.deb","sha256":"abc123"}}}}`
require.NoError(t, os.WriteFile(path, []byte(existing), 0644))

require.NoError(t, writeHotfixConfig(path, hotfixConfig{Hotfixes: map[string]string{"202604.01": "202604.01.9"}}))

Expand Down
Loading
Loading