vendor cleanup: remove unused,non-go and test files

This commit is contained in:
Madhu Rajanna
2019-01-16 00:05:52 +05:30
parent 52cf4aa902
commit b10ba188e7
15421 changed files with 17 additions and 4208853 deletions

View File

@ -1,79 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"arguments.go",
"cgroupdriver.go",
"copy.go",
"endpoint.go",
"error.go",
"marshal.go",
"template.go",
"version.go",
],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"arguments_test.go",
"cgroupdriver_test.go",
"endpoint_test.go",
"error_test.go",
"marshal_test.go",
"template_test.go",
"version_test.go",
],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//cmd/kubeadm/app/util/apiclient:all-srcs",
"//cmd/kubeadm/app/util/audit:all-srcs",
"//cmd/kubeadm/app/util/config:all-srcs",
"//cmd/kubeadm/app/util/dryrun:all-srcs",
"//cmd/kubeadm/app/util/etcd:all-srcs",
"//cmd/kubeadm/app/util/kubeconfig:all-srcs",
"//cmd/kubeadm/app/util/pubkeypin:all-srcs",
"//cmd/kubeadm/app/util/staticpod:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -1,74 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"clientbacked_dryrun.go",
"dryrunclient.go",
"idempotency.go",
"init_dryrun.go",
"wait.go",
],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient",
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//pkg/kubelet/apis:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/registry/core/service/ipallocator:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = [
"dryrunclient_test.go",
"init_dryrun_test.go",
],
embed = [":go_default_library"],
deps = [
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/rbac/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
],
)

View File

