Skip to content

Commit 092f26a

Browse files
authored
[FEATURE/FIX] Viper Configuration and JSON Schema (#472)
1 parent 34be507 commit 092f26a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+7732
-207
lines changed

.golangci.yml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
linters-settings:
2+
errcheck:
3+
check-blank: false # to keep `_ = viper.BindPFlag(...)` from throwing errors

CHANGELOG.md

+29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
# Changelog
22

3+
## v4.1.0
4+
5+
### Important
6+
7+
- Using Viper brings us lots of nice features, but also one problem:
8+
- We had to switch StringArray flags to StringSlice flags, which
9+
- allow to use multiple flag values comma-separated in a single flag, but also
10+
- split flag values that contain a comma into separate parts (and we cannot handle issues that arise due to this)
11+
- so if you rely on commas in your flag values (e.g. for `--env X=a,b,c`), please consider filing an issue or supporting <https://github.com/spf13/viper/issues/246> and <https://github.com/spf13/viper/pull/398>
12+
- `--env X=a,b,c` would be treated the same as `--env X=a`, `--env b`, `--env c`
13+
14+
### Features & Enhancements
15+
16+
- use [viper](https://github.com/spf13/viper) for configuration management
17+
- takes over the job of properly fetching and merging config options from
18+
- CLI arguments/flags
19+
- environment variables
20+
- config file
21+
- this also fixes some issues with using the config file (like cobra defaults overriding config file values)
22+
- add JSON-Schema validation for the `Simple` config file schema
23+
- new config version `k3d.io/v1alpha2` (some naming changes)
24+
- `exposeAPI` -> `kubeAPI`
25+
- `options.k3d.noRollback` -> `options.k3d.disableRollback`
26+
- `options.k3d.prepDisableHostIPInjection` -> `options.k3d.disableHostIPInjection`
27+
28+
### Misc
29+
30+
- tests/e2e: add config override test
31+
332
## v4.0.0
433

534
### Breaking Changes

cmd/cluster/clusterCreate.go

+206-104
Large diffs are not rendered by default.

cmd/config/configInit.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
"fmt"
2626
"os"
2727

28-
config "github.com/rancher/k3d/v4/pkg/config/v1alpha1"
28+
config "github.com/rancher/k3d/v4/pkg/config/v1alpha2"
2929
log "github.com/sirupsen/logrus"
3030
"github.com/spf13/cobra"
3131
)

cmd/root.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ func Execute() {
9494
}
9595

9696
func init() {
97-
cobra.OnInitialize(initLogging, initRuntime)
9897

9998
rootCmd.PersistentFlags().BoolVar(&flags.debugLogging, "verbose", false, "Enable verbose output (debug logging)")
10099
rootCmd.PersistentFlags().BoolVar(&flags.traceLogging, "trace", false, "Enable super verbose output (trace logging)")
@@ -119,6 +118,9 @@ func init() {
119118
printVersion()
120119
},
121120
})
121+
122+
// Init
123+
cobra.OnInitialize(initLogging, initRuntime)
122124
}
123125

124126
// initLogging initializes the logger

