Skip to content

Commit 0a130b5

Browse files
authored
Replace Viper with encoding/json for config loading (#143)
1 parent a9f16fc commit 0a130b5

File tree

5 files changed

+134
-53
lines changed

5 files changed

+134
-53
lines changed

go.mod

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ require (
1717
github.com/schollz/progressbar/v3 v3.19.0
1818
github.com/sirupsen/logrus v1.9.3
1919
github.com/spf13/cobra v1.10.1
20-
github.com/spf13/viper v1.21.0
2120
golang.org/x/sync v0.19.0
2221
google.golang.org/grpc v1.72.3
2322
google.golang.org/protobuf v1.36.8
@@ -49,14 +48,12 @@ require (
4948
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
5049
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
5150
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
52-
github.com/fsnotify/fsnotify v1.9.0 // indirect
5351
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
5452
github.com/go-errors/errors v1.4.2 // indirect
5553
github.com/go-logr/logr v1.4.3 // indirect
5654
github.com/go-openapi/jsonpointer v0.21.0 // indirect
5755
github.com/go-openapi/jsonreference v0.20.2 // indirect
5856
github.com/go-openapi/swag v0.23.0 // indirect
59-
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
6057
github.com/godbus/dbus/v5 v5.0.4 // indirect
6158
github.com/gogo/protobuf v1.3.2 // indirect
6259
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
@@ -78,7 +75,6 @@ require (
7875
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
7976
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
8077
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
81-
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
8278
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
8379
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
8480
github.com/pkg/errors v0.9.1 // indirect
@@ -88,12 +84,7 @@ require (
8884
github.com/prometheus/procfs v0.16.1 // indirect
8985
github.com/rivo/uniseg v0.4.7 // indirect
9086
github.com/russross/blackfriday/v2 v2.1.0 // indirect
91-
github.com/sagikazarmark/locafero v0.11.0 // indirect
92-
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
93-
github.com/spf13/afero v1.15.0 // indirect
94-
github.com/spf13/cast v1.10.0 // indirect
9587
github.com/spf13/pflag v1.0.10 // indirect
96-
github.com/subosito/gotenv v1.6.0 // indirect
9788
github.com/x448/float16 v0.8.4 // indirect
9889
github.com/xlab/treeprint v1.2.0 // indirect
9990
go.opentelemetry.io/otel v1.36.0 // indirect

go.sum

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,6 @@ github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bF
7070
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
7171
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
7272
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
73-
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
74-
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
75-
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
76-
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
7773
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
7874
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
7975
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -92,8 +88,6 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
9288
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
9389
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
9490
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
95-
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
96-
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
9791
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
9892
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
9993
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -168,8 +162,6 @@ github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns
168162
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
169163
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
170164
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
171-
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
172-
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
173165
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
174166
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
175167
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
@@ -192,27 +184,17 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
192184
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
193185
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
194186
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
195-
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
196-
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
197187
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
198188
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
199189
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
200190
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
201191
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
202192
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
203-
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
204-
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
205-
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
206-
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
207-
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
208-
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
209193
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
210194
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
211195
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
212196
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
213197
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
214-
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
215-
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
216198
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
217199
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
218200
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -227,8 +209,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
227209
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
228210
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
229211
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
230-
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
231-
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
232212
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
233213
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
234214
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=

pkg/config/config.go

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package config
22

33
import (
4+
"encoding/json"
45
"fmt"
6+
"os"
7+
"path/filepath"
58
"regexp"
69
"sync"
7-
8-
"github.com/spf13/viper"
910
)
1011

1112
const (
@@ -18,9 +19,6 @@ const (
1819
defaultLogDir = "/var/log/aks-flex-node"
1920
defaultLogLevel = "info"
2021
defaultAzureCloud = "AzurePublicCloud"
21-
22-
// Environment variable prefix
23-
envPrefix = "AKS_NODE_CONTROLLER"
2422
)
2523

2624
// Singleton instance for configuration
@@ -38,38 +36,27 @@ func GetConfig() *Config {
3836
return configInstance
3937
}
4038

41-
// LoadConfig loads configuration from a JSON file and environment variables.
39+
// LoadConfig loads configuration from a JSON file.
4240
// The configPath parameter is required and cannot be empty.
43-
// Environment variables can override config file values using the AKS_NODE_CONTROLLER_ prefix.
44-
// For example: AKS_NODE_CONTROLLER_AZURE_LOCATION=westus2
4541
func LoadConfig(configPath string) (*Config, error) {
4642
// Require config path to be specified
4743
if configPath == "" {
4844
return nil, fmt.Errorf("config file path is required")
4945
}
5046

51-
// Set up viper
52-
v := viper.New()
53-
v.SetConfigType("json")
54-
v.AutomaticEnv()
55-
v.SetEnvPrefix(envPrefix)
56-
57-
// Load the specified config file
58-
v.SetConfigFile(configPath)
59-
if err := v.ReadInConfig(); err != nil {
47+
data, err := os.ReadFile(filepath.Clean(configPath))
48+
if err != nil {
6049
return nil, fmt.Errorf("failed to read config file at %s: %w", configPath, err)
6150
}
6251

63-
// Unmarshal config
6452
config := &Config{}
65-
if err := v.Unmarshal(config); err != nil {
53+
if err := json.Unmarshal(data, config); err != nil {
6654
return nil, fmt.Errorf("error unmarshaling config: %w", err)
6755
}
6856

69-
// Track if managedIdentity was explicitly set in config
70-
// This is necessary because viper unmarshals empty JSON objects {} as nil pointers
71-
// Using viper.IsSet() correctly detects if the key was present in the config file
72-
config.isMIExplicitlySet = v.IsSet("azure.managedIdentity")
57+
// encoding/json deserializes "managedIdentity": {} into a non-nil pointer, so
58+
// presence can be detected directly — unlike Viper which decoded it as nil.
59+
config.isMIExplicitlySet = config.Azure.ManagedIdentity != nil
7360

7461
// Set defaults for any missing values
7562
config.SetDefaults()

pkg/config/config_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package config
22

33
import (
4+
"fmt"
45
"os"
56
"path/filepath"
7+
"sort"
68
"strings"
79
"testing"
810
)
@@ -1069,6 +1071,128 @@ func TestAuthenticationMethodValidation(t *testing.T) {
10691071
}
10701072
}
10711073

1074+
// baseAzureJSON is the minimal valid azure config block shared across label tests.
1075+
const baseAzureJSON = `{
1076+
"azure": {
1077+
"subscriptionId": "12345678-1234-1234-1234-123456789012",
1078+
"tenantId": "12345678-1234-1234-1234-123456789012",
1079+
"cloud": "AzurePublicCloud",
1080+
"bootstrapToken": { "token": "abcdef.0123456789abcdef" },
1081+
"targetCluster": {
1082+
"resourceId": "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/test-rg/providers/Microsoft.ContainerService/managedClusters/test-cluster",
1083+
"location": "eastus"
1084+
}
1085+
},
1086+
"node": {
1087+
"kubelet": {
1088+
"serverURL": "https://test-cluster.hcp.eastus.azmk8s.io:443",
1089+
"caCertData": "LS0tLS1CRUdJTi1DRVJUSUZJQ0FURS0tLS0t"
1090+
},
1091+
"labels": %s
1092+
}
1093+
}`
1094+
1095+
// TestLoadConfigNodeLabels verifies that node label keys are preserved exactly
1096+
// as written in the config file. This is the root motivation: Kubernetes label
1097+
// keys such as "cleanroom.azure.com/flexnode" or "topology.kubernetes.io/zone"
1098+
// contain dots, which Viper's default "." key-path delimiter misinterpreted as
1099+
// nested JSON keys, silently dropping the labels on load.
1100+
func TestLoadConfigNodeLabels(t *testing.T) {
1101+
tests := []struct {
1102+
name string
1103+
labelsJSON string
1104+
expectedLabels map[string]string // only the user-supplied labels; defaults are checked separately
1105+
}{
1106+
{
1107+
name: "plain labels without dots",
1108+
labelsJSON: `{"env": "production", "team": "platform"}`,
1109+
expectedLabels: map[string]string{
1110+
"env": "production",
1111+
"team": "platform",
1112+
},
1113+
},
1114+
{
1115+
name: "label key with a single dot segment (kubernetes.io prefix)",
1116+
labelsJSON: `{"kubernetes.io/nodeReady": "true"}`,
1117+
expectedLabels: map[string]string{
1118+
"kubernetes.io/nodeReady": "true",
1119+
},
1120+
},
1121+
{
1122+
name: "label key with multiple dot segments (topology prefix)",
1123+
labelsJSON: `{"topology.kubernetes.io/zone": "eastus-1"}`,
1124+
expectedLabels: map[string]string{
1125+
"topology.kubernetes.io/zone": "eastus-1",
1126+
},
1127+
},
1128+
{
1129+
name: "label key with three dot segments (org.example.com prefix)",
1130+
labelsJSON: `{"org.example.com/myLabel": "true"}`,
1131+
expectedLabels: map[string]string{
1132+
"org.example.com/myLabel": "true",
1133+
},
1134+
},
1135+
{
1136+
name: "mixed dotted and plain labels all preserved",
1137+
labelsJSON: `{
1138+
"env": "staging",
1139+
"topology.kubernetes.io/zone": "eastus-1",
1140+
"cleanroom.azure.com/flexnode": "true",
1141+
"disktype": "ssd"
1142+
}`,
1143+
expectedLabels: map[string]string{
1144+
"env": "staging",
1145+
"topology.kubernetes.io/zone": "eastus-1",
1146+
"cleanroom.azure.com/flexnode": "true",
1147+
"disktype": "ssd",
1148+
},
1149+
},
1150+
{
1151+
name: "label value containing dots is preserved",
1152+
labelsJSON: `{"version": "1.2.3"}`,
1153+
expectedLabels: map[string]string{
1154+
"version": "1.2.3",
1155+
},
1156+
},
1157+
}
1158+
1159+
for _, tt := range tests {
1160+
t.Run(tt.name, func(t *testing.T) {
1161+
t.Parallel()
1162+
1163+
configJSON := fmt.Sprintf(baseAzureJSON, tt.labelsJSON)
1164+
configFile := filepath.Join(t.TempDir(), "config.json")
1165+
if err := os.WriteFile(configFile, []byte(configJSON), 0o600); err != nil {
1166+
t.Fatalf("os.WriteFile: %v", err)
1167+
}
1168+
1169+
config, err := LoadConfig(configFile)
1170+
if err != nil {
1171+
t.Fatalf("LoadConfig() unexpected error: %v", err)
1172+
}
1173+
1174+
for key, want := range tt.expectedLabels {
1175+
got, ok := config.Node.Labels[key]
1176+
if !ok {
1177+
t.Errorf("label %q not found; got keys: %v", key, labelKeys(config.Node.Labels))
1178+
} else if got != want {
1179+
t.Errorf("label %q = %q, want %q", key, got, want)
1180+
}
1181+
}
1182+
})
1183+
}
1184+
}
1185+
1186+
// labelKeys returns sorted keys of a map for use in test diagnostics.
1187+
func labelKeys(m map[string]string) []string {
1188+
keys := make([]string, 0, len(m))
1189+
for k := range m {
1190+
keys = append(keys, k)
1191+
}
1192+
sort.Strings(keys)
1193+
return keys
1194+
}
1195+
10721196
func TestIsBootstrapTokenConfigured(t *testing.T) {
10731197
tests := []struct {
10741198
name string

pkg/config/structs.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ func (cfg *Config) IsSPConfigured() bool {
172172
}
173173

174174
// IsMIConfigured checks if managed identity configuration is provided in the configuration
175-
// Uses internal flag set during config loading to handle viper's empty object behavior
176175
func (cfg *Config) IsMIConfigured() bool {
177176
return cfg.isMIExplicitlySet
178177
}

0 commit comments

Comments
 (0)