mirror of
https://github.com/kubernetes-sigs/kind.git
synced 2025-12-01 07:26:05 +07:00
refactor config, add machinery similar to a kubernetes API
This commit is contained in:
@@ -22,6 +22,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/test-infra/kind/pkg/cluster"
|
||||
"k8s.io/test-infra/kind/pkg/cluster/config"
|
||||
"k8s.io/test-infra/kind/pkg/cluster/config/encoding"
|
||||
)
|
||||
|
||||
type flags struct {
|
||||
@@ -49,15 +51,15 @@ func NewCommand() *cobra.Command {
|
||||
func run(flags *flags, cmd *cobra.Command, args []string) {
|
||||
// TODO(bentheelder): make this more configurable
|
||||
// load the config
|
||||
config, err := cluster.LoadCreateConfig(flags.Config)
|
||||
cfg, err := encoding.Load(flags.Config)
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading config: %v", err)
|
||||
}
|
||||
// validate the config
|
||||
err = config.Validate()
|
||||
err = cfg.Validate()
|
||||
if err != nil {
|
||||
log.Error("Invalid configuration!")
|
||||
configErrors := err.(cluster.ConfigErrors)
|
||||
configErrors := err.(*config.Errors)
|
||||
for _, problem := range configErrors.Errors() {
|
||||
log.Error(problem)
|
||||
}
|
||||
@@ -68,7 +70,7 @@ func run(flags *flags, cmd *cobra.Command, args []string) {
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create cluster context! %v", err)
|
||||
}
|
||||
err = ctx.Create(config)
|
||||
err = ctx.Create(cfg.ToCurrent())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create cluster: %v", err)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ A non-exhaustive list of tasks (in no-particular order) includes:
|
||||
- [ ] support multiple overlay networks
|
||||
- [x] support advanced configuration via config file
|
||||
- [x] kubeadm config template override
|
||||
- [ ] node lifecycle hooks
|
||||
- [ ] more advanced network configuration (not docker0)
|
||||
- [ ] support for other CRI within the "node" containers (containerd, cri-o)
|
||||
- [ ] switch from `exec.Command("docker", ...)` to the Docker client library
|
||||
|
||||
@@ -24,10 +24,10 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"k8s.io/test-infra/kind/pkg/cluster/config"
|
||||
"k8s.io/test-infra/kind/pkg/cluster/kubeadm"
|
||||
"k8s.io/test-infra/kind/pkg/exec"
|
||||
)
|
||||
@@ -37,7 +37,7 @@ const ClusterLabelKey = "io.k8s.test-infra.kind-cluster"
|
||||
|
||||
// Context is used to create / manipulate kubernetes-in-docker clusters
|
||||
type Context struct {
|
||||
Name string
|
||||
name string
|
||||
}
|
||||
|
||||
// similar to valid docker container names, but since we will prefix
|
||||
@@ -60,20 +60,25 @@ func NewContext(name string) (ctx *Context, err error) {
|
||||
)
|
||||
}
|
||||
return &Context{
|
||||
Name: name,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ClusterLabel returns the docker object label that will be applied
|
||||
// to cluster "node" containers
|
||||
func (c *Context) ClusterLabel() string {
|
||||
return fmt.Sprintf("%s=%s", ClusterLabelKey, c.Name)
|
||||
return fmt.Sprintf("%s=%s", ClusterLabelKey, c.name)
|
||||
}
|
||||
|
||||
// Name returns the context's name
|
||||
func (c *Context) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
// ClusterName returns the Kubernetes cluster name based on the context name
|
||||
// currently this is .Name prefixed with "kind-"
|
||||
func (c *Context) ClusterName() string {
|
||||
return fmt.Sprintf("kind-%s", c.Name)
|
||||
return fmt.Sprintf("kind-%s", c.name)
|
||||
}
|
||||
|
||||
// KubeConfigPath returns the path to where the Kubeconfig would be placed
|
||||
@@ -84,12 +89,12 @@ func (c *Context) KubeConfigPath() string {
|
||||
configDir := filepath.Join(os.Getenv("HOME"), ".kube")
|
||||
// note that the file name however does not, we do not want to overwite
|
||||
// the standard config, though in the future we may (?) merge them
|
||||
fileName := fmt.Sprintf("kind-config-%s", c.Name)
|
||||
fileName := fmt.Sprintf("kind-config-%s", c.name)
|
||||
return filepath.Join(configDir, fileName)
|
||||
}
|
||||
|
||||
// Create provisions and starts a kubernetes-in-docker cluster
|
||||
func (c *Context) Create(config *CreateConfig) error {
|
||||
func (c *Context) Create(config *config.Config) error {
|
||||
// validate config first
|
||||
if err := config.Validate(); err != nil {
|
||||
return err
|
||||
@@ -97,7 +102,7 @@ func (c *Context) Create(config *CreateConfig) error {
|
||||
|
||||
// TODO(bentheelder): multiple nodes ...
|
||||
kubeadmConfig, err := c.provisionControlPlane(
|
||||
fmt.Sprintf("kind-%s-control-plane", c.Name),
|
||||
fmt.Sprintf("kind-%s-control-plane", c.name),
|
||||
config,
|
||||
)
|
||||
|
||||
@@ -130,7 +135,10 @@ func (c *Context) Delete() error {
|
||||
|
||||
// provisionControlPlane provisions the control plane node
|
||||
// and the cluster kubeadm config
|
||||
func (c *Context) provisionControlPlane(nodeName string, config *CreateConfig) (kubeadmConfigPath string, err error) {
|
||||
func (c *Context) provisionControlPlane(
|
||||
nodeName string,
|
||||
config *config.Config,
|
||||
) (kubeadmConfigPath string, err error) {
|
||||
// create the "node" container (docker run, but it is paused, see createNode)
|
||||
node, err := createNode(nodeName, c.ClusterLabel())
|
||||
if err != nil {
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
// CreateConfig contains cluster creation config
|
||||
type CreateConfig struct {
|
||||
// NumNodes is the number of nodes to create (currently only one is supported)
|
||||
NumNodes int `json:"numNodes"`
|
||||
// KubeadmConfigTemplate allows overriding the default template in
|
||||
// cluster/kubeadm
|
||||
KubeadmConfigTemplate string `json:"kubeadmConfigTemplate"`
|
||||
}
|
||||
|
||||
// NewCreateConfig returns a new default CreateConfig
|
||||
func NewCreateConfig() *CreateConfig {
|
||||
return &CreateConfig{
|
||||
NumNodes: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadCreateConfig reads the file at path and attempts to load it as
|
||||
// a yaml encoding of CreateConfig, falling back to json if this fails.
|
||||
// It returns an error if reading the files fails, or if both yaml and json fail
|
||||
// If path is "" then a default config is returned instead
|
||||
func LoadCreateConfig(path string) (config *CreateConfig, err error) {
|
||||
if path == "" {
|
||||
return NewCreateConfig(), nil
|
||||
}
|
||||
// read in file
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// first try yaml
|
||||
config = &CreateConfig{}
|
||||
yamlErr := yaml.Unmarshal(contents, config)
|
||||
if yamlErr == nil {
|
||||
return config, nil
|
||||
}
|
||||
// then try json
|
||||
config = &CreateConfig{}
|
||||
jsonErr := json.Unmarshal(contents, config)
|
||||
if jsonErr == nil {
|
||||
return config, nil
|
||||
}
|
||||
return nil, fmt.Errorf("could not read as yaml: %v or json: %v", yamlErr, jsonErr)
|
||||
}
|
||||
|
||||
// Validate returns a ConfigErrors with an entry for each problem
|
||||
// with the config, or nil if there are none
|
||||
func (c *CreateConfig) Validate() error {
|
||||
errs := []error{}
|
||||
// TODO(bentheelder): support multiple nodes
|
||||
if c.NumNodes != 1 {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"%d nodes requested but only clusters with one node are supported currently",
|
||||
c.NumNodes,
|
||||
))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return ConfigErrors{errs}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigErrors implements error and contains all config errors
|
||||
// This is returned by Config.Validate
|
||||
type ConfigErrors struct {
|
||||
errors []error
|
||||
}
|
||||
|
||||
// assert ConfigErrors implements error
|
||||
var _ error = &ConfigErrors{}
|
||||
|
||||
func (c ConfigErrors) Error() string {
|
||||
var buff bytes.Buffer
|
||||
for _, err := range c.errors {
|
||||
buff.WriteString(err.Error())
|
||||
buff.WriteRune('\n')
|
||||
}
|
||||
return buff.String()
|
||||
}
|
||||
|
||||
// Errors returns the slice of errors contained by ConfigErrors
|
||||
func (c ConfigErrors) Errors() []error {
|
||||
return c.errors
|
||||
}
|
||||
35
pkg/cluster/config/any.go
Normal file
35
pkg/cluster/config/any.go
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
/*
|
||||
This file contains interfaces and code related to all API versions
|
||||
*/
|
||||
|
||||
// Any represents any API version of Config
|
||||
type Any interface {
|
||||
// Validate should return an error of type `*Errors` if config is invalid
|
||||
Validate() error
|
||||
// ToCurrent should convert a config version to the version in this package
|
||||
ToCurrent() *Config
|
||||
// ApplyDefaults should set unset fields to defaults
|
||||
ApplyDefaults()
|
||||
// Kind should return "Config"
|
||||
Kind() string
|
||||
// APIVersion should return the apiVersion for this config
|
||||
APIVersion() string
|
||||
}
|
||||
23
pkg/cluster/config/convert.go
Normal file
23
pkg/cluster/config/convert.go
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
// ToCurrent converts Config to config.Config
|
||||
// It is implemented to meet config.Any, and just deep copies on this type
|
||||
func (c *Config) ToCurrent() *Config {
|
||||
return c.DeepCopy()
|
||||
}
|
||||
79
pkg/cluster/config/deepcopy.go
Normal file
79
pkg/cluster/config/deepcopy.go
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
// TODO(bentheelder): consider kubernetes deep-copy gen
|
||||
// In the meantime the pattern is:
|
||||
// - handle nil receiver
|
||||
// - create a new(OutType)
|
||||
// - *out = *in to copy plain fields
|
||||
// - copy pointer fields by calling their DeepCopy
|
||||
// - copy slices / maps by allocating a new one and performing a copy loop
|
||||
|
||||
// DeepCopy returns a deep copy
|
||||
func (in *Config) DeepCopy() *Config {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Config)
|
||||
*out = *in
|
||||
out.NodeLifecycle = in.NodeLifecycle.DeepCopy()
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopy returns a deep copy
|
||||
func (in *NodeLifecycle) DeepCopy() *NodeLifecycle {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NodeLifecycle)
|
||||
if in.PreBoot != nil {
|
||||
out.PreBoot = make([]LifecycleHook, len(in.PreBoot))
|
||||
for i := range in.PreBoot {
|
||||
out.PreBoot[i] = *(in.PreBoot[i].DeepCopy())
|
||||
}
|
||||
}
|
||||
if in.PreKubeadm != nil {
|
||||
out.PreKubeadm = make([]LifecycleHook, len(in.PreKubeadm))
|
||||
for i := range in.PreKubeadm {
|
||||
out.PreKubeadm[i] = *(in.PreKubeadm[i].DeepCopy())
|
||||
}
|
||||
}
|
||||
if in.PostKubeadm != nil {
|
||||
out.PostKubeadm = make([]LifecycleHook, len(in.PostKubeadm))
|
||||
for i := range in.PostKubeadm {
|
||||
out.PostKubeadm[i] = *(in.PostKubeadm[i].DeepCopy())
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopy returns a deep copy
|
||||
func (in *LifecycleHook) DeepCopy() *LifecycleHook {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LifecycleHook)
|
||||
*out = *in
|
||||
if in.Args != nil {
|
||||
out.Args = make([]string, len(in.Args))
|
||||
for i := range in.Args {
|
||||
out.Args[i] = in.Args[i]
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
73
pkg/cluster/config/deepcopy_test.go
Normal file
73
pkg/cluster/config/deepcopy_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeepCopy(t *testing.T) {
|
||||
cases := []struct {
|
||||
TestName string
|
||||
Config *Config
|
||||
}{
|
||||
{
|
||||
TestName: "Canonical config",
|
||||
Config: New(),
|
||||
},
|
||||
{
|
||||
TestName: "Config with NodeLifecyle hooks",
|
||||
Config: func() *Config {
|
||||
cfg := New()
|
||||
cfg.NodeLifecycle = &NodeLifecycle{
|
||||
PreBoot: []LifecycleHook{
|
||||
{
|
||||
Command: "ps",
|
||||
Args: []string{"aux"},
|
||||
},
|
||||
},
|
||||
PreKubeadm: []LifecycleHook{
|
||||
{
|
||||
Name: "docker ps",
|
||||
Command: "docker",
|
||||
Args: []string{"ps"},
|
||||
},
|
||||
},
|
||||
PostKubeadm: []LifecycleHook{
|
||||
{
|
||||
Name: "docker ps again",
|
||||
Command: "docker",
|
||||
Args: []string{"ps", "-a"},
|
||||
},
|
||||
},
|
||||
}
|
||||
return cfg
|
||||
}(),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
original := tc.Config
|
||||
deepCopy := tc.Config.DeepCopy()
|
||||
if !reflect.DeepEqual(original, deepCopy) {
|
||||
t.Errorf(
|
||||
"case: '%s' deep copy did not equal original: %+v != %+v",
|
||||
tc.TestName, original, deepCopy,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
31
pkg/cluster/config/default.go
Normal file
31
pkg/cluster/config/default.go
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
// New returns a new default Config
|
||||
func New() *Config {
|
||||
cfg := &Config{}
|
||||
cfg.ApplyDefaults()
|
||||
return cfg
|
||||
}
|
||||
|
||||
// ApplyDefaults replaces unset fields with defaults
|
||||
func (c *Config) ApplyDefaults() {
|
||||
if c.NumNodes == 0 {
|
||||
c.NumNodes = 1
|
||||
}
|
||||
}
|
||||
19
pkg/cluster/config/doc.go
Normal file
19
pkg/cluster/config/doc.go
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package config implements the current apiVersion of the `kind` Config
|
||||
// along with some common abstractions
|
||||
package config
|
||||
139
pkg/cluster/config/encoding/encoding.go
Normal file
139
pkg/cluster/config/encoding/encoding.go
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package encoding implements apiVersion aware functionality to
|
||||
// Marshal / Unmarshal / Load Config
|
||||
package encoding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/test-infra/kind/pkg/cluster/config"
|
||||
)
|
||||
|
||||
// Load reads the file at path and attempts to load it as a yaml Config
|
||||
// after detecting the apiVersion in the file
|
||||
// (or defaulting to the current version if none is specified)
|
||||
// If path == "" then the default config for the current version is returned
|
||||
func Load(path string) (config.Any, error) {
|
||||
if path == "" {
|
||||
return config.New(), nil
|
||||
}
|
||||
// read in file
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// read in some config version
|
||||
// TODO(bentheelder): we do not use or respect `kind:` at all
|
||||
// possibly we should require something like `kind: "Config"`
|
||||
cfg, err := Unmarshal(contents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
// LoadCurrent is equivalent to Load followed by cfg.ToCurrent()
|
||||
func LoadCurrent(path string) (*config.Config, error) {
|
||||
cfg, err := Load(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg.ToCurrent(), nil
|
||||
}
|
||||
|
||||
// used to sniff a config for it's api version
|
||||
type configOnlyVersion struct {
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
}
|
||||
|
||||
// helper to sniff and validate apiVersion
|
||||
func detectVersion(raw []byte) (version string, err error) {
|
||||
c := configOnlyVersion{}
|
||||
if err := yaml.Unmarshal(raw, &c); err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch c.APIVersion {
|
||||
// default to the current api version if unspecified, or explicitly specified
|
||||
case config.APIVersion, "":
|
||||
return config.APIVersion, nil
|
||||
}
|
||||
return "", fmt.Errorf("invalid version: %v", c.APIVersion)
|
||||
}
|
||||
|
||||
// Unmarshal is an apiVersion aware yaml.Unmarshall for config.Any
|
||||
func Unmarshal(raw []byte) (config.Any, error) {
|
||||
if raw == nil {
|
||||
return nil, fmt.Errorf("nil input")
|
||||
}
|
||||
// sniff and validate version
|
||||
version, err := detectVersion(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// load version
|
||||
var cfg config.Any
|
||||
switch version {
|
||||
case config.APIVersion:
|
||||
cfg = &config.Config{}
|
||||
}
|
||||
err = yaml.Unmarshal(raw, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// apply defaults before returning
|
||||
cfg.ApplyDefaults()
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// used by `Marshal` to encode the config header
|
||||
type configHeader struct {
|
||||
Kind string `json:"kind"`
|
||||
APIVersion string `json:"apiVersion"`
|
||||
}
|
||||
|
||||
// Marshal marshals any config with kind and apiVersion header
|
||||
func Marshal(cfg config.Any) ([]byte, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("nil input")
|
||||
}
|
||||
var buff bytes.Buffer
|
||||
// write kind, apiVersion header
|
||||
b, err := yaml.Marshal(configHeader{
|
||||
Kind: cfg.Kind(),
|
||||
APIVersion: cfg.APIVersion(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// NOTE: buff.Write can only fail with a panic if it cannot allocate
|
||||
buff.Write(b)
|
||||
// write actual config contents
|
||||
b, err = yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// don't write a `{}` when the config has no set fields
|
||||
if !bytes.Equal(b, []byte("{}\n")) {
|
||||
buff.Write(b)
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
262
pkg/cluster/config/encoding/encoding_test.go
Normal file
262
pkg/cluster/config/encoding/encoding_test.go
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package encoding
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/test-infra/kind/pkg/cluster/config"
|
||||
)
|
||||
|
||||
// TODO(bentheelder): once we have multiple config API versions we
|
||||
// will need more tests for Load and LoadCurrent
|
||||
|
||||
func TestLoadCurrent(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Path string
|
||||
ExpectError bool
|
||||
}{
|
||||
{
|
||||
Name: "valid minimal",
|
||||
Path: "./testdata/valid-minimal.yaml",
|
||||
ExpectError: false,
|
||||
},
|
||||
{
|
||||
Name: "valid with lifecyclehooks",
|
||||
Path: "./testdata/valid-with-lifecyclehooks.yaml",
|
||||
ExpectError: false,
|
||||
},
|
||||
{
|
||||
Name: "invalid path",
|
||||
Path: "./testdata/not-a-file.bogus",
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Name: "invalid apiVersion",
|
||||
Path: "./testdata/invalid-apiversion.yaml",
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Name: "invalid yaml",
|
||||
Path: "./testdata/invalid-yaml.yaml",
|
||||
ExpectError: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
_, err := LoadCurrent(tc.Path)
|
||||
if err != nil && !tc.ExpectError {
|
||||
t.Errorf("case: '%s' got error`Load`ing and expected none: %v", tc.Name, err)
|
||||
} else if err == nil && tc.ExpectError {
|
||||
t.Errorf("case: '%s' got no error `Load`ing but expected one", tc.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDefault(t *testing.T) {
|
||||
cfg, err := Load("")
|
||||
if err != nil {
|
||||
t.Errorf("got error `Load`ing default config but expected none: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
defaultConfig := config.New()
|
||||
if !reflect.DeepEqual(cfg, defaultConfig) {
|
||||
t.Errorf(
|
||||
"Load(\"\") should match config.New() but does not: %v != %v",
|
||||
cfg, defaultConfig,
|
||||
)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodingRoundTrip(t *testing.T) {
|
||||
cfg := config.New()
|
||||
marshaled, err := Marshal(cfg)
|
||||
if err != nil {
|
||||
t.Errorf("got error `Marshal`ing default config: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
roundTripConfig, err := Unmarshal(marshaled)
|
||||
if err != nil {
|
||||
t.Errorf("got error `Unmarshal`ing default config: %v, raw = %s", err, marshaled)
|
||||
t.FailNow()
|
||||
}
|
||||
if !reflect.DeepEqual(cfg, roundTripConfig) {
|
||||
t.Errorf("default config does not match after Unmarshal(Marshal()), %v != %v", cfg, roundTripConfig)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
defaultConfig, err := Marshal(config.New())
|
||||
if err != nil {
|
||||
t.Errorf("Error setting up default config for test: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
cases := []struct {
|
||||
Name string
|
||||
Raw []byte
|
||||
ExpectedConfig config.Any
|
||||
ExpectError bool
|
||||
}{
|
||||
{
|
||||
Name: "default config",
|
||||
Raw: defaultConfig,
|
||||
ExpectedConfig: config.New(),
|
||||
ExpectError: false,
|
||||
},
|
||||
{
|
||||
Name: "config with lifecycle",
|
||||
Raw: []byte(`kind: Config
|
||||
apiVersion: kind.sigs.k8s.io/v1alpha1
|
||||
nodeLifecycle:
|
||||
preKubeadm:
|
||||
- name: "pull an image"
|
||||
command: "docker"
|
||||
args: [ "pull", "ubuntu" ]
|
||||
- name: "pull another image"
|
||||
command: "docker"
|
||||
args: [ "pull", "debian" ]
|
||||
`),
|
||||
ExpectedConfig: func() config.Any {
|
||||
cfg := &config.Config{
|
||||
NodeLifecycle: &config.NodeLifecycle{
|
||||
PreKubeadm: []config.LifecycleHook{
|
||||
{
|
||||
Name: "pull an image",
|
||||
Command: "docker",
|
||||
Args: []string{"pull", "ubuntu"},
|
||||
},
|
||||
{
|
||||
Name: "pull another image",
|
||||
Command: "docker",
|
||||
Args: []string{"pull", "debian"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
cfg.ApplyDefaults()
|
||||
return cfg
|
||||
}(),
|
||||
ExpectError: false,
|
||||
},
|
||||
{
|
||||
Name: "Invalid apiVersion 🤔",
|
||||
Raw: []byte("kind: KindConfig\napiVersion: 🤔"),
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Name: "generically invalid yaml",
|
||||
Raw: []byte("\""),
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Name: "invalid config yaml",
|
||||
Raw: []byte("numNodes: too-many"),
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Name: "nil input",
|
||||
Raw: nil,
|
||||
ExpectError: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
cfg, err := Unmarshal(tc.Raw)
|
||||
if err != nil && !tc.ExpectError {
|
||||
t.Errorf(
|
||||
"case: '%s' got error `Unmarshal`ing and expected none: %v",
|
||||
tc.Name, err,
|
||||
)
|
||||
} else if err == nil && tc.ExpectError {
|
||||
t.Errorf(
|
||||
"case: '%s' got no error `Unmarshal`ing but expected one",
|
||||
tc.Name,
|
||||
)
|
||||
}
|
||||
if !reflect.DeepEqual(cfg, tc.ExpectedConfig) {
|
||||
t.Errorf(
|
||||
"case: '%s' `Unmarshal` result does not match expected: %v != %v",
|
||||
tc.Name, cfg, tc.ExpectedConfig,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalDefaulting(t *testing.T) {
|
||||
// marshal an unset config
|
||||
emptyConfig, err := Marshal(&config.Config{})
|
||||
if err != nil {
|
||||
t.Errorf("Error setting up default config for test: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
// create a config with defaulted values
|
||||
defaulted := &config.Config{}
|
||||
defaulted.ApplyDefaults()
|
||||
// unmarshal the unset config
|
||||
unmarshaledEmpty, err := Unmarshal(emptyConfig)
|
||||
if err != nil {
|
||||
t.Errorf("Error `Unmarshal`ing default config: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
// verify that the unset config should match the default config
|
||||
if !reflect.DeepEqual(unmarshaledEmpty, defaulted) {
|
||||
t.Errorf(
|
||||
"defaulted config does not match unmarshaled empty config: %v != %v",
|
||||
defaulted, unmarshaledEmpty,
|
||||
)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// un-json.Marshal-able type
|
||||
type unencodable <-chan int
|
||||
|
||||
// "implement" config.Any
|
||||
func (u unencodable) ApplyDefaults() {}
|
||||
func (u unencodable) ToCurrent() *config.Config { return nil }
|
||||
func (u unencodable) Validate() error { return nil }
|
||||
func (u unencodable) Kind() string { return "bogus" }
|
||||
func (u unencodable) APIVersion() string { return "bogus" }
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Config config.Any
|
||||
ExpectError bool
|
||||
}{
|
||||
{
|
||||
Name: "nil config",
|
||||
Config: nil,
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Name: "wrong struct",
|
||||
Config: make(unencodable),
|
||||
ExpectError: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
_, err := Marshal(tc.Config)
|
||||
if err != nil && !tc.ExpectError {
|
||||
t.Errorf("case: '%s' got error `Marshal`ing and expected none: %v", tc.Name, err)
|
||||
} else if err == nil && tc.ExpectError {
|
||||
t.Errorf("case: '%s' got no error `Marshal`ing but expected one", tc.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
3
pkg/cluster/config/encoding/testdata/invalid-apiversion.yaml
vendored
Normal file
3
pkg/cluster/config/encoding/testdata/invalid-apiversion.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# this file contains an invalid config api version for testing
|
||||
kind: Config
|
||||
apiVersion: not-valid
|
||||
2
pkg/cluster/config/encoding/testdata/invalid-yaml.yaml
vendored
Normal file
2
pkg/cluster/config/encoding/testdata/invalid-yaml.yaml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# intentionally invalid yaml file for testing
|
||||
":
|
||||
3
pkg/cluster/config/encoding/testdata/valid-minimal.yaml
vendored
Normal file
3
pkg/cluster/config/encoding/testdata/valid-minimal.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# technically valid, minimal config file
|
||||
kind: Config
|
||||
apiVersion: kind.sigs.k8s.io/v1alpha1
|
||||
10
pkg/cluster/config/encoding/testdata/valid-with-lifecyclehooks.yaml
vendored
Normal file
10
pkg/cluster/config/encoding/testdata/valid-with-lifecyclehooks.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
kind: Config
|
||||
apiVersion: kind.sigs.k8s.io/v1alpha1
|
||||
nodeLifecycle:
|
||||
preKubeadm:
|
||||
- name: "pull an image"
|
||||
command: "docker"
|
||||
args: [ "pull", "ubuntu" ]
|
||||
- name: "pull another image"
|
||||
command: "docker"
|
||||
args: [ "pull", "debian" ]
|
||||
48
pkg/cluster/config/errors.go
Normal file
48
pkg/cluster/config/errors.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import "bytes"
|
||||
|
||||
// Errors implements error and contains all config errors
|
||||
// This is returned by Config.Validate
|
||||
type Errors struct {
|
||||
errors []error
|
||||
}
|
||||
|
||||
// NewErrors returns a new Errors from a slice of error
|
||||
func NewErrors(errors []error) *Errors {
|
||||
return &Errors{errors}
|
||||
}
|
||||
|
||||
// assert Errors implements error
|
||||
var _ error = &Errors{}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *Errors) Error() string {
|
||||
var buff bytes.Buffer
|
||||
for _, err := range e.errors {
|
||||
buff.WriteString(err.Error())
|
||||
buff.WriteRune('\n')
|
||||
}
|
||||
return buff.String()
|
||||
}
|
||||
|
||||
// Errors returns the slice of errors contained by Errors
|
||||
func (e *Errors) Errors() []error {
|
||||
return e.errors
|
||||
}
|
||||
58
pkg/cluster/config/types.go
Normal file
58
pkg/cluster/config/types.go
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
// NOTE: if you change these types you likely need to update
|
||||
// Validate() and DeepCopy() at minimum
|
||||
|
||||
// Config contains cluster creation config
|
||||
// This is the current internal config type used by cluster
|
||||
// Other API versions can be converted to this struct with Convert()
|
||||
type Config struct {
|
||||
// NumNodes is the number of nodes to create (currently only one is supported)
|
||||
NumNodes int `json:"numNodes,omitempty"`
|
||||
// KubeadmConfigTemplate allows overriding the default template in
|
||||
// cluster/kubeadm
|
||||
KubeadmConfigTemplate string `json:"kubeadmConfigTemplate,omitempty"`
|
||||
// NodeLifecycle contains LifecycleHooks for phases of node provisioning
|
||||
NodeLifecycle *NodeLifecycle `json:"nodeLifecycle,omitempty"`
|
||||
}
|
||||
|
||||
// ensure current version implements the common interface for
|
||||
// conversion, validation, etc.
|
||||
var _ Any = &Config{}
|
||||
|
||||
// NodeLifecycle contains LifecycleHooks for phases of node provisioning
|
||||
// Within each phase these hooks run in the order specified
|
||||
type NodeLifecycle struct {
|
||||
// PreBoot hooks run before starting systemd
|
||||
PreBoot []LifecycleHook `json:"preBoot,omitempty"`
|
||||
// PreKubeadm hooks run before `kubeadm`
|
||||
PreKubeadm []LifecycleHook `json:"preKubeadm,omitempty"`
|
||||
// PostKubeadm hooks run after `kubeadm`
|
||||
PostKubeadm []LifecycleHook `json:"postKubeadm,omitempty"`
|
||||
}
|
||||
|
||||
// LifecycleHook represents a command to run at points in the node lifecycle
|
||||
type LifecycleHook struct {
|
||||
// Name is used to improve logging (optional)
|
||||
Name string `json:"name,omitempty"`
|
||||
// Command is the command to run on the node
|
||||
Command string `json:"command"`
|
||||
// Args are the arguments to the command (optional)
|
||||
Args []string `json:"args,omitempty"`
|
||||
}
|
||||
68
pkg/cluster/config/validate.go
Normal file
68
pkg/cluster/config/validate.go
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Validate returns a ConfigErrors with an entry for each problem
|
||||
// with the config, or nil if there are none
|
||||
func (c *Config) Validate() error {
|
||||
errs := []error{}
|
||||
// TODO(bentheelder): support multiple nodes
|
||||
if c.NumNodes != 1 {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"%d nodes requested but only clusters with one node are supported currently",
|
||||
c.NumNodes,
|
||||
))
|
||||
}
|
||||
if c.NodeLifecycle != nil {
|
||||
for _, hook := range c.NodeLifecycle.PreBoot {
|
||||
if hook.Command == "" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"preBoot hooks must set command to a non-empty value",
|
||||
))
|
||||
// we don't need to repeat this error and we don't
|
||||
// have any others for this field
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, hook := range c.NodeLifecycle.PreKubeadm {
|
||||
if hook.Command == "" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"preKubeadm hooks must set command to a non-empty value",
|
||||
))
|
||||
// we don't need to repeat this error and we don't
|
||||
// have any others for this field
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, hook := range c.NodeLifecycle.PostKubeadm {
|
||||
if hook.Command == "" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"postKubeadm hooks must set command to a non-empty value",
|
||||
))
|
||||
// we don't need to repeat this error and we don't
|
||||
// have any others for this field
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return NewErrors(errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -14,28 +14,75 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cluster
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCreateConfigValidate(t *testing.T) {
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
cases := []struct {
|
||||
TestName string
|
||||
Config *CreateConfig
|
||||
Config *Config
|
||||
ExpectedErrors int
|
||||
}{
|
||||
{
|
||||
TestName: "Canonical config",
|
||||
Config: NewCreateConfig(),
|
||||
Config: New(),
|
||||
ExpectedErrors: 0,
|
||||
},
|
||||
{
|
||||
TestName: "Invalid number of nodes (not yet supported",
|
||||
Config: &CreateConfig{
|
||||
Config: &Config{
|
||||
NumNodes: 2,
|
||||
},
|
||||
ExpectedErrors: 1,
|
||||
},
|
||||
{
|
||||
TestName: "Invalid PreBoot hook",
|
||||
Config: func() *Config {
|
||||
cfg := New()
|
||||
cfg.NodeLifecycle = &NodeLifecycle{
|
||||
PreBoot: []LifecycleHook{
|
||||
{
|
||||
Command: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
return cfg
|
||||
}(),
|
||||
ExpectedErrors: 1,
|
||||
},
|
||||
{
|
||||
TestName: "Invalid PreKubeadm hook",
|
||||
Config: func() *Config {
|
||||
cfg := New()
|
||||
cfg.NodeLifecycle = &NodeLifecycle{
|
||||
PreKubeadm: []LifecycleHook{
|
||||
{
|
||||
Name: "pull an image",
|
||||
Command: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
return cfg
|
||||
}(),
|
||||
ExpectedErrors: 1,
|
||||
},
|
||||
{
|
||||
TestName: "Invalid PostKubeadm hook",
|
||||
Config: func() *Config {
|
||||
cfg := New()
|
||||
cfg.NodeLifecycle = &NodeLifecycle{
|
||||
PostKubeadm: []LifecycleHook{
|
||||
{
|
||||
Name: "pull an image",
|
||||
Command: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
return cfg
|
||||
}(),
|
||||
ExpectedErrors: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -48,8 +95,8 @@ func TestCreateConfigValidate(t *testing.T) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
// - not castable to ConfigErrors, in which case we have the wrong error type ...
|
||||
configErrors, ok := err.(ConfigErrors)
|
||||
// - not castable to *Errors, in which case we have the wrong error type ...
|
||||
configErrors, ok := err.(*Errors)
|
||||
if !ok {
|
||||
t.Errorf("config.Validate should only return nil or ConfigErrors{...}, got: %v for case: %s", err, tc.TestName)
|
||||
continue
|
||||
33
pkg/cluster/config/version.go
Normal file
33
pkg/cluster/config/version.go
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
// APIVersion is the kubernetes-style API apiVersion for this Config package
|
||||
const APIVersion = "kind.sigs.k8s.io/v1alpha1"
|
||||
|
||||
// Kind is the kubernetes-style API kind identifier for Config
|
||||
const Kind = "Config"
|
||||
|
||||
// Kind returns the `kind:` for Config
|
||||
func (c *Config) Kind() string {
|
||||
return Kind
|
||||
}
|
||||
|
||||
// APIVersion returns the `apiVersion:` for Config
|
||||
func (c *Config) APIVersion() string {
|
||||
return APIVersion
|
||||
}
|
||||
@@ -31,19 +31,21 @@ type ConfigData struct {
|
||||
KubernetesVersion string
|
||||
// UnifiedControlPlaneImage - optional
|
||||
UnifiedControlPlaneImage string
|
||||
// AutoDerivedConfigData is populated by DeriveFields()
|
||||
AutoDerivedConfigData
|
||||
// DerivedConfigData is populated by Derive()
|
||||
// These auto-generated fields are available to Config templates,
|
||||
// but not meant to be set by hand
|
||||
DerivedConfigData
|
||||
}
|
||||
|
||||
// AutoDerivedConfigData fields are automatically derived by
|
||||
// ConfigData.DeriveFieldsif they are not specified / zero valued
|
||||
type AutoDerivedConfigData struct {
|
||||
// DerivedConfigData fields are automatically derived by
|
||||
// ConfigData.Derive if they are not specified / zero valued
|
||||
type DerivedConfigData struct {
|
||||
// DockerStableTag is automatically derived from KubernetesVersion
|
||||
DockerStableTag string
|
||||
}
|
||||
|
||||
// DeriveFields automatically derives DockerStableTag if not specified
|
||||
func (c *ConfigData) DeriveFields() {
|
||||
// Derive automatically derives DockerStableTag if not specified
|
||||
func (c *ConfigData) Derive() {
|
||||
if c.DockerStableTag == "" {
|
||||
c.DockerStableTag = strings.Replace(c.KubernetesVersion, "+", "_", -1)
|
||||
}
|
||||
@@ -77,7 +79,7 @@ func Config(templateSource string, data ConfigData) (config string, err error) {
|
||||
return "", errors.Wrap(err, "failed to parse config template")
|
||||
}
|
||||
// derive any automatic fields if not supplied
|
||||
data.DeriveFields()
|
||||
data.Derive()
|
||||
// execute the template
|
||||
var buff bytes.Buffer
|
||||
err = t.Execute(&buff, data)
|
||||
|
||||
Reference in New Issue
Block a user