Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion aks-node-controller/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,10 @@ application, including variants that share the `azurelinux` OS ID. Their
distro-specific source changes do not trigger payload generation.

The repository keeps a README placeholder so `go:embed` builds without any
script hotfix payloads. Generation replaces it with Ubuntu and Azure Linux YAMLs.
script hotfix payloads. Generation omits any rendered YAML with no `write_files`
entries (including missing, null, or empty lists) and logs the skipped platform.
If all platforms are empty, it writes a README placeholder to keep `go:embed` valid.
This does not add support for non-distro template conditions.
ANC skips application when the local platform's YAML is absent; no separate
activation flag is needed. Other payload read errors are logged.

Expand Down
47 changes: 47 additions & 0 deletions hotfix/hotfix_generate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,53 @@


class HotfixGenerateTests(unittest.TestCase):
def test_production_renderer_omits_empty_platform_payloads(self):
repository = Path(__file__).resolve().parents[1]
lines = (repository / hotfix_generate.TEMPLATE).read_text().splitlines(
keepends=True
)
_, outer_else, end = hotfix_generate.find_block_boundaries(lines)
cases = [
("custom cloud only", hotfix_generate.build_hotfix_template(
{"initAKSCloud"}, lines[outer_else + 1:end]
), set()),
("missing", "#cloud-config\n", set()),
("null", "write_files:\n", set()),
("empty list", "write_files: []\n", set()),
("ubuntu only", 'write_files:\n{{if not IsMariner}}\n'
'- path: /test\n content: hotfix\n{{end}}\n', {"ubuntu"}),
("both", "write_files:\n- path: /test\n content: hotfix\n",
{"ubuntu", "mariner"}),
]
for name, template, platforms in cases:
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
template_path = Path(directory) / "template.yml"
template_path.write_text(template)
output = Path(directory) / "generated"
output.mkdir()
for platform in ("ubuntu", "mariner"):
(output / f"rendered_nodecustomdata_{platform}.yml").write_text(
"write_files:\n" # Stale empty files must also be removed.
)
subprocess.run(
["go", "run", "./hotfix/render-nodecustomdata", "--template",
str(template_path), "--output-dir", str(output)],
cwd=repository, check=True, capture_output=True,
)
expected = {
f"rendered_nodecustomdata_{platform}.yml" for platform in platforms
} or {"README"}
self.assertEqual(expected, {path.name for path in output.iterdir()})
if name == "custom cloud only":
probe = Path(directory) / "main.go"
probe.write_text('package main\nimport "embed"\n'
'//go:embed generated\nvar files embed.FS\n'
'func main() {}\n')
subprocess.run(
["go", "build", "-o", str(Path(directory) / "probe"), str(probe)],
cwd=repository, check=True, capture_output=True,
)

def test_config_modules_are_independently_hotfixable(self):
repository = Path(__file__).resolve().parents[1]
lines = (repository / hotfix_generate.TEMPLATE).read_text().splitlines(
Expand Down
22 changes: 22 additions & 0 deletions hotfix/render-nodecustomdata/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/Azure/agentbaker/pkg/agent"
"github.com/Azure/agentbaker/pkg/agent/datamodel"
"gopkg.in/yaml.v3"
)

type platform struct {
Expand Down Expand Up @@ -41,6 +42,7 @@ func main() {
fatalf("create output directory: %v", err)
}

written := 0
for _, target := range platforms {
rendered, err := agent.RenderLinuxNodeCustomDataTemplate(
templateContent,
Expand All @@ -53,10 +55,30 @@ func main() {
*outputDir,
"rendered_nodecustomdata_"+target.name+".yml",
)
var payload struct {
WriteFiles []yaml.Node `yaml:"write_files"`
}
if err := yaml.Unmarshal([]byte(rendered), &payload); err != nil {
fatalf("parse %s nodecustomdata: %v", target.name, err)
}
if len(payload.WriteFiles) == 0 {
fmt.Fprintf(os.Stderr, "Skipping %s nodecustomdata: no write_files entries\n", target.name)
if err := os.Remove(outputPath); err != nil && !os.IsNotExist(err) {
fatalf("remove empty %s nodecustomdata: %v", target.name, err)
}
continue
}
//nolint:gosec // The caller intentionally selects the output directory; filenames are fixed by this tool.
if err := os.WriteFile(outputPath, []byte(rendered), 0o600); err != nil {
fatalf("write %s nodecustomdata: %v", target.name, err)
}
written++
}
if written == 0 {
// Keep at least one file for embedding when every platform is empty.
if err := os.WriteFile(filepath.Join(*outputDir, "README"), []byte("No applicable script hotfix payloads.\n"), 0o600); err != nil {
fatalf("write empty payload placeholder: %v", err)
}
}
}

Expand Down
Loading