Fresh dep ensure

This commit is contained in:
Mike Cronce
2018-11-26 13:23:56 -05:00
parent 93cb8a04d7
commit 407478ab9a
9016 changed files with 551394 additions and 279685 deletions

View File

@ -11,6 +11,8 @@ go_library(
srcs = [
"arguments.go",
"cgroupdriver.go",
"chroot_unix.go",
"chroot_windows.go",
"copy.go",
"endpoint.go",
"error.go",
@ -21,16 +23,21 @@ go_library(
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",
"//cmd/kubeadm/app/constants:go_default_library",
"//pkg/version:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)
@ -39,6 +46,7 @@ go_test(
srcs = [
"arguments_test.go",
"cgroupdriver_test.go",
"chroot_test.go",
"endpoint_test.go",
"error_test.go",
"marshal_test.go",
@ -48,10 +56,14 @@ go_test(
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",
"//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
@ -72,8 +84,11 @@ filegroup(
"//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/pkiutil:all-srcs",
"//cmd/kubeadm/app/util/pubkeypin:all-srcs",
"//cmd/kubeadm/app/util/runtime:all-srcs",
"//cmd/kubeadm/app/util/staticpod:all-srcs",
"//cmd/kubeadm/app/util/system:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -22,25 +22,26 @@ go_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",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
@ -61,14 +62,17 @@ go_test(
name = "go_default_test",
srcs = [
"dryrunclient_test.go",
"idempotency_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",
"//pkg/kubelet/apis:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library",
],
)

View File

@ -29,6 +29,8 @@ import (
"k8s.io/client-go/rest"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/clientcmd"
"github.com/pkg/errors"
)
// 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
@ -61,11 +63,11 @@ func NewClientBackedDryRunGetter(config *rest.Config) (*ClientBackedDryRunGetter
func NewClientBackedDryRunGetterFromKubeconfig(file string) (*ClientBackedDryRunGetter, error) {
config, err := clientcmd.LoadFromFile(file)
if err != nil {
return nil, fmt.Errorf("failed to load kubeconfig: %v", err)
return nil, errors.Wrap(err, "failed to load kubeconfig")
}
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 nil, errors.Wrap(err, "failed to create API client configuration from kubeconfig")
}
return NewClientBackedDryRunGetter(clientConfig)
}

View File

@ -23,6 +23,7 @@ import (
"io"
"strings"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
clientset "k8s.io/client-go/kubernetes"
@ -114,7 +115,7 @@ func NewDryRunClientWithOpts(opts DryRunClientOptions) clientset.Interface {
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")
return true, nil, errors.New("can't cast get reactor event action object to GetAction interface")
}
handled, obj, err := opts.Getter.HandleGetAction(getAction)
@ -139,7 +140,7 @@ func NewDryRunClientWithOpts(opts DryRunClientOptions) clientset.Interface {
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")
return true, nil, errors.New("can't cast list reactor event action object to ListAction interface")
}
handled, objs, err := opts.Getter.HandleListAction(listAction)

View File

@ -72,7 +72,7 @@ func TestLogDryRunAction(t *testing.T) {
`),
},
{
action: core.NewPatchAction(schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "default", "my-node", []byte(`{"spec":{"taints":[{"key": "foo", "value": "bar"}]}}`)),
action: core.NewPatchAction(schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "default", "my-node", "application/strategic-merge-patch+json", []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:

View File

@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
apps "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
@ -40,11 +41,11 @@ import (
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)
return errors.Wrap(err, "unable to create configmap")
}
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(cm); err != nil {
return fmt.Errorf("unable to update configmap: %v", err)
return errors.Wrap(err, "unable to update configmap")
}
}
return nil
@ -58,7 +59,7 @@ func CreateOrRetainConfigMap(client clientset.Interface, cm *v1.ConfigMap, confi
}
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 errors.Wrap(err, "unable to create configmap")
}
}
}
@ -69,11 +70,11 @@ func CreateOrRetainConfigMap(client clientset.Interface, cm *v1.ConfigMap, confi
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)
return errors.Wrap(err, "unable to create secret")
}
if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Update(secret); err != nil {
return fmt.Errorf("unable to update secret: %v", err)
return errors.Wrap(err, "unable to update secret")
}
}
return nil
@ -85,7 +86,7 @@ func CreateOrUpdateServiceAccount(client clientset.Interface, sa *v1.ServiceAcco
// 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 errors.Wrap(err, "unable to create serviceaccount")
}
}
return nil
@ -95,11 +96,11 @@ func CreateOrUpdateServiceAccount(client clientset.Interface, sa *v1.ServiceAcco
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)
return errors.Wrap(err, "unable to create deployment")
}
if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Update(deploy); err != nil {
return fmt.Errorf("unable to update deployment: %v", err)
return errors.Wrap(err, "unable to update deployment")
}
}
return nil
@ -109,11 +110,11 @@ func CreateOrUpdateDeployment(client clientset.Interface, deploy *apps.Deploymen
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)
return errors.Wrap(err, "unable to create daemonset")
}
if _, err := client.AppsV1().DaemonSets(ds.ObjectMeta.Namespace).Update(ds); err != nil {
return fmt.Errorf("unable to update daemonset: %v", err)
return errors.Wrap(err, "unable to update daemonset")
}
}
return nil
@ -141,11 +142,11 @@ func DeleteDeploymentForeground(client clientset.Interface, namespace, name stri
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)
return errors.Wrap(err, "unable to create RBAC role")
}
if _, err := client.RbacV1().Roles(role.ObjectMeta.Namespace).Update(role); err != nil {
return fmt.Errorf("unable to update RBAC role: %v", err)
return errors.Wrap(err, "unable to update RBAC role")
}
}
return nil
@ -155,11 +156,11 @@ func CreateOrUpdateRole(client clientset.Interface, role *rbac.Role) error {
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)
return errors.Wrap(err, "unable to create RBAC rolebinding")
}
if _, err := client.RbacV1().RoleBindings(roleBinding.ObjectMeta.Namespace).Update(roleBinding); err != nil {
return fmt.Errorf("unable to update RBAC rolebinding: %v", err)
return errors.Wrap(err, "unable to update RBAC rolebinding")
}
}
return nil
@ -169,11 +170,11 @@ func CreateOrUpdateRoleBinding(client clientset.Interface, roleBinding *rbac.Rol
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)
return errors.Wrap(err, "unable to create RBAC clusterrole")
}
if _, err := client.RbacV1().ClusterRoles().Update(clusterRole); err != nil {
return fmt.Errorf("unable to update RBAC clusterrole: %v", err)
return errors.Wrap(err, "unable to update RBAC clusterrole")
}
}
return nil
@ -183,23 +184,26 @@ func CreateOrUpdateClusterRole(client clientset.Interface, clusterRole *rbac.Clu
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)
return errors.Wrap(err, "unable to create RBAC clusterrolebinding")
}
if _, err := client.RbacV1().ClusterRoleBindings().Update(clusterRoleBinding); err != nil {
return fmt.Errorf("unable to update RBAC clusterrolebinding: %v", err)
return errors.Wrap(err, "unable to update RBAC clusterrolebinding")
}
}
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) {
// PatchNodeOnce executes patchFn on the node object found by the node name.
// This is a condition function meant to be used with wait.Poll. false, nil
// implies it is safe to try again, an error indicates no more tries should be
// made and true indicates success.
func PatchNodeOnce(client clientset.Interface, nodeName string, patchFn func(*v1.Node)) func() (bool, error) {
return func() (bool, error) {
// First get the node object
n, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
if err != nil {
// TODO this should only be for timeouts
return false, nil
}
@ -211,7 +215,7 @@ func PatchNode(client clientset.Interface, nodeName string, patchFn func(*v1.Nod
oldData, err := json.Marshal(n)
if err != nil {
return false, err
return false, errors.Wrapf(err, "failed to marshal unmodified node %q into JSON", n.Name)
}
// Execute the mutating function
@ -219,22 +223,32 @@ func PatchNode(client clientset.Interface, nodeName string, patchFn func(*v1.Nod
newData, err := json.Marshal(n)
if err != nil {
return false, err
return false, errors.Wrapf(err, "failed to marshal modified node %q into JSON", n.Name)
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{})
if err != nil {
return false, err
return false, errors.Wrap(err, "failed to create two way merge patch")
}
if _, err := client.CoreV1().Nodes().Patch(n.Name, types.StrategicMergePatchType, patchBytes); err != nil {
// TODO also check for timeouts
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 false, errors.Wrapf(err, "error patching node %q through apiserver", n.Name)
}
return true, nil
})
}
}
// PatchNode tries to patch a node using patchFn for the actual mutating logic.
// Retries are provided by the wait package.
func PatchNode(client clientset.Interface, nodeName string, patchFn func(*v1.Node)) error {
// wait.Poll will rerun the condition function every interval function if
// the function returns false. If the condition function returns an error
// then the retries end and the error is returned.
return wait.Poll(constants.APICallRetryInterval, constants.PatchNodeTimeout, PatchNodeOnce(client, nodeName, patchFn))
}

View File

@ -0,0 +1,83 @@
/*
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 apiclient_test
import (
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
)
func TestPatchNodeNonErrorCases(t *testing.T) {
testcases := []struct {
name string
lookupName string
node v1.Node
success bool
}{
{
name: "simple update",
lookupName: "testnode",
node: v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "testnode",
Labels: map[string]string{kubeletapis.LabelHostname: ""},
},
},
success: true,
},
{
name: "node does not exist",
lookupName: "whale",
success: false,
},
{
name: "node not labelled yet",
lookupName: "robin",
node: v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "robin",
},
},
success: false,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
client := fake.NewSimpleClientset()
_, err := client.Core().Nodes().Create(&tc.node)
if err != nil {
t.Fatalf("failed to create node to fake client: %v", err)
}
conditionFunction := apiclient.PatchNodeOnce(client, tc.lookupName, func(node *v1.Node) {
node.Name = "testNewNode"
})
success, err := conditionFunction()
if err != nil {
t.Fatalf("did not expect error: %v", err)
}
if success != tc.success {
t.Fatalf("expected %v got %v", tc.success, success)
}
})
}
}

View File

@ -17,10 +17,11 @@ limitations under the License.
package apiclient
import (
"fmt"
"net"
"strings"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -79,7 +80,7 @@ func (idr *InitDryRunGetter) HandleListAction(action core.ListAction) (bool, run
}
// 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
// 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
@ -88,12 +89,12 @@ func (idr *InitDryRunGetter) handleKubernetesService(action core.GetAction) (boo
_, svcSubnet, err := net.ParseCIDR(idr.serviceSubnet)
if err != nil {
return true, nil, fmt.Errorf("error parsing CIDR %q: %v", idr.serviceSubnet, err)
return true, nil, errors.Wrapf(err, "error parsing CIDR %q", idr.serviceSubnet)
}
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)
return true, nil, errors.Wrapf(err, "unable to get first IP address from the given CIDR (%s)", svcSubnet.String())
}
// The only used field of this Service object is the ClusterIP, which kube-dns uses to calculate its own IP

View File

@ -1,5 +1,5 @@
/*
Copyright 2017 The Kubernetes Authors.
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.
@ -22,6 +22,7 @@ import (
"net/http"
"time"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -29,6 +30,7 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
@ -49,6 +51,8 @@ type Waiter interface {
WaitForStaticPodControlPlaneHashes(nodeName string) (map[string]string, error)
// WaitForHealthyKubelet blocks until the kubelet /healthz endpoint returns 'ok'
WaitForHealthyKubelet(initalTimeout time.Duration, healthzEndpoint string) error
// WaitForKubeletAndFunc is a wrapper for WaitForHealthyKubelet that also blocks for a function
WaitForKubeletAndFunc(f func() error) error
// SetTimeout adjusts the timeout to the specified duration
SetTimeout(timeout time.Duration)
}
@ -131,24 +135,46 @@ func (w *KubeWaiter) WaitForPodToDisappear(podName string) error {
// WaitForHealthyKubelet blocks until the kubelet /healthz endpoint returns 'ok'
func (w *KubeWaiter) WaitForHealthyKubelet(initalTimeout time.Duration, healthzEndpoint string) error {
time.Sleep(initalTimeout)
fmt.Printf("[kubelet-check] Initial timeout of %v passed.\n", 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.Println("[kubelet-check] It seems like the kubelet isn't running or healthy.")
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.Println("[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 errors.New("the kubelet healthz endpoint is unhealthy")
}
return nil
}, 5) // a failureThreshold of five means waiting for a total of 155 seconds
}
// WaitForKubeletAndFunc waits primarily for the function f to execute, even though it might take some time. If that takes a long time, and the kubelet
// /healthz continuously are unhealthy, kubeadm will error out after a period of exponential backoff
func (w *KubeWaiter) WaitForKubeletAndFunc(f func() error) error {
errorChan := make(chan error)
go func(errC chan error, waiter Waiter) {
if err := waiter.WaitForHealthyKubelet(40*time.Second, fmt.Sprintf("http://localhost:%d/healthz", kubeadmconstants.KubeletHealthzPort)); err != nil {
errC <- err
}
}(errorChan, w)
go func(errC chan error, waiter Waiter) {
// This main goroutine sends whatever the f function returns (error or not) to the channel
// This in order to continue on success (nil error), or just fail if the function returns an error
errC <- f()
}(errorChan, w)
// This call is blocking until one of the goroutines sends to errorChan
return <-errorChan
}
// SetTimeout adjusts the timeout to the specified duration
func (w *KubeWaiter) SetTimeout(timeout time.Duration) {
w.timeout = timeout
@ -211,20 +237,6 @@ func (w *KubeWaiter) WaitForStaticPodHashChange(nodeName, component, previousHas
})
}
// 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) {

View File

@ -20,6 +20,8 @@ import (
"fmt"
"sort"
"strings"
"github.com/pkg/errors"
)
// BuildArgumentListFromMap takes two string-string maps, one with the base arguments and one
@ -89,10 +91,10 @@ func ReplaceArgument(command []string, argMutateFunc func(map[string]string) map
// 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 '--'")
return "", "", errors.New("the argument should start with '--'")
}
if !strings.Contains(arg, "=") {
return "", "", fmt.Errorf("the argument should have a '=' between the flag and the value")
return "", "", errors.New("the argument should have a '=' between the flag and the value")
}
// Remove the starting --
arg = strings.TrimPrefix(arg, "--")
@ -101,10 +103,10 @@ func parseArgument(arg string) (string, string, error) {
// 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")
return "", "", errors.New("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 "", "", errors.New("the argument must have a key")
}
return keyvalSlice[0], keyvalSlice[1], nil

View File

@ -7,11 +7,12 @@ go_library(
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",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/audit/v1:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
@ -20,10 +21,10 @@ go_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",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/audit/v1:go_default_library",
],
)

View File

@ -17,39 +17,40 @@ limitations under the License.
package audit
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
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"
auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/util"
)
// CreateDefaultAuditLogPolicy writes the default audit log policy to disk.
func CreateDefaultAuditLogPolicy(policyFile string) error {
policy := auditv1beta1.Policy{
policy := auditv1.Policy{
TypeMeta: metav1.TypeMeta{
APIVersion: auditv1beta1.SchemeGroupVersion.String(),
APIVersion: auditv1.SchemeGroupVersion.String(),
Kind: "Policy",
},
Rules: []auditv1beta1.PolicyRule{
Rules: []auditv1.PolicyRule{
{
Level: auditv1beta1.LevelMetadata,
Level: auditv1.LevelMetadata,
},
},
}
return writePolicyToDisk(policyFile, &policy)
}
func writePolicyToDisk(policyFile string, policy *auditv1beta1.Policy) error {
func writePolicyToDisk(policyFile string, policy *auditv1.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)
return errors.Wrapf(err, "failed to create directory %q: ", filepath.Dir(policyFile))
}
scheme := runtime.NewScheme()
@ -59,14 +60,14 @@ func writePolicyToDisk(policyFile string, policy *auditv1beta1.Policy) error {
codecs := serializer.NewCodecFactory(scheme)
// writes the policy to disk
serialized, err := util.MarshalToYamlForCodecs(policy, auditv1beta1.SchemeGroupVersion, codecs)
serialized, err := util.MarshalToYamlForCodecs(policy, auditv1.SchemeGroupVersion, codecs)
if err != nil {
return fmt.Errorf("failed to marshal audit policy to YAML: %v", err)
return errors.Wrap(err, "failed to marshal audit policy to YAML")
}
if err := ioutil.WriteFile(policyFile, serialized, 0600); err != nil {
return fmt.Errorf("failed to write audit policy to %v: %v", policyFile, err)
return errors.Wrapf(err, "failed to write audit policy to %v: ", policyFile)
}
return nil

View File

@ -25,7 +25,7 @@ import (
"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"
auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
)
func cleanup(t *testing.T, path string) {
@ -54,7 +54,7 @@ func TestCreateDefaultAuditLogPolicy(t *testing.T) {
scheme := runtime.NewScheme()
install.Install(scheme)
codecs := serializer.NewCodecFactory(scheme)
policy := auditv1beta1.Policy{}
policy := auditv1.Policy{}
err = runtime.DecodeInto(codecs.UniversalDecoder(), policyBytes, &policy)
if err != nil {
t.Fatalf("failed to decode written policy: %v", err)

View File

@ -17,9 +17,10 @@ limitations under the License.
package util
import (
"fmt"
"strings"
"github.com/pkg/errors"
utilsexec "k8s.io/utils/exec"
)
@ -39,7 +40,7 @@ func GetCgroupDriverDocker(execer utilsexec.Interface) (string, error) {
func validateCgroupDriver(driver string) error {
if driver != "cgroupfs" && driver != "systemd" {
return fmt.Errorf("unknown cgroup driver %q", driver)
return errors.Errorf("unknown cgroup driver %q", driver)
}
return nil
}
@ -51,7 +52,7 @@ func validateCgroupDriver(driver string) error {
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 "", errors.Wrap(err, "cannot execute 'docker info'")
}
return string(out), nil
}
@ -71,5 +72,5 @@ func getCgroupDriverFromDockerInfo(info string) (string, error) {
}
return driver, nil
}
return "", fmt.Errorf("cgroup driver is not defined in 'docker info'")
return "", errors.New("cgroup driver is not defined in 'docker info'")
}

View File

@ -0,0 +1,66 @@
// +build !windows
/*
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 (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// Can't just call Chroot() because it will affect other tests in the
// same process. Golang makes it hard to just call fork(), so instead
// we exec ourselves again, and run the test in the new subprocess.
func testChrootReal(t *testing.T) {
testfile := filepath.FromSlash("/" + filepath.Base(os.Args[0]))
dir := filepath.Dir(os.Args[0])
if dir == "." {
t.Skip("skipping: running test at root somehow")
}
if err := Chroot(dir); err != nil {
if strings.Contains(err.Error(), "operation not permitted") {
t.Skip("skipping: insufficient permissions to chroot")
}
t.Fatalf("chroot error: %v", err)
}
// All file access should now be relative to `dir`
if _, err := os.Stat(testfile); err != nil {
t.Errorf("Expected file %q to exist, but got %v", testfile, err)
}
}
func TestChroot(t *testing.T) {
if os.Getenv("GO_TEST_CHROOT_FOR_REALZ") == "1" {
testChrootReal(t)
return
}
cmd := exec.Command(os.Args[0], "-test.v", "-test.run=TestChroot")
cmd.Env = []string{"GO_TEST_CHROOT_FOR_REALZ=1"}
out, err := cmd.Output()
t.Logf("subprocess output:\n%s", out)
if err != nil {
t.Errorf("subprocess error: %v", err)
}
}

View File

@ -0,0 +1,40 @@
// +build !windows
/*
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 (
"github.com/pkg/errors"
"os"
"path/filepath"
"syscall"
)
// Chroot chroot()s to the new path.
// NB: All file paths after this call are effectively relative to
// `rootfs`
func Chroot(rootfs string) error {
if err := syscall.Chroot(rootfs); err != nil {
return errors.Wrapf(err, "unable to chroot to %s", rootfs)
}
root := filepath.FromSlash("/")
if err := os.Chdir(root); err != nil {
return errors.Wrapf(err, "unable to chdir to %s", root)
}
return nil
}

View File

@ -0,0 +1,30 @@
// +build windows
/*
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 (
"github.com/pkg/errors"
)
// Chroot chroot()s to the new path.
// NB: All file paths after this call are effectively relative to
// `rootfs`
func Chroot(rootfs string) error {
return errors.New("chroot is not implemented on Windows")
}

View File

@ -10,49 +10,62 @@ go_library(
name = "go_default_library",
srcs = [
"cluster.go",
"masterconfig.go",
"nodeconfig.go",
"common.go",
"initconfiguration.go",
"joinconfiguration.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/v1beta1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/componentconfigs:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/config/strict: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",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//staging/src/k8s.io/cluster-bootstrap/token/util:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"masterconfig_test.go",
"nodeconfig_test.go",
"cluster_test.go",
"common_test.go",
"initconfiguration_test.go",
"joinconfiguration_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/apis/kubeadm/v1beta1:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/apiclient:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake: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",
"//vendor/github.com/renstrom/dedent:go_default_library",
],
)
@ -65,6 +78,9 @@ filegroup(
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
srcs = [
":package-srcs",
"//cmd/kubeadm/app/util/config/strict:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -17,49 +17,246 @@ limitations under the License.
package config
import (
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"
"github.com/pkg/errors"
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/version"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
certutil "k8s.io/client-go/util/cert"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
"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) {
func FetchConfigFromFileOrCluster(client clientset.Interface, w io.Writer, logPrefix, cfgPath string, newControlPlane bool) (*kubeadmapi.InitConfiguration, error) {
// Load the configuration from a file or the cluster
configBytes, err := loadConfigurationBytes(client, w, logPrefix, cfgPath)
initcfg, err := loadConfiguration(client, w, logPrefix, cfgPath, newControlPlane)
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)
// Apply dynamic defaults
if err := SetInitDynamicDefaults(initcfg); err != nil {
return nil, err
}
return initcfg, err
}
// 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) {
// loadConfiguration loads the configuration byte slice from either a file or the cluster ConfigMap
func loadConfiguration(client clientset.Interface, w io.Writer, logPrefix, cfgPath string, newControlPlane bool) (*kubeadmapi.InitConfiguration, 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)
return loadInitConfigurationFromFile(cfgPath)
}
fmt.Fprintf(w, "[%s] Reading configuration from the cluster...\n", logPrefix)
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.KubeadmConfigConfigMap)
return getInitConfigurationFromCluster(constants.KubernetesDir, client, newControlPlane)
}
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)
func loadInitConfigurationFromFile(cfgPath string) (*kubeadmapi.InitConfiguration, error) {
configBytes, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, 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
// Unmarshal the versioned configuration populated from the file,
// convert it to the internal API types, then default and validate
// NB the file contains multiple YAML, with a combination of
// - a YAML with a InitConfiguration object
// - a YAML with a ClusterConfiguration object (without embedded component configs)
// - separated YAML for components configs
initcfg, err := BytesToInternalConfig(configBytes)
if err != nil {
return nil, err
}
return initcfg, nil
}
func getInitConfigurationFromCluster(kubeconfigDir string, client clientset.Interface, newControlPlane bool) (*kubeadmapi.InitConfiguration, error) {
// TODO: This code should support reading the MasterConfiguration key as well for backwards-compat
// Also, the config map really should be KubeadmConfigConfigMap...
configMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(constants.KubeadmConfigConfigMap, metav1.GetOptions{})
if err != nil {
return nil, errors.Wrap(err, "failed to get config map")
}
// InitConfiguration is composed with data from different places
initcfg := &kubeadmapi.InitConfiguration{}
// gets ClusterConfiguration from kubeadm-config
clusterConfigurationData, ok := configMap.Data[constants.ClusterConfigurationConfigMapKey]
if !ok {
return nil, errors.Errorf("unexpected error when reading kubeadm-config ConfigMap: %s key value pair missing", constants.ClusterConfigurationConfigMapKey)
}
if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), []byte(clusterConfigurationData), &initcfg.ClusterConfiguration); err != nil {
return nil, errors.Wrap(err, "failed to decode cluster configuration data")
}
// gets the component configs from the corresponding config maps
if err := getComponentConfigs(client, &initcfg.ClusterConfiguration); err != nil {
return nil, errors.Wrap(err, "failed to get component configs")
}
// if this isn't a new controlplane instance (e.g. in case of kubeadm upgrades)
// get nodes specific information as well
if !newControlPlane {
// gets the nodeRegistration for the current from the node object
if err := getNodeRegistration(kubeconfigDir, client, &initcfg.NodeRegistration); err != nil {
return nil, errors.Wrap(err, "failed to get node registration")
}
// gets the APIEndpoint for the current node from then ClusterStatus in the kubeadm-config ConfigMap
if err := getAPIEndpoint(configMap.Data, initcfg.NodeRegistration.Name, &initcfg.LocalAPIEndpoint); err != nil {
return nil, errors.Wrap(err, "failed to getAPIEndpoint")
}
}
return initcfg, nil
}
// getNodeRegistration returns the nodeRegistration for the current node
func getNodeRegistration(kubeconfigDir string, client clientset.Interface, nodeRegistration *kubeadmapi.NodeRegistrationOptions) error {
// gets the name of the current node
nodeName, err := getNodeNameFromKubeletConfig(kubeconfigDir)
if err != nil {
return errors.Wrap(err, "failed to get node name from kubelet config")
}
// gets the corresponding node and retrives attributes stored there.
node, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
if err != nil {
return errors.Wrap(err, "faild to get corresponding node")
}
criSocket, ok := node.ObjectMeta.Annotations[constants.AnnotationKubeadmCRISocket]
if !ok {
return errors.Errorf("node %s doesn't have %s annotation", nodeName, constants.AnnotationKubeadmCRISocket)
}
// returns the nodeRegistration attributes
nodeRegistration.Name = nodeName
nodeRegistration.CRISocket = criSocket
nodeRegistration.Taints = node.Spec.Taints
// NB. currently nodeRegistration.KubeletExtraArgs isn't stored at node level but only in the kubeadm-flags.env
// that isn't modified during upgrades
// in future we might reconsider this thus enabling changes to the kubeadm-flags.env during upgrades as well
return nil
}
// getNodeNameFromConfig gets the node name from a kubelet config file
// TODO: in future we want to switch to a more canonical way for doing this e.g. by having this
// information in the local kubelet config.yaml
func getNodeNameFromKubeletConfig(kubeconfigDir string) (string, error) {
// loads the kubelet.conf file
fileName := filepath.Join(kubeconfigDir, constants.KubeletKubeConfigFileName)
config, err := clientcmd.LoadFromFile(fileName)
if err != nil {
return "", err
}
// gets the info about the current user
authInfo := config.AuthInfos[config.Contexts[config.CurrentContext].AuthInfo]
// gets the X509 certificate with current user credentials
var certs []*x509.Certificate
if len(authInfo.ClientCertificateData) > 0 {
// if the config file uses an embedded x509 certificate (e.g. kubelet.conf created by kubeadm), parse it
if certs, err = certutil.ParseCertsPEM(authInfo.ClientCertificateData); err != nil {
return "", err
}
} else if len(authInfo.ClientCertificate) > 0 {
// if the config file links an external x509 certificate (e.g. kubelet.conf created by TLS bootstrap), load it
if certs, err = certutil.CertsFromFile(authInfo.ClientCertificate); err != nil {
return "", err
}
} else {
return "", errors.New("invalid kubelet.conf. X509 certificate expected")
}
// We are only putting one certificate in the certificate pem file, so it's safe to just pick the first one
// TODO: Support multiple certs here in order to be able to rotate certs
cert := certs[0]
// gets the node name from the certificate common name
return strings.TrimPrefix(cert.Subject.CommonName, constants.NodesUserPrefix), nil
}
// getAPIEndpoint returns the APIEndpoint for the current node
func getAPIEndpoint(data map[string]string, nodeName string, apiEndpoint *kubeadmapi.APIEndpoint) error {
// gets the ClusterStatus from kubeadm-config
clusterStatus, err := unmarshalClusterStatus(data)
if err != nil {
return err
}
// gets the APIEndpoint for the current machine from the ClusterStatus
e, ok := clusterStatus.APIEndpoints[nodeName]
if !ok {
return errors.New("failed to get APIEndpoint information for this node")
}
apiEndpoint.AdvertiseAddress = e.AdvertiseAddress
apiEndpoint.BindPort = e.BindPort
return nil
}
// getComponentConfigs gets the component configs from the corresponding config maps
func getComponentConfigs(client clientset.Interface, clusterConfiguration *kubeadmapi.ClusterConfiguration) error {
// some config maps is versioned, so we need the KubernetesVersion for getting the right config map
k8sVersion := version.MustParseGeneric(clusterConfiguration.KubernetesVersion)
for kind, registration := range componentconfigs.Known {
obj, err := registration.GetFromConfigMap(client, k8sVersion)
if err != nil {
return err
}
if ok := registration.SetToInternalConfig(obj, clusterConfiguration); !ok {
return errors.Errorf("couldn't save componentconfig value for kind %q", string(kind))
}
}
return nil
}
// GetClusterStatus returns the kubeadm cluster status read from the kubeadm-config ConfigMap
func GetClusterStatus(client clientset.Interface) (*kubeadmapi.ClusterStatus, error) {
configMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(constants.KubeadmConfigConfigMap, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return &kubeadmapi.ClusterStatus{}, nil
}
if err != nil {
return nil, err
}
clusterStatus, err := unmarshalClusterStatus(configMap.Data)
if err != nil {
return nil, err
}
return clusterStatus, nil
}
func unmarshalClusterStatus(data map[string]string) (*kubeadmapi.ClusterStatus, error) {
clusterStatusData, ok := data[constants.ClusterStatusConfigMapKey]
if !ok {
return nil, errors.Errorf("unexpected error when reading kubeadm-config ConfigMap: %s key value pair missing", constants.ClusterStatusConfigMapKey)
}
clusterStatus := &kubeadmapi.ClusterStatus{}
if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), []byte(clusterStatusData), clusterStatus); err != nil {
return nil, err
}
return clusterStatus, nil
}

View File

@ -0,0 +1,874 @@
/*
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"
"os"
"path/filepath"
"strings"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/version"
clientset "k8s.io/client-go/kubernetes"
clientsetfake "k8s.io/client-go/kubernetes/fake"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
)
var k8sVersionString = "v1.12.0"
var k8sVersion = version.MustParseGeneric(k8sVersionString)
var nodeName = "mynode"
var cfgFiles = map[string][]byte{
"InitConfiguration_v1beta1": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: InitConfiguration
`),
"ClusterConfiguration_v1beta1": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: ` + k8sVersionString + `
`),
"ClusterStatus_v1beta1": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterStatus
apiEndpoints:
` + nodeName + `:
advertiseAddress: 1.2.3.4
bindPort: 1234
`),
"ClusterStatus_v1beta1_Without_APIEndpoints": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterStatus
`),
"InitConfiguration_v1alpha3": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: InitConfiguration
`),
"ClusterConfiguration_v1alpha3": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: ClusterConfiguration
kubernetesVersion: ` + k8sVersionString + `
`),
"ClusterStatus_v1alpha3": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: ClusterStatus
apiEndpoints:
` + nodeName + `:
advertiseAddress: 1.2.3.4
bindPort: 1234
`),
"ClusterStatus_v1alpha3_Without_APIEndpoints": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: ClusterStatus
`),
"Kube-proxy_componentconfig": []byte(`
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
`),
"Kubelet_componentconfig": []byte(`
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
`),
}
var kubeletConfFiles = map[string][]byte{
"withoutX509Cert": []byte(`
apiVersion: v1
clusters:
- cluster:
server: https://10.0.2.15:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: system:node:mynode
name: system:node:mynode@kubernetes
current-context: system:node:mynode@kubernetes
kind: Config
preferences: {}
users:
- name: system:node:mynode
user:
`),
"configWithEmbeddedCert": []byte(`
apiVersion: v1
clusters:
- cluster:
server: https://10.0.2.15:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: system:node:mynode
name: system:node:mynode@kubernetes
current-context: system:node:mynode@kubernetes
kind: Config
preferences: {}
users:
- name: system:node:mynode
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQWl3VURhYk5vZ1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBNU1ERXhOVE14TWpaYUZ3MHhPVEE1TURFeE5qQXhOVGxhTURReApGVEFUQmdOVkJBb1RESE41YzNSbGJUcHViMlJsY3pFYk1Ca0dBMVVFQXhNU2MzbHpkR1Z0T201dlpHVTZiWGx1CmIyUmxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQWs2UXUzeStyNEZYUzZ4VkoKWU1vNE9kSkt3R1d1MHY4TEJIUnhhOUhvVHo1RXZLQnB1OVJoemt5dStUaFczb0xta2ZTRmNJcitHa0M5MW0zOApFelRmVE5JY2dsL0V5YkpmR1QvdGdUazZYd1kxY1UrUUdmSEFNNTBCVzFXTFVHc25CSllJZjA5eENnZTVoTkxLCnREeUJOWWNQZzg1bUJpOU9CNFJ2UlgyQVFRMjJwZ0xrQUpJWklOU0FEdUFrODN2b051SXM2YVY2bHBkR2Vva3YKdDlpTFdNR3p3a3ZTZUZQTlNGeWZ3Q055eENjb1FDQUNtSnJRS3NoeUE2bWNzdVhORWVXdlRQdVVFSWZPVFl4dwpxdkszRVBOK0xUYlA2anhUMWtTcFJUOSt4Z29uSlFhU0RsbUNBd20zRGJkSVppWUt3R2ppMkxKL0kvYWc0cTlzCjNLb0J2UUlEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLcVVrU21jdW85OG5EK015b005VFdEV0pyTndySXpQTUNqRQpCSkdyREhVaHIwcEZlRjc0RHViODNzRXlaNjFxNUVQd2Y0enNLSzdzdDRUTzZhcE9pZWJYVmN3dnZoa09HQ2dFCmFVdGNOMjFhUGxtU0tOd0c4ai8yK3ZhbU80bGplK1NnZzRUUVB0eDZWejh5VXN2RFhxSUZycjNNd1gzSDA1RW4KWXAzN05JYkhKbGxHUW5LVHA5aTg5aXF4WXVhSERqZldiVHlEY3B5NldNVjdVaFYvY1plc3lGL0NBamNHd1V6YgowRlo5bW5tMnFONlBGWHZ4RmdMSGFWZzN2SVVCbkNmVVVyY1BDNE94VFNPK21aUmUxazh3eUFpVWovSk0rZllvCkcrMi9sbThUYVZqb1U3Rmk1S2E1RzVIWTJHTGFSN1ArSXhZY3JNSENsNjJZN1JxY3JuYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
`),
"configWithLinkedCert": []byte(`
apiVersion: v1
clusters:
- cluster:
server: https://10.0.2.15:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: system:node:mynode
name: system:node:mynode@kubernetes
current-context: system:node:mynode@kubernetes
kind: Config
preferences: {}
users:
- name: system:node:mynode
user:
client-certificate: kubelet.pem
`),
}
var pemFiles = map[string][]byte{
"mynode.pem": []byte(`
-----BEGIN CERTIFICATE-----
MIIC8jCCAdqgAwIBAgIIAiwUDabNogQwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0xODA5MDExNTMxMjZaFw0xOTA5MDExNjAxNTlaMDQx
FTATBgNVBAoTDHN5c3RlbTpub2RlczEbMBkGA1UEAxMSc3lzdGVtOm5vZGU6bXlu
b2RlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk6Qu3y+r4FXS6xVJ
YMo4OdJKwGWu0v8LBHRxa9HoTz5EvKBpu9Rhzkyu+ThW3oLmkfSFcIr+GkC91m38
EzTfTNIcgl/EybJfGT/tgTk6XwY1cU+QGfHAM50BW1WLUGsnBJYIf09xCge5hNLK
tDyBNYcPg85mBi9OB4RvRX2AQQ22pgLkAJIZINSADuAk83voNuIs6aV6lpdGeokv
t9iLWMGzwkvSeFPNSFyfwCNyxCcoQCACmJrQKshyA6mcsuXNEeWvTPuUEIfOTYxw
qvK3EPN+LTbP6jxT1kSpRT9+xgonJQaSDlmCAwm3DbdIZiYKwGji2LJ/I/ag4q9s
3KoBvQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH
AwIwDQYJKoZIhvcNAQELBQADggEBAKqUkSmcuo98nD+MyoM9TWDWJrNwrIzPMCjE
BJGrDHUhr0pFeF74Dub83sEyZ61q5EPwf4zsKK7st4TO6apOiebXVcwvvhkOGCgE
aUtcN21aPlmSKNwG8j/2+vamO4lje+Sgg4TQPtx6Vz8yUsvDXqIFrr3MwX3H05En
Yp37NIbHJllGQnKTp9i89iqxYuaHDjfWbTyDcpy6WMV7UhV/cZesyF/CAjcGwUzb
0FZ9mnm2qN6PFXvxFgLHaVg3vIUBnCfUUrcPC4OxTSO+mZRe1k8wyAiUj/JM+fYo
G+2/lm8TaVjoU7Fi5Ka5G5HY2GLaR7P+IxYcrMHCl62Y7Rqcrnc=
-----END CERTIFICATE-----
`),
}
func TestLoadInitConfigurationFromFile(t *testing.T) {
// Create temp folder for the test case
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
var tests = []struct {
name string
fileContents []byte
}{
{
name: "v1beta1.partial1",
fileContents: cfgFiles["InitConfiguration_v1beta1"],
},
{
name: "v1beta1.partial2",
fileContents: cfgFiles["ClusterConfiguration_v1beta1"],
},
{
name: "v1beta1.full",
fileContents: bytes.Join([][]byte{
cfgFiles["InitConfiguration_v1beta1"],
cfgFiles["ClusterConfiguration_v1beta1"],
cfgFiles["Kube-proxy_componentconfig"],
cfgFiles["Kubelet_componentconfig"],
}, []byte(kubeadmconstants.YAMLDocumentSeparator)),
},
{
name: "v1alpha3.partial1",
fileContents: cfgFiles["InitConfiguration_v1alpha3"],
},
{
name: "v1alpha3.partial2",
fileContents: cfgFiles["ClusterConfiguration_v1alpha3"],
},
{
name: "v1alpha3.full",
fileContents: bytes.Join([][]byte{
cfgFiles["InitConfiguration_v1alpha3"],
cfgFiles["ClusterConfiguration_v1alpha3"],
cfgFiles["Kube-proxy_componentconfig"],
cfgFiles["Kubelet_componentconfig"],
}, []byte(kubeadmconstants.YAMLDocumentSeparator)),
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
cfgPath := filepath.Join(tmpdir, rt.name)
err := ioutil.WriteFile(cfgPath, rt.fileContents, 0644)
if err != nil {
t.Errorf("Couldn't create file")
return
}
obj, err := loadInitConfigurationFromFile(cfgPath)
if err != nil {
t.Errorf("Error reading file: %v", err)
return
}
if obj == nil {
t.Errorf("Unexpected nil return value")
}
})
}
}
func TestGetNodeNameFromKubeletConfig(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
var tests = []struct {
name string
kubeconfigContent []byte
pemContent []byte
expectedError bool
}{
{
name: "valid - with embedded cert",
kubeconfigContent: kubeletConfFiles["configWithEmbeddedCert"],
},
{
name: "invalid - linked cert missing",
kubeconfigContent: kubeletConfFiles["configWithLinkedCert"],
expectedError: true,
},
{
name: "valid - with linked cert",
kubeconfigContent: kubeletConfFiles["configWithLinkedCert"],
pemContent: pemFiles["mynode.pem"],
},
{
name: "invalid - without embedded or linked X509Cert",
kubeconfigContent: kubeletConfFiles["withoutX509Cert"],
expectedError: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
if len(rt.pemContent) > 0 {
pemPath := filepath.Join(tmpdir, "kubelet.pem")
err := ioutil.WriteFile(pemPath, rt.pemContent, 0644)
if err != nil {
t.Errorf("Couldn't create pem file: %v", err)
return
}
rt.kubeconfigContent = []byte(strings.Replace(string(rt.kubeconfigContent), "kubelet.pem", pemPath, -1))
}
kubeconfigPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
err := ioutil.WriteFile(kubeconfigPath, rt.kubeconfigContent, 0644)
if err != nil {
t.Errorf("Couldn't create kubeconfig: %v", err)
return
}
name, err := getNodeNameFromKubeletConfig(tmpdir)
if rt.expectedError != (err != nil) {
t.Errorf("unexpected return err from getNodeRegistration: %v", err)
return
}
if rt.expectedError {
return
}
if name != nodeName {
t.Errorf("invalid name")
}
})
}
}
func TestGetNodeRegistration(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
var tests = []struct {
name string
fileContents []byte
node *v1.Node
expectedError bool
}{
{
name: "invalid - no kubelet.conf",
expectedError: true,
},
{
name: "valid",
fileContents: kubeletConfFiles["configWithEmbeddedCert"],
node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: nodeName,
Annotations: map[string]string{
kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
},
},
Spec: v1.NodeSpec{
Taints: []v1.Taint{kubeadmconstants.MasterTaint},
},
},
},
{
name: "invalid - no node",
fileContents: kubeletConfFiles["configWithEmbeddedCert"],
expectedError: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
cfgPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
if len(rt.fileContents) > 0 {
err := ioutil.WriteFile(cfgPath, rt.fileContents, 0644)
if err != nil {
t.Errorf("Couldn't create file")
return
}
}
client := clientsetfake.NewSimpleClientset()
if rt.node != nil {
_, err := client.CoreV1().Nodes().Create(rt.node)
if err != nil {
t.Errorf("couldn't create Node")
return
}
}
cfg := &kubeadmapi.InitConfiguration{}
err = getNodeRegistration(tmpdir, client, &cfg.NodeRegistration)
if rt.expectedError != (err != nil) {
t.Errorf("unexpected return err from getNodeRegistration: %v", err)
return
}
if rt.expectedError {
return
}
if cfg.NodeRegistration.Name != nodeName {
t.Errorf("invalid cfg.NodeRegistration.Name")
}
if cfg.NodeRegistration.CRISocket != "myCRIsocket" {
t.Errorf("invalid cfg.NodeRegistration.CRISocket")
}
if len(cfg.NodeRegistration.Taints) != 1 {
t.Errorf("invalid cfg.NodeRegistration.Taints")
}
})
}
}
func TestGetAPIEndpoint(t *testing.T) {
var tests = []struct {
name string
configMap fakeConfigMap
expectedError bool
}{
{
name: "valid v1beta1",
configMap: fakeConfigMap{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{
kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta1"]),
},
},
},
{
name: "invalid v1beta1 - No ClusterStatus in kubeadm-config ConfigMap",
configMap: fakeConfigMap{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{},
},
expectedError: true,
},
{
name: "invalid v1beta1 - ClusterStatus without APIEndopoints",
configMap: fakeConfigMap{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{
kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta1_Without_APIEndpoints"]),
},
},
expectedError: true,
},
{
name: "valid v1alpha3",
configMap: fakeConfigMap{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{
kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1alpha3"]),
},
},
},
{
name: "invalid v1alpha3 - No ClusterStatus in kubeadm-config ConfigMap",
configMap: fakeConfigMap{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{},
},
expectedError: true,
},
{
name: "invalid v1alpha3 - ClusterStatus without APIEndopoints",
configMap: fakeConfigMap{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{
kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1alpha3_Without_APIEndpoints"]),
},
},
expectedError: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
cfg := &kubeadmapi.InitConfiguration{}
err := getAPIEndpoint(rt.configMap.data, nodeName, &cfg.LocalAPIEndpoint)
if rt.expectedError != (err != nil) {
t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
return
}
if rt.expectedError {
return
}
if cfg.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" || cfg.LocalAPIEndpoint.BindPort != 1234 {
t.Errorf("invalid cfg.APIEndpoint")
}
})
}
}
func TestGetComponentConfigs(t *testing.T) {
var tests = []struct {
name string
configMaps []fakeConfigMap
expectedError bool
}{
{
name: "valid",
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
},
},
{
name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
},
},
},
},
{
name: "invalid - No kubelet component config ConfigMap",
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeProxyConfigMap,
data: map[string]string{
kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
},
},
},
expectedError: true,
},
{
name: "invalid - No kube-proxy component config ConfigMap",
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion),
data: map[string]string{
kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
},
},
},
expectedError: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
client := clientsetfake.NewSimpleClientset()
for _, c := range rt.configMaps {
err := c.create(client)
if err != nil {
t.Errorf("couldn't create ConfigMap %s", c.name)
return
}
}
cfg := &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
KubernetesVersion: k8sVersionString,
},
}
err := getComponentConfigs(client, &cfg.ClusterConfiguration)
if rt.expectedError != (err != nil) {
t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
return
}
if rt.expectedError {
return
}
// Test expected values in InitConfiguration
if cfg.ComponentConfigs.Kubelet == nil {
t.Errorf("invalid cfg.ComponentConfigs.Kubelet")
}
if cfg.ComponentConfigs.KubeProxy == nil {
t.Errorf("invalid cfg.ComponentConfigs.KubeProxy")
}
})
}
}
func TestGetInitConfigurationFromCluster(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
var tests = []struct {
name string
fileContents []byte
node *v1.Node
configMaps []fakeConfigMap
newControlPlane bool
expectedError bool
}{
{
name: "invalid - No kubeadm-config ConfigMap",
expectedError: true,
},
{
name: "invalid - No ClusterConfiguration in kubeadm-config ConfigMap",
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{},
},
},
expectedError: true,
},
{
name: "valid v1beta1 - new control plane == false", // InitConfiguration composed with data from different places, with also node specific information from ClusterStatus and node
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{
kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta1"]),
kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta1"]),
},
},
{
name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
},
},
{
name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
},
},
},
fileContents: kubeletConfFiles["configWithEmbeddedCert"],
node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: nodeName,
Annotations: map[string]string{
kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
},
},
Spec: v1.NodeSpec{
Taints: []v1.Taint{kubeadmconstants.MasterTaint},
},
},
},
{
name: "valid v1beta1 - new control plane == true", // InitConfiguration composed with data from different places, without node specific information
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{
kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta1"]),
},
},
{
name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
},
},
{
name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
},
},
},
newControlPlane: true,
},
{
name: "valid v1alpha3 - new control plane == false", // InitConfiguration composed with data from different places, with also node specific information from ClusterStatus and node
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{
kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1alpha3"]),
kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1alpha3"]),
},
},
{
name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
},
},
{
name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
},
},
},
fileContents: kubeletConfFiles["configWithEmbeddedCert"],
node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: nodeName,
Annotations: map[string]string{
kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
},
},
Spec: v1.NodeSpec{
Taints: []v1.Taint{kubeadmconstants.MasterTaint},
},
},
},
{
name: "valid v1alpha3 - new control plane == true", // InitConfiguration composed with data from different places, without node specific information
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
data: map[string]string{
kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1alpha3"]),
},
},
{
name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
},
},
{
name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap.
data: map[string]string{
kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
},
},
},
newControlPlane: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
cfgPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
if len(rt.fileContents) > 0 {
err := ioutil.WriteFile(cfgPath, rt.fileContents, 0644)
if err != nil {
t.Errorf("Couldn't create file")
return
}
}
client := clientsetfake.NewSimpleClientset()
if rt.node != nil {
_, err := client.CoreV1().Nodes().Create(rt.node)
if err != nil {
t.Errorf("couldn't create Node")
return
}
}
for _, c := range rt.configMaps {
err := c.create(client)
if err != nil {
t.Errorf("couldn't create ConfigMap %s", c.name)
return
}
}
cfg, err := getInitConfigurationFromCluster(tmpdir, client, rt.newControlPlane)
if rt.expectedError != (err != nil) {
t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
return
}
if rt.expectedError {
return
}
// Test expected values in InitConfiguration
if cfg == nil {
t.Errorf("unexpected nil return value")
}
if cfg.ClusterConfiguration.KubernetesVersion != k8sVersionString {
t.Errorf("invalid ClusterConfiguration.KubernetesVersion")
}
if !rt.newControlPlane && (cfg.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" || cfg.LocalAPIEndpoint.BindPort != 1234) {
t.Errorf("invalid cfg.LocalAPIEndpoint")
}
if cfg.ComponentConfigs.Kubelet == nil {
t.Errorf("invalid cfg.ComponentConfigs.Kubelet")
}
if cfg.ComponentConfigs.KubeProxy == nil {
t.Errorf("invalid cfg.ComponentConfigs.KubeProxy")
}
})
}
}
func TestGetGetClusterStatus(t *testing.T) {
var tests = []struct {
name string
configMaps []fakeConfigMap
expectedEndpoints int
expectedError bool
}{
{
name: "invalid missing config map",
expectedEndpoints: 0,
},
{
name: "valid v1beta1",
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeadmConfigConfigMap,
data: map[string]string{
kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1beta1"]),
},
},
},
expectedEndpoints: 1,
},
{
name: "valid v1alpha3",
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeadmConfigConfigMap,
data: map[string]string{
kubeadmconstants.ClusterStatusConfigMapKey: string(cfgFiles["ClusterStatus_v1alpha3"]),
},
},
},
expectedEndpoints: 1,
},
{
name: "invalid missing ClusterStatusConfigMapKey in the config map",
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeadmConfigConfigMap,
data: map[string]string{},
},
},
expectedError: true,
},
{
name: "invalid wrong value in the config map",
configMaps: []fakeConfigMap{
{
name: kubeadmconstants.KubeadmConfigConfigMap,
data: map[string]string{
kubeadmconstants.ClusterStatusConfigMapKey: "not a kubeadm type",
},
},
},
expectedError: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
client := clientsetfake.NewSimpleClientset()
for _, c := range rt.configMaps {
err := c.create(client)
if err != nil {
t.Errorf("couldn't create ConfigMap %s", c.name)
return
}
}
clusterStatus, err := GetClusterStatus(client)
if rt.expectedError != (err != nil) {
t.Errorf("unexpected return err from GetClusterStatus: %v", err)
return
}
if rt.expectedError {
return
}
// Test expected values in clusterStatus
if len(clusterStatus.APIEndpoints) != rt.expectedEndpoints {
t.Errorf("unexpected ClusterStatus return value")
}
})
}
}
type fakeConfigMap struct {
name string
data map[string]string
}
func (c *fakeConfigMap) create(client clientset.Interface) error {
return apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: c.name,
Namespace: metav1.NamespaceSystem,
},
Data: c.data,
})
}

View File

@ -0,0 +1,205 @@
/*
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"
"net"
"reflect"
"strings"
"github.com/pkg/errors"
"k8s.io/klog"
"k8s.io/apimachinery/pkg/runtime"
netutil "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/version"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
// MarshalKubeadmConfigObject marshals an Object registered in the kubeadm scheme. If the object is a InitConfiguration or ClusterConfiguration, some extra logic is run
func MarshalKubeadmConfigObject(obj runtime.Object) ([]byte, error) {
switch internalcfg := obj.(type) {
case *kubeadmapi.InitConfiguration:
return MarshalInitConfigurationToBytes(internalcfg, kubeadmapiv1beta1.SchemeGroupVersion)
case *kubeadmapi.ClusterConfiguration:
return MarshalClusterConfigurationToBytes(internalcfg, kubeadmapiv1beta1.SchemeGroupVersion)
default:
return kubeadmutil.MarshalToYamlForCodecs(obj, kubeadmapiv1beta1.SchemeGroupVersion, kubeadmscheme.Codecs)
}
}
// DetectUnsupportedVersion reads YAML bytes, extracts the TypeMeta information and errors out with an user-friendly message if the API spec is too old for this kubeadm version
func DetectUnsupportedVersion(b []byte) error {
gvks, err := kubeadmutil.GroupVersionKindsFromBytes(b)
if err != nil {
return err
}
// TODO: On our way to making the kubeadm API beta and higher, give good user output in case they use an old config file with a new kubeadm version, and
// tell them how to upgrade. The support matrix will look something like this now and in the future:
// v1.10 and earlier: v1alpha1
// v1.11: v1alpha1 read-only, writes only v1alpha2 config
// v1.12: v1alpha2 read-only, writes only v1alpha3 config. Warns if the user tries to use v1alpha1
// v1.13: v1alpha3 read-only, writes only v1beta1 config. Warns if the user tries to use v1alpha1 or v1alpha2
oldKnownAPIVersions := map[string]string{
"kubeadm.k8s.io/v1alpha1": "v1.11",
"kubeadm.k8s.io/v1alpha2": "v1.12",
}
// If we find an old API version in this gvk list, error out and tell the user why this doesn't work
knownKinds := map[string]bool{}
for _, gvk := range gvks {
if useKubeadmVersion := oldKnownAPIVersions[gvk.GroupVersion().String()]; len(useKubeadmVersion) != 0 {
return errors.Errorf("your configuration file uses an old API spec: %q. Please use kubeadm %s instead and run 'kubeadm config migrate --old-config old.yaml --new-config new.yaml', which will write the new, similar spec using a newer API version.", gvk.GroupVersion().String(), useKubeadmVersion)
}
knownKinds[gvk.Kind] = true
}
// InitConfiguration and JoinConfiguration may not apply together, warn if more than one is specified
mutuallyExclusive := []string{constants.InitConfigurationKind, constants.JoinConfigurationKind}
mutuallyExclusiveCount := 0
for _, kind := range mutuallyExclusive {
if knownKinds[kind] {
mutuallyExclusiveCount++
}
}
if mutuallyExclusiveCount > 1 {
klog.Warningf("WARNING: Detected resource kinds that may not apply: %v", mutuallyExclusive)
}
return nil
}
// NormalizeKubernetesVersion resolves version labels, sets alternative
// image registry if requested for CI builds, and validates minimal
// version that kubeadm SetInitDynamicDefaultssupports.
func NormalizeKubernetesVersion(cfg *kubeadmapi.ClusterConfiguration) error {
// Requested version is automatic CI build, thus use KubernetesCI Image Repository for core images
if kubeadmutil.KubernetesIsCIVersion(cfg.KubernetesVersion) {
cfg.CIImageRepository = constants.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 errors.Wrapf(err, "couldn't parse Kubernetes version %q", cfg.KubernetesVersion)
}
if k8sVersion.LessThan(constants.MinimumControlPlaneVersion) {
return errors.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s", constants.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 {
klog.V(1).Infof("lowercasing SAN %q to %q", san, lowercase)
sans[i] = lowercase
}
}
}
// VerifyAPIServerBindAddress can be used to verify if a bind address for the API Server is 0.0.0.0,
// in which case this address is not valid and should not be used.
func VerifyAPIServerBindAddress(address string) error {
ip := net.ParseIP(address)
if ip == nil {
return errors.Errorf("cannot parse IP address: %s", address)
}
if !ip.IsGlobalUnicast() {
return errors.Errorf("cannot use %q as the bind address for the API Server", address)
}
return nil
}
// ChooseAPIServerBindAddress is a wrapper for netutil.ChooseBindAddress that also handles
// the case where no default routes were found and an IP for the API server could not be obatained.
func ChooseAPIServerBindAddress(bindAddress net.IP) (net.IP, error) {
ip, err := netutil.ChooseBindAddress(bindAddress)
if err != nil {
if netutil.IsNoRoutesError(err) {
klog.Warningf("WARNING: could not obtain a bind address for the API Server: %v; using: %s", err, constants.DefaultAPIServerBindAddress)
defaultIP := net.ParseIP(constants.DefaultAPIServerBindAddress)
if defaultIP == nil {
return nil, errors.Errorf("cannot parse default IP address: %s", constants.DefaultAPIServerBindAddress)
}
return defaultIP, nil
}
return nil, err
}
if bindAddress != nil && !bindAddress.IsUnspecified() && !reflect.DeepEqual(ip, bindAddress) {
klog.Warningf("WARNING: overriding requested API server bind address: requested %q, actual %q", bindAddress, ip)
}
return ip, nil
}
// MigrateOldConfigFromFile migrates an old configuration from a file into a new one (returned as a byte slice). Only kubeadm kinds are migrated. Others are silently ignored.
func MigrateOldConfigFromFile(cfgPath string) ([]byte, error) {
newConfig := [][]byte{}
cfgBytes, err := ioutil.ReadFile(cfgPath)
if err != nil {
return []byte{}, err
}
gvks, err := kubeadmutil.GroupVersionKindsFromBytes(cfgBytes)
if err != nil {
return []byte{}, err
}
// Migrate InitConfiguration and ClusterConfiguration if there are any in the config
if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvks...) || kubeadmutil.GroupVersionKindsHasClusterConfiguration(gvks...) {
o, err := ConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1beta1.InitConfiguration{})
if err != nil {
return []byte{}, err
}
b, err := MarshalKubeadmConfigObject(o)
if err != nil {
return []byte{}, err
}
newConfig = append(newConfig, b)
}
// Migrate JoinConfiguration if there is any
if kubeadmutil.GroupVersionKindsHasJoinConfiguration(gvks...) {
o, err := JoinConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1beta1.JoinConfiguration{})
if err != nil {
return []byte{}, err
}
b, err := MarshalKubeadmConfigObject(o)
if err != nil {
return []byte{}, err
}
newConfig = append(newConfig, b)
}
return bytes.Join(newConfig, []byte(constants.YAMLDocumentSeparator)), nil
}

View File

@ -0,0 +1,514 @@
/*
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"
"os"
"testing"
"github.com/renstrom/dedent"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
var files = map[string][]byte{
"Master_v1alpha1": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha1
kind: InitConfiguration
`),
"Node_v1alpha1": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha1
kind: NodeConfiguration
`),
"Master_v1alpha2": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
`),
"Node_v1alpha2": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha2
kind: NodeConfiguration
`),
"Init_v1alpha3": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: InitConfiguration
`),
"Join_v1alpha3": []byte(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: JoinConfiguration
`),
"Init_v1beta1": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: InitConfiguration
`),
"Join_v1beta1": []byte(`
apiVersion: kubeadm.k8s.io/v1beta1
kind: JoinConfiguration
`),
"NoKind": []byte(`
apiVersion: baz.k8s.io/v1
foo: foo
bar: bar
`),
"NoAPIVersion": []byte(`
kind: Bar
foo: foo
bar: bar
`),
"Foo": []byte(`
apiVersion: foo.k8s.io/v1
kind: Foo
`),
}
func TestDetectUnsupportedVersion(t *testing.T) {
var tests = []struct {
name string
fileContents []byte
expectedErr bool
}{
{
name: "Master_v1alpha1",
fileContents: files["Master_v1alpha1"],
expectedErr: true,
},
{
name: "Node_v1alpha1",
fileContents: files["Node_v1alpha1"],
expectedErr: true,
},
{
name: "Master_v1alpha2",
fileContents: files["Master_v1alpha2"],
expectedErr: true,
},
{
name: "Node_v1alpha2",
fileContents: files["Node_v1alpha2"],
expectedErr: true,
},
{
name: "Init_v1alpha3",
fileContents: files["Init_v1alpha3"],
},
{
name: "Join_v1alpha3",
fileContents: files["Join_v1alpha3"],
},
{
name: "Init_v1beta1",
fileContents: files["Init_v1beta1"],
},
{
name: "Join_v1beta1",
fileContents: files["Join_v1beta1"],
},
{
name: "DuplicateInit v1alpha3",
fileContents: bytes.Join([][]byte{files["Init_v1alpha3"], files["Init_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: true,
},
{
name: "DuplicateInit v1beta1",
fileContents: bytes.Join([][]byte{files["Init_v1beta1"], files["Init_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: true,
},
{
name: "DuplicateInit v1beta1 and v1alpha3",
fileContents: bytes.Join([][]byte{files["Init_v1beta1"], files["Init_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: true,
},
{
name: "DuplicateJoin v1alpha3",
fileContents: bytes.Join([][]byte{files["Join_v1alpha3"], files["Join_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: true,
},
{
name: "DuplicateJoin v1beta1",
fileContents: bytes.Join([][]byte{files["Join_v1beta1"], files["Join_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: true,
},
{
name: "DuplicateJoin v1beta1 and v1alpha3",
fileContents: bytes.Join([][]byte{files["Join_v1beta1"], files["Join_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: true,
},
{
name: "NoKind",
fileContents: files["NoKind"],
expectedErr: true,
},
{
name: "NoAPIVersion",
fileContents: files["NoAPIVersion"],
expectedErr: true,
},
{
name: "Ignore other Kind",
fileContents: bytes.Join([][]byte{files["Foo"], files["Master_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)),
},
{
name: "Ignore other Kind",
fileContents: bytes.Join([][]byte{files["Foo"], files["Master_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)),
},
// CanMixInitJoin cases used to be MustNotMixInitJoin, however due to UX issues DetectUnsupportedVersion had to tolerate that.
// So the following tests actually verify, that Init and Join can be mixed together with no error.
{
name: "CanMixInitJoin v1alpha3",
fileContents: bytes.Join([][]byte{files["Init_v1alpha3"], files["Join_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: false,
},
{
name: "CanMixInitJoin v1alpha3 - v1beta1",
fileContents: bytes.Join([][]byte{files["Init_v1alpha3"], files["Join_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: false,
},
{
name: "CanMixInitJoin v1beta1 - v1alpha3",
fileContents: bytes.Join([][]byte{files["Init_v1beta1"], files["Join_v1alpha3"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: false,
},
{
name: "CanMixInitJoin v1beta1",
fileContents: bytes.Join([][]byte{files["Init_v1beta1"], files["Join_v1beta1"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: false,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
err := DetectUnsupportedVersion(rt.fileContents)
if (err != nil) != rt.expectedErr {
t2.Errorf("expected error: %t, actual: %t", rt.expectedErr, err != nil)
}
})
}
}
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 := &kubeadmapiv1beta1.InitConfiguration{
ClusterConfiguration: kubeadmapiv1beta1.ClusterConfiguration{
APIServer: kubeadmapiv1beta1.APIServer{
CertSANs: test.in,
},
},
}
LowercaseSANs(cfg.APIServer.CertSANs)
if len(cfg.APIServer.CertSANs) != len(test.out) {
t.Fatalf("expected %d elements, got %d", len(test.out), len(cfg.APIServer.CertSANs))
}
for i, expected := range test.out {
if cfg.APIServer.CertSANs[i] != expected {
t.Errorf("expected element %d to be %q, got %q", i, expected, cfg.APIServer.CertSANs[i])
}
}
})
}
}
func TestVerifyAPIServerBindAddress(t *testing.T) {
tests := []struct {
name string
address string
expectedError bool
}{
{
name: "valid address: IPV4",
address: "192.168.0.1",
},
{
name: "valid address: IPV6",
address: "2001:db8:85a3::8a2e:370:7334",
},
{
name: "invalid address: not a global unicast 0.0.0.0",
address: "0.0.0.0",
expectedError: true,
},
{
name: "invalid address: not a global unicast 127.0.0.1",
address: "127.0.0.1",
expectedError: true,
},
{
name: "invalid address: not a global unicast ::",
address: "::",
expectedError: true,
},
{
name: "invalid address: cannot parse IPV4",
address: "255.255.255.255.255",
expectedError: true,
},
{
name: "invalid address: cannot parse IPV6",
address: "2a00:800::2a00:800:10102a00",
expectedError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := VerifyAPIServerBindAddress(test.address); (err != nil) != test.expectedError {
t.Errorf("expected error: %v, got %v, error: %v", test.expectedError, (err != nil), err)
}
})
}
}
func TestMigrateOldConfigFromFile(t *testing.T) {
tests := []struct {
desc string
oldCfg string
expectedKinds []string
expectErr bool
}{
{
desc: "empty file produces empty result",
oldCfg: "",
expectErr: false,
},
{
desc: "bad config produces error",
oldCfg: dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha3
`),
expectErr: true,
},
{
desc: "InitConfiguration only gets migrated",
oldCfg: dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: InitConfiguration
`),
expectedKinds: []string{
constants.InitConfigurationKind,
constants.ClusterConfigurationKind,
},
expectErr: false,
},
{
desc: "ClusterConfiguration only gets migrated",
oldCfg: dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: ClusterConfiguration
`),
expectedKinds: []string{
constants.InitConfigurationKind,
constants.ClusterConfigurationKind,
},
expectErr: false,
},
{
desc: "JoinConfiguration only gets migrated",
oldCfg: dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: JoinConfiguration
token: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
`),
expectedKinds: []string{
constants.JoinConfigurationKind,
},
expectErr: false,
},
{
desc: "Init + Cluster Configurations are migrated",
oldCfg: dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: InitConfiguration
---
apiVersion: kubeadm.k8s.io/v1alpha3
kind: ClusterConfiguration
`),
expectedKinds: []string{
constants.InitConfigurationKind,
constants.ClusterConfigurationKind,
},
expectErr: false,
},
{
desc: "Init + Join Configurations are migrated",
oldCfg: dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: InitConfiguration
---
apiVersion: kubeadm.k8s.io/v1alpha3
kind: JoinConfiguration
token: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
`),
expectedKinds: []string{
constants.InitConfigurationKind,
constants.ClusterConfigurationKind,
constants.JoinConfigurationKind,
},
expectErr: false,
},
{
desc: "Cluster + Join Configurations are migrated",
oldCfg: dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: ClusterConfiguration
---
apiVersion: kubeadm.k8s.io/v1alpha3
kind: JoinConfiguration
token: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
`),
expectedKinds: []string{
constants.InitConfigurationKind,
constants.ClusterConfigurationKind,
constants.JoinConfigurationKind,
},
expectErr: false,
},
{
desc: "Init + Cluster + Join Configurations are migrated",
oldCfg: dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: InitConfiguration
---
apiVersion: kubeadm.k8s.io/v1alpha3
kind: ClusterConfiguration
---
apiVersion: kubeadm.k8s.io/v1alpha3
kind: JoinConfiguration
token: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
`),
expectedKinds: []string{
constants.InitConfigurationKind,
constants.ClusterConfigurationKind,
constants.JoinConfigurationKind,
},
expectErr: false,
},
{
desc: "component configs are not migrated",
oldCfg: dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha3
kind: InitConfiguration
---
apiVersion: kubeadm.k8s.io/v1alpha3
kind: ClusterConfiguration
---
apiVersion: kubeadm.k8s.io/v1alpha3
kind: JoinConfiguration
token: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
`),
expectedKinds: []string{
constants.InitConfigurationKind,
constants.ClusterConfigurationKind,
constants.JoinConfigurationKind,
},
expectErr: false,
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("could not create temporary test file: %v", err)
}
fileName := file.Name()
defer os.Remove(fileName)
_, err = file.WriteString(test.oldCfg)
file.Close()
if err != nil {
t.Fatalf("could not write contents of old config: %v", err)
}
b, err := MigrateOldConfigFromFile(fileName)
if test.expectErr {
if err == nil {
t.Fatalf("unexpected success:\n%s", b)
}
} else {
if err != nil {
t.Fatalf("unexpected failure: %v", err)
}
gvks, err := kubeadmutil.GroupVersionKindsFromBytes(b)
if err != nil {
t.Fatalf("unexpected error returned by GroupVersionKindsFromBytes: %v", err)
}
if len(gvks) != len(test.expectedKinds) {
t.Fatalf("length mismatch between resulting gvks and expected kinds:\n\tlen(gvks)=%d\n\tlen(expectedKinds)=%d",
len(gvks), len(test.expectedKinds))
}
for _, expectedKind := range test.expectedKinds {
if !kubeadmutil.GroupVersionKindsHasKind(gvks, expectedKind) {
t.Fatalf("migration failed to produce config kind: %s", expectedKind)
}
}
}
})
}
}

View File

@ -0,0 +1,376 @@
/*
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 (
"bytes"
"fmt"
"io/ioutil"
"net"
"reflect"
"sort"
"strconv"
"github.com/pkg/errors"
"k8s.io/klog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict"
nodeutil "k8s.io/kubernetes/pkg/util/node"
)
// SetInitDynamicDefaults checks and sets configuration values for the InitConfiguration object
func SetInitDynamicDefaults(cfg *kubeadmapi.InitConfiguration) error {
if err := SetBootstrapTokensDynamicDefaults(&cfg.BootstrapTokens); err != nil {
return err
}
if err := SetNodeRegistrationDynamicDefaults(&cfg.NodeRegistration, true); err != nil {
return err
}
if err := SetAPIEndpointDynamicDefaults(&cfg.LocalAPIEndpoint); err != nil {
return err
}
if err := SetClusterDynamicDefaults(&cfg.ClusterConfiguration, cfg.LocalAPIEndpoint.AdvertiseAddress, cfg.LocalAPIEndpoint.BindPort); err != nil {
return err
}
return nil
}
// SetBootstrapTokensDynamicDefaults checks and sets configuration values for the BootstrapTokens object
func SetBootstrapTokensDynamicDefaults(cfg *[]kubeadmapi.BootstrapToken) error {
// 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 {
if bt.Token != nil && len(bt.Token.String()) > 0 {
continue
}
tokenStr, err := bootstraputil.GenerateBootstrapToken()
if err != nil {
return errors.Wrap(err, "couldn't generate random token")
}
token, err := kubeadmapi.NewBootstrapTokenString(tokenStr)
if err != nil {
return err
}
(*cfg)[i].Token = token
}
return nil
}
// SetNodeRegistrationDynamicDefaults checks and sets configuration values for the NodeRegistration object
func SetNodeRegistrationDynamicDefaults(cfg *kubeadmapi.NodeRegistrationOptions, masterTaint bool) error {
var err error
cfg.Name, err = nodeutil.GetHostname(cfg.Name)
if err != nil {
return err
}
// 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 masterTaint && cfg.Taints == nil {
cfg.Taints = []v1.Taint{kubeadmconstants.MasterTaint}
}
return nil
}
// SetAPIEndpointDynamicDefaults checks and sets configuration values for the APIEndpoint object
func SetAPIEndpointDynamicDefaults(cfg *kubeadmapi.APIEndpoint) error {
// validate cfg.API.AdvertiseAddress.
addressIP := net.ParseIP(cfg.AdvertiseAddress)
if addressIP == nil && cfg.AdvertiseAddress != "" {
return errors.Errorf("couldn't use \"%s\" as \"apiserver-advertise-address\", must be ipv4 or ipv6 address", cfg.AdvertiseAddress)
}
// This is the same logic as the API Server uses, except that if no interface is found the address is set to 0.0.0.0, which is invalid and cannot be used
// for bootstrapping a cluster.
ip, err := ChooseAPIServerBindAddress(addressIP)
if err != nil {
return err
}
cfg.AdvertiseAddress = ip.String()
return nil
}
// SetClusterDynamicDefaults checks and sets values for the ClusterConfiguration object
func SetClusterDynamicDefaults(cfg *kubeadmapi.ClusterConfiguration, advertiseAddress string, bindPort int32) error {
// Default all the embedded ComponentConfig structs
componentconfigs.Known.Default(cfg)
ip := net.ParseIP(advertiseAddress)
if ip.To4() != nil {
cfg.ComponentConfigs.KubeProxy.BindAddress = kubeadmapiv1beta1.DefaultProxyBindAddressv4
} else {
cfg.ComponentConfigs.KubeProxy.BindAddress = kubeadmapiv1beta1.DefaultProxyBindAddressv6
}
// Resolve possible version labels and validate version string
if err := NormalizeKubernetesVersion(cfg); err != nil {
return err
}
// If ControlPlaneEndpoint is specified without a port number defaults it to
// the bindPort number of the APIEndpoint.
// This will allow join of additional control plane instances with different bindPort number
if cfg.ControlPlaneEndpoint != "" {
host, port, err := kubeadmutil.ParseHostPort(cfg.ControlPlaneEndpoint)
if err != nil {
return err
}
if port == "" {
cfg.ControlPlaneEndpoint = net.JoinHostPort(host, strconv.FormatInt(int64(bindPort), 10))
}
}
// Downcase SANs. Some domain names (like ELBs) have capitals in them.
LowercaseSANs(cfg.APIServer.CertSANs)
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 *kubeadmapiv1beta1.InitConfiguration) (*kubeadmapi.InitConfiguration, error) {
internalcfg := &kubeadmapi.InitConfiguration{}
if cfgPath != "" {
// Loads configuration from config file, if provided
// Nb. --config overrides command line flags
klog.V(1).Infoln("loading configuration from the given file")
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, errors.Wrapf(err, "unable to read config from %q ", cfgPath)
}
internalcfg, err = BytesToInternalConfig(b)
if err != nil {
return nil, err
}
} else {
// Takes passed flags into account; the defaulting is executed once again enforcing assignment 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 := SetInitDynamicDefaults(internalcfg); err != nil {
return nil, err
}
// Validates cfg (flags/configs + defaults + dynamic defaults)
if err := validation.ValidateInitConfiguration(internalcfg).ToAggregate(); err != nil {
return nil, err
}
return internalcfg, nil
}
// BytesToInternalConfig converts a byte slice to an internal, defaulted and validated configuration object.
// The byte slice may contain one or many different YAML documents. These YAML documents are parsed one-by-one
// and well-known ComponentConfig GroupVersionKinds are stored inside of the internal InitConfiguration struct
func BytesToInternalConfig(b []byte) (*kubeadmapi.InitConfiguration, error) {
var initcfg *kubeadmapi.InitConfiguration
var clustercfg *kubeadmapi.ClusterConfiguration
decodedComponentConfigObjects := map[componentconfigs.RegistrationKind]runtime.Object{}
if err := DetectUnsupportedVersion(b); err != nil {
return nil, err
}
gvkmap, err := kubeadmutil.SplitYAMLDocuments(b)
if err != nil {
return nil, err
}
for gvk, fileContent := range gvkmap {
// verify the validity of the YAML
strict.VerifyUnmarshalStrict(fileContent, gvk)
// Try to get the registration for the ComponentConfig based on the kind
regKind := componentconfigs.RegistrationKind(gvk.Kind)
registration, found := componentconfigs.Known[regKind]
if found {
// Unmarshal the bytes from the YAML document into a runtime.Object containing the ComponentConfiguration struct
obj, err := registration.Unmarshal(fileContent)
if err != nil {
return nil, err
}
decodedComponentConfigObjects[regKind] = obj
continue
}
if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvk) {
// Set initcfg to an empty struct value the deserializer will populate
initcfg = &kubeadmapi.InitConfiguration{}
// Decode the bytes into the internal struct. Under the hood, the bytes will be unmarshalled into the
// right external version, defaulted, and converted into the internal version.
if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), fileContent, initcfg); err != nil {
return nil, err
}
continue
}
if kubeadmutil.GroupVersionKindsHasClusterConfiguration(gvk) {
// Set clustercfg to an empty struct value the deserializer will populate
clustercfg = &kubeadmapi.ClusterConfiguration{}
// Decode the bytes into the internal struct. Under the hood, the bytes will be unmarshalled into the
// right external version, defaulted, and converted into the internal version.
if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), fileContent, clustercfg); err != nil {
return nil, err
}
continue
}
fmt.Printf("[config] WARNING: Ignored YAML document with GroupVersionKind %v\n", gvk)
}
// Enforce that InitConfiguration and/or ClusterConfiguration has to exist among the YAML documents
if initcfg == nil && clustercfg == nil {
return nil, errors.New("no InitConfiguration or ClusterConfiguration kind was found in the YAML file")
}
// If InitConfiguration wasn't given, default it by creating an external struct instance, default it and convert into the internal type
if initcfg == nil {
extinitcfg := &kubeadmapiv1beta1.InitConfiguration{}
kubeadmscheme.Scheme.Default(extinitcfg)
// Set initcfg to an empty struct value the deserializer will populate
initcfg = &kubeadmapi.InitConfiguration{}
kubeadmscheme.Scheme.Convert(extinitcfg, initcfg, nil)
}
// If ClusterConfiguration was given, populate it in the InitConfiguration struct
if clustercfg != nil {
initcfg.ClusterConfiguration = *clustercfg
}
// Save the loaded ComponentConfig objects in the initcfg object
for kind, obj := range decodedComponentConfigObjects {
registration, found := componentconfigs.Known[kind]
if found {
if ok := registration.SetToInternalConfig(obj, &initcfg.ClusterConfiguration); !ok {
return nil, errors.Errorf("couldn't save componentconfig value for kind %q", string(kind))
}
} else {
// This should never happen in practice
fmt.Printf("[config] WARNING: Decoded a kind that couldn't be saved to the internal configuration: %q\n", string(kind))
}
}
return initcfg, nil
}
func defaultedInternalConfig() *kubeadmapi.ClusterConfiguration {
externalcfg := &kubeadmapiv1beta1.ClusterConfiguration{}
internalcfg := &kubeadmapi.ClusterConfiguration{}
kubeadmscheme.Scheme.Default(externalcfg)
kubeadmscheme.Scheme.Convert(externalcfg, internalcfg, nil)
// Default the embedded ComponentConfig structs
componentconfigs.Known.Default(internalcfg)
return internalcfg
}
// MarshalInitConfigurationToBytes marshals the internal InitConfiguration object to bytes. It writes the embedded
// ClusterConfiguration object with ComponentConfigs out as separate YAML documents
func MarshalInitConfigurationToBytes(cfg *kubeadmapi.InitConfiguration, gv schema.GroupVersion) ([]byte, error) {
initbytes, err := kubeadmutil.MarshalToYamlForCodecs(cfg, gv, kubeadmscheme.Codecs)
if err != nil {
return []byte{}, err
}
allFiles := [][]byte{initbytes}
// Exception: If the specified groupversion is targeting the internal type, don't print embedded ClusterConfiguration contents
// This is mostly used for unit testing. In a real scenario the internal version of the API is never marshalled as-is.
if gv.Version != runtime.APIVersionInternal {
clusterbytes, err := MarshalClusterConfigurationToBytes(&cfg.ClusterConfiguration, gv)
if err != nil {
return []byte{}, err
}
allFiles = append(allFiles, clusterbytes)
}
return bytes.Join(allFiles, []byte(kubeadmconstants.YAMLDocumentSeparator)), nil
}
// MarshalClusterConfigurationToBytes marshals the internal ClusterConfiguration object to bytes. It writes the embedded
// ComponentConfiguration objects out as separate YAML documents
func MarshalClusterConfigurationToBytes(clustercfg *kubeadmapi.ClusterConfiguration, gv schema.GroupVersion) ([]byte, error) {
clusterbytes, err := kubeadmutil.MarshalToYamlForCodecs(clustercfg, gv, kubeadmscheme.Codecs)
if err != nil {
return []byte{}, err
}
allFiles := [][]byte{clusterbytes}
componentConfigContent := map[string][]byte{}
defaultedcfg := defaultedInternalConfig()
for kind, registration := range componentconfigs.Known {
// If the ComponentConfig struct for the current registration is nil, skip it when marshalling
realobj, ok := registration.GetFromInternalConfig(clustercfg)
if !ok {
continue
}
defaultedobj, ok := registration.GetFromInternalConfig(defaultedcfg)
// Invalid: The caller asked to not print the componentconfigs if defaulted, but defaultComponentConfigs() wasn't able to create default objects to use for reference
if !ok {
return []byte{}, errors.New("couldn't create a default componentconfig object")
}
// If the real ComponentConfig object differs from the default, print it out. If not, there's no need to print it out, so skip it
if !reflect.DeepEqual(realobj, defaultedobj) {
contentBytes, err := registration.Marshal(realobj)
if err != nil {
return []byte{}, err
}
componentConfigContent[string(kind)] = contentBytes
}
}
// Sort the ComponentConfig files by kind when marshalling
sortedComponentConfigFiles := consistentOrderByteSlice(componentConfigContent)
allFiles = append(allFiles, sortedComponentConfigFiles...)
return bytes.Join(allFiles, []byte(kubeadmconstants.YAMLDocumentSeparator)), nil
}
// consistentOrderByteSlice takes a map of a string key and a byte slice, and returns a byte slice of byte slices
// with consistent ordering, where the keys in the map determine the ordering of the return value. This has to be
// done as the order of a for...range loop over a map in go is undeterministic, and could otherwise lead to flakes
// in e.g. unit tests when marshalling content with a random order
func consistentOrderByteSlice(content map[string][]byte) [][]byte {
keys := []string{}
sortedContent := [][]byte{}
for key := range content {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
sortedContent = append(sortedContent, content[key])
}
return sortedContent
}

View File

@ -0,0 +1,217 @@
/*
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"
"reflect"
"testing"
"github.com/pmezard/go-difflib/difflib"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
)
const (
master_v1alpha3YAML = "testdata/conversion/master/v1alpha3.yaml"
master_v1beta1YAML = "testdata/conversion/master/v1beta1.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"
)
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
{ // v1alpha3 -> internal
name: "v1alpha3ToInternal",
in: master_v1alpha3YAML,
out: master_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1beta1 -> internal
name: "v1beta1ToInternal",
in: master_v1beta1YAML,
out: master_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha3 -> internal -> v1beta1
name: "v1alpha3Tov1beta1",
in: master_v1alpha3YAML,
out: master_v1beta1YAML,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
},
{ // v1beta1 -> internal -> v1beta1
name: "v1beta1Tov1beta1",
in: master_v1beta1YAML,
out: master_v1beta1YAML,
groupVersion: kubeadmapiv1beta1.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
{ // v1beta1 -> default -> validate -> internal -> v1beta1
name: "incompleteYAMLToDefaultedv1beta1",
in: master_incompleteYAML,
out: master_defaultedYAML,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
},
{ // v1alpha3 -> 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, &kubeadmapiv1beta1.InitConfiguration{})
if err != nil {
if rt.expectedErr {
return
}
t2.Fatalf("couldn't unmarshal test data: %v", err)
}
actual, err := MarshalInitConfigurationToBytes(internalcfg, rt.groupVersion)
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))
}
})
}
}
func TestConsistentOrderByteSlice(t *testing.T) {
var (
aKind = "Akind"
aFile = []byte(`
kind: Akind
apiVersion: foo.k8s.io/v1
`)
aaKind = "Aakind"
aaFile = []byte(`
kind: Aakind
apiVersion: foo.k8s.io/v1
`)
abKind = "Abkind"
abFile = []byte(`
kind: Abkind
apiVersion: foo.k8s.io/v1
`)
)
var tests = []struct {
name string
in map[string][]byte
expected [][]byte
}{
{
name: "a_aa_ab",
in: map[string][]byte{
aKind: aFile,
aaKind: aaFile,
abKind: abFile,
},
expected: [][]byte{aaFile, abFile, aFile},
},
{
name: "a_ab_aa",
in: map[string][]byte{
aKind: aFile,
abKind: abFile,
aaKind: aaFile,
},
expected: [][]byte{aaFile, abFile, aFile},
},
{
name: "aa_a_ab",
in: map[string][]byte{
aaKind: aaFile,
aKind: aFile,
abKind: abFile,
},
expected: [][]byte{aaFile, abFile, aFile},
},
{
name: "aa_ab_a",
in: map[string][]byte{
aaKind: aaFile,
abKind: abFile,
aKind: aFile,
},
expected: [][]byte{aaFile, abFile, aFile},
},
{
name: "ab_a_aa",
in: map[string][]byte{
abKind: abFile,
aKind: aFile,
aaKind: aaFile,
},
expected: [][]byte{aaFile, abFile, aFile},
},
{
name: "ab_aa_a",
in: map[string][]byte{
abKind: abFile,
aaKind: aaFile,
aKind: aFile,
},
expected: [][]byte{aaFile, abFile, aFile},
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
actual := consistentOrderByteSlice(rt.in)
if !reflect.DeepEqual(rt.expected, actual) {
t2.Errorf("the expected and actual output differs.\n\texpected: %s\n\tout: %s\n", rt.expected, actual)
}
})
}
}

View File

@ -0,0 +1,121 @@
/*
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 (
"io/ioutil"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict"
)
// SetJoinDynamicDefaults checks and sets configuration values for the JoinConfiguration object
func SetJoinDynamicDefaults(cfg *kubeadmapi.JoinConfiguration) error {
addMasterTaint := false
if cfg.ControlPlane != nil {
addMasterTaint = true
}
if err := SetNodeRegistrationDynamicDefaults(&cfg.NodeRegistration, addMasterTaint); err != nil {
return err
}
if err := SetJoinControlPlaneDefaults(cfg.ControlPlane); err != nil {
return err
}
return nil
}
// SetJoinControlPlaneDefaults checks and sets configuration values for the JoinControlPlane object
func SetJoinControlPlaneDefaults(cfg *kubeadmapi.JoinControlPlane) error {
if cfg != nil {
if err := SetAPIEndpointDynamicDefaults(&cfg.LocalAPIEndpoint); err != nil {
return err
}
}
return nil
}
// JoinConfigFileAndDefaultsToInternalConfig 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 JoinConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg *kubeadmapiv1beta1.JoinConfiguration) (*kubeadmapi.JoinConfiguration, error) {
internalcfg := &kubeadmapi.JoinConfiguration{}
if cfgPath != "" {
// Loads configuration from config file, if provided
// Nb. --config overrides command line flags, TODO: fix this
klog.V(1).Infoln("loading configuration from the given file")
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, errors.Wrapf(err, "unable to read config from %q ", cfgPath)
}
if err := DetectUnsupportedVersion(b); err != nil {
return nil, err
}
gvkmap, err := kubeadmutil.SplitYAMLDocuments(b)
if err != nil {
return nil, err
}
joinBytes := []byte{}
for gvk, bytes := range gvkmap {
if gvk.Kind == constants.JoinConfigurationKind {
joinBytes = bytes
// verify the validity of the YAML
strict.VerifyUnmarshalStrict(bytes, gvk)
}
}
if len(joinBytes) == 0 {
return nil, errors.Errorf("no %s found in config file %q", constants.JoinConfigurationKind, cfgPath)
}
if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), joinBytes, internalcfg); err != nil {
return nil, err
}
} 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.ValidateJoinConfiguration(internalcfg).ToAggregate(); err != nil {
return nil, err
}
return internalcfg, nil
}

View File

@ -24,54 +24,60 @@ import (
"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"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
const (
node_v1alpha1YAML = "testdata/conversion/node/v1alpha1.yaml"
node_v1alpha2YAML = "testdata/conversion/node/v1alpha2.yaml"
node_v1alpha3YAML = "testdata/conversion/node/v1alpha3.yaml"
node_v1beta1YAML = "testdata/conversion/node/v1beta1.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) {
func TestJoinConfigFileAndDefaultsToInternalConfig(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,
// These tests are reading one file, loading it using JoinConfigFileAndDefaultsToInternalConfig 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,
{ // v1alpha3 -> internal
name: "v1alpha3ToInternal",
in: node_v1alpha3YAML,
out: node_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha2 -> internal
name: "v1alpha2ToInternal",
in: node_v1alpha2YAML,
{ // v1beta1 -> internal
name: "v1beta1ToInternal",
in: node_v1beta1YAML,
out: node_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha1 -> internal -> v1alpha2
name: "v1alpha1WithoutTypeMetaTov1alpha2",
in: node_v1alpha1YAML,
out: node_v1alpha2YAML,
groupVersion: v1alpha2.SchemeGroupVersion,
{ // v1alpha3 -> internal -> v1beta1
name: "v1alpha3Tov1beta1",
in: node_v1alpha3YAML,
out: node_v1beta1YAML,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
},
// These tests are reading one file that has only a subset of the fields populated, loading it using NodeConfigFileAndDefaultsToInternalConfig,
{ // v1beta1 -> internal -> v1beta1
name: "v1beta1Tov1beta1",
in: node_v1beta1YAML,
out: node_v1beta1YAML,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
},
// These tests are reading one file that has only a subset of the fields populated, loading it using JoinConfigFileAndDefaultsToInternalConfig,
// and then marshals the internal object to the expected groupVersion
{ // v1alpha1 -> default -> validate -> internal -> v1alpha2
name: "incompleteYAMLToDefaulted",
{ // v1beta1 -> default -> validate -> internal -> v1beta1
name: "incompleteYAMLToDefaultedv1beta1",
in: node_incompleteYAML,
out: node_defaultedYAML,
groupVersion: v1alpha2.SchemeGroupVersion,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
},
{ // v1alpha1 (faulty) -> validation should fail
{ // v1alpha3 -> validation should fail
name: "invalidYAMLShouldFail",
in: node_invalidYAML,
expectedErr: true,
@ -81,7 +87,7 @@ func TestNodeConfigFileAndDefaultsToInternalConfig(t *testing.T) {
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
internalcfg, err := NodeConfigFileAndDefaultsToInternalConfig(rt.in, &v1alpha2.NodeConfiguration{})
internalcfg, err := JoinConfigFileAndDefaultsToInternalConfig(rt.in, &kubeadmapiv1beta1.JoinConfiguration{})
if err != nil {
if rt.expectedErr {
return

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

@ -0,0 +1,46 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["strict.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict",
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/componentconfigs:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["strict_test.go"],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm/v1alpha3:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library",
"//cmd/kubeadm/app/componentconfigs:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/kube-proxy/config/v1alpha1:go_default_library",
"//staging/src/k8s.io/kubelet/config/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

@ -0,0 +1,58 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package strict
import (
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
"sigs.k8s.io/yaml"
)
// VerifyUnmarshalStrict takes a YAML byte slice and a GroupVersionKind and verifies if the YAML
// schema is known and if it unmarshals with strict mode.
//
// TODO(neolit123): The returned error here is currently ignored everywhere and a klog warning is thrown instead.
// We don't want to turn this into an actual error yet. Eventually this can be controlled with an optional CLI flag.
func VerifyUnmarshalStrict(bytes []byte, gvk schema.GroupVersionKind) error {
var (
iface interface{}
err error
)
iface, err = scheme.Scheme.New(gvk)
if err != nil {
iface, err = componentconfigs.Scheme.New(gvk)
if err != nil {
err := errors.Errorf("unknown configuration %#v for scheme definitions in %q and %q",
gvk, scheme.Scheme.Name(), componentconfigs.Scheme.Name())
klog.Warning(err.Error())
return err
}
}
if err := yaml.UnmarshalStrict(bytes, iface); err != nil {
err := errors.Wrapf(err, "error unmarshaling configuration %#v", gvk)
klog.Warning(err.Error())
return err
}
return nil
}

View File

@ -0,0 +1,168 @@
/*
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 strict
import (
"io/ioutil"
"path/filepath"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
kubeproxyconfigv1alpha1 "k8s.io/kube-proxy/config/v1alpha1"
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
func TestVerifyUnmarshalStrict(t *testing.T) {
const (
pathTestData = "testdata/"
)
var testFiles = []struct {
fileName string
kind string
groupVersion schema.GroupVersion
expectedError bool
}{
// tests with file errors
{
fileName: "invalid_duplicate_field_clustercfg.yaml",
kind: constants.InitConfigurationKind,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
expectedError: true,
},
{
fileName: "invalid_duplicate_field_initcfg.yaml",
kind: constants.InitConfigurationKind,
groupVersion: kubeadmapiv1alpha3.SchemeGroupVersion,
expectedError: true,
},
{
fileName: "invalid_duplicate_field_joincfg.yaml",
kind: constants.JoinConfigurationKind,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
expectedError: true,
},
{
fileName: "invalid_duplicate_field_kubeletcfg.yaml",
kind: string(componentconfigs.KubeletConfigurationKind),
groupVersion: kubeletconfigv1beta1.SchemeGroupVersion,
expectedError: true,
},
{
fileName: "invalid_duplicate_field_kubeproxycfg.yaml",
kind: string(componentconfigs.KubeProxyConfigurationKind),
groupVersion: kubeproxyconfigv1alpha1.SchemeGroupVersion,
expectedError: true,
},
{
fileName: "invalid_unknown_field_clustercfg.yaml",
kind: constants.ClusterConfigurationKind,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
expectedError: true,
},
{
fileName: "invalid_unknown_field_initcfg.yaml",
kind: constants.InitConfigurationKind,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
expectedError: true,
},
{
fileName: "invalid_unknown_field_joincfg.yaml",
kind: constants.JoinConfigurationKind,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
expectedError: true,
},
{
fileName: "invalid_unknown_field_kubeletcfg.yaml",
kind: string(componentconfigs.KubeletConfigurationKind),
groupVersion: kubeletconfigv1beta1.SchemeGroupVersion,
expectedError: true,
},
{
fileName: "invalid_unknown_field_kubeproxycfg.yaml",
kind: string(componentconfigs.KubeProxyConfigurationKind),
groupVersion: kubeproxyconfigv1alpha1.SchemeGroupVersion,
expectedError: true,
},
// test unknown groupVersion and kind
{
fileName: "valid_clustercfg.yaml",
kind: constants.ClusterConfigurationKind,
groupVersion: schema.GroupVersion{Group: "someGroup", Version: "v1"},
expectedError: true,
},
{
fileName: "valid_clustercfg.yaml",
kind: "SomeUnknownKind",
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
expectedError: true,
},
// valid tests
{
fileName: "valid_clustercfg.yaml",
kind: constants.ClusterConfigurationKind,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
expectedError: false,
},
{
fileName: "valid_initcfg.yaml",
kind: constants.InitConfigurationKind,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
expectedError: false,
},
{
fileName: "valid_joincfg.yaml",
kind: constants.JoinConfigurationKind,
groupVersion: kubeadmapiv1beta1.SchemeGroupVersion,
expectedError: false,
},
{
fileName: "valid_kubeletcfg.yaml",
kind: string(componentconfigs.KubeletConfigurationKind),
groupVersion: kubeletconfigv1beta1.SchemeGroupVersion,
expectedError: false,
},
{
fileName: "valid_kubeproxycfg.yaml",
kind: string(componentconfigs.KubeProxyConfigurationKind),
groupVersion: kubeproxyconfigv1alpha1.SchemeGroupVersion,
expectedError: false,
},
}
for _, test := range testFiles {
t.Run(test.fileName, func(t *testing.T) {
bytes, err := ioutil.ReadFile(filepath.Join(pathTestData, test.fileName))
if err != nil {
t.Fatalf("couldn't read test data: %v", err)
}
gvk := schema.GroupVersionKind{
Group: test.groupVersion.Group,
Version: test.groupVersion.Version,
Kind: test.kind,
}
err = VerifyUnmarshalStrict(bytes, gvk)
if (err != nil) != test.expectedError {
t.Errorf("expected error %v, got %v, error: %v", err != nil, test.expectedError, err)
}
})
}
}

View File

@ -0,0 +1,4 @@
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
controlPlaneEndpoint: test1
controlPlaneEndpoint: test2

View File

@ -0,0 +1,6 @@
apiVersion: kubeadm.k8s.io/v1beta1
kind: InitConfiguration
bootstrapTokens:
- token: "9a08jv.c0izixklcxtmnze7"
bootstrapTokens:
- token: "9a08jv.c0izixklcxtmnze7"

View File

@ -0,0 +1,4 @@
apiVersion: kubeadm.k8s.io/v1beta1
kind: JoinConfiguration
caCertPath: relativepath
caCertPath: relativepath

View File

@ -0,0 +1,4 @@
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: 1.2.3.4
address: 1.2.3.4

View File

@ -0,0 +1,4 @@
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
portRange: ""
portRange: ""

View File

@ -0,0 +1,3 @@
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
unknownField: test

View File

@ -0,0 +1,3 @@
apiVersion: kubeadm.k8s.io/v1beta1
kind: InitConfiguration
unknownField: test

View File

@ -0,0 +1,3 @@
apiVersion: kubeadm.k8s.io/v1beta1
kind: JoinConfiguration
unknownField: test

View File

@ -0,0 +1,3 @@
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
unknownField: unknown

View File

@ -0,0 +1,3 @@
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
unknownField: unknown

View File

@ -0,0 +1,3 @@
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
controlPlaneEndpoint: 202.0.100.1

View File

@ -0,0 +1,4 @@
apiVersion: kubeadm.k8s.io/v1beta1
kind: InitConfiguration
bootstrapTokens:
- token: "9a08jv.c0izixklcxtmnze7"

View File

@ -0,0 +1,3 @@
apiVersion: kubeadm.k8s.io/v1beta1
kind: JoinConfiguration
caCertPath: relativepath

View File

@ -0,0 +1,3 @@
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: 1.2.3.4

View File

@ -0,0 +1,3 @@
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
portRange: ""

View File

@ -1,15 +1,19 @@
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: ""
APIServer:
CertSANs: null
ExtraArgs:
authorization-mode: Node,RBAC,Webhook
ExtraVolumes:
- HostPath: /host/read-only
MountPath: /mount/read-only
Name: ReadOnlyVolume
PathType: ""
ReadOnly: true
- HostPath: /host/writable
MountPath: /mount/writable
Name: WritableVolume
PathType: ""
ReadOnly: false
TimeoutForControlPlane: 4m0s
BootstrapTokens:
- Description: ""
Expires: null
@ -23,128 +27,166 @@ BootstrapTokens:
CIImageRepository: ""
CertificatesDir: /etc/kubernetes/pki
ClusterName: kubernetes
ControllerManagerExtraArgs: null
ControllerManagerExtraVolumes: null
ComponentConfigs:
KubeProxy:
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: iptables
NodePortAddresses: null
OOMScoreAdj: -999
PortRange: ""
ResourceContainer: /kube-proxy
UDPIdleTimeout: 250ms
Kubelet:
Address: 1.2.3.4
Authentication:
Anonymous:
Enabled: false
Webhook:
CacheTTL: 2m0s
Enabled: true
X509:
ClientCAFile: /etc/kubernetes/pki/ca.crt
Authorization:
Mode: Webhook
Webhook:
CacheAuthorizedTTL: 5m0s
CacheUnauthorizedTTL: 30s
CPUCFSQuota: true
CPUCFSQuotaPeriod: 0s
CPUManagerPolicy: none
CPUManagerReconcilePeriod: 10s
CgroupDriver: cgroupfs
CgroupRoot: ""
CgroupsPerQOS: true
ClusterDNS:
- 10.96.0.10
ClusterDomain: cluster.local
ConfigMapAndSecretChangeDetectionStrategy: Watch
ContainerLogMaxFiles: 5
ContainerLogMaxSize: 10Mi
ContentType: application/vnd.kubernetes.protobuf
EnableContentionProfiling: false
EnableControllerAttachDetach: true
EnableDebuggingHandlers: true
EnforceNodeAllocatable:
- pods
EventBurst: 10
EventRecordQPS: 5
EvictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
EvictionMaxPodGracePeriod: 0
EvictionMinimumReclaim: null
EvictionPressureTransitionPeriod: 5m0s
EvictionSoft: null
EvictionSoftGracePeriod: null
FailSwapOn: true
FeatureGates: null
FileCheckFrequency: 20s
HTTPCheckFrequency: 20s
HairpinMode: promiscuous-bridge
HealthzBindAddress: 127.0.0.1
HealthzPort: 10248
IPTablesDropBit: 15
IPTablesMasqueradeBit: 14
ImageGCHighThresholdPercent: 85
ImageGCLowThresholdPercent: 80
ImageMinimumGCAge: 2m0s
KubeAPIBurst: 10
KubeAPIQPS: 5
KubeReserved: null
KubeReservedCgroup: ""
KubeletCgroups: ""
MakeIPTablesUtilChains: true
MaxOpenFiles: 1000000
MaxPods: 110
NodeLeaseDurationSeconds: 40
NodeStatusReportFrequency: 1m0s
NodeStatusUpdateFrequency: 10s
OOMScoreAdj: -999
PodCIDR: ""
PodPidsLimit: -1
PodsPerCore: 0
Port: 10250
ProtectKernelDefaults: false
QOSReserved: null
ReadOnlyPort: 0
RegistryBurst: 10
RegistryPullQPS: 5
ResolverConfig: /etc/resolv.conf
RotateCertificates: true
RuntimeRequestTimeout: 2m0s
SerializeImagePulls: true
ServerTLSBootstrap: false
StaticPodPath: /etc/kubernetes/manifests
StaticPodURL: ""
StaticPodURLHeader: null
StreamingConnectionIdleTimeout: 4h0m0s
SyncFrequency: 1m0s
SystemCgroups: ""
SystemReserved: null
SystemReservedCgroup: ""
TLSCertFile: ""
TLSCipherSuites: null
TLSMinVersion: ""
TLSPrivateKeyFile: ""
VolumeStatsAggPeriod: 1m0s
ControlPlaneEndpoint: ""
ControllerManager:
ExtraArgs: null
ExtraVolumes: null
DNS:
ImageRepository: ""
ImageTag: ""
Type: CoreDNS
Etcd:
External: null
Local:
DataDir: /var/lib/etcd
ExtraArgs: null
Image: ""
ImageRepository: ""
ImageTag: ""
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
KubernetesVersion: v1.12.2
LocalAPIEndpoint:
AdvertiseAddress: 192.168.2.2
BindPort: 6443
Networking:
DNSDomain: cluster.local
PodSubnet: ""
@ -156,6 +198,7 @@ NodeRegistration:
Taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
SchedulerExtraArgs: null
SchedulerExtraVolumes: null
UnifiedControlPlaneImage: ""
Scheduler:
ExtraArgs: null
ExtraVolumes: null
UseHyperKubeImage: true

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

@ -0,0 +1,165 @@
apiEndpoint:
advertiseAddress: 192.168.2.2
bindPort: 6443
apiVersion: kubeadm.k8s.io/v1alpha3
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: s73ybu.6tw6wnqgp5z0wb77
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master-1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
---
apiServerExtraArgs:
authorization-mode: Node,RBAC,Webhook
apiServerExtraVolumes:
- hostPath: /host/read-only
mountPath: /mount/read-only
name: ReadOnlyVolume
- hostPath: /host/writable
mountPath: /mount/writable
name: WritableVolume
writable: true
apiVersion: kubeadm.k8s.io/v1alpha3
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controlPlaneEndpoint: ""
etcd:
local:
dataDir: /var/lib/etcd
image: ""
imageRepository: k8s.gcr.io
kind: ClusterConfiguration
kubernetesVersion: v1.12.2
networking:
dnsDomain: cluster.local
podSubnet: ""
serviceSubnet: 10.96.0.0/12
unifiedControlPlaneImage: "k8s.gcr.io/hyperkube:v1.12.2"
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
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
kind: KubeProxyConfiguration
metricsBindAddress: 127.0.0.1:10249
mode: iptables
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
---
address: 1.2.3.4
apiVersion: kubelet.config.k8s.io/v1beta1
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
configMapAndSecretChangeDetectionStrategy: Watch
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuCFSQuotaPeriod: 0s
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
kind: KubeletConfiguration
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeLeaseDurationSeconds: 40
nodeStatusReportFrequency: 1m0s
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

View File

@ -0,0 +1,166 @@
apiVersion: kubeadm.k8s.io/v1beta1
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: s73ybu.6tw6wnqgp5z0wb77
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.2.2
bindPort: 6443
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master-1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
---
apiServer:
extraArgs:
authorization-mode: Node,RBAC,Webhook
extraVolumes:
- hostPath: /host/read-only
mountPath: /mount/read-only
name: ReadOnlyVolume
readOnly: true
- hostPath: /host/writable
mountPath: /mount/writable
name: WritableVolume
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta1
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controlPlaneEndpoint: ""
controllerManager: {}
dns:
type: CoreDNS
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: k8s.gcr.io
kind: ClusterConfiguration
kubernetesVersion: v1.12.2
networking:
dnsDomain: cluster.local
podSubnet: ""
serviceSubnet: 10.96.0.0/12
scheduler: {}
useHyperKubeImage: true
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
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
kind: KubeProxyConfiguration
metricsBindAddress: 127.0.0.1:10249
mode: iptables
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
---
address: 1.2.3.4
apiVersion: kubelet.config.k8s.io/v1beta1
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
configMapAndSecretChangeDetectionStrategy: Watch
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuCFSQuotaPeriod: 0s
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
kind: KubeletConfiguration
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeLeaseDurationSeconds: 40
nodeStatusReportFrequency: 1m0s
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

View File

@ -1,17 +1,21 @@
CACertPath: /etc/kubernetes/pki/ca.crt
ClusterName: kubernetes
DiscoveryFile: ""
DiscoveryTimeout: 5m0s
DiscoveryToken: abcdef.0123456789abcdef
DiscoveryTokenAPIServers:
- kube-apiserver:6443
DiscoveryTokenCACertHashes: null
DiscoveryTokenUnsafeSkipCAVerification: true
FeatureGates: null
ControlPlane:
LocalAPIEndpoint:
AdvertiseAddress: 192.168.2.2
BindPort: 6443
Discovery:
BootstrapToken:
APIServerEndpoint: kube-apiserver:6443
CACertHashes: null
Token: abcdef.0123456789abcdef
UnsafeSkipCAVerification: true
File: null
TLSBootstrapToken: abcdef.0123456789abcdef
Timeout: 5m0s
NodeRegistration:
CRISocket: /var/run/dockershim.sock
KubeletExtraArgs: null
Name: master-1
Taints: null
TLSBootstrapToken: abcdef.0123456789abcdef
Token: abcdef.0123456789abcdef
Taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master

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 +1,21 @@
apiVersion: kubeadm.k8s.io/v1alpha2
apiEndpoint:
advertiseAddress: 192.168.2.2
bindPort: 6443
apiVersion: kubeadm.k8s.io/v1alpha3
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes
controlPlane: true
discoveryFile: ""
discoveryTimeout: 5m0s
discoveryToken: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
kind: NodeConfiguration
kind: JoinConfiguration
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master-1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
tlsBootstrapToken: abcdef.0123456789abcdef
token: abcdef.0123456789abcdef

View File

@ -0,0 +1,20 @@
apiVersion: kubeadm.k8s.io/v1beta1
caCertPath: /etc/kubernetes/pki/ca.crt
controlPlane:
localAPIEndpoint:
advertiseAddress: 192.168.2.2
bindPort: 6443
discovery:
bootstrapToken:
apiServerEndpoint: kube-apiserver:6443
token: abcdef.0123456789abcdef
unsafeSkipCAVerification: true
timeout: 5m0s
tlsBootstrapToken: abcdef.0123456789abcdef
kind: JoinConfiguration
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master-1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master

View File

@ -1,12 +1,4 @@
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
controlPlaneEndpoint: ""
apiVersion: kubeadm.k8s.io/v1alpha2
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
apiVersion: kubeadm.k8s.io/v1beta1
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
@ -15,129 +7,146 @@ bootstrapTokens:
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
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.2.2
bindPort: 6443
nodeRegistration:
criSocket: /var/run/criruntime.sock
name: master-1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
unifiedControlPlaneImage: ""
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta1
certificatesDir: /var/lib/kubernetes/pki
clusterName: kubernetes
controlPlaneEndpoint: ""
controllerManager: {}
dns:
type: CoreDNS
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: my-company.com
kind: ClusterConfiguration
kubernetesVersion: v1.13.0
networking:
dnsDomain: cluster.global
podSubnet: 10.148.0.0/16
serviceSubnet: 10.196.0.0/12
scheduler: {}
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: 10.148.0.0/16
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
kind: KubeProxyConfiguration
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
---
address: 0.0.0.0
apiVersion: kubelet.config.k8s.io/v1beta1
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
configMapAndSecretChangeDetectionStrategy: Watch
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuCFSQuotaPeriod: 100ms
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
kind: KubeletConfiguration
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeLeaseDurationSeconds: 40
nodeStatusReportFrequency: 1m0s
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

View File

@ -1,15 +1,19 @@
# 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:
apiVersion: kubeadm.k8s.io/v1beta1
kind: InitConfiguration
bootstrapTokens:
- token: s73ybu.6tw6wnqgp5z0wb77
localAPIEndpoint:
advertiseAddress: 192.168.2.2
bindPort: 6443
nodeRegistration:
criSocket: /var/run/criruntime.sock
name: master-1
---
apiVersion: kubeadm.k8s.io/v1beta1
certificatesDir: /var/lib/kubernetes/pki
clusterName: kubernetes
criSocket: /var/run/criruntime.sock
imageRepository: my-company.com
kubernetesVersion: v1.10.2
kind: ClusterConfiguration
kubernetesVersion: v1.13.0
networking:
dnsDomain: cluster.global
serviceSubnet: 10.196.0.0/12
nodeName: master-1
token: s73ybu.6tw6wnqgp5z0wb77
podSubnet: 10.148.0.0/16
serviceSubnet: 10.196.0.0/12

View File

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

View File

@ -1,7 +1,11 @@
apiVersion: kubeadm.k8s.io/v1alpha1
kind: NodeConfiguration
apiVersion: kubeadm.k8s.io/v1alpha3
discoveryToken: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
nodeName: thegopher
kind: JoinConfiguration
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: thegopher
tlsBootstrapToken: abcdef.0123456789abcdef
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 +1,7 @@
api:
bindPort: 0
certificatesDir: relativepath
clusterName: kubernetes
criSocket: relativepath
imageRepository: my-company.com
kubernetesVersion: v1.10.2
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
networking:
dnsDomain: cluster.GLOBAL
serviceSubnet: 10.196.1000.0/100
nodeName: MASTER
token: s7bu.6tw6wn
dnsDomain: INVALID-DOMAIN-!!!!
podSubnet: ""
serviceSubnet: 10.96.0.0/12
useHyperKubeImage: false

View File

@ -1,11 +1,15 @@
apiVersion: kubeadm.k8s.io/v1alpha1
apiVersion: kubeadm.k8s.io/v1beta1
kind: NodeConfiguration
caCertPath: relativepath
criSocket: relativepath
discoveryFile: relativepath
discoveryTimeout: not-a-time
discoveryTokenAPIServers:
- INVALID_URL
discoveryTokenUnsafeSkipCAVerification: false
nodeName: NODE-1
token: invalidtoken
discovery:
timeout: not-a-time
bootstrapToken:
token: invalidtoken
apiServerEndpoints:
- INVALID_URL
unsafeSkipCAVerification: false
file:
kubeConfigPath: relativepath
nodeRegistration:
criSocket: relativepath
name: NODE-1

View File

@ -8,8 +8,8 @@ go_library(
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",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
],
)

View File

@ -111,6 +111,11 @@ func (w *Waiter) WaitForHealthyKubelet(_ time.Duration, healthzEndpoint string)
return nil
}
// WaitForKubeletAndFunc is a wrapper for WaitForHealthyKubelet that also blocks for a function
func (w *Waiter) WaitForKubeletAndFunc(f func() error) error {
return nil
}
// SetTimeout is a no-op; we don't wait in this implementation
func (w *Waiter) SetTimeout(_ time.Duration) {}

View File

@ -22,47 +22,51 @@ import (
"net/url"
"strconv"
"github.com/pkg/errors"
"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) {
// - If the ControlPlaneEndpoint is defined, use it.
// - if the ControlPlaneEndpoint is defined but without a port number, use the ControlPlaneEndpoint + api.BindPort is used.
// - Otherwise, in case the ControlPlaneEndpoint is not defined, use the api.AdvertiseAddress + the api.BindPort.
func GetMasterEndpoint(cfg *kubeadmapi.InitConfiguration) (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)
bindPortString := strconv.Itoa(int(cfg.LocalAPIEndpoint.BindPort))
if _, err := ParsePort(bindPortString); err != nil {
return "", errors.Wrapf(err, "invalid value %q given for api.bindPort", cfg.LocalAPIEndpoint.BindPort)
}
// parse the AdvertiseAddress
var ip = net.ParseIP(api.AdvertiseAddress)
var ip = net.ParseIP(cfg.LocalAPIEndpoint.AdvertiseAddress)
if ip == nil {
return "", fmt.Errorf("invalid value `%s` given for api.advertiseAddress", api.AdvertiseAddress)
return "", errors.Errorf("invalid value `%s` given for api.advertiseAddress", cfg.LocalAPIEndpoint.AdvertiseAddress)
}
// set the master url using cfg.API.AdvertiseAddress + the cfg.API.BindPort
masterURL := &url.URL{
Scheme: "https",
Host: net.JoinHostPort(ip.String(), bindPort),
Host: net.JoinHostPort(ip.String(), bindPortString),
}
// if the controlplane endpoint is defined
if len(api.ControlPlaneEndpoint) > 0 {
if len(cfg.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 host, port, err = ParseHostPort(cfg.ControlPlaneEndpoint); err != nil {
return "", errors.Wrapf(err, "invalid value %q given for controlPlaneEndpoint", cfg.ControlPlaneEndpoint)
}
// 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")
if port != bindPortString {
fmt.Println("[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address")
}
} else {
port = bindPort
port = bindPortString
}
// overrides the master url using the controlPlaneAddress (and eventually the bindport)
@ -90,8 +94,8 @@ func ParseHostPort(hostport string) (string, string, error) {
// 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 _, err := ParsePort(port); err != nil {
return "", "", errors.New("port must be a valid number between 1 and 65535, inclusive")
}
}
@ -105,15 +109,16 @@ func ParseHostPort(hostport string) (string, string, error) {
return host, port, nil
}
return "", "", fmt.Errorf("host must be a valid IP address or a valid RFC-1123 DNS subdomain")
return "", "", errors.New("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) {
func ParsePort(port string) (int, error) {
portInt, err := strconv.Atoi(port)
if err == nil && (1 <= portInt && portInt <= 65535) {
return portInt, nil
}
return 0, fmt.Errorf("port must be a valid number between 1 and 65535, inclusive")
return 0, errors.New("port must be a valid number between 1 and 65535, inclusive")
}

View File

@ -25,139 +25,179 @@ import (
func TestGetMasterEndpoint(t *testing.T) {
var tests = []struct {
name string
api *kubeadmapi.API
cfg *kubeadmapi.InitConfiguration
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",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "cp.k8s.io:1234",
},
},
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",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "1.2.3.4:1234",
},
},
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",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "[2001:db8::1]:1234",
},
},
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",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "cp.k8s.io",
},
},
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",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "1.2.3.4",
},
},
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",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "2001:db8::1",
},
},
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",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
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",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
BindPort: 4567,
AdvertiseAddress: "2001:db8::1",
},
},
expectedEndpoint: "https://[2001:db8::1]:4567",
},
{
name: "fail if invalid BindPort",
api: &kubeadmapi.API{
BindPort: 0,
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
BindPort: 0,
},
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (dns)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "bad!!.cp.k8s.io",
BindPort: 4567,
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "bad!!.cp.k8s.io",
},
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (ip4)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1..0",
BindPort: 4567,
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "1..0",
},
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (ip6)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1200::AB00:1234::2552:7777:1313",
BindPort: 4567,
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "1200::AB00:1234::2552:7777:1313",
},
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (port)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "cp.k8s.io:0",
BindPort: 4567,
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "cp.k8s.io:0",
},
},
expectedError: true,
},
{
name: "fail if invalid AdvertiseAddress (ip4)",
api: &kubeadmapi.API{
AdvertiseAddress: "1..0",
BindPort: 4567,
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
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,
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
AdvertiseAddress: "1200::AB00:1234::2552:7777:1313",
BindPort: 4567,
},
},
expectedError: true,
},
}
for _, rt := range tests {
actualEndpoint, actualError := GetMasterEndpoint(rt.api)
actualEndpoint, actualError := GetMasterEndpoint(rt.cfg)
if (actualError != nil) && !rt.expectedError {
t.Errorf("%s unexpected failure: %v", rt.name, actualError)
@ -328,7 +368,7 @@ func TestParsePort(t *testing.T) {
}
for _, rt := range tests {
actualPort, actualError := parsePort(rt.port)
actualPort, actualError := ParsePort(rt.port)
if (actualError != nil) && !rt.expectedError {
t.Errorf("%s unexpected failure: %v", rt.name, actualError)

View File

@ -17,7 +17,7 @@ limitations under the License.
package util
import (
"fmt"
"github.com/pkg/errors"
"testing"
)
@ -36,7 +36,7 @@ func TestCheckErr(t *testing.T) {
expected int
}{
{nil, 0},
{fmt.Errorf(""), DefaultErrorExitCode},
{errors.New(""), DefaultErrorExitCode},
{&pferror{}, PreFlightExitCode},
}
@ -63,14 +63,14 @@ func TestFormatErrMsg(t *testing.T) {
}{
{
errs: []error{
fmt.Errorf(errMsg1),
fmt.Errorf(errMsg2),
errors.New(errMsg1),
errors.New(errMsg2),
},
expect: "\t- " + errMsg1 + "\n" + "\t- " + errMsg2 + "\n",
},
{
errs: []error{
fmt.Errorf(errMsg1),
errors.New(errMsg1),
},
expect: "\t- " + errMsg1 + "\n",
},

View File

@ -8,9 +8,13 @@ go_library(
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/config:go_default_library",
"//cmd/kubeadm/app/util/staticpod:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
"//vendor/github.com/coreos/etcd/pkg/transport:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
@ -20,6 +24,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/test:go_default_library",
],
)

View File

@ -20,14 +20,20 @@ import (
"context"
"crypto/tls"
"fmt"
"net"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/pkg/transport"
"github.com/pkg/errors"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/util/config"
"k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
)
@ -39,6 +45,8 @@ type ClusterInterrogator interface {
GetVersion() (string, error)
HasTLS() bool
WaitForClusterAvailable(delay time.Duration, retries int, retryInterval time.Duration) (bool, error)
Sync() error
AddMember(name string, peerAddrs string) ([]Member, error)
}
// Client provides connection parameters for an etcd cluster
@ -58,7 +66,7 @@ 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)
return false, errors.Wrap(err, "failed to check if etcd pod implements TLS")
}
tlsFlags := []string{
@ -107,21 +115,154 @@ func New(endpoints []string, ca, cert, key string) (*Client, error) {
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)
// NewFromCluster creates an etcd client for the the etcd endpoints defined in the ClusterStatus value stored in
// the kubeadm-config ConfigMap in kube-system namespace.
// Once created, the client synchronizes client's endpoints with the known endpoints from the etcd membership API (reality check).
func NewFromCluster(client clientset.Interface, certificatesDir string) (*Client, error) {
// Kubeadm v1.13 should manage v1.12 clusters and v1.13 clusters
// v1.12 clusters can be have etcd listening on localhost only (if the cluster was created with kubeadm v1.12)
// or etcd listening on localhost and API server advertise address (if the cluster was created with kubeadm v1.13).
// The first case should be dropped in v1.14 when support for v1.12 clusters can be removed from the codebase.
// Detect which type of etcd we are dealing with
// Please note that this test can be executed only on master nodes during upgrades;
// For nodes where we are joining a new control plane node instead we should tolerate that the etcd manifest does not
// exists and try to connect to etcd using API server advertise address; as described above this will lead to a know isse
// for cluster created with v1.12, but a documented workaround will be provided
oldManifest := false
klog.V(1).Infoln("checking etcd manifest")
etcdManifestFile := constants.GetStaticPodFilepath(constants.Etcd, constants.GetStaticPodDirectory())
etcdPod, err := staticpod.ReadStaticPodFromDisk(etcdManifestFile)
if err == nil {
etcdContainer := etcdPod.Spec.Containers[0]
for _, arg := range etcdContainer.Command {
if arg == "--listen-client-urls=https://127.0.0.1:2379" {
klog.V(1).Infoln("etcd manifest created by kubeadm v1.12")
oldManifest = true
}
}
// if etcd is listening on localhost only
if oldManifest == true {
// etcd cluster has a single member "by design"
endpoints := []string{fmt.Sprintf("localhost:%d", constants.EtcdListenClientPort)}
etcdClient, err := New(
endpoints,
filepath.Join(certificatesDir, constants.EtcdCACertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientCertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientKeyName),
)
if err != nil {
return nil, errors.Wrapf(err, "error creating etcd client for %v endpoint", endpoints)
}
return etcdClient, nil
}
}
// etcd is listening on localhost and API server advertise address, and
// the etcd cluster can have more than one etcd members, so it is necessary to get the
// list of endpoints from kubeadm cluster status before connecting
// Gets the cluster status
clusterStatus, err := config.GetClusterStatus(client)
if err != nil {
return nil, fmt.Errorf("could not read manifests from: %s, error: %v", manifestDir, err)
return nil, err
}
if hasTLS {
return New(
endpoints,
filepath.Join(certificatesDir, constants.EtcdCACertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientCertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientKeyName),
)
// Get the list of etcd endpoints from cluster status
endpoints := []string{}
for _, e := range clusterStatus.APIEndpoints {
endpoints = append(endpoints, GetClientURLByIP(e.AdvertiseAddress))
}
return New(endpoints, "", "", "")
klog.V(1).Infof("etcd endpoints read from pods: %s", strings.Join(endpoints, ","))
// Creates an etcd client
etcdClient, err := New(
endpoints,
filepath.Join(certificatesDir, constants.EtcdCACertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientCertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientKeyName),
)
if err != nil {
return nil, errors.Wrapf(err, "error creating etcd client for %v endpoints", endpoints)
}
// synchronizes client's endpoints with the known endpoints from the etcd membership.
err = etcdClient.Sync()
if err != nil {
return nil, errors.Wrap(err, "error syncing endpoints with etc")
}
return etcdClient, nil
}
// Sync synchronizes client's endpoints with the known endpoints from the etcd membership.
func (c Client) Sync() error {
cli, err := clientv3.New(clientv3.Config{
Endpoints: c.Endpoints,
DialTimeout: 20 * time.Second,
TLS: c.TLS,
})
if err != nil {
return err
}
defer cli.Close()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
err = cli.Sync(ctx)
cancel()
if err != nil {
return err
}
klog.V(1).Infof("etcd endpoints read from etcd: %s", strings.Join(cli.Endpoints(), ","))
c.Endpoints = cli.Endpoints()
return nil
}
// Member struct defines an etcd member; it is used for avoiding to spread github.com/coreos/etcd dependency
// across kubeadm codebase
type Member struct {
Name string
PeerURL string
}
// AddMember notifies an existing etcd cluster that a new member is joining
func (c Client) AddMember(name string, peerAddrs string) ([]Member, error) {
cli, err := clientv3.New(clientv3.Config{
Endpoints: c.Endpoints,
DialTimeout: 20 * time.Second,
TLS: c.TLS,
})
if err != nil {
return nil, err
}
defer cli.Close()
// Adds a new member to the cluster
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
resp, err := cli.MemberAdd(ctx, []string{peerAddrs})
cancel()
if err != nil {
return nil, err
}
// Returns the updated list of etcd members
ret := []Member{}
for _, m := range resp.Members {
// fixes the entry for the joining member (that doesn't have a name set in the initialCluster returned by etcd)
if m.Name == "" {
ret = append(ret, Member{Name: name, PeerURL: m.PeerURLs[0]})
} else {
ret = append(ret, Member{Name: m.Name, PeerURL: m.PeerURLs[0]})
}
}
return ret, nil
}
// GetVersion returns the etcd version of the cluster.
@ -135,12 +276,12 @@ func (c Client) GetVersion() (string, error) {
}
for _, v := range versions {
if clusterVersion != "" && clusterVersion != v {
return "", fmt.Errorf("etcd cluster contains endpoints with mismatched versions: %v", versions)
return "", errors.Errorf("etcd cluster contains endpoints with mismatched versions: %v", versions)
}
clusterVersion = v
}
if clusterVersion == "" {
return "", fmt.Errorf("could not determine cluster etcd version")
return "", errors.New("could not determine cluster etcd version")
}
return clusterVersion, nil
}
@ -215,10 +356,28 @@ func (c Client) WaitForClusterAvailable(delay time.Duration, retries int, retryI
}
return resp, nil
}
return false, fmt.Errorf("timeout waiting for etcd cluster to be available")
return false, errors.New("timeout waiting for etcd cluster to be available")
}
// CheckConfigurationIsHA returns true if the given MasterConfiguration etcd block appears to be an HA configuration.
// CheckConfigurationIsHA returns true if the given InitConfiguration etcd block appears to be an HA configuration.
func CheckConfigurationIsHA(cfg *kubeadmapi.Etcd) bool {
return cfg.External != nil && len(cfg.External.Endpoints) > 1
}
// GetClientURL creates an HTTPS URL that uses the configured advertise
// address and client port for the API controller
func GetClientURL(cfg *kubeadmapi.InitConfiguration) string {
return "https://" + net.JoinHostPort(cfg.LocalAPIEndpoint.AdvertiseAddress, strconv.Itoa(constants.EtcdListenClientPort))
}
// GetPeerURL creates an HTTPS URL that uses the configured advertise
// address and peer port for the API controller
func GetPeerURL(cfg *kubeadmapi.InitConfiguration) string {
return "https://" + net.JoinHostPort(cfg.LocalAPIEndpoint.AdvertiseAddress, strconv.Itoa(constants.EtcdListenPeerPort))
}
// GetClientURLByIP creates an HTTPS URL based on an IP address
// and the client listening port.
func GetClientURLByIP(ip string) string {
return "https://" + net.JoinHostPort(ip, strconv.Itoa(constants.EtcdListenClientPort))
}

View File

@ -17,12 +17,15 @@ limitations under the License.
package etcd
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
)
@ -308,3 +311,90 @@ func TestCheckConfigurationIsHA(t *testing.T) {
})
}
}
func testGetURL(t *testing.T, getURLFunc func(*kubeadmapi.InitConfiguration) string, port int) {
portStr := strconv.Itoa(port)
var tests = []struct {
name string
advertiseAddress string
expectedURL string
}{
{
name: "IPv4",
advertiseAddress: "10.10.10.10",
expectedURL: fmt.Sprintf("https://10.10.10.10:%s", portStr),
},
{
name: "IPv6",
advertiseAddress: "2001:db8::2",
expectedURL: fmt.Sprintf("https://[2001:db8::2]:%s", portStr),
},
{
name: "IPv4 localhost",
advertiseAddress: "127.0.0.1",
expectedURL: fmt.Sprintf("https://127.0.0.1:%s", portStr),
},
{
name: "IPv6 localhost",
advertiseAddress: "::1",
expectedURL: fmt.Sprintf("https://[::1]:%s", portStr),
},
}
for _, test := range tests {
cfg := &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
AdvertiseAddress: test.advertiseAddress,
},
}
url := getURLFunc(cfg)
if url != test.expectedURL {
t.Errorf("expected %s, got %s", test.expectedURL, url)
}
}
}
func TestGetClientURL(t *testing.T) {
testGetURL(t, GetClientURL, constants.EtcdListenClientPort)
}
func TestGetPeerURL(t *testing.T) {
testGetURL(t, GetClientURL, constants.EtcdListenClientPort)
}
func TestGetClientURLByIP(t *testing.T) {
portStr := strconv.Itoa(constants.EtcdListenClientPort)
var tests = []struct {
name string
ip string
expectedURL string
}{
{
name: "IPv4",
ip: "10.10.10.10",
expectedURL: fmt.Sprintf("https://10.10.10.10:%s", portStr),
},
{
name: "IPv6",
ip: "2001:db8::2",
expectedURL: fmt.Sprintf("https://[2001:db8::2]:%s", portStr),
},
{
name: "IPv4 localhost",
ip: "127.0.0.1",
expectedURL: fmt.Sprintf("https://127.0.0.1:%s", portStr),
},
{
name: "IPv6 localhost",
ip: "::1",
expectedURL: fmt.Sprintf("https://[::1]:%s", portStr),
},
}
for _, test := range tests {
url := GetClientURLByIP(test.ip)
if url != test.expectedURL {
t.Errorf("expected %s, got %s", test.expectedURL, url)
}
}
}

View File

@ -17,9 +17,10 @@ go_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",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)

View File

@ -22,17 +22,19 @@ import (
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"github.com/pkg/errors"
)
// CreateBasic creates a basic, general KubeConfig object that then can be extended
func CreateBasic(serverURL string, clusterName string, userName string, caCert []byte) *clientcmdapi.Config {
func CreateBasic(serverURL, clusterName, 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,
Server: serverURL,
CertificateAuthorityData: caCert,
},
},
@ -66,11 +68,11 @@ func CreateWithToken(serverURL, clusterName, userName string, caCert []byte, tok
return config
}
// ClientSetFromFile returns a ready-to-use client from a KubeConfig file
// 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 nil, errors.Wrap(err, "failed to load admin kubeconfig")
}
return ToClientSet(config)
}
@ -79,12 +81,12 @@ func ClientSetFromFile(path string) (*clientset.Clientset, error) {
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)
return nil, errors.Wrap(err, "failed to create API client configuration from kubeconfig")
}
client, err := clientset.NewForConfig(clientConfig)
if err != nil {
return nil, fmt.Errorf("failed to create API client: %v", err)
return nil, errors.Wrap(err, "failed to create API client")
}
return client, nil
}

View File

@ -17,16 +17,20 @@ limitations under the License.
package util
import (
"errors"
"fmt"
"bufio"
"bytes"
"io"
yaml "gopkg.in/yaml.v2"
pkgerrors "github.com/pkg/errors"
"sigs.k8s.io/yaml"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/errors"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
// MarshalToYaml marshals an object into yaml.
@ -41,7 +45,7 @@ func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs s
mediaType := "application/yaml"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
if !ok {
return []byte{}, fmt.Errorf("unsupported media type %q", mediaType)
return []byte{}, pkgerrors.Errorf("unsupported media type %q", mediaType)
}
encoder := codecs.EncoderForVersion(info.Serializer, gv)
@ -60,75 +64,100 @@ func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs se
mediaType := "application/yaml"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
if !ok {
return nil, fmt.Errorf("unsupported media type %q", mediaType)
return nil, pkgerrors.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)
// SplitYAMLDocuments reads the YAML bytes per-document, unmarshals the TypeMeta information from each document
// and returns a map between the GroupVersionKind of the document and the document bytes
func SplitYAMLDocuments(yamlBytes []byte) (map[schema.GroupVersionKind][]byte, error) {
gvkmap := map[schema.GroupVersionKind][]byte{}
knownKinds := map[string]bool{}
errs := []error{}
buf := bytes.NewBuffer(yamlBytes)
reader := utilyaml.NewYAMLReader(bufio.NewReader(buf))
for {
typeMetaInfo := runtime.TypeMeta{}
// Read one YAML document at a time, until io.EOF is returned
b, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
return m2
case []interface{}:
for i, v := range x {
x[i] = convert(v)
if len(b) == 0 {
break
}
// Deserialize the TypeMeta information of this byte slice
if err := yaml.Unmarshal(b, &typeMetaInfo); err != nil {
return nil, err
}
// Require TypeMeta information to be present
if len(typeMetaInfo.APIVersion) == 0 || len(typeMetaInfo.Kind) == 0 {
errs = append(errs, pkgerrors.New("invalid configuration: kind and apiVersion is mandatory information that needs to be specified in all YAML documents"))
continue
}
// Check whether the kind has been registered before. If it has, throw an error
if known := knownKinds[typeMetaInfo.Kind]; known {
errs = append(errs, pkgerrors.Errorf("invalid configuration: kind %q is specified twice in YAML file", typeMetaInfo.Kind))
continue
}
knownKinds[typeMetaInfo.Kind] = true
// Build a GroupVersionKind object from the deserialized TypeMeta object
gv, err := schema.ParseGroupVersion(typeMetaInfo.APIVersion)
if err != nil {
errs = append(errs, pkgerrors.Wrap(err, "unable to parse apiVersion"))
continue
}
gvk := gv.WithKind(typeMetaInfo.Kind)
// Save the mapping between the gvk and the bytes that object consists of
gvkmap[gvk] = b
}
if err := errors.NewAggregate(errs); err != nil {
return nil, err
}
return gvkmap, nil
}
// GroupVersionKindsFromBytes parses the bytes and returns a gvk slice
func GroupVersionKindsFromBytes(b []byte) ([]schema.GroupVersionKind, error) {
gvkmap, err := SplitYAMLDocuments(b)
if err != nil {
return nil, err
}
gvks := []schema.GroupVersionKind{}
for gvk := range gvkmap {
gvks = append(gvks, gvk)
}
return gvks, nil
}
// GroupVersionKindsHasKind returns whether the following gvk slice contains the kind given as a parameter
func GroupVersionKindsHasKind(gvks []schema.GroupVersionKind, kind string) bool {
for _, gvk := range gvks {
if gvk.Kind == kind {
return true
}
}
return i
return false
}
// GroupVersionKindsHasClusterConfiguration returns whether the following gvk slice contains a ClusterConfiguration object
func GroupVersionKindsHasClusterConfiguration(gvks ...schema.GroupVersionKind) bool {
return GroupVersionKindsHasKind(gvks, constants.ClusterConfigurationKind)
}
// GroupVersionKindsHasInitConfiguration returns whether the following gvk slice contains a InitConfiguration object
func GroupVersionKindsHasInitConfiguration(gvks ...schema.GroupVersionKind) bool {
return GroupVersionKindsHasKind(gvks, constants.InitConfigurationKind)
}
// GroupVersionKindsHasJoinConfiguration returns whether the following gvk slice contains a JoinConfiguration object
func GroupVersionKindsHasJoinConfiguration(gvks ...schema.GroupVersionKind) bool {
return GroupVersionKindsHasKind(gvks, constants.JoinConfigurationKind)
}

View File

@ -17,15 +17,49 @@ limitations under the License.
package util
import (
"bytes"
"reflect"
"sort"
"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"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
var files = map[string][]byte{
"foo": []byte(`
kind: Foo
apiVersion: foo.k8s.io/v1
fooField: foo
`),
"bar": []byte(`
apiVersion: bar.k8s.io/v2
barField: bar
kind: Bar
`),
"baz": []byte(`
apiVersion: baz.k8s.io/v1
kind: Baz
baz:
foo: bar
`),
"nokind": []byte(`
apiVersion: baz.k8s.io/v1
foo: foo
bar: bar
`),
"noapiversion": []byte(`
kind: Bar
foo: foo
bar: bar
`),
}
func TestMarshalUnmarshalYaml(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
@ -75,51 +109,299 @@ func TestMarshalUnmarshalYaml(t *testing.T) {
}
func TestMarshalUnmarshalToYamlForCodecs(t *testing.T) {
cfg := &kubeadmapiext.MasterConfiguration{
API: kubeadmapiext.API{
AdvertiseAddress: "10.100.0.1",
BindPort: 4332,
cfg := &kubeadmapiv1beta1.InitConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: constants.InitConfigurationKind,
APIVersion: kubeadmapiv1beta1.SchemeGroupVersion.String(),
},
NodeName: "testNode",
NoTaintMaster: true,
Networking: kubeadmapiext.Networking{
ServiceSubnet: "10.100.0.0/24",
PodSubnet: "10.100.1.0/24",
NodeRegistration: kubeadmapiv1beta1.NodeRegistrationOptions{
Name: "testNode",
CRISocket: "/var/run/cri.sock",
},
BootstrapTokens: []kubeadmapiv1beta1.BootstrapToken{
{
Token: &kubeadmapiv1beta1.BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
},
},
// NOTE: Using MarshalToYamlForCodecs and UnmarshalFromYamlForCodecs for ClusterConfiguration fields here won't work
// by design. This is because we have a `json:"-"` annotation in order to avoid struct duplication. See the comment
// at the kubeadmapiv1beta1.InitConfiguration definition.
}
bytes, err := MarshalToYamlForCodecs(cfg, kubeadmapiext.SchemeGroupVersion, scheme.Codecs)
kubeadmapiv1beta1.SetDefaults_InitConfiguration(cfg)
scheme := runtime.NewScheme()
if err := kubeadmapiv1beta1.AddToScheme(scheme); err != nil {
t.Fatal(err)
}
codecs := serializer.NewCodecFactory(scheme)
bytes, err := MarshalToYamlForCodecs(cfg, kubeadmapiv1beta1.SchemeGroupVersion, codecs)
if err != nil {
t.Fatalf("unexpected error marshalling MasterConfiguration: %v", err)
t.Fatalf("unexpected error marshalling InitConfiguration: %v", err)
}
t.Logf("\n%s", bytes)
obj, err := UnmarshalFromYamlForCodecs(bytes, kubeadmapiext.SchemeGroupVersion, scheme.Codecs)
obj, err := UnmarshalFromYamlForCodecs(bytes, kubeadmapiv1beta1.SchemeGroupVersion, codecs)
if err != nil {
t.Fatalf("unexpected error unmarshalling MasterConfiguration: %v", err)
t.Fatalf("unexpected error unmarshalling InitConfiguration: %v", err)
}
cfg2, ok := obj.(*kubeadmapiext.MasterConfiguration)
if !ok {
t.Fatal("did not get MasterConfiguration back")
cfg2, ok := obj.(*kubeadmapiv1beta1.InitConfiguration)
if !ok || cfg2 == nil {
t.Fatal("did not get InitConfiguration 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)
if !reflect.DeepEqual(*cfg, *cfg2) {
t.Errorf("expected %v, got %v", *cfg, *cfg2)
}
}
// {{InitConfiguration kubeadm.k8s.io/v1beta1} [{<nil> nil <nil> [] []}] {testNode /var/run/cri.sock [] map[]} {10.100.0.1 4332} {0xc4200ad2c0 <nil>} {10.100.0.0/24 10.100.1.0/24 cluster.local} stable-1.11 map[] map[] map[] [] [] [] [] /etc/kubernetes/pki k8s.gcr.io { /var/log/kubernetes/audit 0x156e2f4} map[] kubernetes}
// {{InitConfiguration kubeadm.k8s.io/v1beta1} [{<nil> &Duration{Duration:24h0m0s,} <nil> [signing authentication] [system:bootstrappers:kubeadm:default-node-token]}] {testNode /var/run/cri.sock [] map[]} {10.100.0.1 4332} {0xc4205c5260 <nil>} {10.100.0.0/24 10.100.1.0/24 cluster.local} stable-1.11 map[] map[] map[] [] [] [] [] /etc/kubernetes/pki k8s.gcr.io { /var/log/kubernetes/audit 0xc4204dd82c} map[] kubernetes}
// {{InitConfiguration kubeadm.k8s.io/v1beta1} [{abcdef.abcdef0123456789 nil <nil> [] []}] {testNode /var/run/cri.sock [] map[]} {10.100.0.1 4332} {0xc42012ca80 <nil>} {10.100.0.0/24 10.100.1.0/24 cluster.local} stable-1.11 map[] map[] map[] [] [] [] [] /etc/kubernetes/pki k8s.gcr.io { /var/log/kubernetes/audit 0x156e2f4} map[] kubernetes}
// {{InitConfiguration kubeadm.k8s.io/v1beta1} [{abcdef.abcdef0123456789 &Duration{Duration:24h0m0s,} <nil> [signing authentication] [system:bootstrappers:kubeadm:default-node-token]}] {testNode /var/run/cri.sock [] map[]} {10.100.0.1 4332} {0xc42039d1a0 <nil>} {10.100.0.0/24 10.100.1.0/24 cluster.local} stable-1.11 map[] map[] map[] [] [] [] [] /etc/kubernetes/pki k8s.gcr.io { /var/log/kubernetes/audit 0xc4204fef3c} map[] kubernetes}
func TestSplitYAMLDocuments(t *testing.T) {
var tests = []struct {
name string
fileContents []byte
gvkmap map[schema.GroupVersionKind][]byte
expectedErr bool
}{
{
name: "FooOnly",
fileContents: files["foo"],
gvkmap: map[schema.GroupVersionKind][]byte{
{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}: files["foo"],
},
},
{
name: "FooBar",
fileContents: bytes.Join([][]byte{files["foo"], files["bar"]}, []byte(constants.YAMLDocumentSeparator)),
gvkmap: map[schema.GroupVersionKind][]byte{
{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}: files["foo"],
{Group: "bar.k8s.io", Version: "v2", Kind: "Bar"}: files["bar"],
},
},
{
name: "FooTwiceInvalid",
fileContents: bytes.Join([][]byte{files["foo"], files["bar"], files["foo"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: true,
},
{
name: "InvalidBaz",
fileContents: bytes.Join([][]byte{files["foo"], files["baz"]}, []byte(constants.YAMLDocumentSeparator)),
expectedErr: true,
},
{
name: "InvalidNoKind",
fileContents: files["nokind"],
expectedErr: true,
},
{
name: "InvalidNoAPIVersion",
fileContents: files["noapiversion"],
expectedErr: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
gvkmap, err := SplitYAMLDocuments(rt.fileContents)
if (err != nil) != rt.expectedErr {
t2.Errorf("expected error: %t, actual: %t", rt.expectedErr, err != nil)
}
if !reflect.DeepEqual(gvkmap, rt.gvkmap) {
t2.Errorf("expected gvkmap: %s\n\tactual: %s\n", rt.gvkmap, gvkmap)
}
})
}
}
func TestGroupVersionKindsFromBytes(t *testing.T) {
var tests = []struct {
name string
fileContents []byte
gvks []string
expectedErr bool
}{
{
name: "FooOnly",
fileContents: files["foo"],
gvks: []string{
"foo.k8s.io/v1, Kind=Foo",
},
},
{
name: "FooBar",
fileContents: bytes.Join([][]byte{files["foo"], files["bar"]}, []byte(constants.YAMLDocumentSeparator)),
gvks: []string{
"foo.k8s.io/v1, Kind=Foo",
"bar.k8s.io/v2, Kind=Bar",
},
},
{
name: "FooTwiceInvalid",
fileContents: bytes.Join([][]byte{files["foo"], files["bar"], files["foo"]}, []byte(constants.YAMLDocumentSeparator)),
gvks: []string{},
expectedErr: true,
},
{
name: "InvalidBaz",
fileContents: bytes.Join([][]byte{files["foo"], files["baz"]}, []byte(constants.YAMLDocumentSeparator)),
gvks: []string{},
expectedErr: true,
},
{
name: "InvalidNoKind",
fileContents: files["nokind"],
gvks: []string{},
expectedErr: true,
},
{
name: "InvalidNoAPIVersion",
fileContents: files["noapiversion"],
gvks: []string{},
expectedErr: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
gvks, err := GroupVersionKindsFromBytes(rt.fileContents)
if (err != nil) != rt.expectedErr {
t2.Errorf("expected error: %t, actual: %t", rt.expectedErr, err != nil)
}
strgvks := []string{}
for _, gvk := range gvks {
strgvks = append(strgvks, gvk.String())
}
sort.Strings(strgvks)
sort.Strings(rt.gvks)
if !reflect.DeepEqual(strgvks, rt.gvks) {
t2.Errorf("expected gvks: %s\n\tactual: %s\n", rt.gvks, strgvks)
}
})
}
}
func TestGroupVersionKindsHasKind(t *testing.T) {
var tests = []struct {
name string
gvks []schema.GroupVersionKind
kind string
expected bool
}{
{
name: "FooOnly",
gvks: []schema.GroupVersionKind{
{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"},
},
kind: "Foo",
expected: true,
},
{
name: "FooBar",
gvks: []schema.GroupVersionKind{
{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"},
{Group: "bar.k8s.io", Version: "v2", Kind: "Bar"},
},
kind: "Bar",
expected: true,
},
{
name: "FooBazNoBaz",
gvks: []schema.GroupVersionKind{
{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"},
{Group: "bar.k8s.io", Version: "v2", Kind: "Bar"},
},
kind: "Baz",
expected: false,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
actual := GroupVersionKindsHasKind(rt.gvks, rt.kind)
if rt.expected != actual {
t2.Errorf("expected gvks has kind: %t\n\tactual: %t\n", rt.expected, actual)
}
})
}
}
func TestGroupVersionKindsHasInitConfiguration(t *testing.T) {
var tests = []struct {
name string
gvks []schema.GroupVersionKind
kind string
expected bool
}{
{
name: "NoInitConfiguration",
gvks: []schema.GroupVersionKind{
{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"},
},
expected: false,
},
{
name: "InitConfigurationFound",
gvks: []schema.GroupVersionKind{
{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"},
{Group: "bar.k8s.io", Version: "v2", Kind: "InitConfiguration"},
},
expected: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
actual := GroupVersionKindsHasInitConfiguration(rt.gvks...)
if rt.expected != actual {
t2.Errorf("expected gvks has InitConfiguration: %t\n\tactual: %t\n", rt.expected, actual)
}
})
}
}
func TestGroupVersionKindsHasJoinConfiguration(t *testing.T) {
var tests = []struct {
name string
gvks []schema.GroupVersionKind
kind string
expected bool
}{
{
name: "NoJoinConfiguration",
gvks: []schema.GroupVersionKind{
{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"},
},
expected: false,
},
{
name: "JoinConfigurationFound",
gvks: []schema.GroupVersionKind{
{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"},
{Group: "bar.k8s.io", Version: "v2", Kind: "JoinConfiguration"},
},
expected: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
actual := GroupVersionKindsHasJoinConfiguration(rt.gvks...)
if rt.expected != actual {
t2.Errorf("expected gvks has JoinConfiguration: %t\n\tactual: %t\n", rt.expected, actual)
}
})
}
}

View File

@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["pki_helpers_test.go"],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["pki_helpers.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//pkg/registry/core/service/ipallocator:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//vendor/github.com/pkg/errors: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

@ -0,0 +1,506 @@
/*
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 pkiutil
import (
"crypto"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/validation"
certutil "k8s.io/client-go/util/cert"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
)
// NewCertificateAuthority creates new certificate and private key for the certificate authority
func NewCertificateAuthority(config *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) {
key, err := certutil.NewPrivateKey()
if err != nil {
return nil, nil, errors.Wrap(err, "unable to create private key")
}
cert, err := certutil.NewSelfSignedCACert(*config, key)
if err != nil {
return nil, nil, errors.Wrap(err, "unable to create self-signed certificate")
}
return cert, key, nil
}
// NewCertAndKey creates new certificate and key by passing the certificate authority certificate and key
func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) {
key, err := certutil.NewPrivateKey()
if err != nil {
return nil, nil, errors.Wrap(err, "unable to create private key")
}
cert, err := certutil.NewSignedCert(*config, key, caCert, caKey)
if err != nil {
return nil, nil, errors.Wrap(err, "unable to sign certificate")
}
return cert, key, nil
}
// NewCSRAndKey generates a new key and CSR and that could be signed to create the given certificate
func NewCSRAndKey(config *certutil.Config) (*x509.CertificateRequest, *rsa.PrivateKey, error) {
key, err := certutil.NewPrivateKey()
if err != nil {
return nil, nil, errors.Wrap(err, "unable to create private key")
}
csr, err := NewCSR(*config, key)
if err != nil {
return nil, nil, errors.Wrap(err, "unable to generate CSR")
}
return csr, key, nil
}
// HasServerAuth returns true if the given certificate is a ServerAuth
func HasServerAuth(cert *x509.Certificate) bool {
for i := range cert.ExtKeyUsage {
if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth {
return true
}
}
return false
}
// WriteCertAndKey stores certificate and key at the specified location
func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error {
if err := WriteKey(pkiPath, name, key); err != nil {
return errors.Wrap(err, "couldn't write key")
}
return WriteCert(pkiPath, name, cert)
}
// WriteCert stores the given certificate at the given location
func WriteCert(pkiPath, name string, cert *x509.Certificate) error {
if cert == nil {
return errors.New("certificate cannot be nil when writing to file")
}
certificatePath := pathForCert(pkiPath, name)
if err := certutil.WriteCert(certificatePath, certutil.EncodeCertPEM(cert)); err != nil {
return errors.Wrapf(err, "unable to write certificate to file %s", certificatePath)
}
return nil
}
// WriteKey stores the given key at the given location
func WriteKey(pkiPath, name string, key *rsa.PrivateKey) error {
if key == nil {
return errors.New("private key cannot be nil when writing to file")
}
privateKeyPath := pathForKey(pkiPath, name)
if err := certutil.WriteKey(privateKeyPath, certutil.EncodePrivateKeyPEM(key)); err != nil {
return errors.Wrapf(err, "unable to write private key to file %s", privateKeyPath)
}
return nil
}
// WriteCSR writes the pem-encoded CSR data to csrPath.
// The CSR file will be created with file mode 0644.
// If the CSR file already exists, it will be overwritten.
// The parent directory of the csrPath will be created as needed with file mode 0755.
func WriteCSR(csrDir, name string, csr *x509.CertificateRequest) error {
if csr == nil {
return errors.New("certificate request cannot be nil when writing to file")
}
csrPath := pathForCSR(csrDir, name)
if err := os.MkdirAll(filepath.Dir(csrPath), os.FileMode(0755)); err != nil {
return errors.Wrapf(err, "failed to make directory %s", filepath.Dir(csrPath))
}
if err := ioutil.WriteFile(csrPath, EncodeCSRPEM(csr), os.FileMode(0644)); err != nil {
return errors.Wrapf(err, "unable to write CSR to file %s", csrPath)
}
return nil
}
// WritePublicKey stores the given public key at the given location
func WritePublicKey(pkiPath, name string, key *rsa.PublicKey) error {
if key == nil {
return errors.New("public key cannot be nil when writing to file")
}
publicKeyBytes, err := certutil.EncodePublicKeyPEM(key)
if err != nil {
return err
}
publicKeyPath := pathForPublicKey(pkiPath, name)
if err := certutil.WriteKey(publicKeyPath, publicKeyBytes); err != nil {
return errors.Wrapf(err, "unable to write public key to file %s", publicKeyPath)
}
return nil
}
// CertOrKeyExist returns a boolean whether the cert or the key exists
func CertOrKeyExist(pkiPath, name string) bool {
certificatePath, privateKeyPath := PathsForCertAndKey(pkiPath, name)
_, certErr := os.Stat(certificatePath)
_, keyErr := os.Stat(privateKeyPath)
if os.IsNotExist(certErr) && os.IsNotExist(keyErr) {
// The cert or the key did not exist
return false
}
// Both files exist or one of them
return true
}
// CSROrKeyExist returns true if one of the CSR or key exists
func CSROrKeyExist(csrDir, name string) bool {
csrPath := pathForCSR(csrDir, name)
keyPath := pathForKey(csrDir, name)
_, csrErr := os.Stat(csrPath)
_, keyErr := os.Stat(keyPath)
return !(os.IsNotExist(csrErr) && os.IsNotExist(keyErr))
}
// TryLoadCertAndKeyFromDisk tries to load a cert and a key from the disk and validates that they are valid
func TryLoadCertAndKeyFromDisk(pkiPath, name string) (*x509.Certificate, *rsa.PrivateKey, error) {
cert, err := TryLoadCertFromDisk(pkiPath, name)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to load certificate")
}
key, err := TryLoadKeyFromDisk(pkiPath, name)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to load key")
}
return cert, key, nil
}
// TryLoadCertFromDisk tries to load the cert from the disk and validates that it is valid
func TryLoadCertFromDisk(pkiPath, name string) (*x509.Certificate, error) {
certificatePath := pathForCert(pkiPath, name)
certs, err := certutil.CertsFromFile(certificatePath)
if err != nil {
return nil, errors.Wrapf(err, "couldn't load the certificate file %s", certificatePath)
}
// We are only putting one certificate in the certificate pem file, so it's safe to just pick the first one
// TODO: Support multiple certs here in order to be able to rotate certs
cert := certs[0]
// Check so that the certificate is valid now
now := time.Now()
if now.Before(cert.NotBefore) {
return nil, errors.New("the certificate is not valid yet")
}
if now.After(cert.NotAfter) {
return nil, errors.New("the certificate has expired")
}
return cert, nil
}
// TryLoadKeyFromDisk tries to load the key from the disk and validates that it is valid
func TryLoadKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, error) {
privateKeyPath := pathForKey(pkiPath, name)
// Parse the private key from a file
privKey, err := certutil.PrivateKeyFromFile(privateKeyPath)
if err != nil {
return nil, errors.Wrapf(err, "couldn't load the private key file %s", privateKeyPath)
}
// Allow RSA format only
var key *rsa.PrivateKey
switch k := privKey.(type) {
case *rsa.PrivateKey:
key = k
default:
return nil, errors.Errorf("the private key file %s isn't in RSA format", privateKeyPath)
}
return key, nil
}
// TryLoadCSRAndKeyFromDisk tries to load the CSR and key from the disk
func TryLoadCSRAndKeyFromDisk(pkiPath, name string) (*x509.CertificateRequest, *rsa.PrivateKey, error) {
csrPath := pathForCSR(pkiPath, name)
csr, err := CertificateRequestFromFile(csrPath)
if err != nil {
return nil, nil, errors.Wrapf(err, "couldn't load the certificate request %s", csrPath)
}
key, err := TryLoadKeyFromDisk(pkiPath, name)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't load key file")
}
return csr, key, nil
}
// TryLoadPrivatePublicKeyFromDisk tries to load the key from the disk and validates that it is valid
func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rsa.PublicKey, error) {
privateKeyPath := pathForKey(pkiPath, name)
// Parse the private key from a file
privKey, err := certutil.PrivateKeyFromFile(privateKeyPath)
if err != nil {
return nil, nil, errors.Wrapf(err, "couldn't load the private key file %s", privateKeyPath)
}
publicKeyPath := pathForPublicKey(pkiPath, name)
// Parse the public key from a file
pubKeys, err := certutil.PublicKeysFromFile(publicKeyPath)
if err != nil {
return nil, nil, errors.Wrapf(err, "couldn't load the public key file %s", publicKeyPath)
}
// Allow RSA format only
k, ok := privKey.(*rsa.PrivateKey)
if !ok {
return nil, nil, errors.Errorf("the private key file %s isn't in RSA format", privateKeyPath)
}
p := pubKeys[0].(*rsa.PublicKey)
return k, p, nil
}
// PathsForCertAndKey returns the paths for the certificate and key given the path and basename.
func PathsForCertAndKey(pkiPath, name string) (string, string) {
return pathForCert(pkiPath, name), pathForKey(pkiPath, name)
}
func pathForCert(pkiPath, name string) string {
return filepath.Join(pkiPath, fmt.Sprintf("%s.crt", name))
}
func pathForKey(pkiPath, name string) string {
return filepath.Join(pkiPath, fmt.Sprintf("%s.key", name))
}
func pathForPublicKey(pkiPath, name string) string {
return filepath.Join(pkiPath, fmt.Sprintf("%s.pub", name))
}
func pathForCSR(pkiPath, name string) string {
return filepath.Join(pkiPath, fmt.Sprintf("%s.csr", name))
}
// GetAPIServerAltNames builds an AltNames object for to be used when generating apiserver certificate
func GetAPIServerAltNames(cfg *kubeadmapi.InitConfiguration) (*certutil.AltNames, error) {
// advertise address
advertiseAddress := net.ParseIP(cfg.LocalAPIEndpoint.AdvertiseAddress)
if advertiseAddress == nil {
return nil, errors.Errorf("error parsing LocalAPIEndpoint AdvertiseAddress %v: is not a valid textual representation of an IP address",
cfg.LocalAPIEndpoint.AdvertiseAddress)
}
// internal IP address for the API server
_, svcSubnet, err := net.ParseCIDR(cfg.Networking.ServiceSubnet)
if err != nil {
return nil, errors.Wrapf(err, "error parsing CIDR %q", cfg.Networking.ServiceSubnet)
}
internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(svcSubnet, 1)
if err != nil {
return nil, errors.Wrapf(err, "unable to get first IP address from the given CIDR (%s)", svcSubnet.String())
}
// create AltNames with defaults DNSNames/IPs
altNames := &certutil.AltNames{
DNSNames: []string{
cfg.NodeRegistration.Name,
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
fmt.Sprintf("kubernetes.default.svc.%s", cfg.Networking.DNSDomain),
},
IPs: []net.IP{
internalAPIServerVirtualIP,
advertiseAddress,
},
}
// add cluster controlPlaneEndpoint if present (dns or ip)
if len(cfg.ControlPlaneEndpoint) > 0 {
if host, _, err := kubeadmutil.ParseHostPort(cfg.ControlPlaneEndpoint); err == nil {
if ip := net.ParseIP(host); ip != nil {
altNames.IPs = append(altNames.IPs, ip)
} else {
altNames.DNSNames = append(altNames.DNSNames, host)
}
} else {
return nil, errors.Wrapf(err, "error parsing cluster controlPlaneEndpoint %q", cfg.ControlPlaneEndpoint)
}
}
appendSANsToAltNames(altNames, cfg.APIServer.CertSANs, kubeadmconstants.APIServerCertName)
return altNames, nil
}
// GetEtcdAltNames builds an AltNames object for generating the etcd server certificate.
// `advertise address` and localhost are included in the SAN since this is the interfaces the etcd static pod listens on.
// The user can override the listen address with `Etcd.ExtraArgs` and add SANs with `Etcd.ServerCertSANs`.
func GetEtcdAltNames(cfg *kubeadmapi.InitConfiguration) (*certutil.AltNames, error) {
// advertise address
advertiseAddress := net.ParseIP(cfg.LocalAPIEndpoint.AdvertiseAddress)
if advertiseAddress == nil {
return nil, errors.Errorf("error parsing LocalAPIEndpoint AdvertiseAddress %q: is not a valid textual representation of an IP address", cfg.LocalAPIEndpoint.AdvertiseAddress)
}
// create AltNames with defaults DNSNames/IPs
altNames := &certutil.AltNames{
DNSNames: []string{cfg.NodeRegistration.Name, "localhost"},
IPs: []net.IP{advertiseAddress, net.IPv4(127, 0, 0, 1), net.IPv6loopback},
}
if cfg.Etcd.Local != nil {
appendSANsToAltNames(altNames, cfg.Etcd.Local.ServerCertSANs, kubeadmconstants.EtcdServerCertName)
}
return altNames, nil
}
// GetEtcdPeerAltNames builds an AltNames object for generating the etcd peer certificate.
// Hostname and `API.AdvertiseAddress` are included if the user chooses to promote the single node etcd cluster into a multi-node one (stacked etcd).
// The user can override the listen address with `Etcd.ExtraArgs` and add SANs with `Etcd.PeerCertSANs`.
func GetEtcdPeerAltNames(cfg *kubeadmapi.InitConfiguration) (*certutil.AltNames, error) {
// advertise address
advertiseAddress := net.ParseIP(cfg.LocalAPIEndpoint.AdvertiseAddress)
if advertiseAddress == nil {
return nil, errors.Errorf("error parsing LocalAPIEndpoint AdvertiseAddress %v: is not a valid textual representation of an IP address",
cfg.LocalAPIEndpoint.AdvertiseAddress)
}
// create AltNames with defaults DNSNames/IPs
altNames := &certutil.AltNames{
DNSNames: []string{cfg.NodeRegistration.Name, "localhost"},
IPs: []net.IP{advertiseAddress, net.IPv4(127, 0, 0, 1), net.IPv6loopback},
}
if cfg.Etcd.Local != nil {
appendSANsToAltNames(altNames, cfg.Etcd.Local.PeerCertSANs, kubeadmconstants.EtcdPeerCertName)
}
return altNames, nil
}
// appendSANsToAltNames parses SANs from as list of strings and adds them to altNames for use on a specific cert
// altNames is passed in with a pointer, and the struct is modified
// valid IP address strings are parsed and added to altNames.IPs as net.IP's
// RFC-1123 compliant DNS strings are added to altNames.DNSNames as strings
// certNames is used to print user facing warnings and should be the name of the cert the altNames will be used for
func appendSANsToAltNames(altNames *certutil.AltNames, SANs []string, certName string) {
for _, altname := range SANs {
if ip := net.ParseIP(altname); ip != nil {
altNames.IPs = append(altNames.IPs, ip)
} else if len(validation.IsDNS1123Subdomain(altname)) == 0 {
altNames.DNSNames = append(altNames.DNSNames, altname)
} else {
fmt.Printf(
"[certificates] WARNING: '%s' was not added to the '%s' SAN, because it is not a valid IP or RFC-1123 compliant DNS entry\n",
altname,
certName,
)
}
}
}
// EncodeCSRPEM returns PEM-encoded CSR data
func EncodeCSRPEM(csr *x509.CertificateRequest) []byte {
block := pem.Block{
Type: certutil.CertificateRequestBlockType,
Bytes: csr.Raw,
}
return pem.EncodeToMemory(&block)
}
func parseCSRPEM(pemCSR []byte) (*x509.CertificateRequest, error) {
block, _ := pem.Decode(pemCSR)
if block == nil {
return nil, fmt.Errorf("data doesn't contain a valid certificate request")
}
if block.Type != certutil.CertificateRequestBlockType {
var block *pem.Block
return nil, fmt.Errorf("expected block type %q, but PEM had type %v", certutil.CertificateRequestBlockType, block.Type)
}
return x509.ParseCertificateRequest(block.Bytes)
}
// CertificateRequestFromFile returns the CertificateRequest from a given PEM-encoded file.
// Returns an error if the file could not be read or if the CSR could not be parsed.
func CertificateRequestFromFile(file string) (*x509.CertificateRequest, error) {
pemBlock, err := ioutil.ReadFile(file)
if err != nil {
return nil, errors.Wrap(err, "failed to read file")
}
csr, err := parseCSRPEM(pemBlock)
if err != nil {
return nil, fmt.Errorf("error reading certificate request file %s: %v", file, err)
}
return csr, nil
}
// NewCSR creates a new CSR
func NewCSR(cfg certutil.Config, key crypto.Signer) (*x509.CertificateRequest, error) {
template := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: cfg.CommonName,
Organization: cfg.Organization,
},
DNSNames: cfg.AltNames.DNSNames,
IPAddresses: cfg.AltNames.IPs,
}
csrBytes, err := x509.CreateCertificateRequest(cryptorand.Reader, template, key)
if err != nil {
return nil, errors.Wrap(err, "failed to create a CSR")
}
return x509.ParseCertificateRequest(csrBytes)
}

View File

@ -0,0 +1,657 @@
/*
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 pkiutil
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"io/ioutil"
"net"
"os"
"testing"
certutil "k8s.io/client-go/util/cert"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
// certificateRequest is an x509 certificate request in PEM encoded format
// openssl req -new -key rsa2048.pem -sha256 -nodes -out x509certrequest.pem -subj "/C=US/CN=not-valid"
const certificateRequest = `-----BEGIN CERTIFICATE REQUEST-----
MIICZjCCAU4CAQAwITELMAkGA1UEBhMCVVMxEjAQBgNVBAMMCW5vdC12YWxpZDCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMdoBxV0SbSS+7XrgVDF/P4x
tqyun+DLxeRF5265ZOFRJDXCJgYH7wKlxlkEaHZQhnNmnqFiy96MHSKaiQmlkEm4
EhlqTf38yEWx+t98A0CDbHsIPZ0/+MPCjb2kf+OfBXJJl908io0grs02jxN9lceL
RFrKT6vaB+6i7LxbPQcOmjF7OUqWS6S2qSpShw2GY+mJz4HM7OFb9RcN4izh+GF6
7hajYgt7pAFyWF1ua/H98Ysn4FVgIYk30rHCNBkQpJnna7EyGYuj08VuFa088W9g
c/DCpL+VgBDwTel9tfeMxRAoLIPF9iJ8Ftr7dsRZ/Y/SnxfUJo2ed8y7dgIiLuEC
AwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCOjPB/4LKa2G7LarMMLAeNqvWF9SIG
y2VGQoTn9D5blXMvnfzWSYgU6nBzf/E/32q26OwiCriuOPXfxM/cxEMOJ62u7b50
OR52JFvQdONsCZaLgylGWppl0YeqylbTosHjsWJNlp+zjXcQHjCQ9OoLgfmrwYyD
2MsYJR4p7JZ2ZN8FF1hgMUrDzypZ0NSBKAiQMU9TFhxgyk75RNDtmX+2K35zqLyr
0otimyYwPCGPD2GHwNfvu1oP0A+/cT+rCPz6AlXhWEbz2JkLo6/muRfRl0QSRgHE
Q3+eWlA1YdqEBwvp3NEQI9BtMnzxJVWA5dvYluMNllsV/q8s2IEEAFG9
-----END CERTIFICATE REQUEST-----`
func TestNewCertificateAuthority(t *testing.T) {
cert, key, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"})
if cert == nil {
t.Errorf(
"failed NewCertificateAuthority, cert == nil",
)
}
if key == nil {
t.Errorf(
"failed NewCertificateAuthority, key == nil",
)
}
if err != nil {
t.Errorf(
"failed NewCertificateAuthority with an error: %v",
err,
)
}
}
func TestNewCertAndKey(t *testing.T) {
var tests = []struct {
caKeySize int
expected bool
}{
{
// RSA key too small
caKeySize: 128,
expected: false,
},
{
// Should succeed
caKeySize: 2048,
expected: true,
},
}
for _, rt := range tests {
caKey, err := rsa.GenerateKey(rand.Reader, rt.caKeySize)
if err != nil {
t.Fatalf("Couldn't create rsa Private Key")
}
caCert := &x509.Certificate{}
config := &certutil.Config{
CommonName: "test",
Organization: []string{"test"},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
_, _, actual := NewCertAndKey(caCert, caKey, config)
if (actual == nil) != rt.expected {
t.Errorf(
"failed NewCertAndKey:\n\texpected: %t\n\t actual: %t",
rt.expected,
(actual == nil),
)
}
}
}
func TestHasServerAuth(t *testing.T) {
caCert, caKey, _ := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"})
var tests = []struct {
config certutil.Config
expected bool
}{
{
config: certutil.Config{
CommonName: "test",
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
},
expected: true,
},
{
config: certutil.Config{
CommonName: "test",
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
expected: false,
},
}
for _, rt := range tests {
cert, _, err := NewCertAndKey(caCert, caKey, &rt.config)
if err != nil {
t.Fatalf("Couldn't create cert: %v", err)
}
actual := HasServerAuth(cert)
if actual != rt.expected {
t.Errorf(
"failed HasServerAuth:\n\texpected: %t\n\t actual: %t",
rt.expected,
actual,
)
}
}
}
func TestWriteCertAndKey(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("Couldn't create rsa Private Key")
}
caCert := &x509.Certificate{}
actual := WriteCertAndKey(tmpdir, "foo", caCert, caKey)
if actual != nil {
t.Errorf(
"failed WriteCertAndKey with an error: %v",
actual,
)
}
}
func TestWriteCert(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
caCert := &x509.Certificate{}
actual := WriteCert(tmpdir, "foo", caCert)
if actual != nil {
t.Errorf(
"failed WriteCertAndKey with an error: %v",
actual,
)
}
}
func TestWriteKey(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("Couldn't create rsa Private Key")
}
actual := WriteKey(tmpdir, "foo", caKey)
if actual != nil {
t.Errorf(
"failed WriteCertAndKey with an error: %v",
actual,
)
}
}
func TestWritePublicKey(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("Couldn't create rsa Private Key")
}
actual := WritePublicKey(tmpdir, "foo", &caKey.PublicKey)
if actual != nil {
t.Errorf(
"failed WriteCertAndKey with an error: %v",
actual,
)
}
}
func TestCertOrKeyExist(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("Couldn't create rsa Private Key")
}
caCert := &x509.Certificate{}
actual := WriteCertAndKey(tmpdir, "foo", caCert, caKey)
if actual != nil {
t.Errorf(
"failed WriteCertAndKey with an error: %v",
actual,
)
}
var tests = []struct {
path string
name string
expected bool
}{
{
path: "",
name: "",
expected: false,
},
{
path: tmpdir,
name: "foo",
expected: true,
},
}
for _, rt := range tests {
actual := CertOrKeyExist(rt.path, rt.name)
if actual != rt.expected {
t.Errorf(
"failed CertOrKeyExist:\n\texpected: %t\n\t actual: %t",
rt.expected,
actual,
)
}
}
}
func TestTryLoadCertAndKeyFromDisk(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
caCert, caKey, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"})
if err != nil {
t.Errorf(
"failed to create cert and key with an error: %v",
err,
)
}
err = WriteCertAndKey(tmpdir, "foo", caCert, caKey)
if err != nil {
t.Errorf(
"failed to write cert and key with an error: %v",
err,
)
}
var tests = []struct {
path string
name string
expected bool
}{
{
path: "",
name: "",
expected: false,
},
{
path: tmpdir,
name: "foo",
expected: true,
},
}
for _, rt := range tests {
_, _, actual := TryLoadCertAndKeyFromDisk(rt.path, rt.name)
if (actual == nil) != rt.expected {
t.Errorf(
"failed TryLoadCertAndKeyFromDisk:\n\texpected: %t\n\t actual: %t",
rt.expected,
(actual == nil),
)
}
}
}
func TestTryLoadCertFromDisk(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
caCert, _, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"})
if err != nil {
t.Errorf(
"failed to create cert and key with an error: %v",
err,
)
}
err = WriteCert(tmpdir, "foo", caCert)
if err != nil {
t.Errorf(
"failed to write cert and key with an error: %v",
err,
)
}
var tests = []struct {
path string
name string
expected bool
}{
{
path: "",
name: "",
expected: false,
},
{
path: tmpdir,
name: "foo",
expected: true,
},
}
for _, rt := range tests {
_, actual := TryLoadCertFromDisk(rt.path, rt.name)
if (actual == nil) != rt.expected {
t.Errorf(
"failed TryLoadCertAndKeyFromDisk:\n\texpected: %t\n\t actual: %t",
rt.expected,
(actual == nil),
)
}
}
}
func TestTryLoadKeyFromDisk(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
defer os.RemoveAll(tmpdir)
_, caKey, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"})
if err != nil {
t.Errorf(
"failed to create cert and key with an error: %v",
err,
)
}
err = WriteKey(tmpdir, "foo", caKey)
if err != nil {
t.Errorf(
"failed to write cert and key with an error: %v",
err,
)
}
var tests = []struct {
path string
name string
expected bool
}{
{
path: "",
name: "",
expected: false,
},
{
path: tmpdir,
name: "foo",
expected: true,
},
}
for _, rt := range tests {
_, actual := TryLoadKeyFromDisk(rt.path, rt.name)
if (actual == nil) != rt.expected {
t.Errorf(
"failed TryLoadCertAndKeyFromDisk:\n\texpected: %t\n\t actual: %t",
rt.expected,
(actual == nil),
)
}
}
}
func TestPathsForCertAndKey(t *testing.T) {
crtPath, keyPath := PathsForCertAndKey("/foo", "bar")
if crtPath != "/foo/bar.crt" {
t.Errorf("unexpected certificate path: %s", crtPath)
}
if keyPath != "/foo/bar.key" {
t.Errorf("unexpected key path: %s", keyPath)
}
}
func TestPathForCert(t *testing.T) {
crtPath := pathForCert("/foo", "bar")
if crtPath != "/foo/bar.crt" {
t.Errorf("unexpected certificate path: %s", crtPath)
}
}
func TestPathForKey(t *testing.T) {
keyPath := pathForKey("/foo", "bar")
if keyPath != "/foo/bar.key" {
t.Errorf("unexpected certificate path: %s", keyPath)
}
}
func TestPathForPublicKey(t *testing.T) {
pubPath := pathForPublicKey("/foo", "bar")
if pubPath != "/foo/bar.pub" {
t.Errorf("unexpected certificate path: %s", pubPath)
}
}
func TestPathForCSR(t *testing.T) {
csrPath := pathForCSR("/foo", "bar")
if csrPath != "/foo/bar.csr" {
t.Errorf("unexpected certificate path: %s", csrPath)
}
}
func TestGetAPIServerAltNames(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.InitConfiguration
expectedDNSNames []string
expectedIPAddresses []string
}{
{
name: "",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4"},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "api.k8s.io:6443",
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
APIServer: kubeadmapi.APIServer{
CertSANs: []string{"10.1.245.94", "10.1.245.95", "1.2.3.L", "invalid,commas,in,DNS"},
},
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-hostname"},
},
expectedDNSNames: []string{"valid-hostname", "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local", "api.k8s.io"},
expectedIPAddresses: []string{"10.96.0.1", "1.2.3.4", "10.1.245.94", "10.1.245.95"},
},
{
name: "ControlPlaneEndpoint IP",
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4"},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControlPlaneEndpoint: "4.5.6.7:6443",
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
APIServer: kubeadmapi.APIServer{
CertSANs: []string{"10.1.245.94", "10.1.245.95", "1.2.3.L", "invalid,commas,in,DNS"},
},
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-hostname"},
},
expectedDNSNames: []string{"valid-hostname", "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local"},
expectedIPAddresses: []string{"10.96.0.1", "1.2.3.4", "10.1.245.94", "10.1.245.95", "4.5.6.7"},
},
}
for _, rt := range tests {
altNames, err := GetAPIServerAltNames(rt.cfg)
if err != nil {
t.Fatalf("failed calling GetAPIServerAltNames: %s: %v", rt.name, err)
}
for _, DNSName := range rt.expectedDNSNames {
found := false
for _, val := range altNames.DNSNames {
if val == DNSName {
found = true
break
}
}
if !found {
t.Errorf("%s: altNames does not contain DNSName %s but %v", rt.name, DNSName, altNames.DNSNames)
}
}
for _, IPAddress := range rt.expectedIPAddresses {
found := false
for _, val := range altNames.IPs {
if val.Equal(net.ParseIP(IPAddress)) {
found = true
break
}
}
if !found {
t.Errorf("%s: altNames does not contain IPAddress %s but %v", rt.name, IPAddress, altNames.IPs)
}
}
}
}
func TestGetEtcdAltNames(t *testing.T) {
proxy := "user-etcd-proxy"
proxyIP := "10.10.10.100"
cfg := &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
AdvertiseAddress: "1.2.3.4",
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{
Name: "myNode",
},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ServerCertSANs: []string{
proxy,
proxyIP,
"1.2.3.L",
"invalid,commas,in,DNS",
},
},
},
},
}
altNames, err := GetEtcdAltNames(cfg)
if err != nil {
t.Fatalf("failed calling GetEtcdAltNames: %v", err)
}
expectedDNSNames := []string{"myNode", "localhost", proxy}
for _, DNSName := range expectedDNSNames {
found := false
for _, val := range altNames.DNSNames {
if val == DNSName {
found = true
break
}
}
if !found {
t.Errorf("altNames does not contain DNSName %s", DNSName)
}
}
expectedIPAddresses := []string{"1.2.3.4", "127.0.0.1", net.IPv6loopback.String(), proxyIP}
for _, IPAddress := range expectedIPAddresses {
found := false
for _, val := range altNames.IPs {
if val.Equal(net.ParseIP(IPAddress)) {
found = true
break
}
}
if !found {
t.Errorf("altNames does not contain IPAddress %s", IPAddress)
}
}
}
func TestGetEtcdPeerAltNames(t *testing.T) {
hostname := "valid-hostname"
proxy := "user-etcd-proxy"
proxyIP := "10.10.10.100"
advertiseIP := "1.2.3.4"
cfg := &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: advertiseIP},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
PeerCertSANs: []string{
proxy,
proxyIP,
"1.2.3.L",
"invalid,commas,in,DNS",
},
},
},
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: hostname},
}
altNames, err := GetEtcdPeerAltNames(cfg)
if err != nil {
t.Fatalf("failed calling GetEtcdPeerAltNames: %v", err)
}
expectedDNSNames := []string{hostname, proxy}
for _, DNSName := range expectedDNSNames {
found := false
for _, val := range altNames.DNSNames {
if val == DNSName {
found = true
break
}
}
if !found {
t.Errorf("altNames does not contain DNSName %s", DNSName)
}
}
expectedIPAddresses := []string{advertiseIP, proxyIP}
for _, IPAddress := range expectedIPAddresses {
found := false
for _, val := range altNames.IPs {
if val.Equal(net.ParseIP(IPAddress)) {
found = true
break
}
}
if !found {
t.Errorf("altNames does not contain IPAddress %s", IPAddress)
}
}
}

View File

@ -16,6 +16,7 @@ go_library(
name = "go_default_library",
srcs = ["pubkeypin.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin",
deps = ["//vendor/github.com/pkg/errors:go_default_library"],
)
filegroup(

View File

@ -22,8 +22,9 @@ import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"fmt"
"strings"
"github.com/pkg/errors"
)
const (
@ -46,7 +47,7 @@ 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\"")
return errors.New("invalid public key hash, expected \"format:value\"")
}
format, value := parts[0], parts[1]
@ -54,7 +55,7 @@ func (s *Set) Allow(pubKeyHashes ...string) error {
case "sha256":
return s.allowSHA256(value)
default:
return fmt.Errorf("unknown hash format %q", format)
return errors.Errorf("unknown hash format %q", format)
}
}
return nil
@ -65,7 +66,7 @@ func (s *Set) Check(certificate *x509.Certificate) error {
if s.checkSHA256(certificate) {
return nil
}
return fmt.Errorf("public key %s not pinned", Hash(certificate))
return errors.Errorf("public key %s not pinned", Hash(certificate))
}
// Empty returns true if the Set contains no pinned public keys.
@ -86,7 +87,7 @@ 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)
return errors.Errorf("expected a %d byte SHA-256 hash, found %d bytes", sha256.Size, hashLength)
}
// validate that the hash is valid hex

View File

@ -0,0 +1,40 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["runtime.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime",
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["runtime_test.go"],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing: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

@ -0,0 +1,182 @@
/*
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 (
"path/filepath"
goruntime "runtime"
"strings"
pkgerrors "github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/errors"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
utilsexec "k8s.io/utils/exec"
)
// ContainerRuntime is an interface for working with container runtimes
type ContainerRuntime interface {
IsDocker() bool
IsRunning() error
ListKubeContainers() ([]string, error)
RemoveContainers(containers []string) error
PullImage(image string) error
ImageExists(image string) (bool, error)
}
// CRIRuntime is a struct that interfaces with the CRI
type CRIRuntime struct {
exec utilsexec.Interface
criSocket string
}
// DockerRuntime is a struct that interfaces with the Docker daemon
type DockerRuntime struct {
exec utilsexec.Interface
}
// NewContainerRuntime sets up and returns a ContainerRuntime struct
func NewContainerRuntime(execer utilsexec.Interface, criSocket string) (ContainerRuntime, error) {
var toolName string
var runtime ContainerRuntime
if criSocket != kubeadmapiv1beta1.DefaultCRISocket {
toolName = "crictl"
// !!! temporary work around crictl warning:
// Using "/var/run/crio/crio.sock" as endpoint is deprecated,
// please consider using full url format "unix:///var/run/crio/crio.sock"
if filepath.IsAbs(criSocket) && goruntime.GOOS != "windows" {
criSocket = "unix://" + criSocket
}
runtime = &CRIRuntime{execer, criSocket}
} else {
toolName = "docker"
runtime = &DockerRuntime{execer}
}
if _, err := execer.LookPath(toolName); err != nil {
return nil, pkgerrors.Wrapf(err, "%s is required for container runtime", toolName)
}
return runtime, nil
}
// IsDocker returns true if the runtime is docker
func (runtime *CRIRuntime) IsDocker() bool {
return false
}
// IsDocker returns true if the runtime is docker
func (runtime *DockerRuntime) IsDocker() bool {
return true
}
// IsRunning checks if runtime is running
func (runtime *CRIRuntime) IsRunning() error {
if out, err := runtime.exec.Command("crictl", "-r", runtime.criSocket, "info").CombinedOutput(); err != nil {
return pkgerrors.Wrapf(err, "container runtime is not running: output: %s, error", string(out))
}
return nil
}
// IsRunning checks if runtime is running
func (runtime *DockerRuntime) IsRunning() error {
if out, err := runtime.exec.Command("docker", "info").CombinedOutput(); err != nil {
return pkgerrors.Wrapf(err, "container runtime is not running: output: %s, error", string(out))
}
return nil
}
// ListKubeContainers lists running k8s CRI pods
func (runtime *CRIRuntime) ListKubeContainers() ([]string, error) {
out, err := runtime.exec.Command("crictl", "-r", runtime.criSocket, "pods", "-q").CombinedOutput()
if err != nil {
return nil, pkgerrors.Wrapf(err, "output: %s, error", string(out))
}
pods := []string{}
for _, pod := range strings.Fields(string(out)) {
pods = append(pods, pod)
}
return pods, nil
}
// ListKubeContainers lists running k8s containers
func (runtime *DockerRuntime) ListKubeContainers() ([]string, error) {
output, err := runtime.exec.Command("docker", "ps", "-a", "--filter", "name=k8s_", "-q").CombinedOutput()
return strings.Fields(string(output)), err
}
// RemoveContainers removes running k8s pods
func (runtime *CRIRuntime) RemoveContainers(containers []string) error {
errs := []error{}
for _, container := range containers {
out, err := runtime.exec.Command("crictl", "-r", runtime.criSocket, "stopp", container).CombinedOutput()
if err != nil {
// don't stop on errors, try to remove as many containers as possible
errs = append(errs, pkgerrors.Wrapf(err, "failed to stop running pod %s: output: %s, error", container, string(out)))
} else {
out, err = runtime.exec.Command("crictl", "-r", runtime.criSocket, "rmp", container).CombinedOutput()
if err != nil {
errs = append(errs, pkgerrors.Wrapf(err, "failed to remove running container %s: output: %s, error", container, string(out)))
}
}
}
return errors.NewAggregate(errs)
}
// RemoveContainers removes running containers
func (runtime *DockerRuntime) RemoveContainers(containers []string) error {
errs := []error{}
for _, container := range containers {
out, err := runtime.exec.Command("docker", "rm", "--force", "--volumes", container).CombinedOutput()
if err != nil {
// don't stop on errors, try to remove as many containers as possible
errs = append(errs, pkgerrors.Wrapf(err, "failed to remove running container %s: output: %s, error", container, string(out)))
}
}
return errors.NewAggregate(errs)
}
// PullImage pulls the image
func (runtime *CRIRuntime) PullImage(image string) error {
out, err := runtime.exec.Command("crictl", "-r", runtime.criSocket, "pull", image).CombinedOutput()
if err != nil {
return pkgerrors.Wrapf(err, "output: %s, error", string(out))
}
return nil
}
// PullImage pulls the image
func (runtime *DockerRuntime) PullImage(image string) error {
out, err := runtime.exec.Command("docker", "pull", image).CombinedOutput()
if err != nil {
return pkgerrors.Wrapf(err, "output: %s, error", string(out))
}
return nil
}
// ImageExists checks to see if the image exists on the system
func (runtime *CRIRuntime) ImageExists(image string) (bool, error) {
err := runtime.exec.Command("crictl", "-r", runtime.criSocket, "inspecti", image).Run()
return err == nil, nil
}
// ImageExists checks to see if the image exists on the system
func (runtime *DockerRuntime) ImageExists(image string) (bool, error) {
err := runtime.exec.Command("docker", "inspect", image).Run()
return err == nil, nil
}

View File

@ -0,0 +1,307 @@
/*
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 (
"reflect"
"testing"
"github.com/pkg/errors"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/utils/exec"
fakeexec "k8s.io/utils/exec/testing"
)
func TestNewContainerRuntime(t *testing.T) {
execLookPathOK := fakeexec.FakeExec{
LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
}
execLookPathErr := fakeexec.FakeExec{
LookPathFunc: func(cmd string) (string, error) { return "", errors.Errorf("%s not found", cmd) },
}
cases := []struct {
name string
execer fakeexec.FakeExec
criSocket string
isDocker bool
isError bool
}{
{"valid: default cri socket", execLookPathOK, kubeadmapiv1beta1.DefaultCRISocket, true, false},
{"valid: cri-o socket url", execLookPathOK, "unix:///var/run/crio/crio.sock", false, false},
{"valid: cri-o socket path", execLookPathOK, "/var/run/crio/crio.sock", false, false},
{"invalid: no crictl", execLookPathErr, "unix:///var/run/crio/crio.sock", false, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
runtime, err := NewContainerRuntime(&tc.execer, tc.criSocket)
if err != nil {
if !tc.isError {
t.Fatalf("unexpected NewContainerRuntime error. criSocket: %s, error: %v", tc.criSocket, err)
}
return // expected error occurs, impossible to test runtime further
}
if tc.isError && err == nil {
t.Fatalf("unexpected NewContainerRuntime success. criSocket: %s", tc.criSocket)
}
isDocker := runtime.IsDocker()
if tc.isDocker != isDocker {
t.Fatalf("unexpected isDocker() result %v for the criSocket %s", isDocker, tc.criSocket)
}
})
}
}
func genFakeActions(fcmd *fakeexec.FakeCmd, num int) []fakeexec.FakeCommandAction {
var actions []fakeexec.FakeCommandAction
for i := 0; i < num; i++ {
actions = append(actions, func(cmd string, args ...string) exec.Cmd {
return fakeexec.InitFakeCmd(fcmd, cmd, args...)
})
}
return actions
}
func TestIsRunning(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return nil, nil },
func() ([]byte, error) { return []byte("error"), &fakeexec.FakeExitError{Status: 1} },
func() ([]byte, error) { return nil, nil },
func() ([]byte, error) { return []byte("error"), &fakeexec.FakeExitError{Status: 1} },
},
}
criExecer := fakeexec.FakeExec{
CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
}
dockerExecer := fakeexec.FakeExec{
CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/docker", nil },
}
cases := []struct {
name string
criSocket string
execer fakeexec.FakeExec
isError bool
}{
{"valid: CRI-O is running", "unix:///var/run/crio/crio.sock", criExecer, false},
{"invalid: CRI-O is not running", "unix:///var/run/crio/crio.sock", criExecer, true},
{"valid: docker is running", kubeadmapiv1beta1.DefaultCRISocket, dockerExecer, false},
{"invalid: docker is not running", kubeadmapiv1beta1.DefaultCRISocket, dockerExecer, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
runtime, err := NewContainerRuntime(&tc.execer, tc.criSocket)
if err != nil {
t.Fatalf("unexpected NewContainerRuntime error: %v", err)
}
isRunning := runtime.IsRunning()
if tc.isError && isRunning == nil {
t.Error("unexpected IsRunning() success")
}
if !tc.isError && isRunning != nil {
t.Error("unexpected IsRunning() error")
}
})
}
}
func TestListKubeContainers(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return []byte("k8s_p1\nk8s_p2"), nil },
func() ([]byte, error) { return nil, &fakeexec.FakeExitError{Status: 1} },
func() ([]byte, error) { return []byte("k8s_p1\nk8s_p2"), nil },
},
}
execer := fakeexec.FakeExec{
CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
}
cases := []struct {
name string
criSocket string
isError bool
}{
{"valid: list containers using CRI socket url", "unix:///var/run/crio/crio.sock", false},
{"invalid: list containers using CRI socket url", "unix:///var/run/crio/crio.sock", true},
{"valid: list containers using docker", kubeadmapiv1beta1.DefaultCRISocket, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
runtime, err := NewContainerRuntime(&execer, tc.criSocket)
if err != nil {
t.Fatalf("unexpected NewContainerRuntime error: %v", err)
}
containers, err := runtime.ListKubeContainers()
if tc.isError {
if err == nil {
t.Errorf("unexpected ListKubeContainers success")
}
return
} else if err != nil {
t.Errorf("unexpected ListKubeContainers error: %v", err)
}
if !reflect.DeepEqual(containers, []string{"k8s_p1", "k8s_p2"}) {
t.Errorf("unexpected ListKubeContainers output: %v", containers)
}
})
}
}
func TestRemoveContainers(t *testing.T) {
fakeOK := func() ([]byte, error) { return nil, nil }
fakeErr := func() ([]byte, error) { return []byte("error"), &fakeexec.FakeExitError{Status: 1} }
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
fakeOK, fakeOK, fakeOK, fakeOK, fakeOK, fakeOK, // Test case 1
fakeOK, fakeOK, fakeOK, fakeErr, fakeOK, fakeOK,
fakeErr, fakeOK, fakeOK, fakeErr, fakeOK,
fakeOK, fakeOK, fakeOK,
fakeOK, fakeErr, fakeOK,
},
}
execer := fakeexec.FakeExec{
CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
}
cases := []struct {
name string
criSocket string
containers []string
isError bool
}{
{"valid: remove containers using CRI", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, false}, // Test case 1
{"invalid: CRI rmp failure", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, true},
{"invalid: CRI stopp failure", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, true},
{"valid: remove containers using docker", kubeadmapiv1beta1.DefaultCRISocket, []string{"k8s_c1", "k8s_c2", "k8s_c3"}, false},
{"invalid: remove containers using docker", kubeadmapiv1beta1.DefaultCRISocket, []string{"k8s_c1", "k8s_c2", "k8s_c3"}, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
runtime, err := NewContainerRuntime(&execer, tc.criSocket)
if err != nil {
t.Fatalf("unexpected NewContainerRuntime error: %v, criSocket: %s", err, tc.criSocket)
}
err = runtime.RemoveContainers(tc.containers)
if !tc.isError && err != nil {
t.Errorf("unexpected RemoveContainers errors: %v, criSocket: %s, containers: %v", err, tc.criSocket, tc.containers)
}
if tc.isError && err == nil {
t.Errorf("unexpected RemoveContnainers success, criSocket: %s, containers: %v", tc.criSocket, tc.containers)
}
})
}
}
func TestPullImage(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
func() ([]byte, error) { return nil, nil },
func() ([]byte, error) { return []byte("error"), &fakeexec.FakeExitError{Status: 1} },
func() ([]byte, error) { return nil, nil },
func() ([]byte, error) { return []byte("error"), &fakeexec.FakeExitError{Status: 1} },
},
}
execer := fakeexec.FakeExec{
CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
}
cases := []struct {
name string
criSocket string
image string
isError bool
}{
{"valid: pull image using CRI", "unix:///var/run/crio/crio.sock", "image1", false},
{"invalid: CRI pull error", "unix:///var/run/crio/crio.sock", "image2", true},
{"valid: pull image using docker", kubeadmapiv1beta1.DefaultCRISocket, "image1", false},
{"invalide: docer pull error", kubeadmapiv1beta1.DefaultCRISocket, "image2", true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
runtime, err := NewContainerRuntime(&execer, tc.criSocket)
if err != nil {
t.Fatalf("unexpected NewContainerRuntime error: %v, criSocket: %s", err, tc.criSocket)
}
err = runtime.PullImage(tc.image)
if !tc.isError && err != nil {
t.Errorf("unexpected PullImage error: %v, criSocket: %s, image: %s", err, tc.criSocket, tc.image)
}
if tc.isError && err == nil {
t.Errorf("unexpected PullImage success, criSocket: %s, image: %s", tc.criSocket, tc.image)
}
})
}
}
func TestImageExists(t *testing.T) {
fcmd := fakeexec.FakeCmd{
RunScript: []fakeexec.FakeRunAction{
func() ([]byte, []byte, error) { return nil, nil, nil },
func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
func() ([]byte, []byte, error) { return nil, nil, nil },
func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
},
}
execer := fakeexec.FakeExec{
CommandScript: genFakeActions(&fcmd, len(fcmd.RunScript)),
LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
}
cases := []struct {
name string
criSocket string
image string
result bool
}{
{"valid: test if image exists using CRI", "unix:///var/run/crio/crio.sock", "image1", false},
{"invalid: CRI inspecti failure", "unix:///var/run/crio/crio.sock", "image2", true},
{"valid: test if image exists using docker", kubeadmapiv1beta1.DefaultCRISocket, "image1", false},
{"invalid: docker inspect failure", kubeadmapiv1beta1.DefaultCRISocket, "image2", true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
runtime, err := NewContainerRuntime(&execer, tc.criSocket)
if err != nil {
t.Fatalf("unexpected NewContainerRuntime error: %v, criSocket: %s", err, tc.criSocket)
}
result, err := runtime.ImageExists(tc.image)
if !tc.result != result {
t.Errorf("unexpected ImageExists result: %t, criSocket: %s, image: %s, expected result: %t", err, tc.criSocket, tc.image, tc.result)
}
})
}
}

View File

@ -13,11 +13,10 @@ go_test(
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",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
],
)
@ -28,13 +27,13 @@ go_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/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",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)

View File

@ -17,20 +17,21 @@ limitations under the License.
package staticpod
import (
"bytes"
"fmt"
"io/ioutil"
"net"
"net/url"
"os"
"sort"
"strings"
"k8s.io/api/core/v1"
"github.com/pkg/errors"
"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"
@ -82,7 +83,7 @@ func ComponentResources(cpu string) v1.ResourceRequirements {
}
// 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 {
func ComponentProbe(cfg *kubeadmapi.InitConfiguration, componentName string, port int, path string, scheme v1.URIScheme) *v1.Probe {
return &v1.Probe{
Handler: v1.Handler{
HTTPGet: &v1.HTTPGetAction{
@ -99,7 +100,7 @@ func ComponentProbe(cfg *kubeadmapi.MasterConfiguration, componentName string, p
}
// 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 {
func EtcdProbe(cfg *kubeadmapi.InitConfiguration, 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)
@ -146,6 +147,10 @@ func VolumeMapToSlice(volumes map[string]v1.Volume) []v1.Volume {
v = append(v, vol)
}
sort.Slice(v, func(i, j int) bool {
return strings.Compare(v[i].Name, v[j].Name) == -1
})
return v
}
@ -157,6 +162,10 @@ func VolumeMountMapToSlice(volumeMounts map[string]v1.VolumeMount) []v1.VolumeMo
v = append(v, volMount)
}
sort.Slice(v, func(i, j int) bool {
return strings.Compare(v[i].Name, v[j].Name) == -1
})
return v
}
@ -181,19 +190,19 @@ 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)
return errors.Wrapf(err, "failed to create directory %q", manifestDir)
}
// 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)
return errors.Wrapf(err, "failed to marshal manifest for %q to YAML", componentName)
}
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 errors.Wrapf(err, "failed to write static pod manifest file for %q (%q)", componentName, filename)
}
return nil
@ -203,12 +212,12 @@ func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error {
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)
return &v1.Pod{}, errors.Wrapf(err, "failed to read manifest for %q", manifestPath)
}
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)
return &v1.Pod{}, errors.Errorf("failed to unmarshal manifest for %q from YAML: %v", manifestPath, err)
}
pod := obj.(*v1.Pod)
@ -218,7 +227,7 @@ func ReadStaticPodFromDisk(manifestPath string) (*v1.Pod, error) {
// 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 {
func GetProbeAddress(cfg *kubeadmapi.InitConfiguration, 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,
@ -228,17 +237,15 @@ func GetProbeAddress(cfg *kubeadmapi.MasterConfiguration, componentName string)
// 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
if cfg.LocalAPIEndpoint.AdvertiseAddress != "" {
return cfg.LocalAPIEndpoint.AdvertiseAddress
}
case componentName == kubeadmconstants.KubeControllerManager:
if addr, exists := cfg.ControllerManagerExtraArgs[kubeControllerManagerAddressArg]; exists {
if addr, exists := cfg.ControllerManager.ExtraArgs[kubeControllerManagerAddressArg]; exists {
return addr
}
case componentName == kubeadmconstants.KubeScheduler:
if addr, exists := cfg.SchedulerExtraArgs[kubeSchedulerAddressArg]; exists {
if addr, exists := cfg.Scheduler.ExtraArgs[kubeSchedulerAddressArg]; exists {
return addr
}
case componentName == kubeadmconstants.Etcd:
@ -287,3 +294,17 @@ func GetProbeAddress(cfg *kubeadmapi.MasterConfiguration, componentName string)
}
return "127.0.0.1"
}
// ManifestFilesAreEqual compares 2 files. It returns true if their contents are equal, false otherwise
func ManifestFilesAreEqual(path1, path2 string) (bool, error) {
content1, err := ioutil.ReadFile(path1)
if err != nil {
return false, err
}
content2, err := ioutil.ReadFile(path2)
if err != nil {
return false, err
}
return bytes.Equal(content1, content2), nil
}

View File

@ -22,13 +22,12 @@ import (
"path/filepath"
"reflect"
"sort"
"strconv"
"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"
@ -46,7 +45,7 @@ func TestComponentResources(t *testing.T) {
func TestComponentProbe(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.MasterConfiguration
cfg *kubeadmapi.InitConfiguration
component string
port int
path string
@ -55,8 +54,8 @@ func TestComponentProbe(t *testing.T) {
}{
{
name: "default apiserver advertise address with http",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
AdvertiseAddress: "",
},
},
@ -66,26 +65,10 @@ func TestComponentProbe(t *testing.T) {
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{
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
AdvertiseAddress: "",
},
},
@ -97,8 +80,8 @@ func TestComponentProbe(t *testing.T) {
},
{
name: "valid ipv4 apiserver advertise address with http",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
AdvertiseAddress: "1.2.3.4",
},
},
@ -110,8 +93,8 @@ func TestComponentProbe(t *testing.T) {
},
{
name: "valid ipv6 apiserver advertise address with http",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
cfg: &kubeadmapi.InitConfiguration{
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
AdvertiseAddress: "2001:db8::1",
},
},
@ -123,8 +106,12 @@ func TestComponentProbe(t *testing.T) {
},
{
name: "valid IPv4 controller-manager probe",
cfg: &kubeadmapi.MasterConfiguration{
ControllerManagerExtraArgs: map[string]string{"address": "1.2.3.4"},
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"address": "1.2.3.4"},
},
},
},
component: kubeadmconstants.KubeControllerManager,
port: 1,
@ -134,8 +121,12 @@ func TestComponentProbe(t *testing.T) {
},
{
name: "valid IPv6 controller-manager probe",
cfg: &kubeadmapi.MasterConfiguration{
ControllerManagerExtraArgs: map[string]string{"address": "2001:db8::1"},
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
ControllerManager: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"address": "2001:db8::1"},
},
},
},
component: kubeadmconstants.KubeControllerManager,
port: 1,
@ -145,8 +136,12 @@ func TestComponentProbe(t *testing.T) {
},
{
name: "valid IPv4 scheduler probe",
cfg: &kubeadmapi.MasterConfiguration{
SchedulerExtraArgs: map[string]string{"address": "1.2.3.4"},
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
Scheduler: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"address": "1.2.3.4"},
},
},
},
component: kubeadmconstants.KubeScheduler,
port: 1,
@ -156,8 +151,12 @@ func TestComponentProbe(t *testing.T) {
},
{
name: "valid IPv6 scheduler probe",
cfg: &kubeadmapi.MasterConfiguration{
SchedulerExtraArgs: map[string]string{"address": "2001:db8::1"},
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
Scheduler: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{"address": "2001:db8::1"},
},
},
},
component: kubeadmconstants.KubeScheduler,
port: 1,
@ -194,7 +193,7 @@ func TestComponentProbe(t *testing.T) {
func TestEtcdProbe(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.MasterConfiguration
cfg *kubeadmapi.ClusterConfiguration
component string
port int
certsDir string
@ -205,7 +204,7 @@ func TestEtcdProbe(t *testing.T) {
}{
{
name: "valid etcd probe using listen-client-urls IPv4 addresses",
cfg: &kubeadmapi.MasterConfiguration{
cfg: &kubeadmapi.ClusterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
@ -223,7 +222,7 @@ func TestEtcdProbe(t *testing.T) {
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv6 address",
cfg: &kubeadmapi.MasterConfiguration{
cfg: &kubeadmapi.ClusterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
@ -241,7 +240,7 @@ func TestEtcdProbe(t *testing.T) {
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv6 address 2",
cfg: &kubeadmapi.MasterConfiguration{
cfg: &kubeadmapi.ClusterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
@ -259,7 +258,7 @@ func TestEtcdProbe(t *testing.T) {
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv6 address 3",
cfg: &kubeadmapi.MasterConfiguration{
cfg: &kubeadmapi.ClusterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
@ -277,7 +276,7 @@ func TestEtcdProbe(t *testing.T) {
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv4 address",
cfg: &kubeadmapi.MasterConfiguration{
cfg: &kubeadmapi.ClusterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
@ -295,7 +294,7 @@ func TestEtcdProbe(t *testing.T) {
},
{
name: "valid etcd probe using listen-client-urls IPv6 addresses",
cfg: &kubeadmapi.MasterConfiguration{
cfg: &kubeadmapi.ClusterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
@ -313,7 +312,7 @@ func TestEtcdProbe(t *testing.T) {
},
{
name: "valid IPv4 etcd probe using hostname for listen-client-urls",
cfg: &kubeadmapi.MasterConfiguration{
cfg: &kubeadmapi.ClusterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
@ -331,7 +330,11 @@ func TestEtcdProbe(t *testing.T) {
},
}
for _, rt := range tests {
actual := EtcdProbe(rt.cfg, rt.component, rt.port, rt.certsDir, rt.cacert, rt.cert, rt.key)
// TODO: Make EtcdProbe accept a ClusterConfiguration object instead of InitConfiguration
initcfg := &kubeadmapi.InitConfiguration{
ClusterConfiguration: *rt.cfg,
}
actual := EtcdProbe(initcfg, 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,
@ -466,13 +469,19 @@ func TestVolumeMapToSlice(t *testing.T) {
"foo": {
Name: "foo",
},
"bar": {
Name: "bar",
},
}
volumeSlice := VolumeMapToSlice(testVolumes)
if len(volumeSlice) != 1 {
if len(volumeSlice) != 2 {
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)
if volumeSlice[0].Name != "bar" {
t.Errorf("Expected first volume name \"bar\", got %s", volumeSlice[0].Name)
}
if volumeSlice[1].Name != "foo" {
t.Errorf("Expected second volume name \"foo\", got %s", volumeSlice[1].Name)
}
}
@ -481,13 +490,19 @@ func TestVolumeMountMapToSlice(t *testing.T) {
"foo": {
Name: "foo",
},
"bar": {
Name: "bar",
},
}
volumeMountSlice := VolumeMountMapToSlice(testVolumeMounts)
if len(volumeMountSlice) != 1 {
if len(volumeMountSlice) != 2 {
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)
if volumeMountSlice[0].Name != "bar" {
t.Errorf("Expected first volume mount name \"bar\", got %s", volumeMountSlice[0].Name)
}
if volumeMountSlice[1].Name != "foo" {
t.Errorf("Expected second volume name \"foo\", got %s", volumeMountSlice[1].Name)
}
}
@ -607,3 +622,73 @@ func TestReadStaticPodFromDisk(t *testing.T) {
}
}
}
func TestManifestFilesAreEqual(t *testing.T) {
var tests = []struct {
description string
podYamls []string
expectedResult bool
expectErr bool
}{
{
description: "manifests are equal",
podYamls: []string{validPod, validPod},
expectedResult: true,
expectErr: false,
},
{
description: "manifests are not equal",
podYamls: []string{validPod, validPod + "\n"},
expectedResult: false,
expectErr: false,
},
{
description: "first manifest doesn't exist",
podYamls: []string{validPod, ""},
expectedResult: false,
expectErr: true,
},
{
description: "second manifest doesn't exist",
podYamls: []string{"", validPod},
expectedResult: false,
expectErr: true,
},
}
for _, rt := range tests {
tmpdir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpdir)
// write 2 manifests
for i := 0; i < 2; i++ {
if rt.podYamls[i] != "" {
manifestPath := filepath.Join(tmpdir, strconv.Itoa(i)+".yaml")
err := ioutil.WriteFile(manifestPath, []byte(rt.podYamls[i]), 0644)
if err != nil {
t.Fatalf("Failed to write manifest file\n%s\n\tfatal error: %v", rt.description, err)
}
}
}
// compare them
result, actualErr := ManifestFilesAreEqual(filepath.Join(tmpdir, "0.yaml"), filepath.Join(tmpdir, "1.yaml"))
if result != rt.expectedResult {
t.Errorf(
"ManifestFilesAreEqual failed\n%s\nexpected result: %t\nactual result: %t",
rt.description,
rt.expectedResult,
result,
)
}
if (actualErr != nil) != rt.expectErr {
t.Errorf(
"ManifestFilesAreEqual failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
rt.description,
rt.expectErr,
(actualErr != nil),
actualErr,
)
}
}
}

View File

@ -0,0 +1,64 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"cgroup_validator.go",
"docker_validator.go",
"kernel_validator.go",
"kernel_validator_helper.go",
"os_validator.go",
"package_validator.go",
"report.go",
"types.go",
"types_unix.go",
"types_windows.go",
"validators.go",
],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/system",
deps = [
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/github.com/blang/semver:go_default_library",
"//vendor/github.com/docker/docker/api/types:go_default_library",
"//vendor/github.com/docker/docker/client:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"cgroup_validator_test.go",
"docker_validator_test.go",
"kernel_validator_test.go",
"os_validator_test.go",
"package_validator_test.go",
],
embed = [":go_default_library"],
tags = ["e2e"],
deps = [
"//vendor/github.com/docker/docker/api/types:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/stretchr/testify/assert: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

@ -0,0 +1,96 @@
/*
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 system
import (
"bufio"
"os"
"strings"
"github.com/pkg/errors"
)
var _ Validator = &CgroupsValidator{}
type CgroupsValidator struct {
Reporter Reporter
}
func (c *CgroupsValidator) Name() string {
return "cgroups"
}
const (
cgroupsConfigPrefix = "CGROUPS_"
)
func (c *CgroupsValidator) Validate(spec SysSpec) (error, error) {
subsystems, err := c.getCgroupSubsystems()
if err != nil {
return nil, errors.Wrap(err, "failed to get cgroup subsystems")
}
return nil, c.validateCgroupSubsystems(spec.Cgroups, subsystems)
}
func (c *CgroupsValidator) validateCgroupSubsystems(cgroupSpec, subsystems []string) error {
missing := []string{}
for _, cgroup := range cgroupSpec {
found := false
for _, subsystem := range subsystems {
if cgroup == subsystem {
found = true
break
}
}
item := cgroupsConfigPrefix + strings.ToUpper(cgroup)
if found {
c.Reporter.Report(item, "enabled", good)
} else {
c.Reporter.Report(item, "missing", bad)
missing = append(missing, cgroup)
}
}
if len(missing) > 0 {
return errors.Errorf("missing cgroups: %s", strings.Join(missing, " "))
}
return nil
}
func (c *CgroupsValidator) getCgroupSubsystems() ([]string, error) {
f, err := os.Open("/proc/cgroups")
if err != nil {
return nil, err
}
defer f.Close()
subsystems := []string{}
s := bufio.NewScanner(f)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text()
if text[0] != '#' {
parts := strings.Fields(text)
if len(parts) >= 4 && parts[3] != "0" {
subsystems = append(subsystems, parts[0])
}
}
}
return subsystems, nil
}

View File

@ -0,0 +1,56 @@
/*
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 system
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateCgroupSubsystem(t *testing.T) {
v := &CgroupsValidator{
Reporter: DefaultReporter,
}
cgroupSpec := []string{"system1", "system2"}
for desc, test := range map[string]struct {
cgroupSpec []string
subsystems []string
err bool
}{
"missing cgroup subsystem should report error": {
subsystems: []string{"system1"},
err: true,
},
"extra cgroup subsystems should not report error": {
subsystems: []string{"system1", "system2", "system3"},
err: false,
},
"subsystems the same with spec should not report error": {
subsystems: []string{"system1", "system2"},
err: false,
},
} {
err := v.validateCgroupSubsystems(cgroupSpec, test.subsystems)
if !test.err {
assert.Nil(t, err, "%q: Expect error not to occur with cgroup", desc)
} else {
assert.NotNil(t, err, "%q: Expect error to occur with docker info", desc)
}
}
}

View File

@ -0,0 +1,100 @@
/*
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 system
import (
"context"
"regexp"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pkg/errors"
)
var _ Validator = &DockerValidator{}
// DockerValidator validates docker configuration.
type DockerValidator struct {
Reporter Reporter
}
func (d *DockerValidator) Name() string {
return "docker"
}
const (
dockerConfigPrefix = "DOCKER_"
latestValidatedDockerVersion = "18.06"
)
// TODO(random-liu): Add more validating items.
func (d *DockerValidator) Validate(spec SysSpec) (error, error) {
if spec.RuntimeSpec.DockerSpec == nil {
// If DockerSpec is not specified, assume current runtime is not
// docker, skip the docker configuration validation.
return nil, nil
}
c, err := client.NewClient(dockerEndpoint, "", nil, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to create docker client")
}
info, err := c.Info(context.Background())
if err != nil {
return nil, errors.Wrap(err, "failed to get docker info")
}
return d.validateDockerInfo(spec.RuntimeSpec.DockerSpec, info)
}
func (d *DockerValidator) validateDockerInfo(spec *DockerSpec, info types.Info) (error, error) {
// Validate docker version.
matched := false
for _, v := range spec.Version {
r := regexp.MustCompile(v)
if r.MatchString(info.ServerVersion) {
d.Reporter.Report(dockerConfigPrefix+"VERSION", info.ServerVersion, good)
matched = true
}
}
if !matched {
// If it's of the new Docker version scheme but didn't match above, it
// must be a newer version than the most recently validated one.
ver := `\d{2}\.\d+\.\d+(?:-[a-z]{2})?`
r := regexp.MustCompile(ver)
if r.MatchString(info.ServerVersion) {
d.Reporter.Report(dockerConfigPrefix+"VERSION", info.ServerVersion, good)
w := errors.Errorf(
"this Docker version is not on the list of validated versions: %s. Latest validated version: %s",
info.ServerVersion,
latestValidatedDockerVersion,
)
return w, nil
}
d.Reporter.Report(dockerConfigPrefix+"VERSION", info.ServerVersion, bad)
return nil, errors.Errorf("unsupported docker version: %s", info.ServerVersion)
}
// Validate graph driver.
item := dockerConfigPrefix + "GRAPH_DRIVER"
for _, gd := range spec.GraphDriver {
if info.Driver == gd {
d.Reporter.Report(item, info.Driver, good)
return nil, nil
}
}
d.Reporter.Report(item, info.Driver, bad)
return nil, errors.Errorf("unsupported graph driver: %s", info.Driver)
}

View File

@ -0,0 +1,103 @@
/*
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 system
import (
"testing"
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
)
func TestValidateDockerInfo(t *testing.T) {
v := &DockerValidator{
Reporter: DefaultReporter,
}
spec := &DockerSpec{
Version: []string{`1\.1[1-3]\..*`, `17\.0[3,6,9]\..*`, `18\.06\..*`},
GraphDriver: []string{"driver_1", "driver_2"},
}
for _, test := range []struct {
info types.Info
err bool
warn bool
}{
{
info: types.Info{Driver: "driver_1", ServerVersion: "1.10.1"},
err: true,
warn: false,
},
{
info: types.Info{Driver: "bad_driver", ServerVersion: "1.11.1"},
err: true,
warn: false,
},
{
info: types.Info{Driver: "driver_1", ServerVersion: "1.11.1"},
err: false,
warn: false,
},
{
info: types.Info{Driver: "driver_2", ServerVersion: "1.12.1"},
err: false,
warn: false,
},
{
info: types.Info{Driver: "driver_2", ServerVersion: "1.13.1"},
err: false,
warn: false,
},
{
info: types.Info{Driver: "driver_2", ServerVersion: "17.03.0-ce"},
err: false,
warn: false,
},
{
info: types.Info{Driver: "driver_2", ServerVersion: "17.06.0-ce"},
err: false,
warn: false,
},
{
info: types.Info{Driver: "driver_2", ServerVersion: "17.09.0-ce"},
err: false,
warn: false,
},
{
info: types.Info{Driver: "driver_2", ServerVersion: "18.06.0-ce"},
err: false,
warn: false,
},
{
info: types.Info{Driver: "driver_2", ServerVersion: "18.09.0"},
err: false,
warn: true,
},
} {
warn, err := v.validateDockerInfo(spec, test.info)
if !test.err {
assert.Nil(t, err, "Expect error not to occur with docker info %+v", test.info)
} else {
assert.NotNil(t, err, "Expect error to occur with docker info %+v", test.info)
}
if !test.warn {
assert.Nil(t, warn, "Expect error not to occur with docker info %+v", test.info)
} else {
assert.NotNil(t, warn, "Expect error to occur with docker info %+v", test.info)
}
}
}

View File

@ -0,0 +1,264 @@
/*
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 system
import (
"bufio"
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
pkgerrors "github.com/pkg/errors"
"k8s.io/klog"
"k8s.io/apimachinery/pkg/util/errors"
)
var _ Validator = &KernelValidator{}
// KernelValidator validates kernel. Currently only validate kernel version
// and kernel configuration.
type KernelValidator struct {
kernelRelease string
Reporter Reporter
}
func (k *KernelValidator) Name() string {
return "kernel"
}
// kConfigOption is the possible kernel config option.
type kConfigOption string
const (
builtIn kConfigOption = "y"
asModule kConfigOption = "m"
leftOut kConfigOption = "n"
// validKConfigRegex is the regex matching kernel configuration line.
validKConfigRegex = "^CONFIG_[A-Z0-9_]+=[myn]"
// kConfigPrefix is the prefix of kernel configuration.
kConfigPrefix = "CONFIG_"
)
func (k *KernelValidator) Validate(spec SysSpec) (error, error) {
helper := KernelValidatorHelperImpl{}
release, err := helper.GetKernelReleaseVersion()
if err != nil {
return nil, pkgerrors.Wrap(err, "failed to get kernel release")
}
k.kernelRelease = release
var errs []error
errs = append(errs, k.validateKernelVersion(spec.KernelSpec))
// only validate kernel config when necessary (currently no kernel config for windows)
if len(spec.KernelSpec.Required) > 0 || len(spec.KernelSpec.Forbidden) > 0 || len(spec.KernelSpec.Optional) > 0 {
errs = append(errs, k.validateKernelConfig(spec.KernelSpec))
}
return nil, errors.NewAggregate(errs)
}
// validateKernelVersion validates the kernel version.
func (k *KernelValidator) validateKernelVersion(kSpec KernelSpec) error {
versionRegexps := kSpec.Versions
for _, versionRegexp := range versionRegexps {
r := regexp.MustCompile(versionRegexp)
if r.MatchString(k.kernelRelease) {
k.Reporter.Report("KERNEL_VERSION", k.kernelRelease, good)
return nil
}
}
k.Reporter.Report("KERNEL_VERSION", k.kernelRelease, bad)
return pkgerrors.Errorf("unsupported kernel release: %s", k.kernelRelease)
}
// validateKernelConfig validates the kernel configurations.
func (k *KernelValidator) validateKernelConfig(kSpec KernelSpec) error {
allConfig, err := k.getKernelConfig()
if err != nil {
return pkgerrors.Wrap(err, "failed to parse kernel config")
}
return k.validateCachedKernelConfig(allConfig, kSpec)
}
// validateCachedKernelConfig validates the kernel confgiurations cached in internal data type.
func (k *KernelValidator) validateCachedKernelConfig(allConfig map[string]kConfigOption, kSpec KernelSpec) error {
badConfigs := []string{}
// reportAndRecord is a helper function to record bad config when
// report.
reportAndRecord := func(name, msg, desc string, result ValidationResultType) {
if result == bad {
badConfigs = append(badConfigs, name)
}
// report description when the config is bad or warn.
if result != good && desc != "" {
msg = msg + " - " + desc
}
k.Reporter.Report(name, msg, result)
}
const (
required = iota
optional
forbidden
)
validateOpt := func(config KernelConfig, expect int) {
var found, missing ValidationResultType
switch expect {
case required:
found, missing = good, bad
case optional:
found, missing = good, warn
case forbidden:
found, missing = bad, good
}
var name string
var opt kConfigOption
var ok bool
for _, name = range append([]string{config.Name}, config.Aliases...) {
name = kConfigPrefix + name
if opt, ok = allConfig[name]; ok {
break
}
}
if !ok {
reportAndRecord(name, "not set", config.Description, missing)
return
}
switch opt {
case builtIn:
reportAndRecord(name, "enabled", config.Description, found)
case asModule:
reportAndRecord(name, "enabled (as module)", config.Description, found)
case leftOut:
reportAndRecord(name, "disabled", config.Description, missing)
default:
reportAndRecord(name, fmt.Sprintf("unknown option: %s", opt), config.Description, missing)
}
}
for _, config := range kSpec.Required {
validateOpt(config, required)
}
for _, config := range kSpec.Optional {
validateOpt(config, optional)
}
for _, config := range kSpec.Forbidden {
validateOpt(config, forbidden)
}
if len(badConfigs) > 0 {
return pkgerrors.Errorf("unexpected kernel config: %s", strings.Join(badConfigs, " "))
}
return nil
}
// getKernelConfigReader search kernel config file in a predefined list. Once the kernel config
// file is found it will read the configurations into a byte buffer and return. If the kernel
// config file is not found, it will try to load kernel config module and retry again.
func (k *KernelValidator) getKernelConfigReader() (io.Reader, error) {
possibePaths := []string{
"/proc/config.gz",
"/boot/config-" + k.kernelRelease,
"/usr/src/linux-" + k.kernelRelease + "/.config",
"/usr/src/linux/.config",
"/usr/lib/modules/" + k.kernelRelease + "/config",
"/usr/lib/ostree-boot/config-" + k.kernelRelease,
"/usr/lib/kernel/config-" + k.kernelRelease,
"/usr/src/linux-headers-" + k.kernelRelease + "/.config",
"/lib/modules/" + k.kernelRelease + "/build/.config",
}
configsModule := "configs"
modprobeCmd := "modprobe"
// loadModule indicates whether we've tried to load kernel config module ourselves.
loadModule := false
for {
for _, path := range possibePaths {
_, err := os.Stat(path)
if err != nil {
continue
}
// Buffer the whole file, so that we can close the file and unload
// kernel config module in this function.
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
var r io.Reader
r = bytes.NewReader(b)
// This is a gzip file (config.gz), unzip it.
if filepath.Ext(path) == ".gz" {
r, err = gzip.NewReader(r)
if err != nil {
return nil, err
}
}
return r, nil
}
// If we've tried to load kernel config module, break and return error.
if loadModule {
break
}
// If the kernel config file is not found, try to load the kernel
// config module and check again.
output, err := exec.Command(modprobeCmd, configsModule).CombinedOutput()
if err != nil {
return nil, pkgerrors.Wrapf(err, "unable to load kernel module: %q, output: %q, err",
configsModule, output)
}
// Unload the kernel config module to make sure the validation have no side effect.
defer exec.Command(modprobeCmd, "-r", configsModule).Run()
loadModule = true
}
return nil, pkgerrors.Errorf("no config path in %v is available", possibePaths)
}
// getKernelConfig gets kernel config from kernel config file and convert kernel config to internal type.
func (k *KernelValidator) getKernelConfig() (map[string]kConfigOption, error) {
r, err := k.getKernelConfigReader()
if err != nil {
return nil, err
}
return k.parseKernelConfig(r)
}
// parseKernelConfig converts kernel config to internal type.
func (k *KernelValidator) parseKernelConfig(r io.Reader) (map[string]kConfigOption, error) {
config := map[string]kConfigOption{}
regex := regexp.MustCompile(validKConfigRegex)
s := bufio.NewScanner(r)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
line := strings.TrimSpace(s.Text())
if !regex.MatchString(line) {
continue
}
fields := strings.Split(line, "=")
if len(fields) != 2 {
klog.Errorf("Unexpected fields number in config %q", line)
continue
}
config[fields[0]] = kConfigOption(fields[1])
}
return config, nil
}

View File

@ -0,0 +1,23 @@
/*
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 system
// KernelValidatorHelper is an interface intended to help with os specific kernel validation
type KernelValidatorHelper interface {
// GetKernelReleaseVersion gets the current kernel release version of the system
GetKernelReleaseVersion() (string, error)
}

View File

@ -0,0 +1,197 @@
/*
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 system
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateKernelVersion(t *testing.T) {
v := &KernelValidator{
Reporter: DefaultReporter,
}
// Currently, testRegex is align with DefaultSysSpec.KernelVersion, but in the future
// they may be different.
// This is fine, because the test mainly tests the kernel version validation logic,
// not the DefaultSysSpec. The DefaultSysSpec should be tested with node e2e.
testRegex := []string{`3\.[1-9][0-9].*`, `4\..*`}
for _, test := range []struct {
version string
err bool
}{
// first version regex matches
{
version: "3.19.9-99-test",
err: false,
},
// one of version regexes matches
{
version: "4.4.14+",
err: false,
},
// no version regex matches
{
version: "2.0.0",
err: true,
},
{
version: "5.0.0",
err: true,
},
{
version: "3.9.0",
err: true,
},
} {
v.kernelRelease = test.version
err := v.validateKernelVersion(KernelSpec{Versions: testRegex})
if !test.err {
assert.Nil(t, err, "Expect error not to occur with kernel version %q", test.version)
} else {
assert.NotNil(t, err, "Expect error to occur with kenrel version %q", test.version)
}
}
}
func TestValidateCachedKernelConfig(t *testing.T) {
v := &KernelValidator{
Reporter: DefaultReporter,
}
testKernelSpec := KernelSpec{
Required: []KernelConfig{{Name: "REQUIRED_1"}, {Name: "REQUIRED_2", Aliases: []string{"ALIASE_REQUIRED_2"}}},
Optional: []KernelConfig{{Name: "OPTIONAL_1"}, {Name: "OPTIONAL_2"}},
Forbidden: []KernelConfig{
{Name: "FORBIDDEN_1", Description: "TEST FORBIDDEN"},
{Name: "FORBIDDEN_2", Aliases: []string{"ALIASE_FORBIDDEN_2"}},
},
}
for c, test := range []struct {
desc string
config map[string]kConfigOption
err bool
}{
{
desc: "meet all required configurations should not report error.",
config: map[string]kConfigOption{
"REQUIRED_1": builtIn,
"REQUIRED_2": asModule,
},
err: false,
},
{
desc: "one required configuration disabled should report error.",
config: map[string]kConfigOption{
"REQUIRED_1": leftOut,
"REQUIRED_2": builtIn,
},
err: true,
},
{
desc: "one required configuration missing should report error.",
config: map[string]kConfigOption{
"REQUIRED_1": builtIn,
},
err: true,
},
{
desc: "alias of required configuration should not report error.",
config: map[string]kConfigOption{
"REQUIRED_1": builtIn,
"ALIASE_REQUIRED_2": asModule,
},
err: false,
},
{
desc: "optional configuration set or not should not report error.",
config: map[string]kConfigOption{
"REQUIRED_1": builtIn,
"REQUIRED_2": asModule,
"OPTIONAL_1": builtIn,
},
err: false,
},
{
desc: "forbidden configuration disabled should not report error.",
config: map[string]kConfigOption{
"REQUIRED_1": builtIn,
"REQUIRED_2": asModule,
"FORBIDDEN_1": leftOut,
},
err: false,
},
{
desc: "forbidden configuration built-in should report error.",
config: map[string]kConfigOption{
"REQUIRED_1": builtIn,
"REQUIRED_2": asModule,
"FORBIDDEN_1": builtIn,
},
err: true,
},
{
desc: "forbidden configuration built as module should report error.",
config: map[string]kConfigOption{
"REQUIRED_1": builtIn,
"REQUIRED_2": asModule,
"FORBIDDEN_1": asModule,
},
err: true,
},
{
desc: "alias of forbidden configuration should report error.",
config: map[string]kConfigOption{
"REQUIRED_1": builtIn,
"REQUIRED_2": asModule,
"ALIASE_FORBIDDEN_2": asModule,
},
err: true,
},
} {
t.Logf("TestCase #%d %s", c, test.desc)
// Add kernel config prefix.
for k, v := range test.config {
delete(test.config, k)
test.config[kConfigPrefix+k] = v
}
err := v.validateCachedKernelConfig(test.config, testKernelSpec)
if !test.err {
assert.Nil(t, err, "Expect error not to occur with kernel config %q", test.config)
} else {
assert.NotNil(t, err, "Expect error to occur with kenrel config %q", test.config)
}
}
}
func TestValidateParseKernelConfig(t *testing.T) {
config := `CONFIG_1=y
CONFIG_2=m
CONFIG_3=n`
expected := map[string]kConfigOption{
"CONFIG_1": builtIn,
"CONFIG_2": asModule,
"CONFIG_3": leftOut,
}
v := &KernelValidator{
Reporter: DefaultReporter,
}
got, err := v.parseKernelConfig(bytes.NewReader([]byte(config)))
assert.Nil(t, err, "Expect error not to occur when parse kernel configuration %q", config)
assert.Equal(t, expected, got)
}

View File

@ -0,0 +1,51 @@
/*
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 system
import (
"os/exec"
"strings"
"github.com/pkg/errors"
)
var _ Validator = &OSValidator{}
type OSValidator struct {
Reporter Reporter
}
func (o *OSValidator) Name() string {
return "os"
}
func (o *OSValidator) Validate(spec SysSpec) (error, error) {
os, err := exec.Command("uname").CombinedOutput()
if err != nil {
return nil, errors.Wrap(err, "failed to get os name")
}
return nil, o.validateOS(strings.TrimSpace(string(os)), spec.OS)
}
func (o *OSValidator) validateOS(os, specOS string) error {
if os != specOS {
o.Reporter.Report("OS", os, bad)
return errors.Errorf("unsupported operating system: %s", os)
}
o.Reporter.Report("OS", os, good)
return nil
}

View File

@ -0,0 +1,54 @@
/*
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 system
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateOS(t *testing.T) {
v := &OSValidator{
Reporter: DefaultReporter,
}
specOS := "Linux"
for _, test := range []struct {
os string
err bool
}{
{
os: "Linux",
err: false,
},
{
os: "Windows",
err: true,
},
{
os: "Darwin",
err: true,
},
} {
err := v.validateOS(test.os, specOS)
if !test.err {
assert.Nil(t, err, "Expect error not to occur with os %q", test.os)
} else {
assert.NotNil(t, err, "Expect error to occur with os %q", test.os)
}
}
}

View File

@ -0,0 +1,326 @@
/*
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 system
import (
"fmt"
"io/ioutil"
"os/exec"
"strings"
"k8s.io/apimachinery/pkg/util/errors"
"github.com/blang/semver"
pkgerrors "github.com/pkg/errors"
"k8s.io/klog"
)
// semVerDotsCount is the number of dots in a valid semantic version.
const semVerDotsCount int = 2
// packageManager is an interface that abstracts the basic operations of a
// package manager.
type packageManager interface {
// getPackageVersion returns the version of the package given the
// packageName, or an error if no such package exists.
getPackageVersion(packageName string) (string, error)
}
// newPackageManager returns the package manager on the running machine, and an
// error if no package managers is available.
func newPackageManager() (packageManager, error) {
if m, ok := newDPKG(); ok {
return m, nil
}
return nil, pkgerrors.New("failed to find package manager")
}
// dpkg implements packageManager. It uses "dpkg-query" to retrieve package
// information.
type dpkg struct{}
// newDPKG returns a Debian package manager. It returns (nil, false) if no such
// package manager exists on the running machine.
func newDPKG() (packageManager, bool) {
_, err := exec.LookPath("dpkg-query")
if err != nil {
return nil, false
}
return dpkg{}, true
}
// getPackageVersion returns the upstream package version for the package given
// the packageName, and an error if no such package exists.
func (_ dpkg) getPackageVersion(packageName string) (string, error) {
output, err := exec.Command("dpkg-query", "--show", "--showformat='${Version}'", packageName).Output()
if err != nil {
return "", pkgerrors.Wrap(err, "dpkg-query failed")
}
version := extractUpstreamVersion(string(output))
if version == "" {
return "", pkgerrors.New("no version information")
}
return version, nil
}
// packageValidator implements the Validator interface. It validates packages
// and their versions.
type packageValidator struct {
reporter Reporter
kernelRelease string
osDistro string
}
// Name returns the name of the package validator.
func (self *packageValidator) Name() string {
return "package"
}
// Validate checks packages and their versions against the spec using the
// package manager on the running machine, and returns an error on any
// package/version mismatch.
func (self *packageValidator) Validate(spec SysSpec) (error, error) {
if len(spec.PackageSpecs) == 0 {
return nil, nil
}
var err error
if self.kernelRelease, err = getKernelRelease(); err != nil {
return nil, err
}
if self.osDistro, err = getOSDistro(); err != nil {
return nil, err
}
manager, err := newPackageManager()
if err != nil {
return nil, err
}
specs := applyPackageSpecOverride(spec.PackageSpecs, spec.PackageSpecOverrides, self.osDistro)
return self.validate(specs, manager)
}
// Validate checks packages and their versions against the packageSpecs using
// the packageManager, and returns an error on any package/version mismatch.
func (self *packageValidator) validate(packageSpecs []PackageSpec, manager packageManager) (error, error) {
var errs []error
for _, spec := range packageSpecs {
// Substitute variables in package name.
packageName := resolvePackageName(spec.Name, self.kernelRelease)
nameWithVerRange := fmt.Sprintf("%s (%s)", packageName, spec.VersionRange)
// Get the version of the package on the running machine.
version, err := manager.getPackageVersion(packageName)
if err != nil {
klog.V(1).Infof("Failed to get the version for the package %q: %s\n", packageName, err)
errs = append(errs, err)
self.reporter.Report(nameWithVerRange, "not installed", bad)
continue
}
// Version requirement will not be enforced if version range is
// not specified in the spec.
if spec.VersionRange == "" {
self.reporter.Report(packageName, version, good)
continue
}
// Convert both the version range in the spec and the version returned
// from package manager to semantic version format, and then check if
// the version is in the range.
sv, err := semver.Make(toSemVer(version))
if err != nil {
klog.Errorf("Failed to convert %q to semantic version: %s\n", version, err)
errs = append(errs, err)
self.reporter.Report(nameWithVerRange, "internal error", bad)
continue
}
versionRange := semver.MustParseRange(toSemVerRange(spec.VersionRange))
if versionRange(sv) {
self.reporter.Report(nameWithVerRange, version, good)
} else {
errs = append(errs, pkgerrors.Errorf("package \"%s %s\" does not meet the spec \"%s (%s)\"", packageName, sv, packageName, spec.VersionRange))
self.reporter.Report(nameWithVerRange, version, bad)
}
}
return nil, errors.NewAggregate(errs)
}
// getKernelRelease returns the kernel release of the local machine.
func getKernelRelease() (string, error) {
output, err := exec.Command("uname", "-r").Output()
if err != nil {
return "", pkgerrors.Wrap(err, "failed to get kernel release")
}
return strings.TrimSpace(string(output)), nil
}
// getOSDistro returns the OS distro of the local machine.
func getOSDistro() (string, error) {
f := "/etc/lsb-release"
b, err := ioutil.ReadFile(f)
if err != nil {
return "", pkgerrors.Wrapf(err, "failed to read %q", f)
}
content := string(b)
switch {
case strings.Contains(content, "Ubuntu"):
return "ubuntu", nil
case strings.Contains(content, "Chrome OS"):
return "cos", nil
case strings.Contains(content, "CoreOS"):
return "coreos", nil
default:
return "", pkgerrors.Errorf("failed to get OS distro: %s", content)
}
}
// resolvePackageName substitutes the variables in the packageName with the
// local information.
// E.g., "linux-headers-${KERNEL_RELEASE}" -> "linux-headers-4.4.0-75-generic".
func resolvePackageName(packageName string, kernelRelease string) string {
packageName = strings.Replace(packageName, "${KERNEL_RELEASE}", kernelRelease, -1)
return packageName
}
// applyPackageSpecOverride applies the package spec overrides for the given
// osDistro to the packageSpecs and returns the applied result.
func applyPackageSpecOverride(packageSpecs []PackageSpec, overrides []PackageSpecOverride, osDistro string) []PackageSpec {
var override *PackageSpecOverride
for _, o := range overrides {
if o.OSDistro == osDistro {
override = &o
break
}
}
if override == nil {
return packageSpecs
}
// Remove packages in the spec that matches the overrides in
// Subtractions.
var out []PackageSpec
subtractions := make(map[string]bool)
for _, spec := range override.Subtractions {
subtractions[spec.Name] = true
}
for _, spec := range packageSpecs {
if _, ok := subtractions[spec.Name]; !ok {
out = append(out, spec)
}
}
// Add packages in the spec that matches the overrides in Additions.
return append(out, override.Additions...)
}
// extractUpstreamVersion returns the upstream version of the given full
// version in dpkg format. E.g., "1:1.0.6-2ubuntu2.1" -> "1.0.6".
func extractUpstreamVersion(version string) string {
// The full version is in the format of
// "[epoch:]upstream_version[-debian_revision]". See
// https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version.
version = strings.Trim(version, " '")
if i := strings.Index(version, ":"); i != -1 {
version = version[i+1:]
}
if i := strings.Index(version, "-"); i != -1 {
version = version[:i]
}
return version
}
// toSemVerRange converts the input to a semantic version range.
// E.g., ">=1.0" -> ">=1.0.x"
// ">=1" -> ">=1.x"
// ">=1 <=2.3" -> ">=1.x <=2.3.x"
// ">1 || >3.1.0 !4.2" -> ">1.x || >3.1.0 !4.2.x"
func toSemVerRange(input string) string {
var output []string
fields := strings.Fields(input)
for _, f := range fields {
numDots, hasDigits := 0, false
for _, c := range f {
switch {
case c == '.':
numDots++
case c >= '0' && c <= '9':
hasDigits = true
}
}
if hasDigits && numDots < semVerDotsCount {
f = strings.TrimRight(f, " ")
f += ".x"
}
output = append(output, f)
}
return strings.Join(output, " ")
}
// toSemVer converts the input to a semantic version, and an empty string on
// error.
func toSemVer(version string) string {
// Remove the first non-digit and non-dot character as well as the ones
// following it.
// E.g., "1.8.19p1" -> "1.8.19".
if i := strings.IndexFunc(version, func(c rune) bool {
if (c < '0' || c > '9') && c != '.' {
return true
}
return false
}); i != -1 {
version = version[:i]
}
// Remove the trailing dots if there's any, and then returns an empty
// string if nothing left.
version = strings.TrimRight(version, ".")
if version == "" {
return ""
}
numDots := strings.Count(version, ".")
switch {
case numDots < semVerDotsCount:
// Add minor version and patch version.
// E.g. "1.18" -> "1.18.0" and "481" -> "481.0.0".
version += strings.Repeat(".0", semVerDotsCount-numDots)
case numDots > semVerDotsCount:
// Remove anything beyond the patch version
// E.g. "2.0.10.4" -> "2.0.10".
for numDots != semVerDotsCount {
if i := strings.LastIndex(version, "."); i != -1 {
version = version[:i]
numDots--
}
}
}
// Remove leading zeros in major/minor/patch version.
// E.g., "2.02" -> "2.2"
// "8.0.0095" -> "8.0.95"
var subs []string
for _, s := range strings.Split(version, ".") {
s := strings.TrimLeft(s, "0")
if s == "" {
s = "0"
}
subs = append(subs, s)
}
return strings.Join(subs, ".")
}

View File

@ -0,0 +1,266 @@
/*
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 system
import (
"reflect"
"testing"
"github.com/pkg/errors"
)
func TestExtractUpstreamVersion(t *testing.T) {
for _, test := range []struct {
input string
expected string
}{
{
input: "",
expected: "",
},
{
input: "1.0.6",
expected: "1.0.6",
},
{
input: "1:1.0.6",
expected: "1.0.6",
},
{
input: "1.0.6-2ubuntu2.1",
expected: "1.0.6",
},
{
input: "1:1.0.6-2ubuntu2.1",
expected: "1.0.6",
},
} {
got := extractUpstreamVersion(test.input)
if test.expected != got {
t.Errorf("extractUpstreamVersion(%q) = %q, want %q", test.input, got, test.expected)
}
}
}
func TestToSemVer(t *testing.T) {
for _, test := range []struct {
input string
expected string
}{
{
input: "",
expected: "",
},
{
input: "1.2.3",
expected: "1.2.3",
},
{
input: "1.8.19p1",
expected: "1.8.19",
},
{
input: "1.8.19.p1",
expected: "1.8.19",
},
{
input: "p1",
expected: "",
},
{
input: "1.18",
expected: "1.18.0",
},
{
input: "481",
expected: "481.0.0",
},
{
input: "2.0.10.4",
expected: "2.0.10",
},
{
input: "03",
expected: "3.0.0",
},
{
input: "2.02",
expected: "2.2.0",
},
{
input: "8.0.0095",
expected: "8.0.95",
},
} {
got := toSemVer(test.input)
if test.expected != got {
t.Errorf("toSemVer(%q) = %q, want %q", test.input, got, test.expected)
}
}
}
func TestToSemVerRange(t *testing.T) {
for _, test := range []struct {
input string
expected string
}{
{
input: "",
expected: "",
},
{
input: ">=1.0.0",
expected: ">=1.0.0",
},
{
input: ">=1.0",
expected: ">=1.0.x",
},
{
input: ">=1",
expected: ">=1.x",
},
{
input: ">=1 || !2.3",
expected: ">=1.x || !2.3.x",
},
{
input: ">1 || >3.1.0 !4.2",
expected: ">1.x || >3.1.0 !4.2.x",
},
} {
got := toSemVerRange(test.input)
if test.expected != got {
t.Errorf("toSemVerRange(%q) = %q, want %q", test.input, got, test.expected)
}
}
}
// testPackageManager implements the packageManager interface.
type testPackageManager struct {
packageVersions map[string]string
}
func (m testPackageManager) getPackageVersion(packageName string) (string, error) {
if v, ok := m.packageVersions[packageName]; ok {
return v, nil
}
return "", errors.Errorf("package %q does not exist", packageName)
}
func TestValidatePackageVersion(t *testing.T) {
testKernelRelease := "test-kernel-release"
manager := testPackageManager{
packageVersions: map[string]string{
"foo": "1.0.0",
"bar": "2.1.0",
"bar-" + testKernelRelease: "3.0.0",
},
}
v := &packageValidator{
reporter: DefaultReporter,
kernelRelease: testKernelRelease,
}
for _, test := range []struct {
desc string
specs []PackageSpec
err error
}{
{
desc: "all packages meet the spec",
specs: []PackageSpec{
{Name: "foo", VersionRange: ">=1.0"},
{Name: "bar", VersionRange: ">=2.0 <= 3.0"},
},
},
{
desc: "package version does not meet the spec",
specs: []PackageSpec{
{Name: "foo", VersionRange: ">=1.0"},
{Name: "bar", VersionRange: ">=3.0"},
},
err: errors.New("package \"bar 2.1.0\" does not meet the spec \"bar (>=3.0)\""),
},
{
desc: "package not installed",
specs: []PackageSpec{
{Name: "baz"},
},
err: errors.New("package \"baz\" does not exist"),
},
{
desc: "use variable in package name",
specs: []PackageSpec{
{Name: "bar-${KERNEL_RELEASE}", VersionRange: ">=3.0"},
},
},
} {
_, err := v.validate(test.specs, manager)
if test.err == nil && err != nil {
t.Errorf("%s: v.validate(): err = %s", test.desc, err)
}
if test.err != nil {
if err == nil {
t.Errorf("%s: v.validate() is expected to fail.", test.desc)
} else if test.err.Error() != err.Error() {
t.Errorf("%s: v.validate(): err = %q, want = %q", test.desc, err, test.err)
}
}
}
}
func TestApplyPackageOverride(t *testing.T) {
for _, test := range []struct {
overrides []PackageSpecOverride
osDistro string
specs []PackageSpec
expected []PackageSpec
}{
{
specs: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
expected: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
},
{
osDistro: "ubuntu",
overrides: []PackageSpecOverride{
{
OSDistro: "ubuntu",
Subtractions: []PackageSpec{{Name: "foo"}},
Additions: []PackageSpec{{Name: "bar", VersionRange: ">=2.0"}},
},
},
specs: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
expected: []PackageSpec{{Name: "bar", VersionRange: ">=2.0"}},
},
{
osDistro: "ubuntu",
overrides: []PackageSpecOverride{
{
OSDistro: "debian",
Subtractions: []PackageSpec{{Name: "foo"}},
},
},
specs: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
expected: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
},
} {
got := applyPackageSpecOverride(test.specs, test.overrides, test.osDistro)
if !reflect.DeepEqual(test.expected, got) {
t.Errorf("applyPackageSpecOverride(%+v, %+v, %s) = %+v, want = %+v", test.specs, test.overrides, test.osDistro, got, test.expected)
}
}
}

Some files were not shown because too many files have changed in this diff Show More