cmd/util/ports.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ func ParsePortExposureSpec(exposedPortSpec, internalPort string) (*k3d.ExposureO
4141
match := apiPortRegexp.FindStringSubmatch(exposedPortSpec)
4242

4343
if len(match) == 0 {
44-
log.Errorln("Failed to parse Port Exposure specification")
45-
return nil, fmt.Errorf("Port Exposure Spec format error: Must be [(HostIP|HostName):]HostPort")
44+
return nil, fmt.Errorf("Failed to parse Port Exposure specification '%s': Format must be [(HostIP|HostName):]HostPort", exposedPortSpec)
4645
}
4746

4847
submatches := util.MapSubexpNames(apiPortRegexp.SubexpNames(), match)

docs/usage/guides/registries.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ This file can also be used for providing additional information necessary for ac
2929
If you're using a `SimpleConfig` file to configure your k3d cluster, you may as well embed the registries.yaml in there directly:
3030

3131
```yaml
32-
apiVersion: k3d.io/v1alpha1
32+
apiVersion: k3d.io/v1alpha2
3333
kind: Simple
3434
name: test
3535
servers: 1

go.mod

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@ require (
3939
github.com/spf13/viper v1.7.1
4040
github.com/stretchr/testify v1.6.1 // indirect
4141
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
42+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
43+
github.com/xeipuuv/gojsonschema v1.2.0
4244
golang.org/x/sync v0.0.0-20190423024810-112230192c58
4345
google.golang.org/grpc v1.34.0 // indirect
4446
gopkg.in/ini.v1 v1.58.0 // indirect
45-
gopkg.in/yaml.v2 v2.3.0
47+
gopkg.in/yaml.v2 v2.4.0
4648
gotest.tools v2.2.0+incompatible
4749
gotest.tools/v3 v3.0.3 // indirect
4850
k8s.io/client-go v0.17.0
51+
sigs.k8s.io/yaml v1.2.0
4952
)

go.sum

+12
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
448448
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
449449
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
450450
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
451+
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
451452
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
452453
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
453454
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -528,9 +529,16 @@ github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOV
528529
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
529530
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM=
530531
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
532+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
531533
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
534+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
535+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
536+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
532537
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
538+
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f h1:mvXjJIHRZyhNuGassLTcXTwjiWq7NmjdavZsUnmFybQ=
533539
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
540+
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
541+
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
534542
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
535543
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
536544
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
@@ -750,6 +758,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
750758
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
751759
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
752760
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
761+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
762+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
753763
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
754764
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
755765
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
@@ -785,4 +795,6 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
785795
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
786796
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
787797
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
798+
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
799+
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
788800
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

pkg/client/cluster.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import (
3535
"github.com/docker/go-connections/nat"
3636
"github.com/imdario/mergo"
3737
"github.com/rancher/k3d/v4/pkg/actions"
38-
config "github.com/rancher/k3d/v4/pkg/config/v1alpha1"
38+
config "github.com/rancher/k3d/v4/pkg/config/v1alpha2"
3939
k3drt "github.com/rancher/k3d/v4/pkg/runtimes"
4040
"github.com/rancher/k3d/v4/pkg/runtimes/docker"
4141
runtimeErr "github.com/rancher/k3d/v4/pkg/runtimes/errors"

pkg/config/config.go

+18-23
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,33 @@ import (
2929

3030
"github.com/spf13/viper"
3131

32-
conf "github.com/rancher/k3d/v4/pkg/config/v1alpha1"
33-
k3d "github.com/rancher/k3d/v4/pkg/types"
32+
conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2"
3433
)
3534

36-
func ReadConfig(file string) (conf.Config, error) {
37-
cfgViper := viper.New()
35+
func FromViperSimple(config *viper.Viper) (conf.SimpleConfig, error) {
3836

39-
cfgViper.SetConfigFile(file)
37+
var cfg conf.SimpleConfig
4038

41-
cfgViper.SetConfigType("yaml")
42-
cfgViper.SetEnvPrefix(k3d.DefaultObjectNamePrefix)
43-
cfgViper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
44-
cfgViper.AutomaticEnv()
39+
// determine config kind
40+
if config.GetString("kind") != "" && strings.ToLower(config.GetString("kind")) != "simple" {
41+
return cfg, fmt.Errorf("Wrong `kind` '%s' != 'simple' in config file", config.GetString("kind"))
42+
}
4543

46-
// try to read config into memory (viper map structure)
47-
if err := cfgViper.ReadInConfig(); err != nil {
48-
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
49-
log.Errorln("No config file found!")
44+
if err := config.Unmarshal(&cfg); err != nil {
45+
log.Errorln("Failed to unmarshal File config")
5046

51-
return nil, err
52-
}
53-
// config file found but some other error happened
54-
log.Debugf("Failed to read config file: %+v", cfgViper.ConfigFileUsed())
55-
return nil, err
47+
return cfg, err
5648
}
5749

50+
return cfg, nil
51+
}
52+
53+
func FromViper(config *viper.Viper) (conf.Config, error) {
54+
5855
var cfg conf.Config
5956

6057
// determine config kind
61-
switch strings.ToLower(cfgViper.GetString("kind")) {
58+
switch strings.ToLower(config.GetString("kind")) {
6259
case "simple":
6360
cfg = conf.SimpleConfig{}
6461
case "cluster":
@@ -68,16 +65,14 @@ func ReadConfig(file string) (conf.Config, error) {
6865
case "":
6966
return nil, fmt.Errorf("Missing `kind` in config file")
7067
default:
71-
return nil, fmt.Errorf("Unknown `kind` '%s' in config file", cfgViper.GetString("kind"))
68+
return nil, fmt.Errorf("Unknown `kind` '%s' in config file", config.GetString("kind"))
7269
}
7370

74-
if err := cfgViper.Unmarshal(&cfg); err != nil {
71+
if err := config.Unmarshal(&cfg); err != nil {
7572
log.Errorln("Failed to unmarshal File config")
7673

7774
return nil, err
7875
}
7976

80-
log.Infof("Using Config: %s", cfgViper.ConfigFileUsed())
81-
8277
return cfg, nil
8378
}

pkg/config/config_test.go

+61-17
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import (
2626
"time"
2727

2828
"github.com/go-test/deep"
29-
conf "github.com/rancher/k3d/v4/pkg/config/v1alpha1"
29+
conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2"
30+
"github.com/spf13/viper"
3031

3132
k3d "github.com/rancher/k3d/v4/pkg/types"
3233
)
@@ -39,7 +40,7 @@ func TestReadSimpleConfig(t *testing.T) {
3940

4041
expectedConfig := conf.SimpleConfig{
4142
TypeMeta: conf.TypeMeta{
42-
APIVersion: "k3d.io/v1alpha1",
43+
APIVersion: "k3d.io/v1alpha2",
4344
Kind: "Simple",
4445
},
4546
Name: "test",
@@ -94,20 +95,27 @@ func TestReadSimpleConfig(t *testing.T) {
9495

9596
cfgFile := "./test_assets/config_test_simple.yaml"
9697

97-
cfg, err := ReadConfig(cfgFile)
98-
if err != nil {
98+
config := viper.New()
99+
config.SetConfigFile(cfgFile)
100+
101+
// try to read config into memory (viper map structure)
102+
if err := config.ReadInConfig(); err != nil {
103+
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
104+
t.Error(err)
105+
}
106+
// config file found but some other error happened
99107
t.Error(err)
100108
}
101109

102-
simpleCfg, ok := cfg.(conf.SimpleConfig)
103-
if !ok {
104-
t.Error("Config is not of type SimpleConfig")
110+
cfg, err := FromViperSimple(config)
111+
if err != nil {
112+
t.Error(err)
105113
}
106114

107-
t.Logf("\n========== Read Config ==========\n%+v\n=================================\n", simpleCfg)
115+
t.Logf("\n========== Read Config %s ==========\n%+v\n=================================\n", config.ConfigFileUsed(), cfg)
108116

109-
if diff := deep.Equal(simpleCfg, expectedConfig); diff != nil {
110-
t.Errorf("Actual representation\n%+v\ndoes not match expected representation\n%+v\nDiff:\n%+v", simpleCfg, expectedConfig, diff)
117+
if diff := deep.Equal(cfg, expectedConfig); diff != nil {
118+
t.Errorf("Actual representation\n%+v\ndoes not match expected representation\n%+v\nDiff:\n%+v", cfg, expectedConfig, diff)
111119
}
112120

113121
}
@@ -116,7 +124,7 @@ func TestReadClusterConfig(t *testing.T) {
116124

117125
expectedConfig := conf.ClusterConfig{
118126
TypeMeta: conf.TypeMeta{
119-
APIVersion: "k3d.io/v1alpha1",
127+
APIVersion: "k3d.io/v1alpha2",
120128
Kind: "Cluster",
121129
},
122130
Cluster: k3d.Cluster{
@@ -132,9 +140,21 @@ func TestReadClusterConfig(t *testing.T) {
132140

133141
cfgFile := "./test_assets/config_test_cluster.yaml"
134142

135-
readConfig, err := ReadConfig(cfgFile)
143+
config := viper.New()
144+
config.SetConfigFile(cfgFile)
145+
146+
// try to read config into memory (viper map structure)
147+
if err := config.ReadInConfig(); err != nil {
148+
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
149+
t.Error(err)
150+
}
151+
// config file found but some other error happened
152+
t.Error(err)
153+
}
154+
155+
readConfig, err := FromViper(config)
136156
if err != nil {
137-
t.Fatal(err)
157+
t.Error(err)
138158
}
139159

140160
t.Logf("\n========== Read Config ==========\n%+v\n=================================\n", readConfig)
@@ -149,7 +169,7 @@ func TestReadClusterListConfig(t *testing.T) {
149169

150170
expectedConfig := conf.ClusterListConfig{
151171
TypeMeta: conf.TypeMeta{
152-
APIVersion: "k3d.io/v1alpha1",
172+
APIVersion: "k3d.io/v1alpha2",
153173
Kind: "ClusterList",
154174
},
155175
Clusters: []k3d.Cluster{
@@ -176,9 +196,21 @@ func TestReadClusterListConfig(t *testing.T) {
176196

177197
cfgFile := "./test_assets/config_test_cluster_list.yaml"
178198

179-
readConfig, err := ReadConfig(cfgFile)
199+
config := viper.New()
200+
config.SetConfigFile(cfgFile)
201+
202+
// try to read config into memory (viper map structure)
203+
if err := config.ReadInConfig(); err != nil {
204+
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
205+
t.Error(err)
206+
}
207+
// config file found but some other error happened
208+
t.Error(err)
209+
}
210+
211+
readConfig, err := FromViper(config)
180212
if err != nil {
181-
t.Fatal(err)
213+
t.Error(err)
182214
}
183215

184216
t.Logf("\n========== Read Config ==========\n%+v\n=================================\n", readConfig)
@@ -193,7 +225,19 @@ func TestReadUnknownConfig(t *testing.T) {
193225

194226
cfgFile := "./test_assets/config_test_unknown.yaml"
195227

196-
_, err := ReadConfig(cfgFile)
228+
config := viper.New()
229+
config.SetConfigFile(cfgFile)
230+
231+
// try to read config into memory (viper map structure)
232+
if err := config.ReadInConfig(); err != nil {
233+
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
234+
t.Error(err)
235+
}
236+
// config file found but some other error happened
237+
t.Error(err)
238+
}
239+
240+
_, err := FromViperSimple(config)
197241
if err == nil {
198242
t.Fail()
199243
}

0 commit comments

Comments
 (0)