feat: add cloud-init Go parser (Phase 2)
Implement a lightweight cloud-init system for first-boot configuration: - Go parser for YAML config (hostname, network, KubeSolo settings) - Static/DHCP network modes with DNS override - KubeSolo extra flags and API server SAN configuration - Portainer Edge Agent and air-gapped deployment support - New init stage 45-cloud-init.sh runs before network/hostname stages - Stages 50/60 skip gracefully when cloud-init has already applied - Build script compiles static Linux/amd64 binary (~2.7 MB) - 17 unit tests covering parsing, validation, and example files - Full documentation at docs/cloud-init.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
118
cloud-init/kubesolo_test.go
Normal file
118
cloud-init/kubesolo_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package cloudinit
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildExtraFlags(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg Config
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
cfg: Config{},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "extra flags only",
|
||||
cfg: Config{
|
||||
KubeSolo: KubeSoloConfig{ExtraFlags: "--disable traefik"},
|
||||
},
|
||||
want: "--disable traefik",
|
||||
},
|
||||
{
|
||||
name: "extra sans only",
|
||||
cfg: Config{
|
||||
KubeSolo: KubeSoloConfig{
|
||||
ExtraSANs: []string{"node.local", "192.168.1.100"},
|
||||
},
|
||||
},
|
||||
want: "--apiserver-extra-sans node.local --apiserver-extra-sans 192.168.1.100",
|
||||
},
|
||||
{
|
||||
name: "flags and sans",
|
||||
cfg: Config{
|
||||
KubeSolo: KubeSoloConfig{
|
||||
ExtraFlags: "--disable servicelb",
|
||||
ExtraSANs: []string{"edge.local"},
|
||||
},
|
||||
},
|
||||
want: "--disable servicelb --apiserver-extra-sans edge.local",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := buildExtraFlags(&tt.cfg)
|
||||
if got != tt.want {
|
||||
t.Errorf("buildExtraFlags() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyKubeSolo(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
tr := true
|
||||
cfg := &Config{
|
||||
KubeSolo: KubeSoloConfig{
|
||||
ExtraFlags: "--disable traefik",
|
||||
LocalStorage: &tr,
|
||||
ExtraSANs: []string{"test.local"},
|
||||
},
|
||||
}
|
||||
|
||||
if err := ApplyKubeSolo(cfg, dir); err != nil {
|
||||
t.Fatalf("ApplyKubeSolo error: %v", err)
|
||||
}
|
||||
|
||||
// Check extra-flags file
|
||||
flagsData, err := os.ReadFile(filepath.Join(dir, "extra-flags"))
|
||||
if err != nil {
|
||||
t.Fatalf("reading extra-flags: %v", err)
|
||||
}
|
||||
flags := strings.TrimSpace(string(flagsData))
|
||||
if !strings.Contains(flags, "--disable traefik") {
|
||||
t.Errorf("extra-flags missing '--disable traefik': %q", flags)
|
||||
}
|
||||
if !strings.Contains(flags, "--apiserver-extra-sans test.local") {
|
||||
t.Errorf("extra-flags missing SANs: %q", flags)
|
||||
}
|
||||
|
||||
// Check config.yaml
|
||||
configData, err := os.ReadFile(filepath.Join(dir, "config.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("reading config.yaml: %v", err)
|
||||
}
|
||||
config := string(configData)
|
||||
if !strings.Contains(config, "local-storage: true") {
|
||||
t.Errorf("config.yaml missing local-storage: %q", config)
|
||||
}
|
||||
if !strings.Contains(config, "data-dir: /var/lib/kubesolo") {
|
||||
t.Errorf("config.yaml missing data-dir: %q", config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyKubeSoloNoFlags(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
cfg := &Config{}
|
||||
|
||||
if err := ApplyKubeSolo(cfg, dir); err != nil {
|
||||
t.Fatalf("ApplyKubeSolo error: %v", err)
|
||||
}
|
||||
|
||||
// extra-flags should not exist when empty
|
||||
if _, err := os.Stat(filepath.Join(dir, "extra-flags")); !os.IsNotExist(err) {
|
||||
t.Error("extra-flags file should not exist when no flags configured")
|
||||
}
|
||||
|
||||
// config.yaml should still be created with defaults
|
||||
if _, err := os.Stat(filepath.Join(dir, "config.yaml")); err != nil {
|
||||
t.Error("config.yaml should be created even with empty config")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user