From 2307fb8ed77aa0f71eec4f3eb1f76279658325f4 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:00:23 +0100 Subject: [PATCH 1/6] feat(local): add StorageClass, HTTPSPort, MetalLB config fields --- pkg/provider/local/config.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/provider/local/config.go b/pkg/provider/local/config.go index bb65ce67..600ffed0 100644 --- a/pkg/provider/local/config.go +++ b/pkg/provider/local/config.go @@ -1,8 +1,22 @@ package local -// Config represents local K3s configuration +// Config represents local provider configuration type Config struct { KubeContext string `yaml:"kube_context,omitempty"` NodeSelectors map[string]map[string]string `yaml:"node_selectors,omitempty"` + StorageClass string `yaml:"storage_class,omitempty"` + HTTPSPort int `yaml:"https_port,omitempty"` + MetalLB *MetalLBConfig `yaml:"metallb,omitempty"` AdditionalFields map[string]any `yaml:",inline"` } + +// MetalLBConfig holds MetalLB-specific settings for the local provider. +type MetalLBConfig struct { + // Enabled controls whether MetalLB is deployed. Default: true. + // Use a pointer to distinguish "not set" (default true) from "explicitly false". + Enabled *bool `yaml:"enabled,omitempty"` + + // AddressPool is the IP range for MetalLB's IPAddressPool. + // Default: "192.168.1.100-192.168.1.110" + AddressPool string `yaml:"address_pool,omitempty"` +} From 929439631436f75856904ae81144220b9c676088 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:03:19 +0100 Subject: [PATCH 2/6] test(local): add table-driven tests for configurable InfraSettings --- pkg/provider/local/provider_test.go | 127 ++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 15 deletions(-) diff --git a/pkg/provider/local/provider_test.go b/pkg/provider/local/provider_test.go index 86a0d875..f0a67c9f 100644 --- a/pkg/provider/local/provider_test.go +++ b/pkg/provider/local/provider_test.go @@ -12,28 +12,125 @@ var _ provider.Provider = (*Provider)(nil) func TestInfraSettings(t *testing.T) { p := NewProvider() - cfg := &config.ClusterConfig{ - Providers: map[string]any{"local": map[string]any{}}, - } - - settings := p.InfraSettings(cfg) tests := []struct { - name string - got any - want any + name string + providerConfig map[string]any + wantSC string + wantMetalLB bool + wantPool string + wantHTTPSPort int }{ - {"StorageClass", settings.StorageClass, "standard"}, - {"NeedsMetalLB", settings.NeedsMetalLB, true}, - {"MetalLBAddressPool", settings.MetalLBAddressPool, "192.168.1.100-192.168.1.110"}, - {"LoadBalancerAnnotations is empty", len(settings.LoadBalancerAnnotations), 0}, - {"KeycloakBasePath is empty", settings.KeycloakBasePath, ""}, + { + name: "no local config block returns defaults", + providerConfig: nil, + wantSC: "standard", + wantMetalLB: true, + wantPool: "192.168.1.100-192.168.1.110", + wantHTTPSPort: 0, + }, + { + name: "empty local config returns defaults", + providerConfig: map[string]any{"local": map[string]any{}}, + wantSC: "standard", + wantMetalLB: true, + wantPool: "192.168.1.100-192.168.1.110", + wantHTTPSPort: 0, + }, + { + name: "storage_class override", + providerConfig: map[string]any{ + "local": map[string]any{"storage_class": "local-path"}, + }, + wantSC: "local-path", + wantMetalLB: true, + wantPool: "192.168.1.100-192.168.1.110", + wantHTTPSPort: 0, + }, + { + name: "metallb disabled", + providerConfig: map[string]any{ + "local": map[string]any{ + "metallb": map[string]any{"enabled": false}, + }, + }, + wantSC: "standard", + wantMetalLB: false, + wantPool: "192.168.1.100-192.168.1.110", + wantHTTPSPort: 0, + }, + { + name: "metallb address_pool override", + providerConfig: map[string]any{ + "local": map[string]any{ + "metallb": map[string]any{ + "address_pool": "172.18.255.100-172.18.255.110", + }, + }, + }, + wantSC: "standard", + wantMetalLB: true, + wantPool: "172.18.255.100-172.18.255.110", + wantHTTPSPort: 0, + }, + { + name: "https_port override", + providerConfig: map[string]any{ + "local": map[string]any{"https_port": 8443}, + }, + wantSC: "standard", + wantMetalLB: true, + wantPool: "192.168.1.100-192.168.1.110", + wantHTTPSPort: 8443, + }, + { + name: "full override", + providerConfig: map[string]any{ + "local": map[string]any{ + "storage_class": "local-path", + "https_port": 8443, + "metallb": map[string]any{ + "enabled": false, + "address_pool": "10.0.0.100-10.0.0.110", + }, + }, + }, + wantSC: "local-path", + wantMetalLB: false, + wantPool: "10.0.0.100-10.0.0.110", + wantHTTPSPort: 8443, + }, + { + name: "unmarshal error returns defaults", + providerConfig: map[string]any{ + "local": "not-a-map", + }, + wantSC: "standard", + wantMetalLB: true, + wantPool: "192.168.1.100-192.168.1.110", + wantHTTPSPort: 0, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.got != tt.want { - t.Errorf("got %v, want %v", tt.got, tt.want) + cfg := &config.ClusterConfig{ + Providers: tt.providerConfig, + } + + settings := p.InfraSettings(cfg) + + if settings.StorageClass != tt.wantSC { + t.Errorf("StorageClass = %q, want %q", settings.StorageClass, tt.wantSC) + } + if settings.NeedsMetalLB != tt.wantMetalLB { + t.Errorf("NeedsMetalLB = %v, want %v", settings.NeedsMetalLB, tt.wantMetalLB) + } + if settings.MetalLBAddressPool != tt.wantPool { + t.Errorf("MetalLBAddressPool = %q, want %q", settings.MetalLBAddressPool, tt.wantPool) + } + if settings.HTTPSPort != tt.wantHTTPSPort { + t.Errorf("HTTPSPort = %d, want %d", settings.HTTPSPort, tt.wantHTTPSPort) } }) } From bee222c83669118a61fa5bddecfeee8a689d3e47 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:04:18 +0100 Subject: [PATCH 3/6] feat(local): read InfraSettings from config with defaults --- pkg/provider/local/provider.go | 35 ++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/pkg/provider/local/provider.go b/pkg/provider/local/provider.go index 2aa0881e..ea64fb4e 100644 --- a/pkg/provider/local/provider.go +++ b/pkg/provider/local/provider.go @@ -259,10 +259,41 @@ func (p *Provider) Summary(clusterConfig *config.ClusterConfig) map[string]strin } // InfraSettings returns local provider Kubernetes infrastructure settings. -func (p *Provider) InfraSettings(_ *config.ClusterConfig) provider.InfraSettings { - return provider.InfraSettings{ +// Values are read from the local provider config block, falling back to defaults. +// Parse errors are intentionally ignored: InfraSettings is called after Validate() +// has confirmed the config is parseable. If it somehow fails (e.g., nil config in +// tests), we return valid defaults. +func (p *Provider) InfraSettings(cfg *config.ClusterConfig) provider.InfraSettings { + settings := provider.InfraSettings{ StorageClass: "standard", NeedsMetalLB: true, MetalLBAddressPool: "192.168.1.100-192.168.1.110", } + + rawCfg := cfg.ProviderConfig() + if rawCfg == nil { + return settings + } + + var localCfg Config + if err := config.UnmarshalProviderConfig(context.Background(), rawCfg, &localCfg); err != nil { + return settings + } + + if localCfg.StorageClass != "" { + settings.StorageClass = localCfg.StorageClass + } + if localCfg.HTTPSPort != 0 { + settings.HTTPSPort = localCfg.HTTPSPort + } + if localCfg.MetalLB != nil { + if localCfg.MetalLB.Enabled != nil { + settings.NeedsMetalLB = *localCfg.MetalLB.Enabled + } + if localCfg.MetalLB.AddressPool != "" { + settings.MetalLBAddressPool = localCfg.MetalLB.AddressPool + } + } + + return settings } From 8de3def7625658aaae5e40f50b0a7ba16c25a271 Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:10:00 +0100 Subject: [PATCH 4/6] docs: add configurable InfraSettings examples to local-config.yaml --- examples/local-config.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/examples/local-config.yaml b/examples/local-config.yaml index 2e725f5c..5870d50c 100644 --- a/examples/local-config.yaml +++ b/examples/local-config.yaml @@ -14,6 +14,20 @@ cluster: worker: kubernetes.io/os: linux + # Storage class for persistent volumes (default: "standard") + # Use "local-path" for k3s clusters + # storage_class: local-path + + # HTTPS port for Gateway listener (default: 443) + # Override if 443 is already in use or requires root + # https_port: 8443 + + # MetalLB configuration (default: enabled with 192.168.1.100-192.168.1.110 pool) + # Disable for k3s which ships with ServiceLB + # metallb: + # enabled: false + # address_pool: 172.18.255.100-172.18.255.110 + # GitOps repository configuration (optional) # Configures the repository that ArgoCD will use to manage cluster resources git_repository: From a88139c0e3155febe613be2bf76bb950d5c0686e Mon Sep 17 00:00:00 2001 From: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:13:03 +0100 Subject: [PATCH 5/6] test(local): assert LoadBalancerAnnotations and KeycloakBasePath remain empty --- pkg/provider/local/provider_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/provider/local/provider_test.go b/pkg/provider/local/provider_test.go index f0a67c9f..9879facf 100644 --- a/pkg/provider/local/provider_test.go +++ b/pkg/provider/local/provider_test.go @@ -132,6 +132,13 @@ func TestInfraSettings(t *testing.T) { if settings.HTTPSPort != tt.wantHTTPSPort { t.Errorf("HTTPSPort = %d, want %d", settings.HTTPSPort, tt.wantHTTPSPort) } + // Fields not set by local provider should always be zero values + if len(settings.LoadBalancerAnnotations) != 0 { + t.Errorf("LoadBalancerAnnotations = %v, want empty", settings.LoadBalancerAnnotations) + } + if settings.KeycloakBasePath != "" { + t.Errorf("KeycloakBasePath = %q, want empty", settings.KeycloakBasePath) + } }) } } From bf56cf82291382052cb0d6defcb8137d0934f148 Mon Sep 17 00:00:00 2001 From: vinicius douglas cerutti Date: Thu, 30 Apr 2026 10:25:54 -0300 Subject: [PATCH 6/6] fix(test): restore SupportsLocalGitOps assertion lost in merge conflict Six assertion lines were incorrectly pulled into the tests slice as table cases during the merge conflict resolution. Removed them and moved the net-new SupportsLocalGitOps check into the per-case assertion block alongside the existing zero-value field checks. --- pkg/provider/local/provider_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/provider/local/provider_test.go b/pkg/provider/local/provider_test.go index 993b6f50..b66968a6 100644 --- a/pkg/provider/local/provider_test.go +++ b/pkg/provider/local/provider_test.go @@ -110,12 +110,6 @@ func TestInfraSettings(t *testing.T) { wantPool: "192.168.1.100-192.168.1.110", wantHTTPSPort: 0, }, - {"StorageClass", settings.StorageClass, "standard"}, - {"NeedsMetalLB", settings.NeedsMetalLB, true}, - {"MetalLBAddressPool", settings.MetalLBAddressPool, "192.168.1.100-192.168.1.110"}, - {"LoadBalancerAnnotations is empty", len(settings.LoadBalancerAnnotations), 0}, - {"KeycloakBasePath is empty", settings.KeycloakBasePath, ""}, - {"SupportsLocalGitOps", settings.SupportsLocalGitOps, true}, } for _, tt := range tests { @@ -145,6 +139,9 @@ func TestInfraSettings(t *testing.T) { if settings.KeycloakBasePath != "" { t.Errorf("KeycloakBasePath = %q, want empty", settings.KeycloakBasePath) } + if !settings.SupportsLocalGitOps { + t.Error("SupportsLocalGitOps = false, want true") + } }) } }