@ -1,131 +0,0 @@
/*
Copyright 2017 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 apiclient
import (
"encoding/json"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
clientset "k8s.io/client-go/kubernetes"
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/clientcmd"
)
// ClientBackedDryRunGetter implements the DryRunGetter interface for use in NewDryRunClient() and proxies all GET and LIST requests to the backing API server reachable via rest.Config
type ClientBackedDryRunGetter struct {
client clientset.Interface
dynamicClient dynamic.Interface
}
// InitDryRunGetter should implement the DryRunGetter interface
var _ DryRunGetter = &ClientBackedDryRunGetter{}
// NewClientBackedDryRunGetter creates a new ClientBackedDryRunGetter instance based on the rest.Config object
func NewClientBackedDryRunGetter(config *rest.Config) (*ClientBackedDryRunGetter, error) {
client, err := clientset.NewForConfig(config)
if err != nil {
return nil, err
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, err
}
return &ClientBackedDryRunGetter{
client: client,
dynamicClient: dynamicClient,
}, nil
}
// NewClientBackedDryRunGetterFromKubeconfig creates a new ClientBackedDryRunGetter instance from the given KubeConfig file
func NewClientBackedDryRunGetterFromKubeconfig(file string) (*ClientBackedDryRunGetter, error) {
config, err := clientcmd.LoadFromFile(file)
if err != nil {
return nil, fmt.Errorf("failed to load kubeconfig: %v", err)
}
clientConfig, err := clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, fmt.Errorf("failed to create API client configuration from kubeconfig: %v", err)
}
return NewClientBackedDryRunGetter(clientConfig)
}
// HandleGetAction handles GET actions to the dryrun clientset this interface supports
func (clg *ClientBackedDryRunGetter) HandleGetAction(action core.GetAction) (bool, runtime.Object, error) {
unstructuredObj, err := clg.dynamicClient.Resource(action.GetResource()).Namespace(action.GetNamespace()).Get(action.GetName(), metav1.GetOptions{})
// Inform the user that the requested object wasn't found.
printIfNotExists(err)
if err != nil {
return true, nil, err
}
newObj, err := decodeUnstructuredIntoAPIObject(action, unstructuredObj)
if err != nil {
fmt.Printf("error after decode: %v %v\n", unstructuredObj, err)
return true, nil, err
}
return true, newObj, err
}
// HandleListAction handles LIST actions to the dryrun clientset this interface supports
func (clg *ClientBackedDryRunGetter) HandleListAction(action core.ListAction) (bool, runtime.Object, error) {
listOpts := metav1.ListOptions{
LabelSelector: action.GetListRestrictions().Labels.String(),
FieldSelector: action.GetListRestrictions().Fields.String(),
}
unstructuredList, err := clg.dynamicClient.Resource(action.GetResource()).Namespace(action.GetNamespace()).List(listOpts)
if err != nil {
return true, nil, err
}
newObj, err := decodeUnstructuredIntoAPIObject(action, unstructuredList)
if err != nil {
fmt.Printf("error after decode: %v %v\n", unstructuredList, err)
return true, nil, err
}
return true, newObj, err
}
// Client gets the backing clientset.Interface
func (clg *ClientBackedDryRunGetter) Client() clientset.Interface {
return clg.client
}
// decodeUnversionedIntoAPIObject converts the *unversioned.Unversioned object returned from the dynamic client
// to bytes; and then decodes it back _to an external api version (k8s.io/api vs k8s.io/kubernetes/pkg/api*)_ using the normal API machinery
func decodeUnstructuredIntoAPIObject(action core.Action, unstructuredObj runtime.Unstructured) (runtime.Object, error) {
objBytes, err := json.Marshal(unstructuredObj)
if err != nil {
return nil, err
}
newObj, err := runtime.Decode(clientsetscheme.Codecs.UniversalDecoder(action.GetResource().GroupVersion()), objBytes)
if err != nil {
return nil, err
}
return newObj, nil
}
func printIfNotExists(err error) {
if apierrors.IsNotFound(err) {
fmt.Println("[dryrun] The GET request didn't yield any result, the API Server returned a NotFound error.")
}
}

View File

@ -1,242 +0,0 @@
/*
Copyright 2017 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 apiclient
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
clientset "k8s.io/client-go/kubernetes"
fakeclientset "k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
// DryRunGetter is an interface that must be supplied to the NewDryRunClient function in order to contstruct a fully functional fake dryrun clientset
type DryRunGetter interface {
HandleGetAction(core.GetAction) (bool, runtime.Object, error)
HandleListAction(core.ListAction) (bool, runtime.Object, error)
}
// MarshalFunc takes care of converting any object to a byte array for displaying the object to the user
type MarshalFunc func(runtime.Object, schema.GroupVersion) ([]byte, error)
// DefaultMarshalFunc is the default MarshalFunc used; uses YAML to print objects to the user
func DefaultMarshalFunc(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
return kubeadmutil.MarshalToYaml(obj, gv)
}
// DryRunClientOptions specifies options to pass to NewDryRunClientWithOpts in order to get a dryrun clientset
type DryRunClientOptions struct {
Writer io.Writer
Getter DryRunGetter
PrependReactors []core.Reactor
AppendReactors []core.Reactor
MarshalFunc MarshalFunc
PrintGETAndLIST bool
}
// GetDefaultDryRunClientOptions returns the default DryRunClientOptions values
func GetDefaultDryRunClientOptions(drg DryRunGetter, w io.Writer) DryRunClientOptions {
return DryRunClientOptions{
Writer: w,
Getter: drg,
PrependReactors: []core.Reactor{},
AppendReactors: []core.Reactor{},
MarshalFunc: DefaultMarshalFunc,
PrintGETAndLIST: false,
}
}
// actionWithName is the generic interface for an action that has a name associated with it
// This just makes it easier to catch all actions that has a name; instead of hard-coding all request that has it associated
type actionWithName interface {
core.Action
GetName() string
}
// actionWithObject is the generic interface for an action that has an object associated with it
// This just makes it easier to catch all actions that has an object; instead of hard-coding all request that has it associated
type actionWithObject interface {
core.Action
GetObject() runtime.Object
}
// NewDryRunClient is a wrapper for NewDryRunClientWithOpts using some default values
func NewDryRunClient(drg DryRunGetter, w io.Writer) clientset.Interface {
return NewDryRunClientWithOpts(GetDefaultDryRunClientOptions(drg, w))
}
// NewDryRunClientWithOpts returns a clientset.Interface that can be used normally for talking to the Kubernetes API.
// This client doesn't apply changes to the backend. The client gets GET/LIST values from the DryRunGetter implementation.
// This client logs all I/O to the writer w in YAML format
func NewDryRunClientWithOpts(opts DryRunClientOptions) clientset.Interface {
// Build a chain of reactors to act like a normal clientset; but log everything that is happening and don't change any state
client := fakeclientset.NewSimpleClientset()
// Build the chain of reactors. Order matters; first item here will be invoked first on match, then the second one will be evaluated, etc.
defaultReactorChain := []core.Reactor{
// Log everything that happens. Default the object if it's about to be created/updated so that the logged object is representative.
&core.SimpleReactor{
Verb: "*",
Resource: "*",
Reaction: func(action core.Action) (bool, runtime.Object, error) {
logDryRunAction(action, opts.Writer, opts.MarshalFunc)
return false, nil, nil
},
},
// Let the DryRunGetter implementation take care of all GET requests.
// The DryRunGetter implementation may call a real API Server behind the scenes or just fake everything
&core.SimpleReactor{
Verb: "get",
Resource: "*",
Reaction: func(action core.Action) (bool, runtime.Object, error) {
getAction, ok := action.(core.GetAction)
if !ok {
// something's wrong, we can't handle this event
return true, nil, fmt.Errorf("can't cast get reactor event action object to GetAction interface")
}
handled, obj, err := opts.Getter.HandleGetAction(getAction)
if opts.PrintGETAndLIST {
// Print the marshalled object format with one tab indentation
objBytes, err := opts.MarshalFunc(obj, action.GetResource().GroupVersion())
if err == nil {
fmt.Println("[dryrun] Returning faked GET response:")
PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t")
}
}
return handled, obj, err
},
},
// Let the DryRunGetter implementation take care of all GET requests.
// The DryRunGetter implementation may call a real API Server behind the scenes or just fake everything
&core.SimpleReactor{
Verb: "list",
Resource: "*",
Reaction: func(action core.Action) (bool, runtime.Object, error) {
listAction, ok := action.(core.ListAction)
if !ok {
// something's wrong, we can't handle this event
return true, nil, fmt.Errorf("can't cast list reactor event action object to ListAction interface")
}
handled, objs, err := opts.Getter.HandleListAction(listAction)
if opts.PrintGETAndLIST {
// Print the marshalled object format with one tab indentation
objBytes, err := opts.MarshalFunc(objs, action.GetResource().GroupVersion())
if err == nil {
fmt.Println("[dryrun] Returning faked LIST response:")
PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t")
}
}
return handled, objs, err
},
},
// For the verbs that modify anything on the server; just return the object if present and exit successfully
&core.SimpleReactor{
Verb: "create",
Resource: "*",
Reaction: successfulModificationReactorFunc,
},
&core.SimpleReactor{
Verb: "update",
Resource: "*",
Reaction: successfulModificationReactorFunc,
},
&core.SimpleReactor{
Verb: "delete",
Resource: "*",
Reaction: successfulModificationReactorFunc,
},
&core.SimpleReactor{
Verb: "delete-collection",
Resource: "*",
Reaction: successfulModificationReactorFunc,
},
&core.SimpleReactor{
Verb: "patch",
Resource: "*",
Reaction: successfulModificationReactorFunc,
},
}
// The chain of reactors will look like this:
// opts.PrependReactors | defaultReactorChain | opts.AppendReactors | client.Fake.ReactionChain (default reactors for the fake clientset)
fullReactorChain := append(opts.PrependReactors, defaultReactorChain...)
fullReactorChain = append(fullReactorChain, opts.AppendReactors...)
// Prepend the reaction chain with our reactors. Important, these MUST be prepended; not appended due to how the fake clientset works by default
client.Fake.ReactionChain = append(fullReactorChain, client.Fake.ReactionChain...)
return client
}
// successfulModificationReactorFunc is a no-op that just returns the POSTed/PUTed value if present; but does nothing to edit any backing data store.
func successfulModificationReactorFunc(action core.Action) (bool, runtime.Object, error) {
objAction, ok := action.(actionWithObject)
if ok {
return true, objAction.GetObject(), nil
}
return true, nil, nil
}
// logDryRunAction logs the action that was recorded by the "catch-all" (*,*) reactor and tells the user what would have happened in an user-friendly way
func logDryRunAction(action core.Action, w io.Writer, marshalFunc MarshalFunc) {
group := action.GetResource().Group
if len(group) == 0 {
group = "core"
}
fmt.Fprintf(w, "[dryrun] Would perform action %s on resource %q in API group \"%s/%s\"\n", strings.ToUpper(action.GetVerb()), action.GetResource().Resource, group, action.GetResource().Version)
namedAction, ok := action.(actionWithName)
if ok {
fmt.Fprintf(w, "[dryrun] Resource name: %q\n", namedAction.GetName())
}
objAction, ok := action.(actionWithObject)
if ok && objAction.GetObject() != nil {
// Print the marshalled object with a tab indentation
objBytes, err := marshalFunc(objAction.GetObject(), action.GetResource().GroupVersion())
if err == nil {
fmt.Println("[dryrun] Attached object:")
PrintBytesWithLinePrefix(w, objBytes, "\t")
}
}
patchAction, ok := action.(core.PatchAction)
if ok {
// Replace all occurrences of \" with a simple " when printing
fmt.Fprintf(w, "[dryrun] Attached patch:\n\t%s\n", strings.Replace(string(patchAction.GetPatch()), `\"`, `"`, -1))
}
}
// PrintBytesWithLinePrefix prints objBytes to writer w with linePrefix in the beginning of every line
func PrintBytesWithLinePrefix(w io.Writer, objBytes []byte, linePrefix string) {
scanner := bufio.NewScanner(bytes.NewReader(objBytes))
for scanner.Scan() {
fmt.Fprintf(w, "%s%s\n", linePrefix, scanner.Text())
}
}

View File

@ -1,102 +0,0 @@
/*
Copyright 2017 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 apiclient
import (
"bytes"
"testing"
"k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
core "k8s.io/client-go/testing"
)
func TestLogDryRunAction(t *testing.T) {
var tests = []struct {
action core.Action
expectedBytes []byte
buf *bytes.Buffer
}{
{
action: core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "services"}, "default", "kubernetes"),
expectedBytes: []byte(`[dryrun] Would perform action GET on resource "services" in API group "core/v1"
[dryrun] Resource name: "kubernetes"
`),
},
{
action: core.NewRootGetAction(schema.GroupVersionResource{Group: rbac.GroupName, Version: rbac.SchemeGroupVersion.Version, Resource: "clusterrolebindings"}, "system:node"),
expectedBytes: []byte(`[dryrun] Would perform action GET on resource "clusterrolebindings" in API group "rbac.authorization.k8s.io/v1"
[dryrun] Resource name: "system:node"
`),
},
{
action: core.NewListAction(schema.GroupVersionResource{Version: "v1", Resource: "services"}, schema.GroupVersionKind{Version: "v1", Kind: "Service"}, "default", metav1.ListOptions{}),
expectedBytes: []byte(`[dryrun] Would perform action LIST on resource "services" in API group "core/v1"
`),
},
{
action: core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "services"}, "default", &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: v1.ServiceSpec{
ClusterIP: "1.1.1.1",
},
}),
expectedBytes: []byte(`[dryrun] Would perform action CREATE on resource "services" in API group "core/v1"
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
name: foo
spec:
clusterIP: 1.1.1.1
status:
loadBalancer: {}
`),
},
{
action: core.NewPatchAction(schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "default", "my-node", []byte(`{"spec":{"taints":[{"key": "foo", "value": "bar"}]}}`)),
expectedBytes: []byte(`[dryrun] Would perform action PATCH on resource "nodes" in API group "core/v1"
[dryrun] Resource name: "my-node"
[dryrun] Attached patch:
{"spec":{"taints":[{"key": "foo", "value": "bar"}]}}
`),
},
{
action: core.NewDeleteAction(schema.GroupVersionResource{Version: "v1", Resource: "pods"}, "default", "my-pod"),
expectedBytes: []byte(`[dryrun] Would perform action DELETE on resource "pods" in API group "core/v1"
[dryrun] Resource name: "my-pod"
`),
},
}
for _, rt := range tests {
rt.buf = bytes.NewBufferString("")
logDryRunAction(rt.action, rt.buf, DefaultMarshalFunc)
actualBytes := rt.buf.Bytes()
if !bytes.Equal(actualBytes, rt.expectedBytes) {
t.Errorf(
"failed LogDryRunAction:\n\texpected bytes: %q\n\t actual: %q",
rt.expectedBytes,
actualBytes,
)
}
}
}

View File

@ -1,240 +0,0 @@
/*
Copyright 2017 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 apiclient
import (
"encoding/json"
"fmt"
apps "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
)
// TODO: We should invent a dynamic mechanism for this using the dynamic client instead of hard-coding these functions per-type
// TODO: We may want to retry if .Update() fails on 409 Conflict
// CreateOrUpdateConfigMap creates a ConfigMap if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateConfigMap(client clientset.Interface, cm *v1.ConfigMap) error {
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(cm); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create configmap: %v", err)
}
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(cm); err != nil {
return fmt.Errorf("unable to update configmap: %v", err)
}
}
return nil
}
// CreateOrRetainConfigMap creates a ConfigMap if the target resource doesn't exist. If the resource exists already, this function will retain the resource instead.
func CreateOrRetainConfigMap(client clientset.Interface, cm *v1.ConfigMap, configMapName string) error {
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Get(configMapName, metav1.GetOptions{}); err != nil {
if !apierrors.IsNotFound(err) {
return nil
}
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(cm); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create configmap: %v", err)
}
}
}
return nil
}
// CreateOrUpdateSecret creates a Secret if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateSecret(client clientset.Interface, secret *v1.Secret) error {
if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Create(secret); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create secret: %v", err)
}
if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Update(secret); err != nil {
return fmt.Errorf("unable to update secret: %v", err)
}
}
return nil
}
// CreateOrUpdateServiceAccount creates a ServiceAccount if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateServiceAccount(client clientset.Interface, sa *v1.ServiceAccount) error {
if _, err := client.CoreV1().ServiceAccounts(sa.ObjectMeta.Namespace).Create(sa); err != nil {
// Note: We don't run .Update here afterwards as that's probably not required
// Only thing that could be updated is annotations/labels in .metadata, but we don't use that currently
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create serviceaccount: %v", err)
}
}
return nil
}
// CreateOrUpdateDeployment creates a Deployment if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateDeployment(client clientset.Interface, deploy *apps.Deployment) error {
if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Create(deploy); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create deployment: %v", err)
}
if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Update(deploy); err != nil {
return fmt.Errorf("unable to update deployment: %v", err)
}
}
return nil
}
// CreateOrUpdateDaemonSet creates a DaemonSet if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateDaemonSet(client clientset.Interface, ds *apps.DaemonSet) error {
if _, err := client.AppsV1().DaemonSets(ds.ObjectMeta.Namespace).Create(ds); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create daemonset: %v", err)
}
if _, err := client.AppsV1().DaemonSets(ds.ObjectMeta.Namespace).Update(ds); err != nil {
return fmt.Errorf("unable to update daemonset: %v", err)
}
}
return nil
}
// DeleteDaemonSetForeground deletes the specified DaemonSet in foreground mode; i.e. it blocks until/makes sure all the managed Pods are deleted
func DeleteDaemonSetForeground(client clientset.Interface, namespace, name string) error {
foregroundDelete := metav1.DeletePropagationForeground
deleteOptions := &metav1.DeleteOptions{
PropagationPolicy: &foregroundDelete,
}
return client.AppsV1().DaemonSets(namespace).Delete(name, deleteOptions)
}
// DeleteDeploymentForeground deletes the specified Deployment in foreground mode; i.e. it blocks until/makes sure all the managed Pods are deleted
func DeleteDeploymentForeground(client clientset.Interface, namespace, name string) error {
foregroundDelete := metav1.DeletePropagationForeground
deleteOptions := &metav1.DeleteOptions{
PropagationPolicy: &foregroundDelete,
}
return client.AppsV1().Deployments(namespace).Delete(name, deleteOptions)
}
// CreateOrUpdateRole creates a Role if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateRole(client clientset.Interface, role *rbac.Role) error {
if _, err := client.RbacV1().Roles(role.ObjectMeta.Namespace).Create(role); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create RBAC role: %v", err)
}
if _, err := client.RbacV1().Roles(role.ObjectMeta.Namespace).Update(role); err != nil {
return fmt.Errorf("unable to update RBAC role: %v", err)
}
}
return nil
}
// CreateOrUpdateRoleBinding creates a RoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateRoleBinding(client clientset.Interface, roleBinding *rbac.RoleBinding) error {
if _, err := client.RbacV1().RoleBindings(roleBinding.ObjectMeta.Namespace).Create(roleBinding); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create RBAC rolebinding: %v", err)
}
if _, err := client.RbacV1().RoleBindings(roleBinding.ObjectMeta.Namespace).Update(roleBinding); err != nil {
return fmt.Errorf("unable to update RBAC rolebinding: %v", err)
}
}
return nil
}
// CreateOrUpdateClusterRole creates a ClusterRole if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateClusterRole(client clientset.Interface, clusterRole *rbac.ClusterRole) error {
if _, err := client.RbacV1().ClusterRoles().Create(clusterRole); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create RBAC clusterrole: %v", err)
}
if _, err := client.RbacV1().ClusterRoles().Update(clusterRole); err != nil {
return fmt.Errorf("unable to update RBAC clusterrole: %v", err)
}
}
return nil
}
// CreateOrUpdateClusterRoleBinding creates a ClusterRoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateClusterRoleBinding(client clientset.Interface, clusterRoleBinding *rbac.ClusterRoleBinding) error {
if _, err := client.RbacV1().ClusterRoleBindings().Create(clusterRoleBinding); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create RBAC clusterrolebinding: %v", err)
}
if _, err := client.RbacV1().ClusterRoleBindings().Update(clusterRoleBinding); err != nil {
return fmt.Errorf("unable to update RBAC clusterrolebinding: %v", err)
}
}
return nil
}
// PatchNode tries to patch a node using the following client, executing patchFn for the actual mutating logic
func PatchNode(client clientset.Interface, nodeName string, patchFn func(*v1.Node)) error {
// Loop on every false return. Return with an error if raised. Exit successfully if true is returned.
return wait.Poll(constants.APICallRetryInterval, constants.PatchNodeTimeout, func() (bool, error) {
// First get the node object
n, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
if err != nil {
return false, nil
}
// The node may appear to have no labels at first,
// so we wait for it to get hostname label.
if _, found := n.ObjectMeta.Labels[kubeletapis.LabelHostname]; !found {
return false, nil
}
oldData, err := json.Marshal(n)
if err != nil {
return false, err
}
// Execute the mutating function
patchFn(n)
newData, err := json.Marshal(n)
if err != nil {
return false, err
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{})
if err != nil {
return false, err
}
if _, err := client.CoreV1().Nodes().Patch(n.Name, types.StrategicMergePatchType, patchBytes); err != nil {
if apierrors.IsConflict(err) {
fmt.Println("[patchnode] Temporarily unable to update node metadata due to conflict (will retry)")
return false, nil
}
return false, err
}
return true, nil
})
}

View File

@ -1,160 +0,0 @@
/*
Copyright 2017 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 apiclient
import (
"fmt"
"net"
"strings"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
core "k8s.io/client-go/testing"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
)
// InitDryRunGetter implements the DryRunGetter interface and can be used to GET/LIST values in the dryrun fake clientset
// Need to handle these routes in a special manner:
// - GET /default/services/kubernetes -- must return a valid Service
// - GET /clusterrolebindings/system:nodes -- can safely return a NotFound error
// - GET /kube-system/secrets/bootstrap-token-* -- can safely return a NotFound error
// - GET /nodes/<node-name> -- must return a valid Node
// - ...all other, unknown GETs/LISTs will be logged
type InitDryRunGetter struct {
masterName string
serviceSubnet string
}
// InitDryRunGetter should implement the DryRunGetter interface
var _ DryRunGetter = &InitDryRunGetter{}
// NewInitDryRunGetter creates a new instance of the InitDryRunGetter struct
func NewInitDryRunGetter(masterName string, serviceSubnet string) *InitDryRunGetter {
return &InitDryRunGetter{
masterName: masterName,
serviceSubnet: serviceSubnet,
}
}
// HandleGetAction handles GET actions to the dryrun clientset this interface supports
func (idr *InitDryRunGetter) HandleGetAction(action core.GetAction) (bool, runtime.Object, error) {
funcs := []func(core.GetAction) (bool, runtime.Object, error){
idr.handleKubernetesService,
idr.handleGetNode,
idr.handleSystemNodesClusterRoleBinding,
idr.handleGetBootstrapToken,
}
for _, f := range funcs {
handled, obj, err := f(action)
if handled {
return handled, obj, err
}
}
return false, nil, nil
}
// HandleListAction handles GET actions to the dryrun clientset this interface supports.
// Currently there are no known LIST calls during kubeadm init this code has to take care of.
func (idr *InitDryRunGetter) HandleListAction(action core.ListAction) (bool, runtime.Object, error) {
return false, nil, nil
}
// handleKubernetesService returns a faked Kubernetes service in order to be able to continue running kubeadm init.
// The kube-dns addon code GETs the kubernetes service in order to extract the service subnet
func (idr *InitDryRunGetter) handleKubernetesService(action core.GetAction) (bool, runtime.Object, error) {
if action.GetName() != "kubernetes" || action.GetNamespace() != metav1.NamespaceDefault || action.GetResource().Resource != "services" {
// We can't handle this event
return false, nil, nil
}
_, svcSubnet, err := net.ParseCIDR(idr.serviceSubnet)
if err != nil {
return true, nil, fmt.Errorf("error parsing CIDR %q: %v", idr.serviceSubnet, err)
}
internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(svcSubnet, 1)
if err != nil {
return true, nil, fmt.Errorf("unable to get first IP address from the given CIDR (%s): %v", svcSubnet.String(), err)
}
// The only used field of this Service object is the ClusterIP, which kube-dns uses to calculate its own IP
return true, &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "kubernetes",
Namespace: metav1.NamespaceDefault,
Labels: map[string]string{
"component": "apiserver",
"provider": "kubernetes",
},
},
Spec: v1.ServiceSpec{
ClusterIP: internalAPIServerVirtualIP.String(),
Ports: []v1.ServicePort{
{
Name: "https",
Port: 443,
TargetPort: intstr.FromInt(6443),
},
},
},
}, nil
}
// handleGetNode returns a fake node object for the purpose of moving kubeadm init forwards.
func (idr *InitDryRunGetter) handleGetNode(action core.GetAction) (bool, runtime.Object, error) {
if action.GetName() != idr.masterName || action.GetResource().Resource != "nodes" {
// We can't handle this event
return false, nil, nil
}
return true, &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: idr.masterName,
Labels: map[string]string{
"kubernetes.io/hostname": idr.masterName,
},
Annotations: map[string]string{},
},
}, nil
}
// handleSystemNodesClusterRoleBinding handles the GET call to the system:nodes clusterrolebinding
func (idr *InitDryRunGetter) handleSystemNodesClusterRoleBinding(action core.GetAction) (bool, runtime.Object, error) {
if action.GetName() != constants.NodesClusterRoleBinding || action.GetResource().Resource != "clusterrolebindings" {
// We can't handle this event
return false, nil, nil
}
// We can safely return a NotFound error here as the code will just proceed normally and don't care about modifying this clusterrolebinding
// This can only happen on an upgrade; and in that case the ClientBackedDryRunGetter impl will be used
return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), "clusterrolebinding not found")
}
// handleGetBootstrapToken handles the case where kubeadm init creates the default token; and the token code GETs the
// bootstrap token secret first in order to check if it already exists
func (idr *InitDryRunGetter) handleGetBootstrapToken(action core.GetAction) (bool, runtime.Object, error) {
if !strings.HasPrefix(action.GetName(), "bootstrap-token-") || action.GetNamespace() != metav1.NamespaceSystem || action.GetResource().Resource != "secrets" {
// We can't handle this event
return false, nil, nil
}
// We can safely return a NotFound error here as the code will just proceed normally and create the Bootstrap Token
return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), "secret not found")
}

View File

@ -1,126 +0,0 @@
/*
Copyright 2017 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 apiclient
import (
"bytes"
"encoding/json"
"testing"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
core "k8s.io/client-go/testing"
)
func TestHandleGetAction(t *testing.T) {
masterName := "master-foo"
serviceSubnet := "10.96.0.1/12"
idr := NewInitDryRunGetter(masterName, serviceSubnet)
var tests = []struct {
action core.GetActionImpl
expectedHandled bool
expectedObjectJSON []byte
expectedErr bool
}{
{
action: core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "services"}, "default", "kubernetes"),
expectedHandled: true,
expectedObjectJSON: []byte(`{"metadata":{"name":"kubernetes","namespace":"default","creationTimestamp":null,"labels":{"component":"apiserver","provider":"kubernetes"}},"spec":{"ports":[{"name":"https","port":443,"targetPort":6443}],"clusterIP":"10.96.0.1"},"status":{"loadBalancer":{}}}`),
expectedErr: false,
},
{
action: core.NewRootGetAction(schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, masterName),
expectedHandled: true,
expectedObjectJSON: []byte(`{"metadata":{"name":"master-foo","creationTimestamp":null,"labels":{"kubernetes.io/hostname":"master-foo"}},"spec":{},"status":{"daemonEndpoints":{"kubeletEndpoint":{"Port":0}},"nodeInfo":{"machineID":"","systemUUID":"","bootID":"","kernelVersion":"","osImage":"","containerRuntimeVersion":"","kubeletVersion":"","kubeProxyVersion":"","operatingSystem":"","architecture":""}}}`),
expectedErr: false,
},
{
action: core.NewRootGetAction(schema.GroupVersionResource{Group: rbac.GroupName, Version: rbac.SchemeGroupVersion.Version, Resource: "clusterrolebindings"}, "system:node"),
expectedHandled: true,
expectedObjectJSON: []byte(``),
expectedErr: true, // we expect a NotFound error here
},
{
action: core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, "kube-system", "bootstrap-token-abcdef"),
expectedHandled: true,
expectedObjectJSON: []byte(``),
expectedErr: true, // we expect a NotFound error here
},
{ // an ask for a kubernetes service in the _kube-system_ ns should not be answered
action: core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "services"}, "kube-system", "kubernetes"),
expectedHandled: false,
expectedObjectJSON: []byte(``),
expectedErr: false,
},
{ // an ask for an other service than kubernetes should not be answered
action: core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "services"}, "default", "my-other-service"),
expectedHandled: false,
expectedObjectJSON: []byte(``),
expectedErr: false,
},
{ // an ask for an other node than the master should not be answered
action: core.NewRootGetAction(schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "other-node"),
expectedHandled: false,
expectedObjectJSON: []byte(``),
expectedErr: false,
},
{ // an ask for a secret in any other ns than kube-system should not be answered
action: core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, "default", "bootstrap-token-abcdef"),
expectedHandled: false,
expectedObjectJSON: []byte(``),
expectedErr: false,
},
}
for _, rt := range tests {
handled, obj, actualErr := idr.HandleGetAction(rt.action)
objBytes := []byte(``)
if obj != nil {
var err error
objBytes, err = json.Marshal(obj)
if err != nil {
t.Fatalf("couldn't marshal returned object")
}
}
if handled != rt.expectedHandled {
t.Errorf(
"failed HandleGetAction:\n\texpected handled: %t\n\t actual: %t %v",
rt.expectedHandled,
handled,
rt.action,
)
}
if !bytes.Equal(objBytes, rt.expectedObjectJSON) {
t.Errorf(
"failed HandleGetAction:\n\texpected object: %q\n\t actual: %q",
rt.expectedObjectJSON,
objBytes,
)
}
if (actualErr != nil) != rt.expectedErr {
t.Errorf(
"failed HandleGetAction:\n\texpected error: %t\n\t actual: %t %v",
rt.expectedErr,
(actualErr != nil),
rt.action,
)
}
}
}

View File

@ -1,258 +0,0 @@
/*
Copyright 2017 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 apiclient
import (
"fmt"
"io"
"net/http"
"time"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
netutil "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
// Waiter is an interface for waiting for criteria in Kubernetes to happen
type Waiter interface {
// WaitForAPI waits for the API Server's /healthz endpoint to become "ok"
WaitForAPI() error
// WaitForPodsWithLabel waits for Pods in the kube-system namespace to become Ready
WaitForPodsWithLabel(kvLabel string) error
// WaitForPodToDisappear waits for the given Pod in the kube-system namespace to be deleted
WaitForPodToDisappear(staticPodName string) error
// WaitForStaticPodSingleHash fetches sha256 hash for the control plane static pod
WaitForStaticPodSingleHash(nodeName string, component string) (string, error)
// WaitForStaticPodHashChange waits for the given static pod component's static pod hash to get updated.
// By doing that we can be sure that the kubelet has restarted the given Static Pod
WaitForStaticPodHashChange(nodeName, component, previousHash string) error
// WaitForStaticPodControlPlaneHashes fetches sha256 hashes for the control plane static pods
WaitForStaticPodControlPlaneHashes(nodeName string) (map[string]string, error)
// WaitForHealthyKubelet blocks until the kubelet /healthz endpoint returns 'ok'
WaitForHealthyKubelet(initalTimeout time.Duration, healthzEndpoint string) error
// SetTimeout adjusts the timeout to the specified duration
SetTimeout(timeout time.Duration)
}
// KubeWaiter is an implementation of Waiter that is backed by a Kubernetes client
type KubeWaiter struct {
client clientset.Interface
timeout time.Duration
writer io.Writer
}
// NewKubeWaiter returns a new Waiter object that talks to the given Kubernetes cluster
func NewKubeWaiter(client clientset.Interface, timeout time.Duration, writer io.Writer) Waiter {
return &KubeWaiter{
client: client,
timeout: timeout,
writer: writer,
}
}
// WaitForAPI waits for the API Server's /healthz endpoint to report "ok"
func (w *KubeWaiter) WaitForAPI() error {
start := time.Now()
return wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) {
healthStatus := 0
w.client.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus)
if healthStatus != http.StatusOK {
return false, nil
}
fmt.Printf("[apiclient] All control plane components are healthy after %f seconds\n", time.Since(start).Seconds())
return true, nil
})
}
// WaitForPodsWithLabel will lookup pods with the given label and wait until they are all
// reporting status as running.
func (w *KubeWaiter) WaitForPodsWithLabel(kvLabel string) error {
lastKnownPodNumber := -1
return wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) {
listOpts := metav1.ListOptions{LabelSelector: kvLabel}
pods, err := w.client.CoreV1().Pods(metav1.NamespaceSystem).List(listOpts)
if err != nil {
fmt.Fprintf(w.writer, "[apiclient] Error getting Pods with label selector %q [%v]\n", kvLabel, err)
return false, nil
}
if lastKnownPodNumber != len(pods.Items) {
fmt.Fprintf(w.writer, "[apiclient] Found %d Pods for label selector %s\n", len(pods.Items), kvLabel)
lastKnownPodNumber = len(pods.Items)
}
if len(pods.Items) == 0 {
return false, nil
}
for _, pod := range pods.Items {
if pod.Status.Phase != v1.PodRunning {
return false, nil
}
}
return true, nil
})
}
// WaitForPodToDisappear blocks until it timeouts or gets a "NotFound" response from the API Server when getting the Static Pod in question
func (w *KubeWaiter) WaitForPodToDisappear(podName string) error {
return wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) {
_, err := w.client.CoreV1().Pods(metav1.NamespaceSystem).Get(podName, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
fmt.Printf("[apiclient] The old Pod %q is now removed (which is desired)\n", podName)
return true, nil
}
return false, nil
})
}
// WaitForHealthyKubelet blocks until the kubelet /healthz endpoint returns 'ok'
func (w *KubeWaiter) WaitForHealthyKubelet(initalTimeout time.Duration, healthzEndpoint string) error {
time.Sleep(initalTimeout)
return TryRunCommand(func() error {
client := &http.Client{Transport: netutil.SetOldTransportDefaults(&http.Transport{})}
resp, err := client.Get(healthzEndpoint)
if err != nil {
fmt.Printf("[kubelet-check] It seems like the kubelet isn't running or healthy.\n")
fmt.Printf("[kubelet-check] The HTTP call equal to 'curl -sSL %s' failed with error: %v.\n", healthzEndpoint, err)
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Printf("[kubelet-check] It seems like the kubelet isn't running or healthy.")
fmt.Printf("[kubelet-check] The HTTP call equal to 'curl -sSL %s' returned HTTP code %d\n", healthzEndpoint, resp.StatusCode)
return fmt.Errorf("the kubelet healthz endpoint is unhealthy")
}
return nil
}, 5) // a failureThreshold of five means waiting for a total of 155 seconds
}
// SetTimeout adjusts the timeout to the specified duration
func (w *KubeWaiter) SetTimeout(timeout time.Duration) {
w.timeout = timeout
}
// WaitForStaticPodControlPlaneHashes blocks until it timeouts or gets a hash map for all components and their Static Pods
func (w *KubeWaiter) WaitForStaticPodControlPlaneHashes(nodeName string) (map[string]string, error) {
componentHash := ""
var err error
mirrorPodHashes := map[string]string{}
for _, component := range constants.MasterComponents {
err = wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) {
componentHash, err = getStaticPodSingleHash(w.client, nodeName, component)
if err != nil {
return false, nil
}
return true, nil
})
if err != nil {
return nil, err
}
mirrorPodHashes[component] = componentHash
}
return mirrorPodHashes, nil
}
// WaitForStaticPodSingleHash blocks until it timeouts or gets a hash for a single component and its Static Pod
func (w *KubeWaiter) WaitForStaticPodSingleHash(nodeName string, component string) (string, error) {
componentPodHash := ""
var err error
err = wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) {
componentPodHash, err = getStaticPodSingleHash(w.client, nodeName, component)
if err != nil {
return false, nil
}
return true, nil
})
return componentPodHash, err
}
// WaitForStaticPodHashChange blocks until it timeouts or notices that the Mirror Pod (for the Static Pod, respectively) has changed
// This implicitly means this function blocks until the kubelet has restarted the Static Pod in question
func (w *KubeWaiter) WaitForStaticPodHashChange(nodeName, component, previousHash string) error {
return wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) {
hash, err := getStaticPodSingleHash(w.client, nodeName, component)
if err != nil {
return false, nil
}
// We should continue polling until the UID changes
if hash == previousHash {
return false, nil
}
return true, nil
})
}
// getStaticPodControlPlaneHashes computes hashes for all the control plane's Static Pod resources
func getStaticPodControlPlaneHashes(client clientset.Interface, nodeName string) (map[string]string, error) {
mirrorPodHashes := map[string]string{}
for _, component := range constants.MasterComponents {
hash, err := getStaticPodSingleHash(client, nodeName, component)
if err != nil {
return nil, err
}
mirrorPodHashes[component] = hash
}
return mirrorPodHashes, nil
}
// getStaticSinglePodHash computes hashes for a single Static Pod resource
func getStaticPodSingleHash(client clientset.Interface, nodeName string, component string) (string, error) {
staticPodName := fmt.Sprintf("%s-%s", component, nodeName)
staticPod, err := client.CoreV1().Pods(metav1.NamespaceSystem).Get(staticPodName, metav1.GetOptions{})
if err != nil {
return "", err
}
staticPodHash := staticPod.Annotations[kubetypes.ConfigHashAnnotationKey]
fmt.Printf("Static pod: %s hash: %s\n", staticPodName, staticPodHash)
return staticPodHash, nil
}
// TryRunCommand runs a function a maximum of failureThreshold times, and retries on error. If failureThreshold is hit; the last error is returned
func TryRunCommand(f func() error, failureThreshold int) error {
backoff := wait.Backoff{
Duration: 5 * time.Second,
Factor: 2, // double the timeout for every failure
Steps: failureThreshold,
}
return wait.ExponentialBackoff(backoff, func() (bool, error) {
err := f()
if err != nil {
// Retry until the timeout
return false, nil
}
// The last f() call was a success, return cleanly
return true, nil
})
}

View File

@ -1,111 +0,0 @@
/*
Copyright 2017 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 util
import (
"fmt"
"sort"
"strings"
)
// BuildArgumentListFromMap takes two string-string maps, one with the base arguments and one
// with optional override arguments. In the return list override arguments will precede base
// arguments
func BuildArgumentListFromMap(baseArguments map[string]string, overrideArguments map[string]string) []string {
var command []string
var keys []string
for k := range overrideArguments {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := overrideArguments[k]
// values of "" are allowed as well
command = append(command, fmt.Sprintf("--%s=%s", k, v))
}
keys = []string{}
for k := range baseArguments {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := baseArguments[k]
if _, overrideExists := overrideArguments[k]; !overrideExists {
command = append(command, fmt.Sprintf("--%s=%s", k, v))
}
}
return command
}
// ParseArgumentListToMap parses a CLI argument list in the form "--foo=bar" to a string-string map
func ParseArgumentListToMap(arguments []string) map[string]string {
resultingMap := map[string]string{}
for i, arg := range arguments {
key, val, err := parseArgument(arg)
// Ignore if the first argument doesn't satisfy the criteria, it's most often the binary name
// Warn in all other cases, but don't error out. This can happen only if the user has edited the argument list by hand, so they might know what they are doing
if err != nil {
if i != 0 {
fmt.Printf("[kubeadm] WARNING: The component argument %q could not be parsed correctly. The argument must be of the form %q. Skipping...", arg, "--")
}
continue
}
resultingMap[key] = val
}
return resultingMap
}
// ReplaceArgument gets a command list; converts it to a map for easier modification, runs the provided function that
// returns a new modified map, and then converts the map back to a command string slice
func ReplaceArgument(command []string, argMutateFunc func(map[string]string) map[string]string) []string {
argMap := ParseArgumentListToMap(command)
// Save the first command (the executable) if we're sure it's not an argument (i.e. no --)
var newCommand []string
if len(command) > 0 && !strings.HasPrefix(command[0], "--") {
newCommand = append(newCommand, command[0])
}
newArgMap := argMutateFunc(argMap)
newCommand = append(newCommand, BuildArgumentListFromMap(newArgMap, map[string]string{})...)
return newCommand
}
// parseArgument parses the argument "--foo=bar" to "foo" and "bar"
func parseArgument(arg string) (string, string, error) {
if !strings.HasPrefix(arg, "--") {
return "", "", fmt.Errorf("the argument should start with '--'")
}
if !strings.Contains(arg, "=") {
return "", "", fmt.Errorf("the argument should have a '=' between the flag and the value")
}
// Remove the starting --
arg = strings.TrimPrefix(arg, "--")
// Split the string on =. Return only two substrings, since we want only key/value, but the value can include '=' as well
keyvalSlice := strings.SplitN(arg, "=", 2)
// Make sure both a key and value is present
if len(keyvalSlice) != 2 {
return "", "", fmt.Errorf("the argument must have both a key and a value")
}
if len(keyvalSlice[0]) == 0 {
return "", "", fmt.Errorf("the argument must have a key")
}
return keyvalSlice[0], keyvalSlice[1], nil
}

View File

@ -1,339 +0,0 @@
/*
Copyright 2017 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 util
import (
"reflect"
"sort"
"testing"
)
func TestBuildArgumentListFromMap(t *testing.T) {
var tests = []struct {
base map[string]string
overrides map[string]string
expected []string
}{
{ // override an argument from the base
base: map[string]string{
"admission-control": "NamespaceLifecycle",
"insecure-bind-address": "127.0.0.1",
"allow-privileged": "true",
},
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--allow-privileged=true",
"--insecure-bind-address=127.0.0.1",
},
},
{ // add an argument that is not in base
base: map[string]string{
"insecure-bind-address": "127.0.0.1",
"allow-privileged": "true",
},
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--allow-privileged=true",
"--insecure-bind-address=127.0.0.1",
},
},
{ // allow empty strings in base
base: map[string]string{
"insecure-bind-address": "127.0.0.1",
"allow-privileged": "true",
"something-that-allows-empty-string": "",
},
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--allow-privileged=true",
"--insecure-bind-address=127.0.0.1",
"--something-that-allows-empty-string=",
},
},
{ // allow empty strings in overrides
base: map[string]string{
"insecure-bind-address": "127.0.0.1",
"allow-privileged": "true",
"something-that-allows-empty-string": "foo",
},
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
"something-that-allows-empty-string": "",
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--something-that-allows-empty-string=",
"--allow-privileged=true",
"--insecure-bind-address=127.0.0.1",
},
},
}
for _, rt := range tests {
actual := BuildArgumentListFromMap(rt.base, rt.overrides)
if !reflect.DeepEqual(actual, rt.expected) {
t.Errorf("failed BuildArgumentListFromMap:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
}
}
}
func TestParseArgumentListToMap(t *testing.T) {
var tests = []struct {
args []string
expectedMap map[string]string
}{
{
// normal case
args: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
},
expectedMap: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
"insecure-bind-address": "127.0.0.1",
"allow-privileged": "true",
},
},
{
// test that feature-gates is working
args: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
"--feature-gates=EnableFoo=true,EnableBar=false",
},
expectedMap: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
"insecure-bind-address": "127.0.0.1",
"allow-privileged": "true",
"feature-gates": "EnableFoo=true,EnableBar=false",
},
},
{
// test that a binary can be the first arg
args: []string{
"kube-apiserver",
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
"--feature-gates=EnableFoo=true,EnableBar=false",
},
expectedMap: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
"insecure-bind-address": "127.0.0.1",
"allow-privileged": "true",
"feature-gates": "EnableFoo=true,EnableBar=false",
},
},
}
for _, rt := range tests {
actualMap := ParseArgumentListToMap(rt.args)
if !reflect.DeepEqual(actualMap, rt.expectedMap) {
t.Errorf("failed ParseArgumentListToMap:\nexpected:\n%v\nsaw:\n%v", rt.expectedMap, actualMap)
}
}
}
func TestReplaceArgument(t *testing.T) {
var tests = []struct {
args []string
mutateFunc func(map[string]string) map[string]string
expectedArgs []string
}{
{
// normal case
args: []string{
"kube-apiserver",
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
},
mutateFunc: func(argMap map[string]string) map[string]string {
argMap["admission-control"] = "NamespaceLifecycle,LimitRanger,ResourceQuota"
return argMap
},
expectedArgs: []string{
"kube-apiserver",
"--admission-control=NamespaceLifecycle,LimitRanger,ResourceQuota",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
},
},
{
// normal case
args: []string{
"kube-apiserver",
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
},
mutateFunc: func(argMap map[string]string) map[string]string {
argMap["new-arg-here"] = "foo"
return argMap
},
expectedArgs: []string{
"kube-apiserver",
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
"--new-arg-here=foo",
},
},
}
for _, rt := range tests {
actualArgs := ReplaceArgument(rt.args, rt.mutateFunc)
sort.Strings(actualArgs)
sort.Strings(rt.expectedArgs)
if !reflect.DeepEqual(actualArgs, rt.expectedArgs) {
t.Errorf("failed ReplaceArgument:\nexpected:\n%v\nsaw:\n%v", rt.expectedArgs, actualArgs)
}
}
}
func TestRoundtrip(t *testing.T) {
var tests = []struct {
args []string
}{
{
// normal case
args: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
},
},
{
// test that feature-gates is working
args: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
"--feature-gates=EnableFoo=true,EnableBar=false",
},
},
}
for _, rt := range tests {
// These two methods should be each other's opposite functions, test that by chaining the methods and see if you get the same result back
actual := BuildArgumentListFromMap(ParseArgumentListToMap(rt.args), map[string]string{})
sort.Strings(actual)
sort.Strings(rt.args)
if !reflect.DeepEqual(actual, rt.args) {
t.Errorf("failed TestRoundtrip:\nexpected:\n%v\nsaw:\n%v", rt.args, actual)
}
}
}
func TestParseArgument(t *testing.T) {
var tests = []struct {
arg string
expectedKey string
expectedVal string
expectedErr bool
}{
{
// cannot be empty
arg: "",
expectedErr: true,
},
{
// must contain -- and =
arg: "a",
expectedErr: true,
},
{
// must contain -- and =
arg: "a-z",
expectedErr: true,
},
{
// must contain --
arg: "a=b",
expectedErr: true,
},
{
// must contain a key
arg: "--=b",
expectedErr: true,
},
{
// can contain key but no value
arg: "--a=",
expectedKey: "a",
expectedVal: "",
expectedErr: false,
},
{
// simple case
arg: "--a=b",
expectedKey: "a",
expectedVal: "b",
expectedErr: false,
},
{
// keys/values with '-' should be supported
arg: "--very-long-flag-name=some-value",
expectedKey: "very-long-flag-name",
expectedVal: "some-value",
expectedErr: false,
},
{
// numbers should be handled correctly
arg: "--some-number=0.2",
expectedKey: "some-number",
expectedVal: "0.2",
expectedErr: false,
},
{
// lists should be handled correctly
arg: "--admission-control=foo,bar,baz",
expectedKey: "admission-control",
expectedVal: "foo,bar,baz",
expectedErr: false,
},
{
// more than one '=' should be allowed
arg: "--feature-gates=EnableFoo=true,EnableBar=false",
expectedKey: "feature-gates",
expectedVal: "EnableFoo=true,EnableBar=false",
expectedErr: false,
},
}
for _, rt := range tests {
key, val, actual := parseArgument(rt.arg)
if (actual != nil) != rt.expectedErr {
t.Errorf("failed parseArgument:\nexpected error:\n%t\nsaw error:\n%v", rt.expectedErr, actual)
}
if (key != rt.expectedKey) || (val != rt.expectedVal) {
t.Errorf("failed parseArgument:\nexpected key: %s\nsaw key: %s\nexpected value: %s\nsaw value: %s", rt.expectedKey, key, rt.expectedVal, val)
}
}
}

View File

@ -1,42 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["utils.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/audit",
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/util:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["utils_test.go"],
embed = [":go_default_library"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -1,73 +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 audit
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/apis/audit/install"
auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/util"
)
// CreateDefaultAuditLogPolicy writes the default audit log policy to disk.
func CreateDefaultAuditLogPolicy(policyFile string) error {
policy := auditv1beta1.Policy{
TypeMeta: metav1.TypeMeta{
APIVersion: auditv1beta1.SchemeGroupVersion.String(),
Kind: "Policy",
},
Rules: []auditv1beta1.PolicyRule{
{
Level: auditv1beta1.LevelMetadata,
},
},
}
return writePolicyToDisk(policyFile, &policy)
}
func writePolicyToDisk(policyFile string, policy *auditv1beta1.Policy) error {
// creates target folder if not already exists
if err := os.MkdirAll(filepath.Dir(policyFile), 0700); err != nil {
return fmt.Errorf("failed to create directory %q: %v", filepath.Dir(policyFile), err)
}
scheme := runtime.NewScheme()
// Registers the API group with the scheme and adds types to a scheme
install.Install(scheme)
codecs := serializer.NewCodecFactory(scheme)
// writes the policy to disk
serialized, err := util.MarshalToYamlForCodecs(policy, auditv1beta1.SchemeGroupVersion, codecs)
if err != nil {
return fmt.Errorf("failed to marshal audit policy to YAML: %v", err)
}
if err := ioutil.WriteFile(policyFile, serialized, 0600); err != nil {
return fmt.Errorf("failed to write audit policy to %v: %v", policyFile, err)
}
return nil
}

View File

@ -1,65 +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 audit
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/apis/audit/install"
auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
)
func cleanup(t *testing.T, path string) {
err := os.RemoveAll(path)
if err != nil {
t.Fatalf("Failed to clean up %v: %v", path, err)
}
}
func TestCreateDefaultAuditLogPolicy(t *testing.T) {
// make a tempdir
tempDir, err := ioutil.TempDir("/tmp", "audit-test")
if err != nil {
t.Fatalf("could not create a tempdir: %v", err)
}
defer cleanup(t, tempDir)
auditPolicyFile := filepath.Join(tempDir, "test.yaml")
if err = CreateDefaultAuditLogPolicy(auditPolicyFile); err != nil {
t.Fatalf("failed to create audit log policy: %v", err)
}
// turn the audit log back into a policy
policyBytes, err := ioutil.ReadFile(auditPolicyFile)
if err != nil {
t.Fatalf("failed to read %v: %v", auditPolicyFile, err)
}
scheme := runtime.NewScheme()
install.Install(scheme)
codecs := serializer.NewCodecFactory(scheme)
policy := auditv1beta1.Policy{}
err = runtime.DecodeInto(codecs.UniversalDecoder(), policyBytes, &policy)
if err != nil {
t.Fatalf("failed to decode written policy: %v", err)
}
if policy.Kind != "Policy" {
t.Fatalf("did not decode policy properly")
}
}

View File

@ -1,75 +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 util
import (
"fmt"
"strings"
utilsexec "k8s.io/utils/exec"
)
// TODO: add support for detecting the cgroup driver for CRI other than
// Docker. Currently only Docker driver detection is supported:
// Discussion:
// https://github.com/kubernetes/kubeadm/issues/844
// GetCgroupDriverDocker runs 'docker info' to obtain the docker cgroup driver
func GetCgroupDriverDocker(execer utilsexec.Interface) (string, error) {
info, err := callDockerInfo(execer)
if err != nil {
return "", err
}
return getCgroupDriverFromDockerInfo(info)
}
func validateCgroupDriver(driver string) error {
if driver != "cgroupfs" && driver != "systemd" {
return fmt.Errorf("unknown cgroup driver %q", driver)
}
return nil
}
// TODO: Docker 1.13 has a new way to obatain the cgroup driver:
// docker info -f "{{.CgroupDriver}}
// If the minimum supported Docker version in K8s becomes 1.13, move to
// this syntax.
func callDockerInfo(execer utilsexec.Interface) (string, error) {
out, err := execer.Command("docker", "info").Output()
if err != nil {
return "", fmt.Errorf("cannot execute 'docker info': %v", err)
}
return string(out), nil
}
func getCgroupDriverFromDockerInfo(info string) (string, error) {
lineSeparator := ": "
prefix := "Cgroup Driver"
for _, line := range strings.Split(info, "\n") {
if !strings.Contains(line, prefix+lineSeparator) {
continue
}
lineSplit := strings.Split(line, lineSeparator)
// At this point len(lineSplit) is always >= 2
driver := lineSplit[1]
if err := validateCgroupDriver(driver); err != nil {
return "", err
}
return driver, nil
}
return "", fmt.Errorf("cgroup driver is not defined in 'docker info'")
}

View File

@ -1,68 +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 util
import (
"testing"
)
func TestGetCgroupDriverDocker(t *testing.T) {
testCases := []struct {
name string
info string
expectedError bool
}{
{
name: "valid: value is 'cgroupfs'",
info: `Cgroup Driver: cgroupfs`,
expectedError: false,
},
{
name: "valid: value is 'systemd'",
info: `Cgroup Driver: systemd`,
expectedError: false,
},
{
name: "invalid: missing 'Cgroup Driver' key and value",
info: "",
expectedError: true,
},
{
name: "invalid: only a 'Cgroup Driver' key is present",
info: `Cgroup Driver`,
expectedError: true,
},
{
name: "invalid: empty 'Cgroup Driver' value",
info: `Cgroup Driver: `,
expectedError: true,
},
{
name: "invalid: unknown 'Cgroup Driver' value",
info: `Cgroup Driver: invalid-value`,
expectedError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if _, err := getCgroupDriverFromDockerInfo(tc.info); (err != nil) != tc.expectedError {
t.Fatalf("expected error: %v, saw: %v, error: %v", tc.expectedError, (err != nil), err)
}
})
}
}

View File

@ -1,70 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"cluster.go",
"masterconfig.go",
"nodeconfig.go",
],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/config",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/util:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"masterconfig_test.go",
"nodeconfig_test.go",
],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//vendor/github.com/pmezard/go-difflib/difflib:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,65 +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 config
import (
"fmt"
"io"
"io/ioutil"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
// TODO: Add unit tests for this file
// FetchConfigFromFileOrCluster fetches configuration required for upgrading your cluster from a file (which has precedence) or a ConfigMap in the cluster
func FetchConfigFromFileOrCluster(client clientset.Interface, w io.Writer, logPrefix, cfgPath string) (*kubeadmapi.MasterConfiguration, error) {
// Load the configuration from a file or the cluster
configBytes, err := loadConfigurationBytes(client, w, logPrefix, cfgPath)
if err != nil {
return nil, err
}
// Take the versioned configuration populated from the file or ConfigMap, convert it to internal, default and validate
return BytesToInternalConfig(configBytes)
}
// loadConfigurationBytes loads the configuration byte slice from either a file or the cluster ConfigMap
func loadConfigurationBytes(client clientset.Interface, w io.Writer, logPrefix, cfgPath string) ([]byte, error) {
// The config file has the highest priority
if cfgPath != "" {
fmt.Fprintf(w, "[%s] Reading configuration options from a file: %s\n", logPrefix, cfgPath)
return ioutil.ReadFile(cfgPath)
}
fmt.Fprintf(w, "[%s] Reading configuration from the cluster...\n", logPrefix)
configMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(constants.MasterConfigurationConfigMap, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
// Return the apierror directly so the caller of this function can know what type of error occurred and act based on that
return []byte{}, err
} else if err != nil {
return []byte{}, fmt.Errorf("an unexpected error happened when trying to get the ConfigMap %q in the %s namespace: %v", constants.MasterConfigurationConfigMap, metav1.NamespaceSystem, err)
}
fmt.Fprintf(w, "[%s] FYI: You can look at this config file with 'kubectl -n %s get cm %s -oyaml'\n", logPrefix, metav1.NamespaceSystem, constants.MasterConfigurationConfigMap)
return []byte(configMap.Data[constants.MasterConfigurationConfigMapKey]), nil
}

View File

@ -1,232 +0,0 @@
/*
Copyright 2017 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"
"io/ioutil"
"net"
"strings"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
netutil "k8s.io/apimachinery/pkg/util/net"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/pkg/util/node"
"k8s.io/kubernetes/pkg/util/version"
)
// SetInitDynamicDefaults checks and sets configuration values for the MasterConfiguration object
func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
// validate cfg.API.AdvertiseAddress.
addressIP := net.ParseIP(cfg.API.AdvertiseAddress)
if addressIP == nil && cfg.API.AdvertiseAddress != "" {
return fmt.Errorf("couldn't use \"%s\" as \"apiserver-advertise-address\", must be ipv4 or ipv6 address", cfg.API.AdvertiseAddress)
}
// Choose the right address for the API Server to advertise. If the advertise address is localhost or 0.0.0.0, the default interface's IP address is used
// This is the same logic as the API Server uses
ip, err := netutil.ChooseBindAddress(addressIP)
if err != nil {
return err
}
cfg.API.AdvertiseAddress = ip.String()
ip = net.ParseIP(cfg.API.AdvertiseAddress)
if ip.To4() != nil {
cfg.KubeProxy.Config.BindAddress = kubeadmapiv1alpha2.DefaultProxyBindAddressv4
} else {
cfg.KubeProxy.Config.BindAddress = kubeadmapiv1alpha2.DefaultProxyBindAddressv6
}
// Resolve possible version labels and validate version string
if err := NormalizeKubernetesVersion(cfg); err != nil {
return err
}
// Downcase SANs. Some domain names (like ELBs) have capitals in them.
LowercaseSANs(cfg.APIServerCertSANs)
// Populate the .Token field with a random value if unset
// We do this at this layer, and not the API defaulting layer
// because of possible security concerns, and more practically
// because we can't return errors in the API object defaulting
// process but here we can.
for i, bt := range cfg.BootstrapTokens {
if bt.Token != nil && len(bt.Token.String()) > 0 {
continue
}
tokenStr, err := bootstraputil.GenerateBootstrapToken()
if err != nil {
return fmt.Errorf("couldn't generate random token: %v", err)
}
token, err := kubeadmapi.NewBootstrapTokenString(tokenStr)
if err != nil {
return err
}
cfg.BootstrapTokens[i].Token = token
}
cfg.NodeRegistration.Name = node.GetHostname(cfg.NodeRegistration.Name)
// Only if the slice is nil, we should append the master taint. This allows the user to specify an empty slice for no default master taint
if cfg.NodeRegistration.Taints == nil {
cfg.NodeRegistration.Taints = []v1.Taint{kubeadmconstants.MasterTaint}
}
return nil
}
// ConfigFileAndDefaultsToInternalConfig takes a path to a config file and a versioned configuration that can serve as the default config
// If cfgPath is specified, defaultversionedcfg will always get overridden. Otherwise, the default config (often populated by flags) will be used.
// Then the external, versioned configuration is defaulted and converted to the internal type.
// Right thereafter, the configuration is defaulted again with dynamic values (like IP addresses of a machine, etc)
// Lastly, the internal config is validated and returned.
func ConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg *kubeadmapiv1alpha2.MasterConfiguration) (*kubeadmapi.MasterConfiguration, error) {
internalcfg := &kubeadmapi.MasterConfiguration{}
if cfgPath != "" {
// Loads configuration from config file, if provided
// Nb. --config overrides command line flags
glog.V(1).Infoln("loading configuration from the given file")
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
}
return BytesToInternalConfig(b)
}
// Takes passed flags into account; the defaulting is executed once again enforcing assignement of
// static default values to cfg only for values not provided with flags
kubeadmscheme.Scheme.Default(defaultversionedcfg)
kubeadmscheme.Scheme.Convert(defaultversionedcfg, internalcfg, nil)
return defaultAndValidate(internalcfg)
}
// BytesToInternalConfig converts a byte array to an internal, defaulted and validated configuration object
func BytesToInternalConfig(b []byte) (*kubeadmapi.MasterConfiguration, error) {
internalcfg := &kubeadmapi.MasterConfiguration{}
decoded, err := kubeadmutil.LoadYAML(b)
if err != nil {
return nil, fmt.Errorf("unable to decode config from bytes: %v", err)
}
// As there was a bug in kubeadm v1.10 and earlier that made the YAML uploaded to the cluster configmap NOT have metav1.TypeMeta information
// we need to populate this here manually. If kind or apiVersion is empty, we know the apiVersion is v1alpha1, as by the time kubeadm had this bug,
// it could only write
// TODO: Remove this "hack" in v1.12 when we know the ConfigMap always contains v1alpha2 content written by kubeadm v1.11. Also, we will drop support for
// v1alpha1 in v1.12
kind := decoded["kind"]
apiVersion := decoded["apiVersion"]
if kind == nil || len(kind.(string)) == 0 {
decoded["kind"] = "MasterConfiguration"
}
if apiVersion == nil || len(apiVersion.(string)) == 0 {
decoded["apiVersion"] = kubeadmapiv1alpha1.SchemeGroupVersion.String()
}
// Between v1.9 and v1.10 the proxy componentconfig in the v1alpha1 MasterConfiguration changed unexpectedly, which broke unmarshalling out-of-the-box
// Hence, we need to workaround this bug in the v1alpha1 API
if decoded["apiVersion"] == kubeadmapiv1alpha1.SchemeGroupVersion.String() {
v1alpha1cfg := &kubeadmapiv1alpha1.MasterConfiguration{}
if err := kubeadmapiv1alpha1.Migrate(decoded, v1alpha1cfg, kubeadmscheme.Codecs); err != nil {
return nil, fmt.Errorf("unable to migrate config from previous version: %v", err)
}
// Default and convert to the internal version
kubeadmscheme.Scheme.Default(v1alpha1cfg)
kubeadmscheme.Scheme.Convert(v1alpha1cfg, internalcfg, nil)
} else if decoded["apiVersion"] == kubeadmapiv1alpha2.SchemeGroupVersion.String() {
v1alpha2cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), b, v1alpha2cfg); err != nil {
return nil, fmt.Errorf("unable to decode config: %v", err)
}
// Default and convert to the internal version
kubeadmscheme.Scheme.Default(v1alpha2cfg)
kubeadmscheme.Scheme.Convert(v1alpha2cfg, internalcfg, nil)
} else {
// TODO: Add support for an upcoming v1alpha2 API
// TODO: In the future, we can unmarshal any two or more external types into the internal object directly using the following syntax.
// Long-term we don't need this if/else clause. In the future this will do
// runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmapiv2alpha3.SchemeGroupVersion), b, internalcfg)
return nil, fmt.Errorf("unknown API version for kubeadm configuration")
}
return defaultAndValidate(internalcfg)
}
func defaultAndValidate(cfg *kubeadmapi.MasterConfiguration) (*kubeadmapi.MasterConfiguration, error) {
// Applies dynamic defaults to settings not provided with flags
if err := SetInitDynamicDefaults(cfg); err != nil {
return nil, err
}
// Validates cfg (flags/configs + defaults + dynamic defaults)
if err := validation.ValidateMasterConfiguration(cfg).ToAggregate(); err != nil {
return nil, err
}
return cfg, nil
}
// NormalizeKubernetesVersion resolves version labels, sets alternative
// image registry if requested for CI builds, and validates minimal
// version that kubeadm supports.
func NormalizeKubernetesVersion(cfg *kubeadmapi.MasterConfiguration) error {
// Requested version is automatic CI build, thus use KubernetesCI Image Repository for core images
if kubeadmutil.KubernetesIsCIVersion(cfg.KubernetesVersion) {
cfg.CIImageRepository = kubeadmconstants.DefaultCIImageRepository
}
// Parse and validate the version argument and resolve possible CI version labels
ver, err := kubeadmutil.KubernetesReleaseVersion(cfg.KubernetesVersion)
if err != nil {
return err
}
cfg.KubernetesVersion = ver
// Parse the given kubernetes version and make sure it's higher than the lowest supported
k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion)
if err != nil {
return fmt.Errorf("couldn't parse kubernetes version %q: %v", cfg.KubernetesVersion, err)
}
if k8sVersion.LessThan(kubeadmconstants.MinimumControlPlaneVersion) {
return fmt.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s", kubeadmconstants.MinimumControlPlaneVersion.String(), cfg.KubernetesVersion)
}
return nil
}
// LowercaseSANs can be used to force all SANs to be lowercase so it passes IsDNS1123Subdomain
func LowercaseSANs(sans []string) {
for i, san := range sans {
lowercase := strings.ToLower(san)
if lowercase != san {
glog.V(1).Infof("lowercasing SAN %q to %q", san, lowercase)
sans[i] = lowercase
}
}
}

View File

@ -1,225 +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 config
import (
"bytes"
"io/ioutil"
"testing"
"github.com/pmezard/go-difflib/difflib"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
const (
master_v1alpha1YAML = "testdata/conversion/master/v1alpha1.yaml"
master_v1alpha1WithoutTypeMetaYAML = "testdata/conversion/master/v1alpha1_without_TypeMeta.yaml"
master_v1alpha2YAML = "testdata/conversion/master/v1alpha2.yaml"
master_internalYAML = "testdata/conversion/master/internal.yaml"
master_incompleteYAML = "testdata/defaulting/master/incomplete.yaml"
master_defaultedYAML = "testdata/defaulting/master/defaulted.yaml"
master_invalidYAML = "testdata/validation/invalid_mastercfg.yaml"
master_beforeUpgradeYAML = "testdata/v1alpha1_upgrade/before.yaml"
master_afterUpgradeYAML = "testdata/v1alpha1_upgrade/after.yaml"
)
func diff(expected, actual []byte) string {
// Write out the diff
var diffBytes bytes.Buffer
difflib.WriteUnifiedDiff(&diffBytes, difflib.UnifiedDiff{
A: difflib.SplitLines(string(expected)),
B: difflib.SplitLines(string(actual)),
FromFile: "expected",
ToFile: "actual",
Context: 3,
})
return diffBytes.String()
}
func TestConfigFileAndDefaultsToInternalConfig(t *testing.T) {
var tests = []struct {
name, in, out string
groupVersion schema.GroupVersion
expectedErr bool
}{
// These tests are reading one file, loading it using ConfigFileAndDefaultsToInternalConfig that all of kubeadm is using for unmarshal of our API types,
// and then marshals the internal object to the expected groupVersion
{ // v1alpha1 (faulty) -> internal
name: "v1alpha1WithoutTypeMetaToInternal",
in: master_v1alpha1WithoutTypeMetaYAML,
out: master_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha1 -> internal
name: "v1alpha1ToInternal",
in: master_v1alpha1YAML,
out: master_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha2 -> internal
name: "v1alpha2ToInternal",
in: master_v1alpha2YAML,
out: master_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha1 (faulty) -> internal -> v1alpha2
name: "v1alpha1WithoutTypeMetaTov1alpha2",
in: master_v1alpha1WithoutTypeMetaYAML,
out: master_v1alpha2YAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
{ // v1alpha1 -> internal -> v1alpha2
name: "v1alpha1Tov1alpha2",
in: master_v1alpha1YAML,
out: master_v1alpha2YAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
// These tests are reading one file that has only a subset of the fields populated, loading it using ConfigFileAndDefaultsToInternalConfig,
// and then marshals the internal object to the expected groupVersion
{ // v1alpha1 (faulty) -> default -> validate -> internal -> v1alpha2
name: "incompleteYAMLToDefaultedv1alpha2",
in: master_incompleteYAML,
out: master_defaultedYAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
{ // v1alpha1 (faulty) -> validation should fail
name: "invalidYAMLShouldFail",
in: master_invalidYAML,
expectedErr: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
internalcfg, err := ConfigFileAndDefaultsToInternalConfig(rt.in, &v1alpha2.MasterConfiguration{})
if err != nil {
if rt.expectedErr {
return
}
t2.Fatalf("couldn't unmarshal test data: %v", err)
}
actual, err := kubeadmutil.MarshalToYamlForCodecs(internalcfg, rt.groupVersion, scheme.Codecs)
if err != nil {
t2.Fatalf("couldn't marshal internal object: %v", err)
}
expected, err := ioutil.ReadFile(rt.out)
if err != nil {
t2.Fatalf("couldn't read test data: %v", err)
}
if !bytes.Equal(expected, actual) {
t2.Errorf("the expected and actual output differs.\n\tin: %s\n\tout: %s\n\tgroupversion: %s\n\tdiff: \n%s\n",
rt.in, rt.out, rt.groupVersion.String(), diff(expected, actual))
}
})
}
}
// TestUpgrade tests reading a faulty YAML representation of the MasterConfiguration object (as found in kubeadm clusters <= v1.9.x),
// fixes the problems internally and verifies the marshalled output is the expected output
func TestUpgrade(t *testing.T) {
before, err := ioutil.ReadFile(master_beforeUpgradeYAML)
if err != nil {
t.Fatalf("couldn't read test data: %v", err)
}
afterExpected, err := ioutil.ReadFile(master_afterUpgradeYAML)
if err != nil {
t.Fatalf("couldn't read test data: %v", err)
}
decoded, err := kubeadmutil.LoadYAML(before)
if err != nil {
t.Fatalf("couldn't unmarshal test yaml: %v", err)
}
scheme := runtime.NewScheme()
v1alpha1.AddToScheme(scheme)
codecs := serializer.NewCodecFactory(scheme)
obj := &v1alpha1.MasterConfiguration{}
if err := v1alpha1.Migrate(decoded, obj, codecs); err != nil {
t.Fatalf("couldn't decode migrated object: %v", err)
}
afterActual, err := kubeadmutil.MarshalToYamlForCodecs(obj, v1alpha1.SchemeGroupVersion, codecs)
if err != nil {
t.Fatalf("couldn't marshal object: %v", err)
}
if !bytes.Equal(afterExpected, afterActual) {
t.Errorf("v1alpha1 object after unmarshal, conversion and marshal didn't match expected value.\n\tdiff: \n%s\n", diff(afterExpected, afterActual))
}
}
func TestLowercaseSANs(t *testing.T) {
tests := []struct {
name string
in []string
out []string
}{
{
name: "empty struct",
},
{
name: "already lowercase",
in: []string{"example.k8s.io"},
out: []string{"example.k8s.io"},
},
{
name: "ip addresses and uppercase",
in: []string{"EXAMPLE.k8s.io", "10.100.0.1"},
out: []string{"example.k8s.io", "10.100.0.1"},
},
{
name: "punycode and uppercase",
in: []string{"xn--7gq663byk9a.xn--fiqz9s", "ANOTHEREXAMPLE.k8s.io"},
out: []string{"xn--7gq663byk9a.xn--fiqz9s", "anotherexample.k8s.io"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cfg := &v1alpha2.MasterConfiguration{
APIServerCertSANs: test.in,
}
LowercaseSANs(cfg.APIServerCertSANs)
if len(cfg.APIServerCertSANs) != len(test.out) {
t.Fatalf("expected %d elements, got %d", len(test.out), len(cfg.APIServerCertSANs))
}
for i, expected := range test.out {
if cfg.APIServerCertSANs[i] != expected {
t.Errorf("expected element %d to be %q, got %q", i, expected, cfg.APIServerCertSANs[i])
}
}
})
}
}

View File

@ -1,71 +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 config
import (
"fmt"
"io/ioutil"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/runtime"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/pkg/util/node"
)
// SetJoinDynamicDefaults checks and sets configuration values for the NodeConfiguration object
func SetJoinDynamicDefaults(cfg *kubeadmapi.NodeConfiguration) error {
cfg.NodeRegistration.Name = node.GetHostname(cfg.NodeRegistration.Name)
return nil
}
// NodeConfigFileAndDefaultsToInternalConfig
func NodeConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg *kubeadmapiv1alpha2.NodeConfiguration) (*kubeadmapi.NodeConfiguration, error) {
internalcfg := &kubeadmapi.NodeConfiguration{}
if cfgPath != "" {
// Loads configuration from config file, if provided
// Nb. --config overrides command line flags
glog.V(1).Infoln("loading configuration from the given file")
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
}
runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(kubeadmapiv1alpha1.SchemeGroupVersion, kubeadmapiv1alpha2.SchemeGroupVersion), b, internalcfg)
} else {
// Takes passed flags into account; the defaulting is executed once again enforcing assignement of
// static default values to cfg only for values not provided with flags
kubeadmscheme.Scheme.Default(defaultversionedcfg)
kubeadmscheme.Scheme.Convert(defaultversionedcfg, internalcfg, nil)
}
// Applies dynamic defaults to settings not provided with flags
if err := SetJoinDynamicDefaults(internalcfg); err != nil {
return nil, err
}
// Validates cfg (flags/configs + defaults)
if err := validation.ValidateNodeConfiguration(internalcfg).ToAggregate(); err != nil {
return nil, err
}
return internalcfg, nil
}

View File

@ -1,108 +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 config
import (
"bytes"
"io/ioutil"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
const (
node_v1alpha1YAML = "testdata/conversion/node/v1alpha1.yaml"
node_v1alpha2YAML = "testdata/conversion/node/v1alpha2.yaml"
node_internalYAML = "testdata/conversion/node/internal.yaml"
node_incompleteYAML = "testdata/defaulting/node/incomplete.yaml"
node_defaultedYAML = "testdata/defaulting/node/defaulted.yaml"
node_invalidYAML = "testdata/validation/invalid_nodecfg.yaml"
)
func TestNodeConfigFileAndDefaultsToInternalConfig(t *testing.T) {
var tests = []struct {
name, in, out string
groupVersion schema.GroupVersion
expectedErr bool
}{
// These tests are reading one file, loading it using NodeConfigFileAndDefaultsToInternalConfig that all of kubeadm is using for unmarshal of our API types,
// and then marshals the internal object to the expected groupVersion
{ // v1alpha1 -> internal
name: "v1alpha1ToInternal",
in: node_v1alpha1YAML,
out: node_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha2 -> internal
name: "v1alpha2ToInternal",
in: node_v1alpha2YAML,
out: node_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha1 -> internal -> v1alpha2
name: "v1alpha1WithoutTypeMetaTov1alpha2",
in: node_v1alpha1YAML,
out: node_v1alpha2YAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
// These tests are reading one file that has only a subset of the fields populated, loading it using NodeConfigFileAndDefaultsToInternalConfig,
// and then marshals the internal object to the expected groupVersion
{ // v1alpha1 -> default -> validate -> internal -> v1alpha2
name: "incompleteYAMLToDefaulted",
in: node_incompleteYAML,
out: node_defaultedYAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
{ // v1alpha1 (faulty) -> validation should fail
name: "invalidYAMLShouldFail",
in: node_invalidYAML,
expectedErr: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
internalcfg, err := NodeConfigFileAndDefaultsToInternalConfig(rt.in, &v1alpha2.NodeConfiguration{})
if err != nil {
if rt.expectedErr {
return
}
t2.Fatalf("couldn't unmarshal test data: %v", err)
}
actual, err := kubeadmutil.MarshalToYamlForCodecs(internalcfg, rt.groupVersion, scheme.Codecs)
if err != nil {
t2.Fatalf("couldn't marshal internal object: %v", err)
}
expected, err := ioutil.ReadFile(rt.out)
if err != nil {
t2.Fatalf("couldn't read test data: %v", err)
}
if !bytes.Equal(expected, actual) {
t2.Errorf("the expected and actual output differs.\n\tin: %s\n\tout: %s\n\tgroupversion: %s\n\tdiff: \n%s\n",
rt.in, rt.out, rt.groupVersion.String(), diff(expected, actual))
}
})
}
}

View File

@ -1,161 +0,0 @@
API:
AdvertiseAddress: 192.168.2.2
BindPort: 6443
ControlPlaneEndpoint: ""
APIServerCertSANs: null
APIServerExtraArgs:
authorization-mode: Node,RBAC,Webhook
APIServerExtraVolumes: null
AuditPolicyConfiguration:
LogDir: /var/log/kubernetes/audit
LogMaxAge: 2
Path: ""
BootstrapTokens:
- Description: ""
Expires: null
Groups:
- system:bootstrappers:kubeadm:default-node-token
TTL: 24h0m0s
Token: s73ybu.6tw6wnqgp5z0wb77
Usages:
- signing
- authentication
CIImageRepository: ""
CertificatesDir: /etc/kubernetes/pki
ClusterName: kubernetes
ControllerManagerExtraArgs: null
ControllerManagerExtraVolumes: null
Etcd:
External: null
Local:
DataDir: /var/lib/etcd
ExtraArgs: null
Image: ""
PeerCertSANs: null
ServerCertSANs: null
FeatureGates: null
ImageRepository: k8s.gcr.io
KubeProxy:
Config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates:
ServiceNodeExclusion: true
SupportIPVSProxyMode: true
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
KubeletConfiguration:
BaseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
rotateCertificates: true
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
KubernetesVersion: v1.10.2
Networking:
DNSDomain: cluster.local
PodSubnet: ""
ServiceSubnet: 10.96.0.0/12
NodeRegistration:
CRISocket: /var/run/dockershim.sock
KubeletExtraArgs: null
Name: master-1
Taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
SchedulerExtraArgs: null
SchedulerExtraVolumes: null
UnifiedControlPlaneImage: ""

View File

@ -1,149 +0,0 @@
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
controlPlaneEndpoint: ""
apiVersion: kubeadm.k8s.io/v1alpha1
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
authorizationModes:
- Node
- RBAC
- Webhook
certificatesDir: /etc/kubernetes/pki
cloudProvider: ""
clusterName: kubernetes
criSocket: /var/run/dockershim.sock
etcd:
caFile: ""
certFile: ""
dataDir: /var/lib/etcd
endpoints: null
image: ""
keyFile: ""
imageRepository: k8s.gcr.io
kind: MasterConfiguration
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates:
ServiceNodeExclusion: true
SupportIPVSProxyMode: true
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
kubeletConfiguration:
baseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.local
podSubnet: ""
serviceSubnet: 10.96.0.0/12
nodeName: master-1
privilegedPods: false
token: s73ybu.6tw6wnqgp5z0wb77
tokenGroups:
- system:bootstrappers:kubeadm:default-node-token
tokenTTL: 24h0m0s
tokenUsages:
- signing
- authentication
unifiedControlPlaneImage: ""

View File

@ -1,146 +0,0 @@
# This file don't have TypeMeta set. kubeadm should then unmarshal it as a apiVersion=kubeadm.k8s.io/v1alpha1 and kind=MasterConfiguration
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
controlPlaneEndpoint: ""
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
authorizationModes:
- Node
- RBAC
- Webhook
certificatesDir: /etc/kubernetes/pki
cloudProvider: ""
clusterName: kubernetes
criSocket: /var/run/dockershim.sock
etcd:
caFile: ""
certFile: ""
dataDir: /var/lib/etcd
endpoints: null
image: ""
keyFile: ""
imageRepository: k8s.gcr.io
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates: "SupportIPVSProxyMode=true,ServiceNodeExclusion=true"
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
kubeletConfiguration:
baseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.local
podSubnet: ""
serviceSubnet: 10.96.0.0/12
nodeName: master-1
privilegedPods: false
token: s73ybu.6tw6wnqgp5z0wb77
tokenGroups:
- system:bootstrappers:kubeadm:default-node-token
tokenTTL: 24h0m0s
tokenUsages:
- signing
- authentication
unifiedControlPlaneImage: ""

View File

@ -1,148 +0,0 @@
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
controlPlaneEndpoint: ""
apiServerExtraArgs:
authorization-mode: Node,RBAC,Webhook
apiVersion: kubeadm.k8s.io/v1alpha2
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: s73ybu.6tw6wnqgp5z0wb77
ttl: 24h0m0s
usages:
- signing
- authentication
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
etcd:
local:
dataDir: /var/lib/etcd
image: ""
imageRepository: k8s.gcr.io
kind: MasterConfiguration
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates:
ServiceNodeExclusion: true
SupportIPVSProxyMode: true
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
kubeletConfiguration:
baseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
rotateCertificates: true
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.local
podSubnet: ""
serviceSubnet: 10.96.0.0/12
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master-1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
unifiedControlPlaneImage: ""

View File

@ -1,17 +0,0 @@
CACertPath: /etc/kubernetes/pki/ca.crt
ClusterName: kubernetes
DiscoveryFile: ""
DiscoveryTimeout: 5m0s
DiscoveryToken: abcdef.0123456789abcdef
DiscoveryTokenAPIServers:
- kube-apiserver:6443
DiscoveryTokenCACertHashes: null
DiscoveryTokenUnsafeSkipCAVerification: true
FeatureGates: null
NodeRegistration:
CRISocket: /var/run/dockershim.sock
KubeletExtraArgs: null
Name: master-1
Taints: null
TLSBootstrapToken: abcdef.0123456789abcdef
Token: abcdef.0123456789abcdef

View File

@ -1,14 +0,0 @@
apiVersion: kubeadm.k8s.io/v1alpha1
kind: NodeConfiguration
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes
criSocket: /var/run/dockershim.sock
discoveryFile: ""
discoveryTimeout: 5m0s
discoveryToken: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
nodeName: master-1
tlsBootstrapToken: abcdef.0123456789abcdef
token: abcdef.0123456789abcdef

View File

@ -1,15 +0,0 @@
apiVersion: kubeadm.k8s.io/v1alpha2
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes
discoveryFile: ""
discoveryTimeout: 5m0s
discoveryToken: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
kind: NodeConfiguration
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master-1
tlsBootstrapToken: abcdef.0123456789abcdef
token: abcdef.0123456789abcdef

View File

@ -1,143 +0,0 @@
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
controlPlaneEndpoint: ""
apiVersion: kubeadm.k8s.io/v1alpha2
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: s73ybu.6tw6wnqgp5z0wb77
ttl: 24h0m0s
usages:
- signing
- authentication
certificatesDir: /var/lib/kubernetes/pki
clusterName: kubernetes
etcd:
local:
dataDir: /var/lib/etcd
image: ""
imageRepository: my-company.com
kind: MasterConfiguration
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
kubeletConfiguration:
baseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.192.0.10
clusterDomain: cluster.global
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
rotateCertificates: true
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.global
podSubnet: ""
serviceSubnet: 10.196.0.0/12
nodeRegistration:
criSocket: /var/run/criruntime.sock
name: master-1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
unifiedControlPlaneImage: ""

View File

@ -1,15 +0,0 @@
# This file _should_ set TypeMeta, but at some point earlier we supported deserializing MasterConfigurations without TypeMeta, so we need to support that as long as we
# support the v1alpha1 API. In the meantime kubeadm will treat this as v1alpha1 automatically when unmarshalling.
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
certificatesDir: /var/lib/kubernetes/pki
clusterName: kubernetes
criSocket: /var/run/criruntime.sock
imageRepository: my-company.com
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.global
serviceSubnet: 10.196.0.0/12
nodeName: master-1
token: s73ybu.6tw6wnqgp5z0wb77

View File

@ -1,15 +0,0 @@
apiVersion: kubeadm.k8s.io/v1alpha2
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes
discoveryFile: ""
discoveryTimeout: 5m0s
discoveryToken: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
kind: NodeConfiguration
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: thegopher
tlsBootstrapToken: abcdef.0123456789abcdef
token: abcdef.0123456789abcdef

View File

@ -1,7 +0,0 @@
apiVersion: kubeadm.k8s.io/v1alpha1
kind: NodeConfiguration
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
nodeName: thegopher
token: abcdef.0123456789abcdef

View File

@ -1,73 +0,0 @@
api:
advertiseAddress: 172.31.93.180
bindPort: 6443
controlPlaneEndpoint: ""
apiVersion: kubeadm.k8s.io/v1alpha1
auditPolicy:
logDir: ""
path: ""
authorizationModes:
- Node
- RBAC
certificatesDir: /etc/kubernetes/pki
cloudProvider: aws
etcd:
caFile: ""
certFile: ""
dataDir: /var/lib/etcd
endpoints: null
image: ""
keyFile: ""
imageRepository: gcr.io/google_containers
kind: MasterConfiguration
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: 192.168.0.0/16
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates:
ServiceNodeExclusion: true
SupportIPVSProxyMode: true
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 0s
kubeletConfiguration: {}
kubernetesVersion: v1.9.6
networking:
dnsDomain: cluster.local
podSubnet: 192.168.0.0/16
serviceSubnet: 10.96.0.0/12
nodeName: ip-172-31-93-180.ec2.internal
privilegedPods: false
token: 8d69af.cd3e1c58f6228dfc
tokenTTL: 24h0m0s
unifiedControlPlaneImage: ""

View File

@ -1,64 +0,0 @@
# This MasterConfiguration object is wrong in two ways: it hasn't TypeMeta set, and .kubeProxy.config.featureGates is a string as it was in v1.9
# In v1.10 however, it changed in an inbackwards-compatible way to a map[string]string, so we have to workaround that to unmarshal this object
api:
advertiseAddress: 172.31.93.180
bindPort: 6443
authorizationModes:
- Node
- RBAC
certificatesDir: /etc/kubernetes/pki
cloudProvider: aws
etcd:
caFile: ""
certFile: ""
dataDir: /var/lib/etcd
endpoints: null
image: ""
keyFile: ""
imageRepository: gcr.io/google_containers
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: 192.168.0.0/16
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates: "SupportIPVSProxyMode=true,ServiceNodeExclusion=true"
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpTimeoutMilliseconds: 250ms
kubeletConfiguration: {}
kubernetesVersion: v1.9.6
networking:
dnsDomain: cluster.local
podSubnet: 192.168.0.0/16
serviceSubnet: 10.96.0.0/12
nodeName: ip-172-31-93-180.ec2.internal
token: 8d69af.cd3e1c58f6228dfc
tokenTTL: 24h0m0s
unifiedControlPlaneImage: ""

View File

@ -1,12 +0,0 @@
api:
bindPort: 0
certificatesDir: relativepath
clusterName: kubernetes
criSocket: relativepath
imageRepository: my-company.com
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.GLOBAL
serviceSubnet: 10.196.1000.0/100
nodeName: MASTER
token: s7bu.6tw6wn

View File

@ -1,11 +0,0 @@
apiVersion: kubeadm.k8s.io/v1alpha1
kind: NodeConfiguration
caCertPath: relativepath
criSocket: relativepath
discoveryFile: relativepath
discoveryTimeout: not-a-time
discoveryTokenAPIServers:
- INVALID_URL
discoveryTokenUnsafeSkipCAVerification: false
nodeName: NODE-1
token: invalidtoken

View File

@ -1,31 +0,0 @@
/*
Copyright 2017 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 util
import (
"os/exec"
)
// CopyDir copies the content of a folder
func CopyDir(src string, dst string) error {
cmd := exec.Command("cp", "-r", src, dst)
err := cmd.Run()
if err != nil {
return err
}
return nil
}

View File

@ -1,28 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["dryrun.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun",
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/apiclient:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -1,135 +0,0 @@
/*
Copyright 2017 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 dryrun
import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
)
// FileToPrint represents a temporary file on disk that might want to be aliased when printing
// Useful for things like loading a file from /tmp/ but saying to the user "Would write file foo to /etc/kubernetes/..."
type FileToPrint struct {
RealPath string
PrintPath string
}
// NewFileToPrint makes a new instance of FileToPrint with the specified arguments
func NewFileToPrint(realPath, printPath string) FileToPrint {
return FileToPrint{
RealPath: realPath,
PrintPath: printPath,
}
}
// PrintDryRunFile is a helper method around PrintDryRunFiles
func PrintDryRunFile(fileName, realDir, printDir string, w io.Writer) error {
return PrintDryRunFiles([]FileToPrint{
NewFileToPrint(filepath.Join(realDir, fileName), filepath.Join(printDir, fileName)),
}, w)
}
// PrintDryRunFiles prints the contents of the FileToPrints given to it to the writer w
func PrintDryRunFiles(files []FileToPrint, w io.Writer) error {
errs := []error{}
for _, file := range files {
if len(file.RealPath) == 0 {
continue
}
fileBytes, err := ioutil.ReadFile(file.RealPath)
if err != nil {
errs = append(errs, err)
continue
}
// Make it possible to fake the path of the file; i.e. you may want to tell the user
// "Here is what would be written to /etc/kubernetes/admin.conf", although you wrote it to /tmp/kubeadm-dryrun/admin.conf and are loading it from there
// Fall back to the "real" path if PrintPath is not set
outputFilePath := file.PrintPath
if len(outputFilePath) == 0 {
outputFilePath = file.RealPath
}
fmt.Fprintf(w, "[dryrun] Would write file %q with content:\n", outputFilePath)
apiclient.PrintBytesWithLinePrefix(w, fileBytes, "\t")
}
return errors.NewAggregate(errs)
}
// Waiter is an implementation of apiclient.Waiter that should be used for dry-running
type Waiter struct{}
// NewWaiter returns a new Waiter object that talks to the given Kubernetes cluster
func NewWaiter() apiclient.Waiter {
return &Waiter{}
}
// WaitForAPI just returns a dummy nil, to indicate that the program should just proceed
func (w *Waiter) WaitForAPI() error {
fmt.Println("[dryrun] Would wait for the API Server's /healthz endpoint to return 'ok'")
return nil
}
// WaitForPodsWithLabel just returns a dummy nil, to indicate that the program should just proceed
func (w *Waiter) WaitForPodsWithLabel(kvLabel string) error {
fmt.Printf("[dryrun] Would wait for the Pods with the label %q in the %s namespace to become Running\n", kvLabel, metav1.NamespaceSystem)
return nil
}
// WaitForPodToDisappear just returns a dummy nil, to indicate that the program should just proceed
func (w *Waiter) WaitForPodToDisappear(podName string) error {
fmt.Printf("[dryrun] Would wait for the %q Pod in the %s namespace to be deleted\n", podName, metav1.NamespaceSystem)
return nil
}
// WaitForHealthyKubelet blocks until the kubelet /healthz endpoint returns 'ok'
func (w *Waiter) WaitForHealthyKubelet(_ time.Duration, healthzEndpoint string) error {
fmt.Printf("[dryrun] Would make sure the kubelet %q endpoint is healthy\n", healthzEndpoint)
return nil
}
// SetTimeout is a no-op; we don't wait in this implementation
func (w *Waiter) SetTimeout(_ time.Duration) {}
// WaitForStaticPodControlPlaneHashes returns an empty hash for all control plane images;
func (w *Waiter) WaitForStaticPodControlPlaneHashes(_ string) (map[string]string, error) {
return map[string]string{
constants.KubeAPIServer: "",
constants.KubeControllerManager: "",
constants.KubeScheduler: "",
}, nil
}
// WaitForStaticPodSingleHash returns an empty hash
// but the empty strings there are needed
func (w *Waiter) WaitForStaticPodSingleHash(_ string, _ string) (string, error) {
return "", nil
}
// WaitForStaticPodHashChange returns a dummy nil error in order for the flow to just continue as we're dryrunning
func (w *Waiter) WaitForStaticPodHashChange(_, _, _ string) error {
return nil
}

View File

@ -1,119 +0,0 @@
/*
Copyright 2017 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 util
import (
"fmt"
"net"
"net/url"
"strconv"
"k8s.io/apimachinery/pkg/util/validation"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
// GetMasterEndpoint returns a properly formatted endpoint for the control plane built according following rules:
// - If the api.ControlPlaneEndpoint is defined, use it.
// - if the api.ControlPlaneEndpoint is defined but without a port number, use the api.ControlPlaneEndpoint + api.BindPort is used.
// - Otherwise, in case the api.ControlPlaneEndpoint is not defined, use the api.AdvertiseAddress + the api.BindPort.
func GetMasterEndpoint(api *kubeadmapi.API) (string, error) {
// parse the bind port
var bindPort = strconv.Itoa(int(api.BindPort))
if _, err := parsePort(bindPort); err != nil {
return "", fmt.Errorf("invalid value %q given for api.bindPort: %s", api.BindPort, err)
}
// parse the AdvertiseAddress
var ip = net.ParseIP(api.AdvertiseAddress)
if ip == nil {
return "", fmt.Errorf("invalid value `%s` given for api.advertiseAddress", api.AdvertiseAddress)
}
// set the master url using cfg.API.AdvertiseAddress + the cfg.API.BindPort
masterURL := &url.URL{
Scheme: "https",
Host: net.JoinHostPort(ip.String(), bindPort),
}
// if the controlplane endpoint is defined
if len(api.ControlPlaneEndpoint) > 0 {
// parse the controlplane endpoint
var host, port string
var err error
if host, port, err = ParseHostPort(api.ControlPlaneEndpoint); err != nil {
return "", fmt.Errorf("invalid value %q given for api.controlPlaneEndpoint: %s", api.ControlPlaneEndpoint, err)
}
// if a port is provided within the controlPlaneAddress warn the users we are using it, else use the bindport
if port != "" {
fmt.Println("[endpoint] WARNING: port specified in api.controlPlaneEndpoint overrides api.bindPort in the controlplane address")
} else {
port = bindPort
}
// overrides the master url using the controlPlaneAddress (and eventually the bindport)
masterURL = &url.URL{
Scheme: "https",
Host: net.JoinHostPort(host, port),
}
}
return masterURL.String(), nil
}
// ParseHostPort parses a network address of the form "host:port", "ipv4:port", "[ipv6]:port" into host and port;
// ":port" can be eventually omitted.
// If the string is not a valid representation of network address, ParseHostPort returns an error.
func ParseHostPort(hostport string) (string, string, error) {
var host, port string
var err error
// try to split host and port
if host, port, err = net.SplitHostPort(hostport); err != nil {
// if SplitHostPort returns an error, the entire hostport is considered as host
host = hostport
}
// if port is defined, parse and validate it
if port != "" {
if _, err := parsePort(port); err != nil {
return "", "", fmt.Errorf("port must be a valid number between 1 and 65535, inclusive")
}
}
// if host is a valid IP, returns it
if ip := net.ParseIP(host); ip != nil {
return host, port, nil
}
// if host is a validate RFC-1123 subdomain, returns it
if errs := validation.IsDNS1123Subdomain(host); len(errs) == 0 {
return host, port, nil
}
return "", "", fmt.Errorf("host must be a valid IP address or a valid RFC-1123 DNS subdomain")
}
// ParsePort parses a string representing a TCP port.
// If the string is not a valid representation of a TCP port, ParsePort returns an error.
func parsePort(port string) (int, error) {
if portInt, err := strconv.Atoi(port); err == nil && (1 <= portInt && portInt <= 65535) {
return portInt, nil
}
return 0, fmt.Errorf("port must be a valid number between 1 and 65535, inclusive")
}

View File

@ -1,345 +0,0 @@
/*
Copyright 2017 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 util
import (
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func TestGetMasterEndpoint(t *testing.T) {
var tests = []struct {
name string
api *kubeadmapi.API
expectedEndpoint string
expectedError bool
}{
{
name: "use ControlPlaneEndpoint (dns) if fully defined",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "cp.k8s.io:1234",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
expectedEndpoint: "https://cp.k8s.io:1234",
},
{
name: "use ControlPlaneEndpoint (ipv4) if fully defined",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1.2.3.4:1234",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
expectedEndpoint: "https://1.2.3.4:1234",
},
{
name: "use ControlPlaneEndpoint (ipv6) if fully defined",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "[2001:db8::1]:1234",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
expectedEndpoint: "https://[2001:db8::1]:1234",
},
{
name: "use ControlPlaneEndpoint (dns) + BindPort if ControlPlaneEndpoint defined without port",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "cp.k8s.io",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
expectedEndpoint: "https://cp.k8s.io:4567",
},
{
name: "use ControlPlaneEndpoint (ipv4) + BindPort if ControlPlaneEndpoint defined without port",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1.2.3.4",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
expectedEndpoint: "https://1.2.3.4:4567",
},
{
name: "use ControlPlaneEndpoint (ipv6) + BindPort if ControlPlaneEndpoint defined without port",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "2001:db8::1",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
expectedEndpoint: "https://[2001:db8::1]:4567",
},
{
name: "use AdvertiseAddress (ipv4) + BindPort if ControlPlaneEndpoint is not defined",
api: &kubeadmapi.API{
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
expectedEndpoint: "https://4.5.6.7:4567",
},
{
name: "use AdvertiseAddress (ipv6) + BindPort if ControlPlaneEndpoint is not defined",
api: &kubeadmapi.API{
BindPort: 4567,
AdvertiseAddress: "2001:db8::1",
},
expectedEndpoint: "https://[2001:db8::1]:4567",
},
{
name: "fail if invalid BindPort",
api: &kubeadmapi.API{
BindPort: 0,
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (dns)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "bad!!.cp.k8s.io",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (ip4)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1..0",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (ip6)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1200::AB00:1234::2552:7777:1313",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (port)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "cp.k8s.io:0",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid AdvertiseAddress (ip4)",
api: &kubeadmapi.API{
AdvertiseAddress: "1..0",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid AdvertiseAddress (ip6)",
api: &kubeadmapi.API{
AdvertiseAddress: "1200::AB00:1234::2552:7777:1313",
BindPort: 4567,
},
expectedError: true,
},
}
for _, rt := range tests {
actualEndpoint, actualError := GetMasterEndpoint(rt.api)
if (actualError != nil) && !rt.expectedError {
t.Errorf("%s unexpected failure: %v", rt.name, actualError)
continue
} else if (actualError == nil) && rt.expectedError {
t.Errorf("%s passed when expected to fail", rt.name)
continue
}
if actualEndpoint != rt.expectedEndpoint {
t.Errorf("%s returned invalid endpoint %s, expected %s", rt.name, actualEndpoint, rt.expectedEndpoint)
}
}
}
func TestParseHostPort(t *testing.T) {
var tests = []struct {
name string
hostport string
expectedHost string
expectedPort string
expectedError bool
}{
{
name: "valid dns",
hostport: "cp.k8s.io",
expectedHost: "cp.k8s.io",
expectedPort: "",
},
{
name: "valid dns:port",
hostport: "cp.k8s.io:1234",
expectedHost: "cp.k8s.io",
expectedPort: "1234",
},
{
name: "valid ip4",
hostport: "1.2.3.4",
expectedHost: "1.2.3.4",
expectedPort: "",
},
{
name: "valid ipv4:port",
hostport: "1.2.3.4:1234",
expectedHost: "1.2.3.4",
expectedPort: "1234",
},
{
name: "valid ipv6",
hostport: "2001:db8::1",
expectedHost: "2001:db8::1",
expectedPort: "",
},
{
name: "valid ipv6:port",
hostport: "[2001:db8::1]:1234",
expectedHost: "2001:db8::1",
expectedPort: "1234",
},
{
name: "invalid port(not a number)",
hostport: "cp.k8s.io:aaa",
expectedError: true,
},
{
name: "invalid port(out of range, positive port number)",
hostport: "cp.k8s.io:987654321",
expectedError: true,
},
{
name: "invalid port(out of range, negative port number)",
hostport: "cp.k8s.io:-987654321",
expectedError: true,
},
{
name: "invalid port(out of range, negative port number)",
hostport: "cp.k8s.io:123:123",
expectedError: true,
},
{
name: "invalid dns",
hostport: "bad!!cp.k8s.io",
expectedError: true,
},
{
name: "invalid valid dns:port",
hostport: "bad!!cp.k8s.io:1234",
expectedError: true,
},
{
name: "invalid ip4, but valid DNS",
hostport: "259.2.3.4",
expectedHost: "259.2.3.4",
},
{
name: "invalid ip4",
hostport: "1..3.4",
expectedError: true,
},
{
name: "invalid ip4(2):port",
hostport: "1..3.4:1234",
expectedError: true,
},
{
name: "invalid ipv6",
hostport: "1200::AB00:1234::2552:7777:1313",
expectedError: true,
},
{
name: "invalid ipv6:port",
hostport: "[1200::AB00:1234::2552:7777:1313]:1234",
expectedError: true,
},
}
for _, rt := range tests {
actualHost, actualPort, actualError := ParseHostPort(rt.hostport)
if (actualError != nil) && !rt.expectedError {
t.Errorf("%s unexpected failure: %v", rt.name, actualError)
continue
} else if (actualError == nil) && rt.expectedError {
t.Errorf("%s passed when expected to fail", rt.name)
continue
}
if actualHost != rt.expectedHost {
t.Errorf("%s returned invalid host %s, expected %s", rt.name, actualHost, rt.expectedHost)
continue
}
if actualPort != rt.expectedPort {
t.Errorf("%s returned invalid port %s, expected %s", rt.name, actualPort, rt.expectedPort)
}
}
}
func TestParsePort(t *testing.T) {
var tests = []struct {
name string
port string
expectedPort int
expectedError bool
}{
{
name: "valid port",
port: "1234",
expectedPort: 1234,
},
{
name: "invalid port (not a number)",
port: "a",
expectedError: true,
},
{
name: "invalid port (<1)",
port: "-10",
expectedError: true,
},
{
name: "invalid port (>65535)",
port: "66535",
expectedError: true,
},
}
for _, rt := range tests {
actualPort, actualError := parsePort(rt.port)
if (actualError != nil) && !rt.expectedError {
t.Errorf("%s unexpected failure: %v", rt.name, actualError)
continue
} else if (actualError == nil) && rt.expectedError {
t.Errorf("%s passed when expected to fail", rt.name)
continue
}
if actualPort != rt.expectedPort {
t.Errorf("%s returned invalid port %d, expected %d", rt.name, actualPort, rt.expectedPort)
}
}
}

View File

@ -1,87 +0,0 @@
/*
Copyright 2016 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 util
import (
"fmt"
"os"
"strings"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
const (
// DefaultErrorExitCode defines exit the code for failed action generally
DefaultErrorExitCode = 1
// PreFlightExitCode defines exit the code for preflight checks
PreFlightExitCode = 2
// ValidationExitCode defines the exit code validation checks
ValidationExitCode = 3
)
// fatal prints the message if set and then exits.
func fatal(msg string, code int) {
if len(msg) > 0 {
// add newline if needed
if !strings.HasSuffix(msg, "\n") {
msg += "\n"
}
fmt.Fprint(os.Stderr, msg)
}
os.Exit(code)
}
// CheckErr prints a user friendly error to STDERR and exits with a non-zero
// exit code. Unrecognized errors will be printed with an "error: " prefix.
//
// This method is generic to the command in use and may be used by non-Kubectl
// commands.
func CheckErr(err error) {
checkErr(err, fatal)
}
// preflightError allows us to know if the error is a preflight error or not
// defining the interface here avoids an import cycle of pulling in preflight into the util package
type preflightError interface {
Preflight() bool
}
// checkErr formats a given error as a string and calls the passed handleErr
// func with that string and an exit code.
func checkErr(err error, handleErr func(string, int)) {
switch err.(type) {
case nil:
return
case preflightError:
handleErr(err.Error(), PreFlightExitCode)
case utilerrors.Aggregate:
handleErr(err.Error(), ValidationExitCode)
default:
handleErr(err.Error(), DefaultErrorExitCode)
}
}
// FormatErrMsg returns a human-readable string describing the slice of errors passed to the function
func FormatErrMsg(errs []error) string {
var errMsg string
for _, err := range errs {
errMsg = fmt.Sprintf("%s\t- %s\n", errMsg, err.Error())
}
return errMsg
}

View File

@ -1,85 +0,0 @@
/*
Copyright 2016 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 util
import (
"fmt"
"testing"
)
type pferror struct{}
func (p *pferror) Preflight() bool { return true }
func (p *pferror) Error() string { return "" }
func TestCheckErr(t *testing.T) {
var codeReturned int
errHandle := func(err string, code int) {
codeReturned = code
}
var tokenTest = []struct {
e error
expected int
}{
{nil, 0},
{fmt.Errorf(""), DefaultErrorExitCode},
{&pferror{}, PreFlightExitCode},
}
for _, rt := range tokenTest {
codeReturned = 0
checkErr(rt.e, errHandle)
if codeReturned != rt.expected {
t.Errorf(
"failed checkErr:\n\texpected: %d\n\t actual: %d",
rt.expected,
codeReturned,
)
}
}
}
func TestFormatErrMsg(t *testing.T) {
errMsg1 := "specified version to upgrade to v1.9.0-alpha.3 is equal to or lower than the cluster version v1.10.0-alpha.0.69+638add6ddfb6d2. Downgrades are not supported yet"
errMsg2 := "specified version to upgrade to v1.9.0-alpha.3 is higher than the kubeadm version v1.9.0-alpha.1.3121+84178212527295-dirty. Upgrade kubeadm first using the tool you used to install kubeadm"
testCases := []struct {
errs []error
expect string
}{
{
errs: []error{
fmt.Errorf(errMsg1),
fmt.Errorf(errMsg2),
},
expect: "\t- " + errMsg1 + "\n" + "\t- " + errMsg2 + "\n",
},
{
errs: []error{
fmt.Errorf(errMsg1),
},
expect: "\t- " + errMsg1 + "\n",
},
}
for _, testCase := range testCases {
got := FormatErrMsg(testCase.errs)
if got != testCase.expect {
t.Errorf("FormatErrMsg error, expect: %v, got: %v", testCase.expect, got)
}
}
}

View File

@ -1,39 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["etcd.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd",
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/staticpod:go_default_library",
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
"//vendor/github.com/coreos/etcd/pkg/transport:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["etcd_test.go"],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/test:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -1,224 +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 etcd
import (
"context"
"crypto/tls"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/pkg/transport"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
)
// ClusterInterrogator is an interface to get etcd cluster related information
type ClusterInterrogator interface {
ClusterAvailable() (bool, error)
GetClusterStatus() (map[string]*clientv3.StatusResponse, error)
GetClusterVersions() (map[string]string, error)
GetVersion() (string, error)
HasTLS() bool
WaitForClusterAvailable(delay time.Duration, retries int, retryInterval time.Duration) (bool, error)
}
// Client provides connection parameters for an etcd cluster
type Client struct {
Endpoints []string
TLS *tls.Config
}
// HasTLS returns true if etcd is configured for TLS
func (c Client) HasTLS() bool {
return c.TLS != nil
}
// PodManifestsHaveTLS reads the etcd staticpod manifest from disk and returns false if the TLS flags
// are missing from the command list. If all the flags are present it returns true.
func PodManifestsHaveTLS(ManifestDir string) (bool, error) {
etcdPodPath := constants.GetStaticPodFilepath(constants.Etcd, ManifestDir)
etcdPod, err := staticpod.ReadStaticPodFromDisk(etcdPodPath)
if err != nil {
return false, fmt.Errorf("failed to check if etcd pod implements TLS: %v", err)
}
tlsFlags := []string{
"--cert-file=",
"--key-file=",
"--trusted-ca-file=",
"--client-cert-auth=",
"--peer-cert-file=",
"--peer-key-file=",
"--peer-trusted-ca-file=",
"--peer-client-cert-auth=",
}
FlagLoop:
for _, flag := range tlsFlags {
for _, container := range etcdPod.Spec.Containers {
for _, arg := range container.Command {
if strings.Contains(arg, flag) {
continue FlagLoop
}
}
}
// flag not found in any container
return false, nil
}
// all flags were found in container args; pod fully implements TLS
return true, nil
}
// New creates a new EtcdCluster client
func New(endpoints []string, ca, cert, key string) (*Client, error) {
client := Client{Endpoints: endpoints}
if ca != "" || cert != "" || key != "" {
tlsInfo := transport.TLSInfo{
CertFile: cert,
KeyFile: key,
TrustedCAFile: ca,
}
tlsConfig, err := tlsInfo.ClientConfig()
if err != nil {
return nil, err
}
client.TLS = tlsConfig
}
return &client, nil
}
// NewFromStaticPod creates a GenericClient from the given endpoints, manifestDir, and certificatesDir
func NewFromStaticPod(endpoints []string, manifestDir string, certificatesDir string) (*Client, error) {
hasTLS, err := PodManifestsHaveTLS(manifestDir)
if err != nil {
return nil, fmt.Errorf("could not read manifests from: %s, error: %v", manifestDir, err)
}
if hasTLS {
return New(
endpoints,
filepath.Join(certificatesDir, constants.EtcdCACertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientCertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientKeyName),
)
}
return New(endpoints, "", "", "")
}
// GetVersion returns the etcd version of the cluster.
// An error is returned if the version of all endpoints do not match
func (c Client) GetVersion() (string, error) {
var clusterVersion string
versions, err := c.GetClusterVersions()
if err != nil {
return "", err
}
for _, v := range versions {
if clusterVersion != "" && clusterVersion != v {
return "", fmt.Errorf("etcd cluster contains endpoints with mismatched versions: %v", versions)
}
clusterVersion = v
}
if clusterVersion == "" {
return "", fmt.Errorf("could not determine cluster etcd version")
}
return clusterVersion, nil
}
// GetClusterVersions returns a map of the endpoints and their associated versions
func (c Client) GetClusterVersions() (map[string]string, error) {
versions := make(map[string]string)
statuses, err := c.GetClusterStatus()
if err != nil {
return versions, err
}
for ep, status := range statuses {
versions[ep] = status.Version
}
return versions, nil
}
// ClusterAvailable returns true if the cluster status indicates the cluster is available.
func (c Client) ClusterAvailable() (bool, error) {
_, err := c.GetClusterStatus()
if err != nil {
return false, err
}
return true, nil
}
// GetClusterStatus returns nil for status Up or error for status Down
func (c Client) GetClusterStatus() (map[string]*clientv3.StatusResponse, error) {
cli, err := clientv3.New(clientv3.Config{
Endpoints: c.Endpoints,
DialTimeout: 5 * time.Second,
TLS: c.TLS,
})
if err != nil {
return nil, err
}
defer cli.Close()
clusterStatus := make(map[string]*clientv3.StatusResponse)
for _, ep := range c.Endpoints {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
resp, err := cli.Status(ctx, ep)
cancel()
if err != nil {
return nil, err
}
clusterStatus[ep] = resp
}
return clusterStatus, nil
}
// WaitForClusterAvailable returns true if all endpoints in the cluster are available after an initial delay and retry attempts, an error is returned otherwise
func (c Client) WaitForClusterAvailable(delay time.Duration, retries int, retryInterval time.Duration) (bool, error) {
fmt.Printf("[util/etcd] Waiting %v for initial delay\n", delay)
time.Sleep(delay)
for i := 0; i < retries; i++ {
if i > 0 {
fmt.Printf("[util/etcd] Waiting %v until next retry\n", retryInterval)
time.Sleep(retryInterval)
}
fmt.Printf("[util/etcd] Attempting to see if all cluster endpoints are available %d/%d\n", i+1, retries)
resp, err := c.ClusterAvailable()
if err != nil {
switch err {
case context.DeadlineExceeded:
fmt.Println("[util/etcd] Attempt timed out")
default:
fmt.Printf("[util/etcd] Attempt failed with error: %v\n", err)
}
continue
}
return resp, nil
}
return false, fmt.Errorf("timeout waiting for etcd cluster to be available")
}
// CheckConfigurationIsHA returns true if the given MasterConfiguration etcd block appears to be an HA configuration.
func CheckConfigurationIsHA(cfg *kubeadmapi.Etcd) bool {
return cfg.External != nil && len(cfg.External.Endpoints) > 1
}

View File

@ -1,310 +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 etcd
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
)
const (
secureEtcdPod = `# generated by kubeadm v1.10.0
apiVersion: v1
kind: Pod
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
creationTimestamp: null
labels:
component: etcd
tier: control-plane
name: etcd
namespace: kube-system
spec:
containers:
- command:
- etcd
- --advertise-client-urls=https://127.0.0.1:2379
- --data-dir=/var/lib/etcd
- --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
- --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --listen-client-urls=https://127.0.0.1:2379
- --peer-client-cert-auth=true
- --cert-file=/etc/kubernetes/pki/etcd/server.crt
- --key-file=/etc/kubernetes/pki/etcd/server.key
- --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
- --client-cert-auth=true
image: k8s.gcr.io/etcd-amd64:3.1.12
livenessProbe:
exec:
command:
- /bin/sh
- -ec
- ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
get foo
failureThreshold: 8
initialDelaySeconds: 15
timeoutSeconds: 15
name: etcd
resources: {}
volumeMounts:
- mountPath: /var/lib/etcd
name: etcd-data
- mountPath: /etc/kubernetes/pki/etcd
name: etcd-certs
hostNetwork: true
volumes:
- hostPath:
path: /var/lib/etcd
type: DirectoryOrCreate
name: etcd-data
- hostPath:
path: /etc/kubernetes/pki/etcd
type: DirectoryOrCreate
name: etcd-certs
status: {}
`
secureExposedEtcdPod = `
apiVersion: v1
kind: Pod
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
creationTimestamp: null
labels:
component: etcd
tier: control-plane
name: etcd
namespace: kube-system
spec:
containers:
- command:
- etcd
- --advertise-client-urls=https://10.0.5.5:2379
- --data-dir=/var/lib/etcd
- --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
- --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --listen-client-urls=https://[::0:0]:2379
- --peer-client-cert-auth=true
- --cert-file=/etc/kubernetes/pki/etcd/server.crt
- --key-file=/etc/kubernetes/pki/etcd/server.key
- --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
- --client-cert-auth=true
image: k8s.gcr.io/etcd-amd64:3.1.12
livenessProbe:
exec:
command:
- /bin/sh
- -ec
- ETCDCTL_API=3 etcdctl --endpoints=https://[::1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
get foo
failureThreshold: 8
initialDelaySeconds: 15
timeoutSeconds: 15
name: etcd
resources: {}
volumeMounts:
- mountPath: /var/lib/etcd
name: etcd-data
- mountPath: /etc/kubernetes/pki/etcd
name: etcd-certs
hostNetwork: true
volumes:
- hostPath:
path: /var/lib/etcd
type: DirectoryOrCreate
name: etcd-data
- hostPath:
path: /etc/kubernetes/pki/etcd
type: DirectoryOrCreate
name: etcd-certs
status: {}
`
insecureEtcdPod = `# generated by kubeadm v1.9.6
apiVersion: v1
kind: Pod
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
creationTimestamp: null
labels:
component: etcd
tier: control-plane
name: etcd
namespace: kube-system
spec:
containers:
- command:
- etcd
- --listen-client-urls=http://127.0.0.1:2379
- --advertise-client-urls=http://127.0.0.1:2379
- --data-dir=/var/lib/etcd
image: gcr.io/google_containers/etcd-amd64:3.1.11
livenessProbe:
failureThreshold: 8
httpGet:
host: 127.0.0.1
path: /health
port: 2379
scheme: HTTP
initialDelaySeconds: 15
timeoutSeconds: 15
name: etcd
resources: {}
volumeMounts:
- mountPath: /var/lib/etcd
name: etcd
hostNetwork: true
volumes:
- hostPath:
path: /var/lib/etcd
type: DirectoryOrCreate
name: etcd
status: {}
`
invalidPod = `---{ broken yaml @@@`
)
func TestPodManifestHasTLS(t *testing.T) {
tests := []struct {
description string
podYaml string
hasTLS bool
expectErr bool
writeManifest bool
}{
{
description: "secure etcd returns true",
podYaml: secureEtcdPod,
hasTLS: true,
writeManifest: true,
expectErr: false,
},
{
description: "secure exposed etcd returns true",
podYaml: secureExposedEtcdPod,
hasTLS: true,
writeManifest: true,
expectErr: false,
},
{
description: "insecure etcd returns false",
podYaml: insecureEtcdPod,
hasTLS: false,
writeManifest: true,
expectErr: false,
},
{
description: "invalid pod fails to unmarshal",
podYaml: invalidPod,
hasTLS: false,
writeManifest: true,
expectErr: true,
},
{
description: "non-existent file returns error",
podYaml: ``,
hasTLS: false,
writeManifest: false,
expectErr: true,
},
}
for _, rt := range tests {
tmpdir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpdir)
manifestPath := filepath.Join(tmpdir, "etcd.yaml")
if rt.writeManifest {
err := ioutil.WriteFile(manifestPath, []byte(rt.podYaml), 0644)
if err != nil {
t.Fatalf("Failed to write pod manifest\n%s\n\tfatal error: %v", rt.description, err)
}
}
hasTLS, actualErr := PodManifestsHaveTLS(tmpdir)
if (actualErr != nil) != rt.expectErr {
t.Errorf(
"PodManifestHasTLS failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
rt.description,
rt.expectErr,
(actualErr != nil),
actualErr,
)
}
if hasTLS != rt.hasTLS {
t.Errorf("PodManifestHasTLS failed\n%s\n\texpected hasTLS: %t\n\tgot: %t", rt.description, rt.hasTLS, hasTLS)
}
}
}
func TestCheckConfigurationIsHA(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.Etcd
expected bool
}{
{
name: "HA etcd",
cfg: &kubeadmapi.Etcd{
External: &kubeadmapi.ExternalEtcd{
Endpoints: []string{"10.100.0.1:2379", "10.100.0.2:2379", "10.100.0.3:2379"},
},
},
expected: true,
},
{
name: "single External etcd",
cfg: &kubeadmapi.Etcd{
External: &kubeadmapi.ExternalEtcd{
Endpoints: []string{"10.100.0.1:2379"},
},
},
expected: false,
},
{
name: "local etcd",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{},
},
expected: false,
},
{
name: "empty etcd struct",
cfg: &kubeadmapi.Etcd{},
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if isHA := CheckConfigurationIsHA(test.cfg); isHA != test.expected {
t.Errorf("expected isHA to be %v, got %v", test.expected, isHA)
}
})
}
}

View File

@ -1,37 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["kubeconfig_test.go"],
embed = [":go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["kubeconfig.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig",
deps = [
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,112 +0,0 @@
/*
Copyright 2017 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 kubeconfig
import (
"fmt"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// CreateBasic creates a basic, general KubeConfig object that then can be extended
func CreateBasic(serverURL string, clusterName string, userName string, caCert []byte) *clientcmdapi.Config {
// Use the cluster and the username as the context name
contextName := fmt.Sprintf("%s@%s", userName, clusterName)
return &clientcmdapi.Config{
Clusters: map[string]*clientcmdapi.Cluster{
clusterName: {
Server: serverURL,
CertificateAuthorityData: caCert,
},
},
Contexts: map[string]*clientcmdapi.Context{
contextName: {
Cluster: clusterName,
AuthInfo: userName,
},
},
AuthInfos: map[string]*clientcmdapi.AuthInfo{},
CurrentContext: contextName,
}
}
// CreateWithCerts creates a KubeConfig object with access to the API server with client certificates
func CreateWithCerts(serverURL, clusterName, userName string, caCert []byte, clientKey []byte, clientCert []byte) *clientcmdapi.Config {
config := CreateBasic(serverURL, clusterName, userName, caCert)
config.AuthInfos[userName] = &clientcmdapi.AuthInfo{
ClientKeyData: clientKey,
ClientCertificateData: clientCert,
}
return config
}
// CreateWithToken creates a KubeConfig object with access to the API server with a token
func CreateWithToken(serverURL, clusterName, userName string, caCert []byte, token string) *clientcmdapi.Config {
config := CreateBasic(serverURL, clusterName, userName, caCert)
config.AuthInfos[userName] = &clientcmdapi.AuthInfo{
Token: token,
}
return config
}
// ClientSetFromFile returns a ready-to-use client from a KubeConfig file
func ClientSetFromFile(path string) (*clientset.Clientset, error) {
config, err := clientcmd.LoadFromFile(path)
if err != nil {
return nil, fmt.Errorf("failed to load admin kubeconfig [%v]", err)
}
return ToClientSet(config)
}
// ToClientSet converts a KubeConfig object to a client
func ToClientSet(config *clientcmdapi.Config) (*clientset.Clientset, error) {
clientConfig, err := clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, fmt.Errorf("failed to create API client configuration from kubeconfig: %v", err)
}
client, err := clientset.NewForConfig(clientConfig)
if err != nil {
return nil, fmt.Errorf("failed to create API client: %v", err)
}
return client, nil
}
// WriteToDisk writes a KubeConfig object down to disk with mode 0600
func WriteToDisk(filename string, kubeconfig *clientcmdapi.Config) error {
err := clientcmd.WriteToFile(*kubeconfig, filename)
if err != nil {
return err
}
return nil
}
// GetClusterFromKubeConfig returns the default Cluster of the specified KubeConfig
func GetClusterFromKubeConfig(config *clientcmdapi.Config) *clientcmdapi.Cluster {
// If there is an unnamed cluster object, use it
if config.Clusters[""] != nil {
return config.Clusters[""]
}
if config.Contexts[config.CurrentContext] != nil {
return config.Clusters[config.Contexts[config.CurrentContext].Cluster]
}
return nil
}

View File

@ -1,180 +0,0 @@
/*
Copyright 2017 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 kubeconfig
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"testing"
)
const (
configOut1 = `apiVersion: v1
clusters:
- cluster:
server: ""
name: k8s
contexts:
- context:
cluster: k8s
user: user1
name: user1@k8s
current-context: user1@k8s
kind: Config
preferences: {}
users:
- name: user1
user:
token: abc
`
configOut2 = `apiVersion: v1
clusters:
- cluster:
server: localhost:8080
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: user2
name: user2@kubernetes
current-context: user2@kubernetes
kind: Config
preferences: {}
users:
- name: user2
user:
token: cba
`
)
type configClient struct {
clusterName string
userName string
serverURL string
caCert []byte
}
type configClientWithCerts struct {
clientKey []byte
clientCert []byte
}
type configClientWithToken struct {
token string
}
func TestCreateWithCerts(t *testing.T) {
var createBasicTest = []struct {
cc configClient
ccWithCerts configClientWithCerts
expected string
}{
{configClient{}, configClientWithCerts{}, ""},
{configClient{clusterName: "kubernetes"}, configClientWithCerts{}, ""},
}
for _, rt := range createBasicTest {
cwc := CreateWithCerts(
rt.cc.serverURL,
rt.cc.clusterName,
rt.cc.userName,
rt.cc.caCert,
rt.ccWithCerts.clientKey,
rt.ccWithCerts.clientCert,
)
if cwc.Kind != rt.expected {
t.Errorf(
"failed CreateWithCerts:\n\texpected: %s\n\t actual: %s",
rt.expected,
cwc.Kind,
)
}
}
}
func TestCreateWithToken(t *testing.T) {
var createBasicTest = []struct {
cc configClient
ccWithToken configClientWithToken
expected string
}{
{configClient{}, configClientWithToken{}, ""},
{configClient{clusterName: "kubernetes"}, configClientWithToken{}, ""},
}
for _, rt := range createBasicTest {
cwc := CreateWithToken(
rt.cc.serverURL,
rt.cc.clusterName,
rt.cc.userName,
rt.cc.caCert,
rt.ccWithToken.token,
)
if cwc.Kind != rt.expected {
t.Errorf(
"failed CreateWithToken:\n\texpected: %s\n\t actual: %s",
rt.expected,
cwc.Kind,
)
}
}
}
func TestWriteKubeconfigToDisk(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
var writeConfig = []struct {
name string
cc configClient
ccWithToken configClientWithToken
expected error
file []byte
}{
{"test1", configClient{clusterName: "k8s", userName: "user1"}, configClientWithToken{token: "abc"}, nil, []byte(configOut1)},
{"test2", configClient{clusterName: "kubernetes", userName: "user2", serverURL: "localhost:8080"}, configClientWithToken{token: "cba"}, nil, []byte(configOut2)},
}
for _, rt := range writeConfig {
c := CreateWithToken(
rt.cc.serverURL,
rt.cc.clusterName,
rt.cc.userName,
rt.cc.caCert,
rt.ccWithToken.token,
)
configPath := fmt.Sprintf("%s/etc/kubernetes/%s.conf", tmpdir, rt.name)
err := WriteToDisk(configPath, c)
if err != rt.expected {
t.Errorf(
"failed WriteToDisk with an error:\n\texpected: %s\n\t actual: %s",
rt.expected,
err,
)
}
newFile, _ := ioutil.ReadFile(configPath)
if !bytes.Equal(newFile, rt.file) {
t.Errorf(
"failed WriteToDisk config write:\n\texpected: %s\n\t actual: %s",
rt.file,
newFile,
)
}
}
}

View File

@ -1,134 +0,0 @@
/*
Copyright 2017 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 util
import (
"errors"
"fmt"
yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
)
// MarshalToYaml marshals an object into yaml.
func MarshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
return MarshalToYamlForCodecs(obj, gv, clientsetscheme.Codecs)
}
// MarshalToYamlForCodecs marshals an object into yaml using the specified codec
// TODO: Is specifying the gv really needed here?
// TODO: Can we support json out of the box easily here?
func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) ([]byte, error) {
mediaType := "application/yaml"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
if !ok {
return []byte{}, fmt.Errorf("unsupported media type %q", mediaType)
}
encoder := codecs.EncoderForVersion(info.Serializer, gv)
return runtime.Encode(encoder, obj)
}
// UnmarshalFromYaml unmarshals yaml into an object.
func UnmarshalFromYaml(buffer []byte, gv schema.GroupVersion) (runtime.Object, error) {
return UnmarshalFromYamlForCodecs(buffer, gv, clientsetscheme.Codecs)
}
// UnmarshalFromYamlForCodecs unmarshals yaml into an object using the specified codec
// TODO: Is specifying the gv really needed here?
// TODO: Can we support json out of the box easily here?
func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs serializer.CodecFactory) (runtime.Object, error) {
mediaType := "application/yaml"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
if !ok {
return nil, fmt.Errorf("unsupported media type %q", mediaType)
}
decoder := codecs.DecoderToVersion(info.Serializer, gv)
return runtime.Decode(decoder, buffer)
}
// GroupVersionKindFromBytes parses the bytes and returns the gvk
func GroupVersionKindFromBytes(buffer []byte, codecs serializer.CodecFactory) (schema.GroupVersionKind, error) {
decoded, err := LoadYAML(buffer)
if err != nil {
return schema.EmptyObjectKind.GroupVersionKind(), fmt.Errorf("unable to decode config from bytes: %v", err)
}
kindStr, apiVersionStr := "", ""
// As there was a bug in kubeadm v1.10 and earlier that made the YAML uploaded to the cluster configmap NOT have metav1.TypeMeta information
// we need to populate this here manually. If kind or apiVersion is empty, we know the apiVersion is v1alpha1, as by the time kubeadm had this bug,
// it could only write
// TODO: Remove this "hack" in v1.12 when we know the ConfigMap always contains v1alpha2 content written by kubeadm v1.11. Also, we will drop support for
// v1alpha1 in v1.12
kind := decoded["kind"]
apiVersion := decoded["apiVersion"]
if kind == nil || len(kind.(string)) == 0 {
kindStr = "MasterConfiguration"
} else {
kindStr = kind.(string)
}
if apiVersion == nil || len(apiVersion.(string)) == 0 {
apiVersionStr = kubeadmapiv1alpha1.SchemeGroupVersion.String()
} else {
apiVersionStr = apiVersion.(string)
}
gv, err := schema.ParseGroupVersion(apiVersionStr)
if err != nil {
return schema.EmptyObjectKind.GroupVersionKind(), fmt.Errorf("unable to parse apiVersion: %v", err)
}
return gv.WithKind(kindStr), nil
}
// LoadYAML is a small wrapper around go-yaml that ensures all nested structs are map[string]interface{} instead of map[interface{}]interface{}.
func LoadYAML(bytes []byte) (map[string]interface{}, error) {
var decoded map[interface{}]interface{}
if err := yaml.Unmarshal(bytes, &decoded); err != nil {
return map[string]interface{}{}, fmt.Errorf("couldn't unmarshal YAML: %v", err)
}
converted, ok := convert(decoded).(map[string]interface{})
if !ok {
return map[string]interface{}{}, errors.New("yaml is not a map")
}
return converted, nil
}
// https://stackoverflow.com/questions/40737122/convert-yaml-to-json-without-struct-golang
func convert(i interface{}) interface{} {
switch x := i.(type) {
case map[interface{}]interface{}:
m2 := map[string]interface{}{}
for k, v := range x {
m2[k.(string)] = convert(v)
}
return m2
case []interface{}:
for i, v := range x {
x[i] = convert(v)
}
}
return i
}

View File

@ -1,125 +0,0 @@
/*
Copyright 2017 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 util
import (
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
)
func TestMarshalUnmarshalYaml(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "someName",
Namespace: "testNamespace",
Labels: map[string]string{
"test": "yes",
},
},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
},
}
bytes, err := MarshalToYaml(pod, corev1.SchemeGroupVersion)
if err != nil {
t.Fatalf("unexpected error marshalling: %v", err)
}
t.Logf("\n%s", bytes)
obj2, err := UnmarshalFromYaml(bytes, corev1.SchemeGroupVersion)
if err != nil {
t.Fatalf("unexpected error marshalling: %v", err)
}
pod2, ok := obj2.(*corev1.Pod)
if !ok {
t.Fatal("did not get a Pod")
}
if pod2.Name != pod.Name {
t.Errorf("expected %q, got %q", pod.Name, pod2.Name)
}
if pod2.Namespace != pod.Namespace {
t.Errorf("expected %q, got %q", pod.Namespace, pod2.Namespace)
}
if !reflect.DeepEqual(pod2.Labels, pod.Labels) {
t.Errorf("expected %v, got %v", pod.Labels, pod2.Labels)
}
if pod2.Spec.RestartPolicy != pod.Spec.RestartPolicy {
t.Errorf("expected %q, got %q", pod.Spec.RestartPolicy, pod2.Spec.RestartPolicy)
}
}
func TestMarshalUnmarshalToYamlForCodecs(t *testing.T) {
cfg := &kubeadmapiext.MasterConfiguration{
API: kubeadmapiext.API{
AdvertiseAddress: "10.100.0.1",
BindPort: 4332,
},
NodeName: "testNode",
NoTaintMaster: true,
Networking: kubeadmapiext.Networking{
ServiceSubnet: "10.100.0.0/24",
PodSubnet: "10.100.1.0/24",
},
}
bytes, err := MarshalToYamlForCodecs(cfg, kubeadmapiext.SchemeGroupVersion, scheme.Codecs)
if err != nil {
t.Fatalf("unexpected error marshalling MasterConfiguration: %v", err)
}
t.Logf("\n%s", bytes)
obj, err := UnmarshalFromYamlForCodecs(bytes, kubeadmapiext.SchemeGroupVersion, scheme.Codecs)
if err != nil {
t.Fatalf("unexpected error unmarshalling MasterConfiguration: %v", err)
}
cfg2, ok := obj.(*kubeadmapiext.MasterConfiguration)
if !ok {
t.Fatal("did not get MasterConfiguration back")
}
if cfg2.API.AdvertiseAddress != cfg.API.AdvertiseAddress {
t.Errorf("expected %q, got %q", cfg.API.AdvertiseAddress, cfg2.API.AdvertiseAddress)
}
if cfg2.API.BindPort != cfg.API.BindPort {
t.Errorf("expected %d, got %d", cfg.API.BindPort, cfg2.API.BindPort)
}
if cfg2.NodeName != cfg.NodeName {
t.Errorf("expected %q, got %q", cfg.NodeName, cfg2.NodeName)
}
if cfg2.NoTaintMaster != cfg.NoTaintMaster {
t.Errorf("expected %v, got %v", cfg.NoTaintMaster, cfg2.NoTaintMaster)
}
if cfg2.Networking.ServiceSubnet != cfg.Networking.ServiceSubnet {
t.Errorf("expected %v, got %v", cfg.Networking.ServiceSubnet, cfg2.Networking.ServiceSubnet)
}
if cfg2.Networking.PodSubnet != cfg.Networking.PodSubnet {
t.Errorf("expected %v, got %v", cfg.Networking.PodSubnet, cfg2.Networking.PodSubnet)
}
}

View File

@ -1,32 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["pubkeypin_test.go"],
embed = [":go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["pubkeypin.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,108 +0,0 @@
/*
Copyright 2017 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 pubkeypin provides primitives for x509 public key pinning in the
// style of RFC7469.
package pubkeypin
import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"fmt"
"strings"
)
const (
// formatSHA256 is the prefix for pins that are full-length SHA-256 hashes encoded in base 16 (hex)
formatSHA256 = "sha256"
)
// Set is a set of pinned x509 public keys.
type Set struct {
sha256Hashes map[string]bool
}
// NewSet returns a new, empty PubKeyPinSet
func NewSet() *Set {
return &Set{make(map[string]bool)}
}
// Allow adds an allowed public key hash to the Set
func (s *Set) Allow(pubKeyHashes ...string) error {
for _, pubKeyHash := range pubKeyHashes {
parts := strings.Split(pubKeyHash, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid public key hash, expected \"format:value\"")
}
format, value := parts[0], parts[1]
switch strings.ToLower(format) {
case "sha256":
return s.allowSHA256(value)
default:
return fmt.Errorf("unknown hash format %q", format)
}
}
return nil
}
// Check if a certificate matches one of the public keys in the set
func (s *Set) Check(certificate *x509.Certificate) error {
if s.checkSHA256(certificate) {
return nil
}
return fmt.Errorf("public key %s not pinned", Hash(certificate))
}
// Empty returns true if the Set contains no pinned public keys.
func (s *Set) Empty() bool {
return len(s.sha256Hashes) == 0
}
// Hash calculates the SHA-256 hash of the Subject Public Key Information (SPKI)
// object in an x509 certificate (in DER encoding). It returns the full hash as a
// hex encoded string (suitable for passing to Set.Allow).
func Hash(certificate *x509.Certificate) string {
spkiHash := sha256.Sum256(certificate.RawSubjectPublicKeyInfo)
return formatSHA256 + ":" + strings.ToLower(hex.EncodeToString(spkiHash[:]))
}
// allowSHA256 validates a "sha256" format hash and adds a canonical version of it into the Set
func (s *Set) allowSHA256(hash string) error {
// validate that the hash is the right length to be a full SHA-256 hash
hashLength := hex.DecodedLen(len(hash))
if hashLength != sha256.Size {
return fmt.Errorf("expected a %d byte SHA-256 hash, found %d bytes", sha256.Size, hashLength)
}
// validate that the hash is valid hex
_, err := hex.DecodeString(hash)
if err != nil {
return err
}
// in the end, just store the original hex string in memory (in lowercase)
s.sha256Hashes[strings.ToLower(hash)] = true
return nil
}
// checkSHA256 returns true if the certificate's "sha256" hash is pinned in the Set
func (s *Set) checkSHA256(certificate *x509.Certificate) bool {
actualHash := sha256.Sum256(certificate.RawSubjectPublicKeyInfo)
actualHashHex := strings.ToLower(hex.EncodeToString(actualHash[:]))
return s.sha256Hashes[actualHashHex]
}

View File

@ -1,158 +0,0 @@
/*
Copyright 2017 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 pubkeypin
import (
"crypto/x509"
"encoding/pem"
"strings"
"testing"
)
// testCertPEM is a simple self-signed test certificate issued with the openssl CLI:
// openssl req -new -newkey rsa:2048 -days 36500 -nodes -x509 -keyout /dev/null -out test.crt
const testCertPEM = `
-----BEGIN CERTIFICATE-----
MIIDRDCCAiygAwIBAgIJAJgVaCXvC6HkMA0GCSqGSIb3DQEBBQUAMB8xHTAbBgNV
BAMTFGt1YmVhZG0ta2V5cGlucy10ZXN0MCAXDTE3MDcwNTE3NDMxMFoYDzIxMTcw
NjExMTc0MzEwWjAfMR0wGwYDVQQDExRrdWJlYWRtLWtleXBpbnMtdGVzdDCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK0ba8mHU9UtYlzM1Own2Fk/XGjR
J4uJQvSeGLtz1hID1IA0dLwruvgLCPadXEOw/f/IWIWcmT+ZmvIHZKa/woq2iHi5
+HLhXs7aG4tjKGLYhag1hLjBI7icqV7ovkjdGAt9pWkxEzhIYClFMXDjKpMSynu+
YX6nZ9tic1cOkHmx2yiZdMkuriRQnpTOa7bb03OC1VfGl7gHlOAIYaj4539WCOr8
+ACTUMJUFEHcRZ2o8a/v6F9GMK+7SC8SJUI+GuroXqlMAdhEv4lX5Co52enYaClN
+D9FJLRpBv2YfiCQdJRaiTvCBSxEFz6BN+PtP5l2Hs703ZWEkOqCByM6HV8CAwEA
AaOBgDB+MB0GA1UdDgQWBBRQgUX8MhK2rWBWQiPHWcKzoWDH5DBPBgNVHSMESDBG
gBRQgUX8MhK2rWBWQiPHWcKzoWDH5KEjpCEwHzEdMBsGA1UEAxMUa3ViZWFkbS1r
ZXlwaW5zLXRlc3SCCQCYFWgl7wuh5DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB
BQUAA4IBAQCaAUif7Pfx3X0F08cxhx8/Hdx4jcJw6MCq6iq6rsXM32ge43t8OHKC
pJW08dk58a3O1YQSMMvD6GJDAiAfXzfwcwY6j258b1ZlI9Ag0VokvhMl/XfdCsdh
AWImnL1t4hvU5jLaImUUMlYxMcSfHBGAm7WJIZ2LdEfg6YWfZh+WGbg1W7uxLxk6
y4h5rWdNnzBHWAGf7zJ0oEDV6W6RSwNXtC0JNnLaeIUm/6xdSddJlQPwUv8YH4jX
c1vuFqTnJBPcb7W//R/GI2Paicm1cmns9NLnPR35exHxFTy+D1yxmGokpoPMdife
aH+sfuxT8xeTPb3kjzF9eJTlnEquUDLM
-----END CERTIFICATE-----`
// expectedHash can be verified using the openssl CLI:
// openssl x509 -pubkey -in test.crt openssl rsa -pubin -outform der 2>&/dev/null | openssl dgst -sha256 -hex
const expectedHash = `sha256:345959acb2c3b2feb87d281961c893f62a314207ef02599f1cc4a5fb255480b3`
// testCert2PEM is a second test cert generated the same way as testCertPEM
const testCert2PEM = `
-----BEGIN CERTIFICATE-----
MIID9jCCAt6gAwIBAgIJAN5MXZDic7qYMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCXRlc3RDZXJ0MjAgFw0xNzA3MjQxNjA0
MDFaGA8yMTE3MDYzMDE2MDQwMVowWTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNv
bWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAG
A1UEAxMJdGVzdENlcnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
0brwpJYN2ytPWzRBtZSVc3dhkQlA59AzxzqeLLkano0Pxo9NIc3T/y58nnRI8uaS
I1P7BzUfJTiUEvmAtX8NggqKK4ld/gPrU+IRww1CUYS4KCkA/0d0ctPy0JwBCjD+
b57G3rmNE8c+0jns6J96ZzNtqmv6N+ZlFBAXm1p4S+k0kGi5+hoQ6H7SYXjk2lG+
r/8jPQEjy/NSdw1dcCA0Nc6o+hPr32927dS6J9KOhBeXNYUNdbuDDmroM9/gN2e/
YMSA1olLeDPQ7Xvhk0PIyEDnHh83AffPCx5yM3htVRGddjIsPAVUJEL3z5leJtxe
fzyPghOhHJY0PXqznDQTcwIDAQABo4G+MIG7MB0GA1UdDgQWBBRP0IJqv/5rQ4Uf
SByl77dJeEapRDCBiwYDVR0jBIGDMIGAgBRP0IJqv/5rQ4UfSByl77dJeEapRKFd
pFswWTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAxMJdGVzdENlcnQyggkA
3kxdkOJzupgwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA0RIMHc10
wHHPMh9UflqBgDMF7gfbOL0juJfGloAOcohWWfMZBBJ0CQKMy3xRyoK3HmbW1eeb
iATjesw7t4VEAwf7mgKAd+eTfWYB952uq5qYJ2TI28mSofEq1Wz3RmrNkC1KCBs1
u+YMFGwyl6necV9zKCeiju4jeovI1GA38TvH7MgYln6vMJ+FbgOXj7XCpek7dQiY
KGaeSSH218mGNQaWRQw2Sm3W6cFdANoCJUph4w18s7gjtFpfV63s80hXRps+vEyv
jEQMEQpG8Ss7HGJLGLBw/xAmG0e//XS/o2dDonbGbvzToFByz8OGxjMhk6yV6hdd
+iyvsLAw/MYMSA==
-----END CERTIFICATE-----
`
// testCert is a small helper to get a test x509.Certificate from the PEM constants
func testCert(t *testing.T, pemString string) *x509.Certificate {
// Decode the example certificate from a PEM file into a PEM block
pemBlock, _ := pem.Decode([]byte(pemString))
if pemBlock == nil {
t.Fatal("failed to parse test certificate PEM")
return nil
}
// Parse the PEM block into an x509.Certificate
result, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
t.Fatalf("failed to parse test certificate: %v", err)
return nil
}
return result
}
func TestSet(t *testing.T) {
s := NewSet()
if !s.Empty() {
t.Error("expected a new set to be empty")
return
}
err := s.Allow("xyz")
if err == nil || !s.Empty() {
t.Error("expected allowing junk to fail")
return
}
err = s.Allow("0011223344")
if err == nil || !s.Empty() {
t.Error("expected allowing something too short to fail")
return
}
err = s.Allow(expectedHash + expectedHash)
if err == nil || !s.Empty() {
t.Error("expected allowing something too long to fail")
return
}
err = s.Check(testCert(t, testCertPEM))
if err == nil {
t.Error("expected test cert to not be allowed (yet)")
return
}
err = s.Allow(strings.ToUpper(expectedHash))
if err != nil || s.Empty() {
t.Error("expected allowing uppercase expectedHash to succeed")
return
}
err = s.Check(testCert(t, testCertPEM))
if err != nil {
t.Errorf("expected test cert to be allowed, but got back: %v", err)
return
}
err = s.Check(testCert(t, testCert2PEM))
if err == nil {
t.Error("expected the second test cert to be disallowed")
return
}
}
func TestHash(t *testing.T) {
actualHash := Hash(testCert(t, testCertPEM))
if actualHash != expectedHash {
t.Errorf(
"failed to Hash() to the expected value\n\texpected: %q\n\t actual: %q",
expectedHash,
actualHash,
)
}
}

View File

@ -1,52 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["utils_test.go"],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//cmd/kubeadm/test:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["utils.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,289 +0,0 @@
/*
Copyright 2017 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 staticpod
import (
"fmt"
"io/ioutil"
"net"
"net/url"
"os"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/util"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
const (
// kubeControllerManagerAddressArg represents the address argument of the kube-controller-manager configuration.
kubeControllerManagerAddressArg = "address"
// kubeSchedulerAddressArg represents the address argument of the kube-scheduler configuration.
kubeSchedulerAddressArg = "address"
// etcdListenClientURLsArg represents the listen-client-urls argument of the etcd configuration.
etcdListenClientURLsArg = "listen-client-urls"
)
// ComponentPod returns a Pod object from the container and volume specifications
func ComponentPod(container v1.Container, volumes map[string]v1.Volume) v1.Pod {
return v1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
ObjectMeta: metav1.ObjectMeta{
Name: container.Name,
Namespace: metav1.NamespaceSystem,
Annotations: map[string]string{kubetypes.CriticalPodAnnotationKey: ""},
// The component and tier labels are useful for quickly identifying the control plane Pods when doing a .List()
// against Pods in the kube-system namespace. Can for example be used together with the WaitForPodsWithLabel function
Labels: map[string]string{"component": container.Name, "tier": "control-plane"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{container},
PriorityClassName: "system-cluster-critical",
HostNetwork: true,
Volumes: VolumeMapToSlice(volumes),
},
}
}
// ComponentResources returns the v1.ResourceRequirements object needed for allocating a specified amount of the CPU
func ComponentResources(cpu string) v1.ResourceRequirements {
return v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpu),
},
}
}
// ComponentProbe is a helper function building a ready v1.Probe object from some simple parameters
func ComponentProbe(cfg *kubeadmapi.MasterConfiguration, componentName string, port int, path string, scheme v1.URIScheme) *v1.Probe {
return &v1.Probe{
Handler: v1.Handler{
HTTPGet: &v1.HTTPGetAction{
Host: GetProbeAddress(cfg, componentName),
Path: path,
Port: intstr.FromInt(port),
Scheme: scheme,
},
},
InitialDelaySeconds: 15,
TimeoutSeconds: 15,
FailureThreshold: 8,
}
}
// EtcdProbe is a helper function for building a shell-based, etcdctl v1.Probe object to healthcheck etcd
func EtcdProbe(cfg *kubeadmapi.MasterConfiguration, componentName string, port int, certsDir string, CACertName string, CertName string, KeyName string) *v1.Probe {
tlsFlags := fmt.Sprintf("--cacert=%[1]s/%[2]s --cert=%[1]s/%[3]s --key=%[1]s/%[4]s", certsDir, CACertName, CertName, KeyName)
// etcd pod is alive if a linearizable get succeeds.
cmd := fmt.Sprintf("ETCDCTL_API=3 etcdctl --endpoints=https://[%s]:%d %s get foo", GetProbeAddress(cfg, componentName), port, tlsFlags)
return &v1.Probe{
Handler: v1.Handler{
Exec: &v1.ExecAction{
Command: []string{"/bin/sh", "-ec", cmd},
},
},
InitialDelaySeconds: 15,
TimeoutSeconds: 15,
FailureThreshold: 8,
}
}
// NewVolume creates a v1.Volume with a hostPath mount to the specified location
func NewVolume(name, path string, pathType *v1.HostPathType) v1.Volume {
return v1.Volume{
Name: name,
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: path,
Type: pathType,
},
},
}
}
// NewVolumeMount creates a v1.VolumeMount to the specified location
func NewVolumeMount(name, path string, readOnly bool) v1.VolumeMount {
return v1.VolumeMount{
Name: name,
MountPath: path,
ReadOnly: readOnly,
}
}
// VolumeMapToSlice returns a slice of volumes from a map's values
func VolumeMapToSlice(volumes map[string]v1.Volume) []v1.Volume {
v := make([]v1.Volume, 0, len(volumes))
for _, vol := range volumes {
v = append(v, vol)
}
return v
}
// VolumeMountMapToSlice returns a slice of volumes from a map's values
func VolumeMountMapToSlice(volumeMounts map[string]v1.VolumeMount) []v1.VolumeMount {
v := make([]v1.VolumeMount, 0, len(volumeMounts))
for _, volMount := range volumeMounts {
v = append(v, volMount)
}
return v
}
// GetExtraParameters builds a list of flag arguments two string-string maps, one with default, base commands and one with overrides
func GetExtraParameters(overrides map[string]string, defaults map[string]string) []string {
var command []string
for k, v := range overrides {
if len(v) > 0 {
command = append(command, fmt.Sprintf("--%s=%s", k, v))
}
}
for k, v := range defaults {
if _, overrideExists := overrides[k]; !overrideExists {
command = append(command, fmt.Sprintf("--%s=%s", k, v))
}
}
return command
}
// WriteStaticPodToDisk writes a static pod file to disk
func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error {
// creates target folder if not already exists
if err := os.MkdirAll(manifestDir, 0700); err != nil {
return fmt.Errorf("failed to create directory %q: %v", manifestDir, err)
}
// writes the pod to disk
serialized, err := util.MarshalToYaml(&pod, v1.SchemeGroupVersion)
if err != nil {
return fmt.Errorf("failed to marshal manifest for %q to YAML: %v", componentName, err)
}
filename := kubeadmconstants.GetStaticPodFilepath(componentName, manifestDir)
if err := ioutil.WriteFile(filename, serialized, 0600); err != nil {
return fmt.Errorf("failed to write static pod manifest file for %q (%q): %v", componentName, filename, err)
}
return nil
}
// ReadStaticPodFromDisk reads a static pod file from disk
func ReadStaticPodFromDisk(manifestPath string) (*v1.Pod, error) {
buf, err := ioutil.ReadFile(manifestPath)
if err != nil {
return &v1.Pod{}, fmt.Errorf("failed to read manifest for %q: %v", manifestPath, err)
}
obj, err := util.UnmarshalFromYaml(buf, v1.SchemeGroupVersion)
if err != nil {
return &v1.Pod{}, fmt.Errorf("failed to unmarshal manifest for %q from YAML: %v", manifestPath, err)
}
pod := obj.(*v1.Pod)
return pod, nil
}
// GetProbeAddress returns an IP address or 127.0.0.1 to use for liveness probes
// in static pod manifests.
func GetProbeAddress(cfg *kubeadmapi.MasterConfiguration, componentName string) string {
switch {
case componentName == kubeadmconstants.KubeAPIServer:
// In the case of a self-hosted deployment, the initial host on which kubeadm --init is run,
// will generate a DaemonSet with a nodeSelector such that all nodes with the label
// node-role.kubernetes.io/master='' will have the API server deployed to it. Since the init
// is run only once on an initial host, the API advertise address will be invalid for any
// future hosts that do not have the same address. Furthermore, since liveness and readiness
// probes do not support the Downward API we cannot dynamically set the advertise address to
// the node's IP. The only option then is to use localhost.
if features.Enabled(cfg.FeatureGates, features.SelfHosting) {
return "127.0.0.1"
} else if cfg.API.AdvertiseAddress != "" {
return cfg.API.AdvertiseAddress
}
case componentName == kubeadmconstants.KubeControllerManager:
if addr, exists := cfg.ControllerManagerExtraArgs[kubeControllerManagerAddressArg]; exists {
return addr
}
case componentName == kubeadmconstants.KubeScheduler:
if addr, exists := cfg.SchedulerExtraArgs[kubeSchedulerAddressArg]; exists {
return addr
}
case componentName == kubeadmconstants.Etcd:
if cfg.Etcd.Local != nil && cfg.Etcd.Local.ExtraArgs != nil {
if arg, exists := cfg.Etcd.Local.ExtraArgs[etcdListenClientURLsArg]; exists {
// Use the first url in the listen-client-urls if multiple url's are specified.
if strings.ContainsAny(arg, ",") {
arg = strings.Split(arg, ",")[0]
}
parsedURL, err := url.Parse(arg)
if err != nil || parsedURL.Hostname() == "" {
break
}
// Return the IP if the URL contains an address instead of a name.
if ip := net.ParseIP(parsedURL.Hostname()); ip != nil {
// etcdctl doesn't support auto-converting zero addresses into loopback addresses
if ip.Equal(net.IPv4zero) {
return "127.0.0.1"
}
if ip.Equal(net.IPv6zero) {
return net.IPv6loopback.String()
}
return ip.String()
}
// Use the local resolver to try resolving the name within the URL.
// If the name can not be resolved, return an IPv4 loopback address.
// Otherwise, select the first valid IPv4 address.
// If the name does not resolve to an IPv4 address, select the first valid IPv6 address.
addrs, err := net.LookupIP(parsedURL.Hostname())
if err != nil {
break
}
var ip net.IP
for _, addr := range addrs {
if addr.To4() != nil {
ip = addr
break
}
if addr.To16() != nil && ip == nil {
ip = addr
}
}
return ip.String()
}
}
}
return "127.0.0.1"
}

View File

@ -1,609 +0,0 @@
/*
Copyright 2017 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 staticpod
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
)
func TestComponentResources(t *testing.T) {
a := ComponentResources("250m")
if a.Requests == nil {
t.Errorf(
"failed componentResources, return value was nil",
)
}
}
func TestComponentProbe(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.MasterConfiguration
component string
port int
path string
scheme v1.URIScheme
expected string
}{
{
name: "default apiserver advertise address with http",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "",
},
},
component: kubeadmconstants.KubeAPIServer,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "127.0.0.1",
},
{
name: "default apiserver advertise address with http",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.3.4",
},
FeatureGates: map[string]bool{
features.SelfHosting: true,
},
},
component: kubeadmconstants.KubeAPIServer,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "127.0.0.1",
},
{
name: "default apiserver advertise address with https",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "",
},
},
component: kubeadmconstants.KubeAPIServer,
port: 2,
path: "bar",
scheme: v1.URISchemeHTTPS,
expected: "127.0.0.1",
},
{
name: "valid ipv4 apiserver advertise address with http",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.3.4",
},
},
component: kubeadmconstants.KubeAPIServer,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "1.2.3.4",
},
{
name: "valid ipv6 apiserver advertise address with http",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "2001:db8::1",
},
},
component: kubeadmconstants.KubeAPIServer,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "2001:db8::1",
},
{
name: "valid IPv4 controller-manager probe",
cfg: &kubeadmapi.MasterConfiguration{
ControllerManagerExtraArgs: map[string]string{"address": "1.2.3.4"},
},
component: kubeadmconstants.KubeControllerManager,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "1.2.3.4",
},
{
name: "valid IPv6 controller-manager probe",
cfg: &kubeadmapi.MasterConfiguration{
ControllerManagerExtraArgs: map[string]string{"address": "2001:db8::1"},
},
component: kubeadmconstants.KubeControllerManager,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "2001:db8::1",
},
{
name: "valid IPv4 scheduler probe",
cfg: &kubeadmapi.MasterConfiguration{
SchedulerExtraArgs: map[string]string{"address": "1.2.3.4"},
},
component: kubeadmconstants.KubeScheduler,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "1.2.3.4",
},
{
name: "valid IPv6 scheduler probe",
cfg: &kubeadmapi.MasterConfiguration{
SchedulerExtraArgs: map[string]string{"address": "2001:db8::1"},
},
component: kubeadmconstants.KubeScheduler,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "2001:db8::1",
},
}
for _, rt := range tests {
actual := ComponentProbe(rt.cfg, rt.component, rt.port, rt.path, rt.scheme)
if actual.Handler.HTTPGet.Host != rt.expected {
t.Errorf("%s test case failed:\n\texpected: %s\n\t actual: %s",
rt.name, rt.expected,
actual.Handler.HTTPGet.Host)
}
if actual.Handler.HTTPGet.Port != intstr.FromInt(rt.port) {
t.Errorf("%s test case failed:\n\texpected: %v\n\t actual: %v",
rt.name, rt.port,
actual.Handler.HTTPGet.Port)
}
if actual.Handler.HTTPGet.Path != rt.path {
t.Errorf("%s test case failed:\n\texpected: %s\n\t actual: %s",
rt.name, rt.path,
actual.Handler.HTTPGet.Path)
}
if actual.Handler.HTTPGet.Scheme != rt.scheme {
t.Errorf("%s test case failed:\n\texpected: %v\n\t actual: %v",
rt.name, rt.scheme,
actual.Handler.HTTPGet.Scheme)
}
}
}
func TestEtcdProbe(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.MasterConfiguration
component string
port int
certsDir string
cacert string
cert string
key string
expected string
}{
{
name: "valid etcd probe using listen-client-urls IPv4 addresses",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://1.2.3.4:2379,http://4.3.2.1:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsA",
cacert: "ca1",
cert: "cert1",
key: "key1",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[1.2.3.4]:1 --cacert=secretsA/ca1 --cert=secretsA/cert1 --key=secretsA/key1 get foo",
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv6 address",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://[0:0:0:0:0:0:0:0]:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsB",
cacert: "ca2",
cert: "cert2",
key: "key2",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[::1]:1 --cacert=secretsB/ca2 --cert=secretsB/cert2 --key=secretsB/key2 get foo",
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv6 address 2",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://[::0:0]:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsB",
cacert: "ca2",
cert: "cert2",
key: "key2",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[::1]:1 --cacert=secretsB/ca2 --cert=secretsB/cert2 --key=secretsB/key2 get foo",
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv6 address 3",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://[::]:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsB",
cacert: "ca2",
cert: "cert2",
key: "key2",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[::1]:1 --cacert=secretsB/ca2 --cert=secretsB/cert2 --key=secretsB/key2 get foo",
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv4 address",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://1.2.3.4:2379,http://4.3.2.1:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsA",
cacert: "ca1",
cert: "cert1",
key: "key1",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[1.2.3.4]:1 --cacert=secretsA/ca1 --cert=secretsA/cert1 --key=secretsA/key1 get foo",
},
{
name: "valid etcd probe using listen-client-urls IPv6 addresses",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://[2001:db8::1]:2379,http://[2001:db8::2]:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsB",
cacert: "ca2",
cert: "cert2",
key: "key2",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[2001:db8::1]:1 --cacert=secretsB/ca2 --cert=secretsB/cert2 --key=secretsB/key2 get foo",
},
{
name: "valid IPv4 etcd probe using hostname for listen-client-urls",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://localhost:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsC",
cacert: "ca3",
cert: "cert3",
key: "key3",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:1 --cacert=secretsC/ca3 --cert=secretsC/cert3 --key=secretsC/key3 get foo",
},
}
for _, rt := range tests {
actual := EtcdProbe(rt.cfg, rt.component, rt.port, rt.certsDir, rt.cacert, rt.cert, rt.key)
if actual.Handler.Exec.Command[2] != rt.expected {
t.Errorf("%s test case failed:\n\texpected: %s\n\t actual: %s",
rt.name, rt.expected,
actual.Handler.Exec.Command[2])
}
}
}
func TestComponentPod(t *testing.T) {
var tests = []struct {
name string
expected v1.Pod
}{
{
name: "foo",
expected: v1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "kube-system",
Annotations: map[string]string{"scheduler.alpha.kubernetes.io/critical-pod": ""},
Labels: map[string]string{"component": "foo", "tier": "control-plane"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
},
},
PriorityClassName: "system-cluster-critical",
HostNetwork: true,
Volumes: []v1.Volume{},
},
},
},
}
for _, rt := range tests {
c := v1.Container{Name: rt.name}
actual := ComponentPod(c, map[string]v1.Volume{})
if !reflect.DeepEqual(rt.expected, actual) {
t.Errorf(
"failed componentPod:\n\texpected: %v\n\t actual: %v",
rt.expected,
actual,
)
}
}
}
func TestNewVolume(t *testing.T) {
hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
var tests = []struct {
name string
path string
expected v1.Volume
pathType *v1.HostPathType
}{
{
name: "foo",
path: "/etc/foo",
expected: v1.Volume{
Name: "foo",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: "/etc/foo",
Type: &hostPathDirectoryOrCreate,
},
},
},
pathType: &hostPathDirectoryOrCreate,
},
}
for _, rt := range tests {
actual := NewVolume(rt.name, rt.path, rt.pathType)
if !reflect.DeepEqual(actual, rt.expected) {
t.Errorf(
"failed newVolume:\n\texpected: %v\n\t actual: %v",
rt.expected,
actual,
)
}
}
}
func TestNewVolumeMount(t *testing.T) {
var tests = []struct {
name string
path string
ro bool
expected v1.VolumeMount
}{
{
name: "foo",
path: "/etc/foo",
ro: false,
expected: v1.VolumeMount{
Name: "foo",
MountPath: "/etc/foo",
ReadOnly: false,
},
},
{
name: "bar",
path: "/etc/foo/bar",
ro: true,
expected: v1.VolumeMount{
Name: "bar",
MountPath: "/etc/foo/bar",
ReadOnly: true,
},
},
}
for _, rt := range tests {
actual := NewVolumeMount(rt.name, rt.path, rt.ro)
if !reflect.DeepEqual(actual, rt.expected) {
t.Errorf(
"failed newVolumeMount:\n\texpected: %v\n\t actual: %v",
rt.expected,
actual,
)
}
}
}
func TestVolumeMapToSlice(t *testing.T) {
testVolumes := map[string]v1.Volume{
"foo": {
Name: "foo",
},
}
volumeSlice := VolumeMapToSlice(testVolumes)
if len(volumeSlice) != 1 {
t.Errorf("Expected slice length of 1, got %d", len(volumeSlice))
}
if volumeSlice[0].Name != "foo" {
t.Errorf("Expected volume name \"foo\", got %s", volumeSlice[0].Name)
}
}
func TestVolumeMountMapToSlice(t *testing.T) {
testVolumeMounts := map[string]v1.VolumeMount{
"foo": {
Name: "foo",
},
}
volumeMountSlice := VolumeMountMapToSlice(testVolumeMounts)
if len(volumeMountSlice) != 1 {
t.Errorf("Expected slice length of 1, got %d", len(volumeMountSlice))
}
if volumeMountSlice[0].Name != "foo" {
t.Errorf("Expected volume mount name \"foo\", got %s", volumeMountSlice[0].Name)
}
}
func TestGetExtraParameters(t *testing.T) {
var tests = []struct {
overrides map[string]string
defaults map[string]string
expected []string
}{
{
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
},
defaults: map[string]string{
"admission-control": "NamespaceLifecycle",
"insecure-bind-address": "127.0.0.1",
"allow-privileged": "true",
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
},
},
{
overrides: map[string]string{
"admission-control": "NamespaceLifecycle,LimitRanger",
},
defaults: map[string]string{
"insecure-bind-address": "127.0.0.1",
"allow-privileged": "true",
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
},
},
}
for _, rt := range tests {
actual := GetExtraParameters(rt.overrides, rt.defaults)
sort.Strings(actual)
sort.Strings(rt.expected)
if !reflect.DeepEqual(actual, rt.expected) {
t.Errorf("failed getExtraParameters:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
}
}
}
const (
validPod = `
apiVersion: v1
kind: Pod
metadata:
labels:
component: etcd
tier: control-plane
name: etcd
namespace: kube-system
spec:
containers:
- image: gcr.io/google_containers/etcd-amd64:3.1.11
status: {}
`
invalidPod = `---{ broken yaml @@@`
)
func TestReadStaticPodFromDisk(t *testing.T) {
tests := []struct {
description string
podYaml string
expectErr bool
writeManifest bool
}{
{
description: "valid pod is marshaled",
podYaml: validPod,
writeManifest: true,
expectErr: false,
},
{
description: "invalid pod fails to unmarshal",
podYaml: invalidPod,
writeManifest: true,
expectErr: true,
},
{
description: "non-existent file returns error",
podYaml: ``,
writeManifest: false,
expectErr: true,
},
}
for _, rt := range tests {
tmpdir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpdir)
manifestPath := filepath.Join(tmpdir, "pod.yaml")
if rt.writeManifest {
err := ioutil.WriteFile(manifestPath, []byte(rt.podYaml), 0644)
if err != nil {
t.Fatalf("Failed to write pod manifest\n%s\n\tfatal error: %v", rt.description, err)
}
}
_, actualErr := ReadStaticPodFromDisk(manifestPath)
if (actualErr != nil) != rt.expectErr {
t.Errorf(
"ReadStaticPodFromDisk failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
rt.description,
rt.expectErr,
(actualErr != nil),
actualErr,
)
}
}
}

View File

@ -1,37 +0,0 @@
/*
Copyright 2017 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 util
import (
"bytes"
"fmt"
"text/template"
)
// ParseTemplate validates and parses passed as argument template
func ParseTemplate(strtmpl string, obj interface{}) ([]byte, error) {
var buf bytes.Buffer
tmpl, err := template.New("template").Parse(strtmpl)
if err != nil {
return nil, fmt.Errorf("error when parsing template: %v", err)
}
err = tmpl.Execute(&buf, obj)
if err != nil {
return nil, fmt.Errorf("error when executing template: %v", err)
}
return buf.Bytes(), nil
}

View File

@ -1,90 +0,0 @@
/*
Copyright 2017 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 util
import (
"testing"
)
const (
validTmpl = "image: {{ .ImageRepository }}/pause-{{ .Arch }}:3.1"
validTmplOut = "image: k8s.gcr.io/pause-amd64:3.1"
doNothing = "image: k8s.gcr.io/pause-amd64:3.1"
invalidTmpl1 = "{{ .baz }/d}"
invalidTmpl2 = "{{ !foobar }}"
)
func TestParseTemplate(t *testing.T) {
var tmplTests = []struct {
template string
data interface{}
output string
errExpected bool
}{
// should parse a valid template and set the right values
{
template: validTmpl,
data: struct{ ImageRepository, Arch string }{
ImageRepository: "k8s.gcr.io",
Arch: "amd64",
},
output: validTmplOut,
errExpected: false,
},
// should noop if there aren't any {{ .foo }} present
{
template: doNothing,
data: struct{ ImageRepository, Arch string }{
ImageRepository: "k8s.gcr.io",
Arch: "amd64",
},
output: doNothing,
errExpected: false,
},
// invalid syntax, passing nil
{
template: invalidTmpl1,
data: nil,
output: "",
errExpected: true,
},
// invalid syntax
{
template: invalidTmpl2,
data: struct{}{},
output: "",
errExpected: true,
},
}
for _, tt := range tmplTests {
outbytes, err := ParseTemplate(tt.template, tt.data)
if tt.errExpected != (err != nil) {
t.Errorf(
"failed TestParseTemplate:\n\texpected err: %t\n\t actual: %s",
tt.errExpected,
err,
)
}
if tt.output != string(outbytes) {
t.Errorf(
"failed TestParseTemplate:\n\texpected bytes: %s\n\t actual: %s",
tt.output,
outbytes,
)
}
}
}

View File

@ -1,150 +0,0 @@
/*
Copyright 2016 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 util
import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
netutil "k8s.io/apimachinery/pkg/util/net"
)
var (
kubeReleaseBucketURL = "https://dl.k8s.io"
kubeReleaseRegex = regexp.MustCompile(`^v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)([-0-9a-zA-Z_\.+]*)?$`)
kubeReleaseLabelRegex = regexp.MustCompile(`^[[:lower:]]+(-[-\w_\.]+)?$`)
kubeBucketPrefixes = regexp.MustCompile(`^((release|ci|ci-cross)/)?([-\w_\.+]+)$`)
)
// KubernetesReleaseVersion is helper function that can fetch
// available version information from release servers based on
// label names, like "stable" or "latest".
//
// If argument is already semantic version string, it
// will return same string.
//
// In case of labels, it tries to fetch from release
// servers and then return actual semantic version.
//
// Available names on release servers:
// stable (latest stable release)
// stable-1 (latest stable release in 1.x)
// stable-1.0 (and similarly 1.1, 1.2, 1.3, ...)
// latest (latest release, including alpha/beta)
// latest-1 (latest release in 1.x, including alpha/beta)
// latest-1.0 (and similarly 1.1, 1.2, 1.3, ...)
func KubernetesReleaseVersion(version string) (string, error) {
ver := normalizedBuildVersion(version)
if len(ver) != 0 {
return ver, nil
}
bucketURL, versionLabel, err := splitVersion(version)
if err != nil {
return "", err
}
// revalidate, if exact build from e.g. CI bucket requested.
ver = normalizedBuildVersion(versionLabel)
if len(ver) != 0 {
return ver, nil
}
if kubeReleaseLabelRegex.MatchString(versionLabel) {
url := fmt.Sprintf("%s/%s.txt", bucketURL, versionLabel)
body, err := fetchFromURL(url)
if err != nil {
return "", err
}
// Re-validate received version and return.
return KubernetesReleaseVersion(body)
}
return "", fmt.Errorf("version %q doesn't match patterns for neither semantic version nor labels (stable, latest, ...)", version)
}
// KubernetesVersionToImageTag is helper function that replaces all
// non-allowed symbols in tag strings with underscores.
// Image tag can only contain lowercase and uppercase letters, digits,
// underscores, periods and dashes.
// Current usage is for CI images where all of symbols except '+' are valid,
// but function is for generic usage where input can't be always pre-validated.
func KubernetesVersionToImageTag(version string) string {
allowed := regexp.MustCompile(`[^-a-zA-Z0-9_\.]`)
return allowed.ReplaceAllString(version, "_")
}
// KubernetesIsCIVersion checks if user requested CI version
func KubernetesIsCIVersion(version string) bool {
subs := kubeBucketPrefixes.FindAllStringSubmatch(version, 1)
if len(subs) == 1 && len(subs[0]) == 4 && strings.HasPrefix(subs[0][2], "ci") {
return true
}
return false
}
// Internal helper: returns normalized build version (with "v" prefix if needed)
// If input doesn't match known version pattern, returns empty string.
func normalizedBuildVersion(version string) string {
if kubeReleaseRegex.MatchString(version) {
if strings.HasPrefix(version, "v") {
return version
}
return "v" + version
}
return ""
}
// Internal helper: split version parts,
// Return base URL and cleaned-up version
func splitVersion(version string) (string, string, error) {
var urlSuffix string
subs := kubeBucketPrefixes.FindAllStringSubmatch(version, 1)
if len(subs) != 1 || len(subs[0]) != 4 {
return "", "", fmt.Errorf("invalid version %q", version)
}
switch {
case strings.HasPrefix(subs[0][2], "ci"):
// Just use whichever the user specified
urlSuffix = subs[0][2]
default:
urlSuffix = "release"
}
url := fmt.Sprintf("%s/%s", kubeReleaseBucketURL, urlSuffix)
return url, subs[0][3], nil
}
// Internal helper: return content of URL
func fetchFromURL(url string) (string, error) {
client := &http.Client{Transport: netutil.SetOldTransportDefaults(&http.Transport{})}
resp, err := client.Get(url)
if err != nil {
return "", fmt.Errorf("unable to get URL %q: %s", url, err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("unable to fetch file. URL: %q Status: %v", url, resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("unable to read content of URL %q: %s", url, err.Error())
}
return strings.TrimSpace(string(body)), nil
}

View File

@ -1,292 +0,0 @@
/*
Copyright 2016 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 util
import (
"net/http"
"net/http/httptest"
"path"
"strings"
"testing"
)
func TestEmptyVersion(t *testing.T) {
ver, err := KubernetesReleaseVersion("")
if err == nil {
t.Error("KubernetesReleaseVersion returned successfully, but error expected")
}
if ver != "" {
t.Error("KubernetesReleaseVersion returned value, expected only error")
}
}
func TestValidVersion(t *testing.T) {
validVersions := []string{
"v1.3.0",
"v1.4.0-alpha.0",
"v1.4.5",
"v1.4.0-beta.0",
"v2.0.0",
"v1.6.0-alpha.0.536+d60d9f3269288f",
"v1.5.0-alpha.0.1078+1044b6822497da-pull",
"v1.5.0-alpha.1.822+49b9e32fad9f32-pull-gke-gci",
"v1.6.1+coreos.0",
}
for _, s := range validVersions {
ver, err := KubernetesReleaseVersion(s)
t.Log("Valid: ", s, ver, err)
if err != nil {
t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
}
if ver != s {
t.Errorf("KubernetesReleaseVersion should return same valid version string. %q != %q", s, ver)
}
}
}
func TestInvalidVersion(t *testing.T) {
invalidVersions := []string{
"v1.3",
"1.4",
"b1.4.0",
"c1.4.5+git",
"something1.2",
}
for _, s := range invalidVersions {
ver, err := KubernetesReleaseVersion(s)
t.Log("Invalid: ", s, ver, err)
if err == nil {
t.Errorf("KubernetesReleaseVersion error expected for version %q, but returned successfully", s)
}
if ver != "" {
t.Errorf("KubernetesReleaseVersion should return empty string in case of error. Returned %q for version %q", ver, s)
}
}
}
func TestValidConvenientForUserVersion(t *testing.T) {
validVersions := []string{
"1.4.0",
"1.4.5+git",
"1.6.1_coreos.0",
}
for _, s := range validVersions {
ver, err := KubernetesReleaseVersion(s)
t.Log("Valid: ", s, ver, err)
if err != nil {
t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
}
if ver != "v"+s {
t.Errorf("KubernetesReleaseVersion should return semantic version string. %q vs. %q", s, ver)
}
}
}
func TestVersionFromNetwork(t *testing.T) {
type T struct {
Content string
Status int
Expected string
ErrorExpected bool
}
cases := map[string]T{
"stable": {"stable-1", http.StatusOK, "v1.4.6", false}, // recursive pointer to stable-1
"stable-1": {"v1.4.6", http.StatusOK, "v1.4.6", false},
"stable-1.3": {"v1.3.10", http.StatusOK, "v1.3.10", false},
"latest": {"v1.6.0-alpha.0", http.StatusOK, "v1.6.0-alpha.0", false},
"latest-1.3": {"v1.3.11-beta.0", http.StatusOK, "v1.3.11-beta.0", false},
"empty": {"", http.StatusOK, "", true},
"garbage": {"<?xml version='1.0'?><Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message></Error>", http.StatusOK, "", true},
"unknown": {"The requested URL was not found on this server.", http.StatusNotFound, "", true},
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := strings.TrimSuffix(path.Base(r.URL.Path), ".txt")
res, found := cases[key]
if found {
http.Error(w, res.Content, res.Status)
} else {
http.Error(w, "Unknown test case key!", http.StatusNotFound)
}
}))
defer server.Close()
kubeReleaseBucketURL = server.URL
for k, v := range cases {
ver, err := KubernetesReleaseVersion(k)
t.Logf("Key: %q. Result: %q, Error: %v", k, ver, err)
switch {
case err != nil && !v.ErrorExpected:
t.Errorf("KubernetesReleaseVersion: unexpected error for %q. Error: %v", k, err)
case err == nil && v.ErrorExpected:
t.Errorf("KubernetesReleaseVersion: error expected for key %q, but result is %q", k, ver)
case ver != v.Expected:
t.Errorf("KubernetesReleaseVersion: unexpected result for key %q. Expected: %q Actual: %q", k, v.Expected, ver)
}
}
}
func TestVersionToTag(t *testing.T) {
type T struct {
input string
expected string
}
cases := []T{
// NOP
{"", ""},
// Official releases
{"v1.0.0", "v1.0.0"},
// CI or custom builds
{"v10.1.2-alpha.1.100+0123456789abcdef+SOMETHING", "v10.1.2-alpha.1.100_0123456789abcdef_SOMETHING"},
// random and invalid input: should return safe value
{"v1,0!0+üñµ", "v1_0_0____"},
}
for _, tc := range cases {
tag := KubernetesVersionToImageTag(tc.input)
t.Logf("KubernetesVersionToImageTag: Input: %q. Result: %q. Expected: %q", tc.input, tag, tc.expected)
if tag != tc.expected {
t.Errorf("failed KubernetesVersionToImageTag: Input: %q. Result: %q. Expected: %q", tc.input, tag, tc.expected)
}
}
}
func TestSplitVersion(t *testing.T) {
type T struct {
input string
bucket string
label string
valid bool
}
cases := []T{
// Release area
{"v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
{"v1.8.0-alpha.2.1231+afabd012389d53a", "https://dl.k8s.io/release", "v1.8.0-alpha.2.1231+afabd012389d53a", true},
{"release/v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
{"release/latest-1.7", "https://dl.k8s.io/release", "latest-1.7", true},
// CI builds area, lookup actual builds at ci-cross/*.txt
{"ci/latest", "https://dl.k8s.io/ci", "latest", true},
{"ci-cross/latest", "https://dl.k8s.io/ci-cross", "latest", true},
{"ci/latest-1.7", "https://dl.k8s.io/ci", "latest-1.7", true},
{"ci-cross/latest-1.7", "https://dl.k8s.io/ci-cross", "latest-1.7", true},
// unknown label in default (release) area: splitVersion validate only areas.
{"unknown-1", "https://dl.k8s.io/release", "unknown-1", true},
// unknown area, not valid input.
{"unknown/latest-1", "", "", false},
}
// kubeReleaseBucketURL can be overridden during network tests, thus ensure
// it will contain value corresponding to expected outcome for this unit test
kubeReleaseBucketURL = "https://dl.k8s.io"
for _, tc := range cases {
bucket, label, err := splitVersion(tc.input)
switch {
case err != nil && tc.valid:
t.Errorf("splitVersion: unexpected error for %q. Error: %v", tc.input, err)
case err == nil && !tc.valid:
t.Errorf("splitVersion: error expected for key %q, but result is %q, %q", tc.input, bucket, label)
case bucket != tc.bucket:
t.Errorf("splitVersion: unexpected bucket result for key %q. Expected: %q Actual: %q", tc.input, tc.bucket, bucket)
case label != tc.label:
t.Errorf("splitVersion: unexpected label result for key %q. Expected: %q Actual: %q", tc.input, tc.label, label)
}
}
}
func TestKubernetesIsCIVersion(t *testing.T) {
type T struct {
input string
expected bool
}
cases := []T{
{"", false},
// Official releases
{"v1.0.0", false},
{"release/v1.0.0", false},
// CI builds
{"ci/latest-1", true},
{"ci-cross/latest", true},
{"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
{"ci-cross/v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
}
for _, tc := range cases {
result := KubernetesIsCIVersion(tc.input)
t.Logf("KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
if result != tc.expected {
t.Errorf("failed KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
}
}
}
// Validate KubernetesReleaseVersion but with bucket prefixes
func TestCIBuildVersion(t *testing.T) {
type T struct {
input string
expected string
valid bool
}
cases := []T{
// Official releases
{"v1.7.0", "v1.7.0", true},
{"release/v1.8.0", "v1.8.0", true},
{"1.4.0-beta.0", "v1.4.0-beta.0", true},
{"release/0invalid", "", false},
// CI or custom builds
{"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
{"ci-cross/v1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
{"ci/1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
{"ci-cross/1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
{"ci/0invalid", "", false},
}
for _, tc := range cases {
ver, err := KubernetesReleaseVersion(tc.input)
t.Logf("Input: %q. Result: %q, Error: %v", tc.input, ver, err)
switch {
case err != nil && tc.valid:
t.Errorf("KubernetesReleaseVersion: unexpected error for input %q. Error: %v", tc.input, err)
case err == nil && !tc.valid:
t.Errorf("KubernetesReleaseVersion: error expected for input %q, but result is %q", tc.input, ver)
case ver != tc.expected:
t.Errorf("KubernetesReleaseVersion: unexpected result for input %q. Expected: %q Actual: %q", tc.input, tc.expected, ver)
}
}
}
func TestNormalizedBuildVersionVersion(t *testing.T) {
type T struct {
input string
expected string
}
cases := []T{
{"v1.7.0", "v1.7.0"},
{"v1.8.0-alpha.2.1231+afabd012389d53a", "v1.8.0-alpha.2.1231+afabd012389d53a"},
{"1.7.0", "v1.7.0"},
{"unknown-1", ""},
}
for _, tc := range cases {
output := normalizedBuildVersion(tc.input)
if output != tc.expected {
t.Errorf("normalizedBuildVersion: unexpected output %q for input %q. Expected: %q", output, tc.input, tc.expected)
}
}
}