vendor update for CSI 0.3.0

This commit is contained in:
gman
2018-07-18 16:47:22 +02:00
parent 6f484f92fc
commit 8ea659f0d5
6810 changed files with 438061 additions and 193861 deletions

View File

@ -0,0 +1,163 @@
{
"Rules": [
{
"SelectorRegexp": "k8s[.]io/(api/|apimachinery/|apiextensions-apiserver/|apiserver/)",
"AllowedPrefixes": [
""
]
},
{
"SelectorRegexp": "k8s[.]io/client-go/",
"AllowedPrefixes": [
""
]
},
{
"SelectorRegexp": "k8s[.]io/kube-openapi/",
"AllowedPrefixes": [
"k8s.io/kube-openapi/pkg/util/proto"
]
},
{
"SelectorRegexp": "k8s[.]io/kubernetes/cmd",
"AllowedPrefixes": [
"k8s.io/kubernetes/cmd/kubeadm"
]
},
{
"SelectorRegexp": "k8s[.]io/kubernetes/test",
"AllowedPrefixes": [
"k8s.io/kubernetes/test/e2e_node/system"
]
},
{
"SelectorRegexp": "k8s[.]io/utils",
"AllowedPrefixes": [
"k8s.io/utils/exec"
]
},
{
"SelectorRegexp": "k8s[.]io/kubernetes/pkg",
"AllowedPrefixes": [
"k8s.io/kubernetes/pkg/api/endpoints",
"k8s.io/kubernetes/pkg/api/events",
"k8s.io/kubernetes/pkg/api/legacyscheme",
"k8s.io/kubernetes/pkg/api/pod",
"k8s.io/kubernetes/pkg/api/ref",
"k8s.io/kubernetes/pkg/api/resource",
"k8s.io/kubernetes/pkg/api/service",
"k8s.io/kubernetes/pkg/api/v1/pod",
"k8s.io/kubernetes/pkg/apis/admissionregistration",
"k8s.io/kubernetes/pkg/apis/admissionregistration/install",
"k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1",
"k8s.io/kubernetes/pkg/apis/apps",
"k8s.io/kubernetes/pkg/apis/apps/v1",
"k8s.io/kubernetes/pkg/apis/apps/v1beta1",
"k8s.io/kubernetes/pkg/apis/authentication",
"k8s.io/kubernetes/pkg/apis/authentication/install",
"k8s.io/kubernetes/pkg/apis/authentication/v1beta1",
"k8s.io/kubernetes/pkg/apis/authorization",
"k8s.io/kubernetes/pkg/apis/authorization/install",
"k8s.io/kubernetes/pkg/apis/autoscaling",
"k8s.io/kubernetes/pkg/apis/batch",
"k8s.io/kubernetes/pkg/apis/batch/install",
"k8s.io/kubernetes/pkg/apis/batch/v1beta1",
"k8s.io/kubernetes/pkg/apis/batch/v2alpha1",
"k8s.io/kubernetes/pkg/apis/certificates",
"k8s.io/kubernetes/pkg/apis/componentconfig",
"k8s.io/kubernetes/pkg/apis/core",
"k8s.io/kubernetes/pkg/apis/core/helper",
"k8s.io/kubernetes/pkg/apis/core/pods",
"k8s.io/kubernetes/pkg/apis/core/v1",
"k8s.io/kubernetes/pkg/apis/core/v1/helper/qos",
"k8s.io/kubernetes/pkg/apis/core/validation",
"k8s.io/kubernetes/pkg/apis/events",
"k8s.io/kubernetes/pkg/apis/extensions",
"k8s.io/kubernetes/pkg/apis/networking",
"k8s.io/kubernetes/pkg/apis/networking/v1",
"k8s.io/kubernetes/pkg/apis/policy",
"k8s.io/kubernetes/pkg/apis/rbac",
"k8s.io/kubernetes/pkg/apis/rbac/v1",
"k8s.io/kubernetes/pkg/apis/scheduling",
"k8s.io/kubernetes/pkg/apis/settings",
"k8s.io/kubernetes/pkg/apis/settings/install",
"k8s.io/kubernetes/pkg/apis/settings/v1alpha1",
"k8s.io/kubernetes/pkg/apis/storage",
"k8s.io/kubernetes/pkg/capabilities",
"k8s.io/kubernetes/pkg/cloudprovider",
"k8s.io/kubernetes/pkg/controller",
"k8s.io/kubernetes/pkg/controller/bootstrap",
"k8s.io/kubernetes/pkg/credentialprovider",
"k8s.io/kubernetes/pkg/features",
"k8s.io/kubernetes/pkg/fieldpath",
"k8s.io/kubernetes/pkg/generated",
"k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes",
"k8s.io/kubernetes/pkg/kubectl",
"k8s.io/kubernetes/pkg/kubectl/apps",
"k8s.io/kubernetes/pkg/kubectl/cmd/scalejob",
"k8s.io/kubernetes/pkg/kubectl/cmd/templates",
"k8s.io/kubernetes/pkg/kubectl/cmd/util",
"k8s.io/kubernetes/pkg/kubectl/genericclioptions",
"k8s.io/kubernetes/pkg/kubectl/plugins",
"k8s.io/kubernetes/pkg/kubectl/scheme",
"k8s.io/kubernetes/pkg/kubectl/util",
"k8s.io/kubernetes/pkg/kubectl/util/i18n",
"k8s.io/kubernetes/pkg/kubectl/validation",
"k8s.io/kubernetes/pkg/kubelet/apis",
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig",
"k8s.io/kubernetes/pkg/kubelet/qos",
"k8s.io/kubernetes/pkg/kubelet/types",
"k8s.io/kubernetes/pkg/master/ports",
"k8s.io/kubernetes/pkg/master/reconcilers",
"k8s.io/kubernetes/pkg/printers",
"k8s.io/kubernetes/pkg/printers/internalversion",
"k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig",
"k8s.io/kubernetes/pkg/registry/core/endpoint",
"k8s.io/kubernetes/pkg/registry/core/service/allocator",
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator",
"k8s.io/kubernetes/pkg/registry/rbac/validation",
"k8s.io/kubernetes/pkg/scheduler/algorithm",
"k8s.io/kubernetes/pkg/scheduler/api",
"k8s.io/kubernetes/pkg/scheduler/cache",
"k8s.io/kubernetes/pkg/scheduler/util",
"k8s.io/kubernetes/pkg/security/apparmor",
"k8s.io/kubernetes/pkg/serviceaccount",
"k8s.io/kubernetes/pkg/util/file",
"k8s.io/kubernetes/pkg/util/hash",
"k8s.io/kubernetes/pkg/util/initsystem",
"k8s.io/kubernetes/pkg/util/interrupt",
"k8s.io/kubernetes/pkg/util/io",
"k8s.io/kubernetes/pkg/util/labels",
"k8s.io/kubernetes/pkg/util/metrics",
"k8s.io/kubernetes/pkg/util/mount",
"k8s.io/kubernetes/pkg/util/net/sets",
"k8s.io/kubernetes/pkg/util/node",
"k8s.io/kubernetes/pkg/util/normalizer",
"k8s.io/kubernetes/pkg/util/nsenter",
"k8s.io/kubernetes/pkg/util/parsers",
"k8s.io/kubernetes/pkg/util/pointer",
"k8s.io/kubernetes/pkg/util/procfs",
"k8s.io/kubernetes/pkg/util/slice",
"k8s.io/kubernetes/pkg/util/taints",
"k8s.io/kubernetes/pkg/util/version",
"k8s.io/kubernetes/pkg/util/ipvs",
"k8s.io/kubernetes/pkg/version",
"k8s.io/kubernetes/pkg/volume",
"k8s.io/kubernetes/pkg/volume/util"
],
"ForbiddenPrefixes": [
"k8s.io/kubernetes/pkg/cloudprovider/providers",
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws",
"k8s.io/kubernetes/pkg/cloudprovider/providers/azure",
"k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack",
"k8s.io/kubernetes/pkg/cloudprovider/providers/fake",
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce",
"k8s.io/kubernetes/pkg/cloudprovider/providers/openstack",
"k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt",
"k8s.io/kubernetes/pkg/cloudprovider/providers/photon",
"k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
]
}
]
}

View File

@ -1,16 +1,19 @@
approvers:
- jbeda
- luxas
- krousey
- timothysc
reviewers:
- luxas
- dmmcquay
- krousey
- timothysc
- fabriziopandini
- jamiehannaford
reviewers:
- luxas
- timothysc
- fabriziopandini
- kad
- xiangpengzhao
- mattmoyer
- kargakis
- stealthybox
- liztio
- chuckha
- detiber
- dixudx
# Might want to add @kargakis, @jamiehannaford, @krousey and/or @dmmcquay back in the future
labels:
- area/kubeadm
- sig/cluster-lifecycle

View File

@ -10,9 +10,10 @@ go_library(
srcs = ["kubeadm.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app",
deps = [
"//cmd/kubeadm/app/apis/kubeadm/install:go_default_library",
"//cmd/kubeadm/app/cmd:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
],
)
@ -43,6 +44,7 @@ filegroup(
"//cmd/kubeadm/app/phases/kubeconfig:all-srcs",
"//cmd/kubeadm/app/phases/kubelet:all-srcs",
"//cmd/kubeadm/app/phases/markmaster:all-srcs",
"//cmd/kubeadm/app/phases/patchnode:all-srcs",
"//cmd/kubeadm/app/phases/selfhosting:all-srcs",
"//cmd/kubeadm/app/phases/upgrade:all-srcs",
"//cmd/kubeadm/app/phases/uploadconfig:all-srcs",

View File

@ -3,11 +3,14 @@ package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"bootstraptokenhelpers.go",
"bootstraptokenstring.go",
"doc.go",
"register.go",
"types.go",
@ -17,10 +20,13 @@ go_library(
deps = [
"//pkg/kubelet/apis/kubeletconfig/v1beta1:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library",
"//vendor/github.com/google/gofuzz: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/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/util:go_default_library",
],
)
@ -36,9 +42,23 @@ filegroup(
srcs = [
":package-srcs",
"//cmd/kubeadm/app/apis/kubeadm/fuzzer:all-srcs",
"//cmd/kubeadm/app/apis/kubeadm/install:all-srcs",
"//cmd/kubeadm/app/apis/kubeadm/scheme:all-srcs",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:all-srcs",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:all-srcs",
"//cmd/kubeadm/app/apis/kubeadm/validation:all-srcs",
],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = [
"bootstraptokenhelpers_test.go",
"bootstraptokenstring_test.go",
],
embed = [":go_default_library"],
deps = [
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)

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 kubeadm
import (
"fmt"
"sort"
"strings"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
)
// ToSecret converts the given BootstrapToken object to its Secret representation that
// may be submitted to the API Server in order to be stored.
func (bt *BootstrapToken) ToSecret() *v1.Secret {
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: bootstraputil.BootstrapTokenSecretName(bt.Token.ID),
Namespace: metav1.NamespaceSystem,
},
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken),
Data: encodeTokenSecretData(bt, time.Now()),
}
}
// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret
// now is passed in order to be able to used in unit testing
func encodeTokenSecretData(token *BootstrapToken, now time.Time) map[string][]byte {
data := map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(token.Token.ID),
bootstrapapi.BootstrapTokenSecretKey: []byte(token.Token.Secret),
}
if len(token.Description) > 0 {
data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(token.Description)
}
// If for some strange reason both token.TTL and token.Expires would be set
// (they are mutually exlusive in validation so this shouldn't be the case),
// token.Expires has higher priority, as can be seen in the logic here.
if token.Expires != nil {
// Format the expiration date accordingly
// TODO: This maybe should be a helper function in bootstraputil?
expirationString := token.Expires.Time.Format(time.RFC3339)
data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(expirationString)
} else if token.TTL != nil && token.TTL.Duration > 0 {
// Only if .Expires is unset, TTL might have an effect
// Get the current time, add the specified duration, and format it accordingly
expirationString := now.Add(token.TTL.Duration).Format(time.RFC3339)
data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(expirationString)
}
for _, usage := range token.Usages {
data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true")
}
if len(token.Groups) > 0 {
data[bootstrapapi.BootstrapTokenExtraGroupsKey] = []byte(strings.Join(token.Groups, ","))
}
return data
}
// BootstrapTokenFromSecret returns a BootstrapToken object from the given Secret
func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) {
// Get the Token ID field from the Secret data
tokenID := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
if len(tokenID) == 0 {
return nil, fmt.Errorf("Bootstrap Token Secret has no token-id data: %s", secret.Name)
}
// Enforce the right naming convention
if secret.Name != bootstraputil.BootstrapTokenSecretName(tokenID) {
return nil, fmt.Errorf("bootstrap token name is not of the form '%s(token-id)'. Actual: %q. Expected: %q",
bootstrapapi.BootstrapTokenSecretPrefix, secret.Name, bootstraputil.BootstrapTokenSecretName(tokenID))
}
tokenSecret := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
if len(tokenSecret) == 0 {
return nil, fmt.Errorf("Bootstrap Token Secret has no token-secret data: %s", secret.Name)
}
// Create the BootstrapTokenString object based on the ID and Secret
bts, err := NewBootstrapTokenStringFromIDAndSecret(tokenID, tokenSecret)
if err != nil {
return nil, fmt.Errorf("Bootstrap Token Secret is invalid and couldn't be parsed: %v", err)
}
// Get the description (if any) from the Secret
description := getSecretString(secret, bootstrapapi.BootstrapTokenDescriptionKey)
// Expiration time is optional, if not specified this implies the token
// never expires.
secretExpiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey)
var expires *metav1.Time
if len(secretExpiration) > 0 {
expTime, err := time.Parse(time.RFC3339, secretExpiration)
if err != nil {
return nil, fmt.Errorf("can't parse expiration time of bootstrap token %q: %v", secret.Name, err)
}
expires = &metav1.Time{Time: expTime}
}
// Build an usages string slice from the Secret data
var usages []string
for k, v := range secret.Data {
// Skip all fields that don't include this prefix
if !strings.HasPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix) {
continue
}
// Skip those that don't have this usage set to true
if string(v) != "true" {
continue
}
usages = append(usages, strings.TrimPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix))
}
// Only sort the slice if defined
if usages != nil {
sort.Strings(usages)
}
// Get the extra groups information from the Secret
// It's done this way to make .Groups be nil in case there is no items, rather than an
// empty slice or an empty slice with a "" string only
var groups []string
groupsString := getSecretString(secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
g := strings.Split(groupsString, ",")
if len(g) > 0 && len(g[0]) > 0 {
groups = g
}
return &BootstrapToken{
Token: bts,
Description: description,
Expires: expires,
Usages: usages,
Groups: groups,
}, nil
}
// getSecretString returns the string value for the given key in the specified Secret
func getSecretString(secret *v1.Secret, key string) string {
if secret.Data == nil {
return ""
}
if val, ok := secret.Data[key]; ok {
return string(val)
}
return ""
}

View File

@ -0,0 +1,473 @@
/*
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 kubeadm
import (
"encoding/json"
"reflect"
"testing"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// This timestamp is used as the reference value when computing expiration dates based on TTLs in these unit tests
var refTime = time.Date(1970, time.January, 1, 1, 1, 1, 0, time.UTC)
func TestToSecret(t *testing.T) {
var tests = []struct {
bt *BootstrapToken
secret *v1.Secret
}{
{
&BootstrapToken{ // all together
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Description: "foo",
Expires: &metav1.Time{
Time: refTime,
},
Usages: []string{"signing", "authentication"},
Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"},
},
&v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "bootstrap-token-abcdef",
Namespace: "kube-system",
},
Type: v1.SecretType("bootstrap.kubernetes.io/token"),
Data: map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"description": []byte("foo"),
"expiration": []byte(refTime.Format(time.RFC3339)),
"usage-bootstrap-signing": []byte("true"),
"usage-bootstrap-authentication": []byte("true"),
"auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"),
},
},
},
}
for _, rt := range tests {
actual := rt.bt.ToSecret()
if !reflect.DeepEqual(actual, rt.secret) {
t.Errorf(
"failed BootstrapToken.ToSecret():\n\texpected: %v\n\t actual: %v",
rt.secret,
actual,
)
}
}
}
func TestBootstrapTokenToSecretRoundtrip(t *testing.T) {
var tests = []struct {
bt *BootstrapToken
}{
{
&BootstrapToken{
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Description: "foo",
Expires: &metav1.Time{
Time: refTime,
},
Usages: []string{"authentication", "signing"},
Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"},
},
},
}
for _, rt := range tests {
actual, err := BootstrapTokenFromSecret(rt.bt.ToSecret())
if err != nil {
t.Errorf("failed BootstrapToken to Secret roundtrip with error: %v", err)
}
if !reflect.DeepEqual(actual, rt.bt) {
t.Errorf(
"failed BootstrapToken to Secret roundtrip:\n\texpected: %v\n\t actual: %v",
rt.bt,
actual,
)
}
}
}
func TestEncodeTokenSecretData(t *testing.T) {
var tests = []struct {
bt *BootstrapToken
data map[string][]byte
}{
{
&BootstrapToken{ // the minimum amount of information needed to be specified
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
},
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
},
},
{
&BootstrapToken{ // adds description
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Description: "foo",
},
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"description": []byte("foo"),
},
},
{
&BootstrapToken{ // adds ttl
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
TTL: &metav1.Duration{
Duration: mustParseDuration("2h", t),
},
},
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"expiration": []byte(refTime.Add(mustParseDuration("2h", t)).Format(time.RFC3339)),
},
},
{
&BootstrapToken{ // adds expiration
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Expires: &metav1.Time{
Time: refTime,
},
},
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"expiration": []byte(refTime.Format(time.RFC3339)),
},
},
{
&BootstrapToken{ // adds ttl and expiration, should favor expiration
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
TTL: &metav1.Duration{
Duration: mustParseDuration("2h", t),
},
Expires: &metav1.Time{
Time: refTime,
},
},
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"expiration": []byte(refTime.Format(time.RFC3339)),
},
},
{
&BootstrapToken{ // adds usages
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Usages: []string{"authentication", "signing"},
},
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"usage-bootstrap-signing": []byte("true"),
"usage-bootstrap-authentication": []byte("true"),
},
},
{
&BootstrapToken{ // adds groups
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"},
},
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"),
},
},
{
&BootstrapToken{ // all together
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Description: "foo",
TTL: &metav1.Duration{
Duration: mustParseDuration("2h", t),
},
Expires: &metav1.Time{
Time: refTime,
},
Usages: []string{"authentication", "signing"},
Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"},
},
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"description": []byte("foo"),
"expiration": []byte(refTime.Format(time.RFC3339)),
"usage-bootstrap-signing": []byte("true"),
"usage-bootstrap-authentication": []byte("true"),
"auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"),
},
},
}
for _, rt := range tests {
actual := encodeTokenSecretData(rt.bt, refTime)
if !reflect.DeepEqual(actual, rt.data) {
t.Errorf(
"failed encodeTokenSecretData:\n\texpected: %v\n\t actual: %v",
rt.data,
actual,
)
}
}
}
func mustParseDuration(durationStr string, t *testing.T) time.Duration {
d, err := time.ParseDuration(durationStr)
if err != nil {
t.Fatalf("couldn't parse duration %q: %v", durationStr, err)
}
return d
}
func TestBootstrapTokenFromSecret(t *testing.T) {
var tests = []struct {
name string
data map[string][]byte
bt *BootstrapToken
expectedError bool
}{
{ // minimum information
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
},
&BootstrapToken{
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
},
false,
},
{ // invalid token id
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdeF"),
"token-secret": []byte("abcdef0123456789"),
},
nil,
true,
},
{ // invalid secret naming
"foo",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
},
nil,
true,
},
{ // invalid token secret
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("ABCDEF0123456789"),
},
nil,
true,
},
{ // adds description
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"description": []byte("foo"),
},
&BootstrapToken{
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Description: "foo",
},
false,
},
{ // adds expiration
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"expiration": []byte(refTime.Format(time.RFC3339)),
},
&BootstrapToken{
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Expires: &metav1.Time{
Time: refTime,
},
},
false,
},
{ // invalid expiration
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"expiration": []byte("invalid date"),
},
nil,
true,
},
{ // adds usages
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"usage-bootstrap-signing": []byte("true"),
"usage-bootstrap-authentication": []byte("true"),
},
&BootstrapToken{
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Usages: []string{"authentication", "signing"},
},
false,
},
{ // should ignore usages that aren't set to true
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"usage-bootstrap-signing": []byte("true"),
"usage-bootstrap-authentication": []byte("true"),
"usage-bootstrap-foo": []byte("false"),
"usage-bootstrap-bar": []byte(""),
},
&BootstrapToken{
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Usages: []string{"authentication", "signing"},
},
false,
},
{ // adds groups
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"),
},
&BootstrapToken{
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"},
},
false,
},
{ // all fields set
"bootstrap-token-abcdef",
map[string][]byte{
"token-id": []byte("abcdef"),
"token-secret": []byte("abcdef0123456789"),
"description": []byte("foo"),
"expiration": []byte(refTime.Format(time.RFC3339)),
"usage-bootstrap-signing": []byte("true"),
"usage-bootstrap-authentication": []byte("true"),
"auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"),
},
&BootstrapToken{
Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Description: "foo",
Expires: &metav1.Time{
Time: refTime,
},
Usages: []string{"authentication", "signing"},
Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"},
},
false,
},
}
for _, rt := range tests {
actual, err := BootstrapTokenFromSecret(&v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: rt.name,
Namespace: "kube-system",
},
Type: v1.SecretType("bootstrap.kubernetes.io/token"),
Data: rt.data,
})
if (err != nil) != rt.expectedError {
t.Errorf(
"failed BootstrapTokenFromSecret\n\texpected error: %t\n\t actual error: %v",
rt.expectedError,
err,
)
} else {
if actual == nil && rt.bt == nil {
// if both pointers are nil, it's okay, just continue
continue
}
// If one of the pointers is defined but the other isn't, throw error. If both pointers are defined but unequal, throw error
if (actual == nil && rt.bt != nil) || (actual != nil && rt.bt == nil) || !reflect.DeepEqual(*actual, *rt.bt) {
t.Errorf(
"failed BootstrapTokenFromSecret\n\texpected: %s\n\t actual: %s",
jsonMarshal(rt.bt),
jsonMarshal(actual),
)
}
}
}
}
func jsonMarshal(bt *BootstrapToken) string {
b, _ := json.Marshal(*bt)
return string(b)
}
func TestGetSecretString(t *testing.T) {
var tests = []struct {
secret *v1.Secret
key string
expectedVal string
}{
{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Data: map[string][]byte{
"foo": []byte("bar"),
},
},
key: "foo",
expectedVal: "bar",
},
{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Data: map[string][]byte{
"foo": []byte("bar"),
},
},
key: "baz",
expectedVal: "",
},
{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
},
key: "foo",
expectedVal: "",
},
}
for _, rt := range tests {
actual := getSecretString(rt.secret, rt.key)
if actual != rt.expectedVal {
t.Errorf(
"failed getSecretString:\n\texpected: %s\n\t actual: %s",
rt.expectedVal,
actual,
)
}
}
}

View File

@ -0,0 +1,90 @@
/*
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 kubeadm holds the internal kubeadm API types
// Note: This file should be kept in sync with the similar one for the external API
// TODO: The BootstrapTokenString object should move out to either k8s.io/client-go or k8s.io/api in the future
// (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now.
package kubeadm
import (
"fmt"
"strings"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
)
// BootstrapTokenString is a token of the format abcdef.abcdef0123456789 that is used
// for both validation of the practically of the API server from a joining node's point
// of view and as an authentication method for the node in the bootstrap phase of
// "kubeadm join". This token is and should be short-lived
type BootstrapTokenString struct {
ID string
Secret string
}
// MarshalJSON implements the json.Marshaler interface.
func (bts BootstrapTokenString) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, bts.String())), nil
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (bts *BootstrapTokenString) UnmarshalJSON(b []byte) error {
// If the token is represented as "", just return quickly without an error
if len(b) == 0 {
return nil
}
// Remove unnecessary " characters coming from the JSON parser
token := strings.Replace(string(b), `"`, ``, -1)
// Convert the string Token to a BootstrapTokenString object
newbts, err := NewBootstrapTokenString(token)
if err != nil {
return err
}
bts.ID = newbts.ID
bts.Secret = newbts.Secret
return nil
}
// String returns the string representation of the BootstrapTokenString
func (bts BootstrapTokenString) String() string {
if len(bts.ID) > 0 && len(bts.Secret) > 0 {
return bootstraputil.TokenFromIDAndSecret(bts.ID, bts.Secret)
}
return ""
}
// NewBootstrapTokenString converts the given Bootstrap Token as a string
// to the BootstrapTokenString object used for serialization/deserialization
// and internal usage. It also automatically validates that the given token
// is of the right format
func NewBootstrapTokenString(token string) (*BootstrapTokenString, error) {
substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token)
// TODO: Add a constant for the 3 value here, and explain better why it's needed (other than because how the regexp parsin works)
if len(substrs) != 3 {
return nil, fmt.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern)
}
return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil
}
// NewBootstrapTokenStringFromIDAndSecret is a wrapper around NewBootstrapTokenString
// that allows the caller to specify the ID and Secret separately
func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) {
return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret))
}

View File

@ -0,0 +1,236 @@
/*
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 kubeadm
import (
"encoding/json"
"fmt"
"reflect"
"testing"
)
func TestMarshalJSON(t *testing.T) {
var tests = []struct {
bts BootstrapTokenString
expected string
}{
{BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, `"abcdef.abcdef0123456789"`},
{BootstrapTokenString{ID: "foo", Secret: "bar"}, `"foo.bar"`},
{BootstrapTokenString{ID: "h", Secret: "b"}, `"h.b"`},
}
for _, rt := range tests {
b, err := json.Marshal(rt.bts)
if err != nil {
t.Fatalf("json.Marshal returned an unexpected error: %v", err)
}
if string(b) != rt.expected {
t.Errorf(
"failed BootstrapTokenString.MarshalJSON:\n\texpected: %s\n\t actual: %s",
rt.expected,
string(b),
)
}
}
}
func TestUnmarshalJSON(t *testing.T) {
var tests = []struct {
input string
bts *BootstrapTokenString
expectedError bool
}{
{`"f.s"`, &BootstrapTokenString{}, true},
{`"abcdef."`, &BootstrapTokenString{}, true},
{`"abcdef:abcdef0123456789"`, &BootstrapTokenString{}, true},
{`abcdef.abcdef0123456789`, &BootstrapTokenString{}, true},
{`"abcdef.abcdef0123456789`, &BootstrapTokenString{}, true},
{`"abcdef.ABCDEF0123456789"`, &BootstrapTokenString{}, true},
{`"abcdef.abcdef0123456789"`, &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, false},
{`"123456.aabbccddeeffgghh"`, &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}, false},
}
for _, rt := range tests {
newbts := &BootstrapTokenString{}
err := json.Unmarshal([]byte(rt.input), newbts)
if (err != nil) != rt.expectedError {
t.Errorf("failed BootstrapTokenString.UnmarshalJSON:\n\texpected error: %t\n\t actual error: %v", rt.expectedError, err)
} else if !reflect.DeepEqual(rt.bts, newbts) {
t.Errorf(
"failed BootstrapTokenString.UnmarshalJSON:\n\texpected: %v\n\t actual: %v",
rt.bts,
newbts,
)
}
}
}
func TestJSONRoundtrip(t *testing.T) {
var tests = []struct {
input string
bts *BootstrapTokenString
}{
{`"abcdef.abcdef0123456789"`, nil},
{"", &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}},
}
for _, rt := range tests {
if err := roundtrip(rt.input, rt.bts); err != nil {
t.Errorf("failed BootstrapTokenString JSON roundtrip with error: %v", err)
}
}
}
func roundtrip(input string, bts *BootstrapTokenString) error {
var b []byte
var err error
newbts := &BootstrapTokenString{}
// If string input was specified, roundtrip like this: string -> (unmarshal) -> object -> (marshal) -> string
if len(input) > 0 {
if err := json.Unmarshal([]byte(input), newbts); err != nil {
return fmt.Errorf("expected no unmarshal error, got error: %v", err)
}
if b, err = json.Marshal(newbts); err != nil {
return fmt.Errorf("expected no marshal error, got error: %v", err)
}
if input != string(b) {
return fmt.Errorf(
"expected token: %s\n\t actual: %s",
input,
string(b),
)
}
} else { // Otherwise, roundtrip like this: object -> (marshal) -> string -> (unmarshal) -> object
if b, err = json.Marshal(bts); err != nil {
return fmt.Errorf("expected no marshal error, got error: %v", err)
}
if err := json.Unmarshal(b, newbts); err != nil {
return fmt.Errorf("expected no unmarshal error, got error: %v", err)
}
if !reflect.DeepEqual(bts, newbts) {
return fmt.Errorf(
"expected object: %v\n\t actual: %v",
bts,
newbts,
)
}
}
return nil
}
func TestTokenFromIDAndSecret(t *testing.T) {
var tests = []struct {
bts BootstrapTokenString
expected string
}{
{BootstrapTokenString{ID: "foo", Secret: "bar"}, "foo.bar"},
{BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, "abcdef.abcdef0123456789"},
{BootstrapTokenString{ID: "h", Secret: "b"}, "h.b"},
}
for _, rt := range tests {
actual := rt.bts.String()
if actual != rt.expected {
t.Errorf(
"failed BootstrapTokenString.String():\n\texpected: %s\n\t actual: %s",
rt.expected,
actual,
)
}
}
}
func TestNewBootstrapTokenString(t *testing.T) {
var tests = []struct {
token string
expectedError bool
bts *BootstrapTokenString
}{
{token: "", expectedError: true, bts: nil},
{token: ".", expectedError: true, bts: nil},
{token: "1234567890123456789012", expectedError: true, bts: nil}, // invalid parcel size
{token: "12345.1234567890123456", expectedError: true, bts: nil}, // invalid parcel size
{token: ".1234567890123456", expectedError: true, bts: nil}, // invalid parcel size
{token: "123456.", expectedError: true, bts: nil}, // invalid parcel size
{token: "123456:1234567890.123456", expectedError: true, bts: nil}, // invalid separation
{token: "abcdef:1234567890123456", expectedError: true, bts: nil}, // invalid separation
{token: "Abcdef.1234567890123456", expectedError: true, bts: nil}, // invalid token id
{token: "123456.AABBCCDDEEFFGGHH", expectedError: true, bts: nil}, // invalid token secret
{token: "123456.AABBCCD-EEFFGGHH", expectedError: true, bts: nil}, // invalid character
{token: "abc*ef.1234567890123456", expectedError: true, bts: nil}, // invalid character
{token: "abcdef.1234567890123456", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "1234567890123456"}},
{token: "123456.aabbccddeeffgghh", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}},
{token: "abcdef.abcdef0123456789", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}},
{token: "123456.1234560123456789", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "1234560123456789"}},
}
for _, rt := range tests {
actual, err := NewBootstrapTokenString(rt.token)
if (err != nil) != rt.expectedError {
t.Errorf(
"failed NewBootstrapTokenString for the token %q\n\texpected error: %t\n\t actual error: %v",
rt.token,
rt.expectedError,
err,
)
} else if !reflect.DeepEqual(actual, rt.bts) {
t.Errorf(
"failed NewBootstrapTokenString for the token %q\n\texpected: %v\n\t actual: %v",
rt.token,
rt.bts,
actual,
)
}
}
}
func TestNewBootstrapTokenStringFromIDAndSecret(t *testing.T) {
var tests = []struct {
id, secret string
expectedError bool
bts *BootstrapTokenString
}{
{id: "", secret: "", expectedError: true, bts: nil},
{id: "1234567890123456789012", secret: "", expectedError: true, bts: nil}, // invalid parcel size
{id: "12345", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid parcel size
{id: "", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid parcel size
{id: "123456", secret: "", expectedError: true, bts: nil}, // invalid parcel size
{id: "Abcdef", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid token id
{id: "123456", secret: "AABBCCDDEEFFGGHH", expectedError: true, bts: nil}, // invalid token secret
{id: "123456", secret: "AABBCCD-EEFFGGHH", expectedError: true, bts: nil}, // invalid character
{id: "abc*ef", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid character
{id: "abcdef", secret: "1234567890123456", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "1234567890123456"}},
{id: "123456", secret: "aabbccddeeffgghh", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}},
{id: "abcdef", secret: "abcdef0123456789", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}},
{id: "123456", secret: "1234560123456789", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "1234560123456789"}},
}
for _, rt := range tests {
actual, err := NewBootstrapTokenStringFromIDAndSecret(rt.id, rt.secret)
if (err != nil) != rt.expectedError {
t.Errorf(
"failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected error: %t\n\t actual error: %v",
rt.id,
rt.secret,
rt.expectedError,
err,
)
} else if !reflect.DeepEqual(actual, rt.bts) {
t.Errorf(
"failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected: %v\n\t actual: %v",
rt.id,
rt.secret,
rt.bts,
actual,
)
}
}
}

View File

@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
@ -15,6 +16,7 @@ go_library(
"//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library",
"//pkg/util/pointer:go_default_library",
"//vendor/github.com/google/gofuzz: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/runtime/serializer:go_default_library",
],
@ -32,3 +34,13 @@ filegroup(
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["fuzzer_test.go"],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library",
],
)

View File

@ -19,8 +19,9 @@ package fuzzer
import (
"time"
"github.com/google/gofuzz"
fuzz "github.com/google/gofuzz"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
@ -39,44 +40,64 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
obj.API.AdvertiseAddress = "foo"
obj.Networking.ServiceSubnet = "foo"
obj.Networking.DNSDomain = "foo"
obj.AuthorizationModes = []string{"foo"}
obj.CertificatesDir = "foo"
obj.APIServerCertSANs = []string{"foo"}
obj.Etcd.ServerCertSANs = []string{"foo"}
obj.Etcd.PeerCertSANs = []string{"foo"}
obj.Token = "foo"
obj.CRISocket = "foo"
obj.TokenTTL = &metav1.Duration{Duration: 1 * time.Hour}
obj.TokenUsages = []string{"foo"}
obj.TokenGroups = []string{"foo"}
obj.Etcd.Image = "foo"
obj.Etcd.DataDir = "foo"
obj.BootstrapTokens = []kubeadm.BootstrapToken{
{
Token: &kubeadm.BootstrapTokenString{
ID: "abcdef",
Secret: "abcdef0123456789",
},
TTL: &metav1.Duration{Duration: 1 * time.Hour},
Usages: []string{"foo"},
Groups: []string{"foo"},
},
}
obj.ImageRepository = "foo"
obj.CIImageRepository = ""
obj.UnifiedControlPlaneImage = "foo"
obj.FeatureGates = map[string]bool{"foo": true}
obj.ClusterName = "foo"
obj.APIServerExtraArgs = map[string]string{"foo": "foo"}
obj.APIServerExtraVolumes = []kubeadm.HostPathMount{{
Name: "foo",
HostPath: "foo",
MountPath: "foo",
Writable: false,
}}
obj.Etcd.ExtraArgs = map[string]string{"foo": "foo"}
obj.Etcd.SelfHosted = &kubeadm.SelfHostedEtcd{
CertificatesDir: "/etc/kubernetes/pki/etcd",
ClusterServiceName: "etcd-cluster",
EtcdVersion: "v0.1.0",
OperatorVersion: "v0.1.0",
// Note: We don't set values here for obj.Etcd.External, as these are mutually exlusive.
// And to make sure the fuzzer doesn't set a random value for obj.Etcd.External, we let
// kubeadmapi.Etcd implement fuzz.Interface (we handle that ourselves)
obj.Etcd.Local = &kubeadm.LocalEtcd{
Image: "foo",
DataDir: "foo",
ServerCertSANs: []string{"foo"},
PeerCertSANs: []string{"foo"},
ExtraArgs: map[string]string{"foo": "foo"},
}
obj.NodeRegistration = kubeadm.NodeRegistrationOptions{
CRISocket: "foo",
Name: "foo",
Taints: []v1.Taint{},
}
obj.KubeletConfiguration = kubeadm.KubeletConfiguration{
BaseConfig: &kubeletconfigv1beta1.KubeletConfiguration{
StaticPodPath: "foo",
ClusterDNS: []string{"foo"},
ClusterDomain: "foo",
Authorization: kubeletconfigv1beta1.KubeletAuthorization{Mode: "foo"},
Authentication: kubeletconfigv1beta1.KubeletAuthentication{
X509: kubeletconfigv1beta1.KubeletX509Authentication{ClientCAFile: "foo"},
Authorization: kubeletconfigv1beta1.KubeletAuthorization{
Mode: "Webhook",
},
Authentication: kubeletconfigv1beta1.KubeletAuthentication{
X509: kubeletconfigv1beta1.KubeletX509Authentication{
ClientCAFile: "/etc/kubernetes/pki/ca.crt",
},
Anonymous: kubeletconfigv1beta1.KubeletAnonymousAuthentication{
Enabled: utilpointer.BoolPtr(false),
},
},
RotateCertificates: true,
},
}
kubeletconfigv1beta1.SetDefaults_KubeletConfiguration(obj.KubeletConfiguration.BaseConfig)
@ -127,9 +148,14 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
obj.DiscoveryFile = "foo"
obj.DiscoveryToken = "foo"
obj.DiscoveryTokenAPIServers = []string{"foo"}
obj.DiscoveryTimeout = &metav1.Duration{Duration: 1}
obj.TLSBootstrapToken = "foo"
obj.Token = "foo"
obj.CRISocket = "foo"
obj.ClusterName = "foo"
obj.NodeRegistration = kubeadm.NodeRegistrationOptions{
CRISocket: "foo",
Name: "foo",
}
},
}
}

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.
@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package install
package fuzzer
import (
"testing"
"k8s.io/apimachinery/pkg/api/testing/roundtrip"
kubeadmfuzzer "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/fuzzer"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
)
func TestRoundTripTypes(t *testing.T) {
roundtrip.RoundTripTestForAPIGroup(t, Install, kubeadmfuzzer.Funcs)
roundtrip.RoundTripTestForAPIGroup(t, scheme.AddToScheme, Funcs)
}

View File

@ -1,47 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"install.go",
],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["install_test.go"],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm/fuzzer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library",
],
)

View File

@ -1,46 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package install
import (
"k8s.io/apimachinery/pkg/apimachinery/announced"
"k8s.io/apimachinery/pkg/apimachinery/registered"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
)
func init() {
Install(legacyscheme.GroupFactoryRegistry, legacyscheme.Registry, legacyscheme.Scheme)
}
// Install registers the API group and adds types to a scheme
func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) {
if err := announced.NewGroupMetaFactory(
&announced.GroupMetaFactoryArgs{
GroupName: kubeadm.GroupName,
VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version},
AddInternalObjectsToScheme: kubeadm.AddToScheme,
},
announced.VersionToSchemeFunc{
v1alpha1.SchemeGroupVersion.Version: v1alpha1.AddToScheme,
},
).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil {
panic(err)
}
}

View File

@ -2,18 +2,17 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"spec.go",
"zz_generated.deepcopy.go",
],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd/spec",
srcs = ["scheme.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/api/core/v1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2: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/runtime/serializer:go_default_library",
],
)

View File

@ -0,0 +1,46 @@
/*
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 scheme
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
)
// Scheme is the runtime.Scheme to which all kubeadm api types are registered.
var Scheme = runtime.NewScheme()
// Codecs provides access to encoding and decoding for the scheme.
var Codecs = serializer.NewCodecFactory(Scheme)
func init() {
metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
AddToScheme(Scheme)
}
// AddToScheme builds the Kubeadm scheme using all known versions of the kubeadm api.
func AddToScheme(scheme *runtime.Scheme) {
kubeadm.AddToScheme(scheme)
v1alpha1.AddToScheme(scheme)
v1alpha2.AddToScheme(scheme)
scheme.SetVersionPriority(v1alpha1.SchemeGroupVersion)
}

View File

@ -17,6 +17,8 @@ limitations under the License.
package kubeadm
import (
fuzz "github.com/google/gofuzz"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeletconfigv1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
@ -30,6 +32,21 @@ import (
type MasterConfiguration struct {
metav1.TypeMeta
// `kubeadm init`-only information. These fields are solely used the first time `kubeadm init` runs.
// After that, the information in the fields ARE NOT uploaded to the `kubeadm-config` ConfigMap
// that is used by `kubeadm upgrade` for instance.
// BootstrapTokens is respected at `kubeadm init` time and describes a set of Bootstrap Tokens to create.
// This information IS NOT uploaded to the kubeadm cluster configmap, partly because of its sensitive nature
BootstrapTokens []BootstrapToken
// NodeRegistration holds fields that relate to registering the new master node to the cluster
NodeRegistration NodeRegistrationOptions
// Cluster-wide configuration
// TODO: Move these fields under some kind of ClusterConfiguration or similar struct that describes
// one cluster. Eventually we want this kind of spec to align well with the Cluster API spec.
// API holds configuration for the k8s apiserver.
API API
// KubeProxy holds configuration for the k8s service proxy.
@ -42,37 +59,6 @@ type MasterConfiguration struct {
Networking Networking
// KubernetesVersion is the target version of the control plane.
KubernetesVersion string
// CloudProvider is the name of the cloud provider.
CloudProvider string
// NodeName is the name of the node that will host the k8s control plane.
// Defaults to the hostname if not provided.
NodeName string
// AuthorizationModes is a set of authorization modes used inside the cluster.
// If not specified, defaults to Node and RBAC, meaning both the node
// authorizer and RBAC are enabled.
AuthorizationModes []string
// NoTaintMaster will, if set, suppress the tainting of the
// master node allowing workloads to be run on it (e.g. in
// single node configurations).
NoTaintMaster bool
// Mark the controller and api server pods as privileged as some cloud
// controllers like openstack need escalated privileges under some conditions
// example - loading a config drive to fetch node information.
PrivilegedPods bool
// Token is used for establishing bidirectional trust between nodes and masters.
// Used for joining nodes in the cluster.
Token string
// TokenTTL defines the ttl for Token. Defaults to 24h.
TokenTTL *metav1.Duration
// TokenUsages describes the ways in which this token can be used.
TokenUsages []string
// Extra groups that this token will authenticate as when used for authentication
TokenGroups []string
// CRISocket is used to retrieve container runtime info.
CRISocket string
// APIServerExtraArgs is a set of extra flags to pass to the API Server or override
// default ones in form of <flagname>=<value>.
@ -104,9 +90,6 @@ type MasterConfiguration struct {
// CertificatesDir specifies where to store or look for all required certificates.
CertificatesDir string
// ImagePullPolicy for control plane images. Can be Always, IfNotPresent or Never.
ImagePullPolicy v1.PullPolicy
// ImageRepository is the container registry to pull control plane images from.
ImageRepository string
@ -124,29 +107,52 @@ type MasterConfiguration struct {
// FeatureGates enabled by the user.
FeatureGates map[string]bool
// The cluster name
ClusterName string
}
// API struct contains elements of API server address.
type API struct {
// AdvertiseAddress sets the IP address for the API server to advertise.
AdvertiseAddress string
// ControlPlaneEndpoint sets the DNS address for the API server
// ControlPlaneEndpoint sets a stable IP address or DNS name for the control plane; it
// can be a valid IP address or a RFC-1123 DNS subdomain, both with optional TCP port.
// In case the ControlPlaneEndpoint is not specified, the AdvertiseAddress + BindPort
// are used; in case the ControlPlaneEndpoint is specified but without a TCP port,
// the BindPort is used.
// Possible usages are:
// e.g. In an cluster with more than one control plane instances, this field should be
// assigned the address of the external load balancer in front of the
// control plane instances.
// e.g. in environments with enforced node recycling, the ControlPlaneEndpoint
// could be used for assigning a stable DNS to the control plane.
ControlPlaneEndpoint string
// BindPort sets the secure port for the API Server to bind to.
// Defaults to 6443.
BindPort int32
}
// TokenDiscovery contains elements needed for token discovery.
type TokenDiscovery struct {
// ID is the first part of a bootstrap token. Considered public information.
// It is used when referring to a token without leaking the secret part.
ID string
// Secret is the second part of a bootstrap token. Should only be shared
// with trusted parties.
Secret string
// TODO: Seems unused. Remove?
// Addresses []string
// NodeRegistrationOptions holds fields that relate to registering a new master or node to the cluster, either via "kubeadm init" or "kubeadm join"
type NodeRegistrationOptions struct {
// Name is the `.Metadata.Name` field of the Node API object that will be created in this `kubeadm init` or `kubeadm joiń` operation.
// This field is also used in the CommonName field of the kubelet's client certificate to the API server.
// Defaults to the hostname of the node if not provided.
Name string
// CRISocket is used to retrieve container runtime info. This information will be annotated to the Node API object, for later re-use
CRISocket string
// Taints specifies the taints the Node API object should be registered with. If this field is unset, i.e. nil, in the `kubeadm init` process
// it will be defaulted to []v1.Taint{'node-role.kubernetes.io/master=""'}. If you don't want to taint your master node, set this field to an
// empty slice, i.e. `taints: {}` in the YAML file. This field is solely used for Node registration.
Taints []v1.Taint
// KubeletExtraArgs passes through extra arguments to the kubelet. The arguments here are passed to the kubelet command line via the environment file
// kubeadm writes at runtime for the kubelet to source. This overrides the generic base-level configuration in the kubelet-config-1.X ConfigMap
// Flags have higher higher priority when parsing. These values are local and specific to the node kubeadm is executing on.
KubeletExtraArgs map[string]string
}
// Networking contains elements describing cluster's networking configuration.
@ -159,8 +165,75 @@ type Networking struct {
DNSDomain string
}
// BootstrapToken describes one bootstrap token, stored as a Secret in the cluster
// TODO: The BootstrapToken object should move out to either k8s.io/client-go or k8s.io/api in the future
// (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now.
type BootstrapToken struct {
// Token is used for establishing bidirectional trust between nodes and masters.
// Used for joining nodes in the cluster.
Token *BootstrapTokenString
// Description sets a human-friendly message why this token exists and what it's used
// for, so other administrators can know its purpose.
Description string
// TTL defines the time to live for this token. Defaults to 24h.
// Expires and TTL are mutually exclusive.
TTL *metav1.Duration
// Expires specifies the timestamp when this token expires. Defaults to being set
// dynamically at runtime based on the TTL. Expires and TTL are mutually exclusive.
Expires *metav1.Time
// Usages describes the ways in which this token can be used. Can by default be used
// for establishing bidirectional trust, but that can be changed here.
Usages []string
// Groups specifies the extra groups that this token will authenticate as when/if
// used for authentication
Groups []string
}
// Etcd contains elements describing Etcd configuration.
type Etcd struct {
// Local provides configuration knobs for configuring the local etcd instance
// Local and External are mutually exclusive
Local *LocalEtcd
// External describes how to connect to an external etcd cluster
// Local and External are mutually exclusive
External *ExternalEtcd
}
// Fuzz is a dummy function here to get the roundtrip tests working in cmd/kubeadm/app/apis/kubeadm/fuzzer working.
// As we split the monolith-etcd struct into two smaller pieces with pointers and they are mutually exclusive, roundtrip
// tests that randomize all values in this struct isn't feasible. Instead, we override the fuzzing function for .Etcd with
// this func by letting Etcd implement the fuzz.Interface interface. As this func does nothing, we rely on the values given
// in fuzzer/fuzzer.go for the roundtrip tests, which is exactly what we want.
// TODO: Remove this function when we remove the v1alpha1 API
func (e Etcd) Fuzz(c fuzz.Continue) {}
// LocalEtcd describes that kubeadm should run an etcd cluster locally
type LocalEtcd struct {
// Image specifies which container image to use for running etcd.
// If empty, automatically populated by kubeadm using the image
// repository and default etcd version.
Image string
// DataDir is the directory etcd will place its data.
// Defaults to "/var/lib/etcd".
DataDir string
// ExtraArgs are extra arguments provided to the etcd binary
// when run inside a static pod.
ExtraArgs map[string]string
// ServerCertSANs sets extra Subject Alternative Names for the etcd server signing cert.
ServerCertSANs []string
// PeerCertSANs sets extra Subject Alternative Names for the etcd peer signing cert.
PeerCertSANs []string
}
// ExternalEtcd describes an external etcd cluster
type ExternalEtcd struct {
// Endpoints of etcd members. Useful for using external etcd.
// If not provided, kubeadm will run etcd in a static pod.
Endpoints []string
@ -170,37 +243,6 @@ type Etcd struct {
CertFile string
// KeyFile is an SSL key file used to secure etcd communication.
KeyFile string
// DataDir is the directory etcd will place its data.
// Defaults to "/var/lib/etcd".
DataDir string
// ExtraArgs are extra arguments provided to the etcd binary
// when run inside a static pod.
ExtraArgs map[string]string
// Image specifies which container image to use for running etcd.
// If empty, automatically populated by kubeadm using the image
// repository and default etcd version.
Image string
// SelfHosted holds configuration for self-hosting etcd.
SelfHosted *SelfHostedEtcd
// ServerCertSANs sets extra Subject Alternative Names for the etcd server
// signing cert. This is currently used for the etcd static-pod.
ServerCertSANs []string
// PeerCertSANs sets extra Subject Alternative Names for the etcd peer
// signing cert. This is currently used for the etcd static-pod.
PeerCertSANs []string
}
// SelfHostedEtcd describes options required to configure self-hosted etcd.
type SelfHostedEtcd struct {
// CertificatesDir represents the directory where all etcd TLS assets are stored.
// Defaults to "/etc/kubernetes/pki/etcd".
CertificatesDir string
// ClusterServiceName is the name of the service that load balances the etcd cluster.
ClusterServiceName string
// EtcdVersion is the version of etcd running in the cluster.
EtcdVersion string
// OperatorVersion is the version of the etcd-operator to use.
OperatorVersion string
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -210,6 +252,9 @@ type SelfHostedEtcd struct {
type NodeConfiguration struct {
metav1.TypeMeta
// NodeRegistration holds fields that relate to registering the new master node to the cluster
NodeRegistration NodeRegistrationOptions
// CACertPath is the path to the SSL certificate authority used to
// secure comunications between node and master.
// Defaults to "/etc/kubernetes/pki/ca.crt".
@ -224,16 +269,15 @@ type NodeConfiguration struct {
// will be fetched. Currently we only pay attention to one API server but
// hope to support >1 in the future.
DiscoveryTokenAPIServers []string
// NodeName is the name of the node to join the cluster. Defaults
// to the name of the host.
NodeName string
// DiscoveryTimeout modifies the discovery timeout
DiscoveryTimeout *metav1.Duration
// TLSBootstrapToken is a token used for TLS bootstrapping.
// Defaults to Token.
TLSBootstrapToken string
// Token is used for both discovery and TLS bootstrapping.
Token string
// CRISocket is used to retrieve container runtime info.
CRISocket string
// The cluster name
ClusterName string
// DiscoveryTokenCACertHashes specifies a set of public key pins to verify
// when token-based discovery is used. The root CA found during discovery
@ -281,6 +325,10 @@ type HostPathMount struct {
HostPath string
// MountPath is the path inside the pod where hostPath will be mounted.
MountPath string
// Writable controls write access to the volume
Writable bool
// PathType is the type of the HostPath.
PathType v1.HostPathType
}
// KubeProxy contains elements describing the proxy configuration.
@ -298,3 +346,50 @@ type AuditPolicyConfiguration struct {
LogMaxAge *int32
//TODO(chuckha) add other options for audit policy.
}
// CommonConfiguration defines the list of common configuration elements and the getter
// methods that must exist for both the MasterConfiguration and NodeConfiguration objects.
// This is used internally to deduplicate the kubeadm preflight checks.
type CommonConfiguration interface {
GetCRISocket() string
GetNodeName() string
GetKubernetesVersion() string
}
// GetCRISocket will return the CRISocket that is defined for the MasterConfiguration.
// This is used internally to deduplicate the kubeadm preflight checks.
func (cfg *MasterConfiguration) GetCRISocket() string {
return cfg.NodeRegistration.CRISocket
}
// GetNodeName will return the NodeName that is defined for the MasterConfiguration.
// This is used internally to deduplicate the kubeadm preflight checks.
func (cfg *MasterConfiguration) GetNodeName() string {
return cfg.NodeRegistration.Name
}
// GetKubernetesVersion will return the KubernetesVersion that is defined for the MasterConfiguration.
// This is used internally to deduplicate the kubeadm preflight checks.
func (cfg *MasterConfiguration) GetKubernetesVersion() string {
return cfg.KubernetesVersion
}
// GetCRISocket will return the CRISocket that is defined for the NodeConfiguration.
// This is used internally to deduplicate the kubeadm preflight checks.
func (cfg *NodeConfiguration) GetCRISocket() string {
return cfg.NodeRegistration.CRISocket
}
// GetNodeName will return the NodeName that is defined for the NodeConfiguration.
// This is used internally to deduplicate the kubeadm preflight checks.
func (cfg *NodeConfiguration) GetNodeName() string {
return cfg.NodeRegistration.Name
}
// GetKubernetesVersion will return an empty string since KubernetesVersion is not a
// defined property for NodeConfiguration. This will just cause the regex validation
// of the defined version to be skipped during the preflight checks.
// This is used internally to deduplicate the kubeadm preflight checks.
func (cfg *NodeConfiguration) GetKubernetesVersion() string {
return ""
}

View File

@ -1,12 +1,14 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"conversion.go",
"defaults.go",
"doc.go",
"register.go",
"types.go",
"upgrade.go",
"zz_generated.conversion.go",
"zz_generated.deepcopy.go",
"zz_generated.defaults.go",
@ -51,16 +53,19 @@ 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",
"//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library",
"//pkg/kubelet/apis/kubeletconfig/v1beta1:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library",
"//pkg/util/pointer:go_default_library",
"//vendor/github.com/ugorji/go/codec: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/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion: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",
],
)
@ -77,3 +82,21 @@ filegroup(
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["upgrade_test.go"],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library"],
)
go_test(
name = "go_default_xtest",
srcs = ["conversion_test.go"],
deps = [
":go_default_library",
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)

View File

@ -0,0 +1,226 @@
/*
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 v1alpha1
import (
"fmt"
"reflect"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
func addConversionFuncs(scheme *runtime.Scheme) error {
// Add non-generated conversion functions
err := scheme.AddConversionFuncs(
Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration,
Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration,
Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration,
Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration,
Convert_v1alpha1_Etcd_To_kubeadm_Etcd,
Convert_kubeadm_Etcd_To_v1alpha1_Etcd,
)
if err != nil {
return err
}
return nil
}
// Upgrades below
func Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in *MasterConfiguration, out *kubeadm.MasterConfiguration, s conversion.Scope) error {
if err := autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in, out, s); err != nil {
return err
}
UpgradeCloudProvider(in, out)
UpgradeAuthorizationModes(in, out)
UpgradeNodeRegistrationOptionsForMaster(in, out)
if err := UpgradeBootstrapTokens(in, out); err != nil {
return err
}
// We don't support migrating information from the .PrivilegedPods field which was removed in v1alpha2
// We don't support migrating information from the .ImagePullPolicy field which was removed in v1alpha2
return nil
}
func Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in *NodeConfiguration, out *kubeadm.NodeConfiguration, s conversion.Scope) error {
if err := autoConvert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in, out, s); err != nil {
return err
}
// .NodeName has moved to .NodeRegistration.Name
out.NodeRegistration.Name = in.NodeName
// .CRISocket has moved to .NodeRegistration.CRISocket
out.NodeRegistration.CRISocket = in.CRISocket
return nil
}
func Convert_v1alpha1_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conversion.Scope) error {
if err := autoConvert_v1alpha1_Etcd_To_kubeadm_Etcd(in, out, s); err != nil {
return err
}
// The .Etcd schema changed between v1alpha1 and v1alpha2 API types. The change was to basically only split up the fields into two sub-structs, which can be seen here
if len(in.Endpoints) != 0 {
out.External = &kubeadm.ExternalEtcd{
Endpoints: in.Endpoints,
CAFile: in.CAFile,
CertFile: in.CertFile,
KeyFile: in.KeyFile,
}
} else {
out.Local = &kubeadm.LocalEtcd{
Image: in.Image,
DataDir: in.DataDir,
ExtraArgs: in.ExtraArgs,
ServerCertSANs: in.ServerCertSANs,
PeerCertSANs: in.PeerCertSANs,
}
}
// No need to transfer information about .Etcd.Selfhosted to v1alpha2
return nil
}
// UpgradeCloudProvider handles the removal of .CloudProvider as smoothly as possible
func UpgradeCloudProvider(in *MasterConfiguration, out *kubeadm.MasterConfiguration) {
if len(in.CloudProvider) != 0 {
if out.APIServerExtraArgs == nil {
out.APIServerExtraArgs = map[string]string{}
}
if out.ControllerManagerExtraArgs == nil {
out.ControllerManagerExtraArgs = map[string]string{}
}
if out.NodeRegistration.KubeletExtraArgs == nil {
out.NodeRegistration.KubeletExtraArgs = map[string]string{}
}
out.APIServerExtraArgs["cloud-provider"] = in.CloudProvider
out.ControllerManagerExtraArgs["cloud-provider"] = in.CloudProvider
out.NodeRegistration.KubeletExtraArgs["cloud-provider"] = in.CloudProvider
}
}
func UpgradeAuthorizationModes(in *MasterConfiguration, out *kubeadm.MasterConfiguration) {
// If .AuthorizationModes was set to something else than the default, preserve the information via extraargs
if !reflect.DeepEqual(in.AuthorizationModes, strings.Split(DefaultAuthorizationModes, ",")) {
if out.APIServerExtraArgs == nil {
out.APIServerExtraArgs = map[string]string{}
}
out.APIServerExtraArgs["authorization-mode"] = strings.Join(in.AuthorizationModes, ",")
}
}
func UpgradeNodeRegistrationOptionsForMaster(in *MasterConfiguration, out *kubeadm.MasterConfiguration) {
// .NodeName has moved to .NodeRegistration.Name
out.NodeRegistration.Name = in.NodeName
// .CRISocket has moved to .NodeRegistration.CRISocket
out.NodeRegistration.CRISocket = in.CRISocket
// Transfer the information from .NoTaintMaster to the new layout
if in.NoTaintMaster {
out.NodeRegistration.Taints = []v1.Taint{}
} else {
out.NodeRegistration.Taints = []v1.Taint{constants.MasterTaint}
}
}
// UpgradeBootstrapTokens should create at least one empty bootstrap token in the out config.
func UpgradeBootstrapTokens(in *MasterConfiguration, out *kubeadm.MasterConfiguration) error {
bts, err := kubeadm.NewBootstrapTokenString(in.Token)
// Ignore the error if the incoming token was empty.
if err != nil && in.Token != "" {
return fmt.Errorf("can't parse .Token, and hence can't convert v1alpha1 API to a newer version: %v", err)
}
out.BootstrapTokens = []kubeadm.BootstrapToken{
{
Token: bts,
TTL: in.TokenTTL,
Usages: in.TokenUsages,
Groups: in.TokenGroups,
},
}
return nil
}
// Downgrades below
// This downgrade path IS NOT SUPPORTED. This is just here for roundtripping purposes at the moment.
func Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in *kubeadm.MasterConfiguration, out *MasterConfiguration, s conversion.Scope) error {
if err := autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in, out, s); err != nil {
return err
}
// Converting from newer API version to an older API version isn't supported. This is here only for the roundtrip tests meanwhile.
out.NodeName = in.NodeRegistration.Name
out.CRISocket = in.NodeRegistration.CRISocket
out.NoTaintMaster = in.NodeRegistration.Taints != nil && len(in.NodeRegistration.Taints) == 0
if len(in.BootstrapTokens) > 0 {
out.Token = in.BootstrapTokens[0].Token.String()
out.TokenTTL = in.BootstrapTokens[0].TTL
out.TokenUsages = in.BootstrapTokens[0].Usages
out.TokenGroups = in.BootstrapTokens[0].Groups
}
return nil
}
// This downgrade path IS NOT SUPPORTED. This is just here for roundtripping purposes at the moment.
func Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kubeadm.NodeConfiguration, out *NodeConfiguration, s conversion.Scope) error {
if err := autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in, out, s); err != nil {
return err
}
// Converting from newer API version to an older API version isn't supported. This is here only for the roundtrip tests meanwhile.
out.NodeName = in.NodeRegistration.Name
out.CRISocket = in.NodeRegistration.CRISocket
return nil
}
// This downgrade path IS NOT SUPPORTED. This is just here for roundtripping purposes at the moment.
func Convert_kubeadm_Etcd_To_v1alpha1_Etcd(in *kubeadm.Etcd, out *Etcd, s conversion.Scope) error {
if err := autoConvert_kubeadm_Etcd_To_v1alpha1_Etcd(in, out, s); err != nil {
return err
}
if in.External != nil {
out.Endpoints = in.External.Endpoints
out.CAFile = in.External.CAFile
out.CertFile = in.External.CertFile
out.KeyFile = in.External.KeyFile
} else {
out.Image = in.Local.Image
out.DataDir = in.Local.DataDir
out.ExtraArgs = in.Local.ExtraArgs
out.ServerCertSANs = in.Local.ServerCertSANs
out.PeerCertSANs = in.Local.PeerCertSANs
}
return nil
}

View File

@ -0,0 +1,103 @@
/*
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 v1alpha1_test
import (
"reflect"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
)
func TestUpgradeBootstrapTokens(t *testing.T) {
testcases := []struct {
name string
in *v1alpha1.MasterConfiguration
expectedOut *kubeadm.MasterConfiguration
expectError bool
}{
{
name: "empty configs should create at least one token",
in: &v1alpha1.MasterConfiguration{},
expectedOut: &kubeadm.MasterConfiguration{
BootstrapTokens: []kubeadm.BootstrapToken{
{
Token: nil,
},
},
},
expectError: false,
},
{
name: "fail at parsing incoming token",
in: &v1alpha1.MasterConfiguration{
Token: "some fake token",
},
expectError: true,
},
{
name: "input has values",
in: &v1alpha1.MasterConfiguration{
Token: "abcdef.abcdefghijklmnop",
TokenTTL: &metav1.Duration{
Duration: time.Duration(10 * time.Hour),
},
TokenUsages: []string{"action"},
TokenGroups: []string{"group", "group2"},
},
expectedOut: &kubeadm.MasterConfiguration{
BootstrapTokens: []kubeadm.BootstrapToken{
{
Token: &kubeadm.BootstrapTokenString{
ID: "abcdef",
Secret: "abcdefghijklmnop",
},
TTL: &metav1.Duration{
Duration: time.Duration(10 * time.Hour),
},
Usages: []string{"action"},
Groups: []string{"group", "group2"},
},
},
},
expectError: false,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
out := &kubeadm.MasterConfiguration{}
err := v1alpha1.UpgradeBootstrapTokens(tc.in, out)
if tc.expectError {
if err == nil {
t.Fatal("expected an error but did not get one.")
}
// do not continue if we got an expected error
return
}
if !reflect.DeepEqual(out.BootstrapTokens, tc.expectedOut.BootstrapTokens) {
t.Fatalf("\nexpected: %v\ngot: %v", tc.expectedOut.BootstrapTokens, out.BootstrapTokens)
}
})
}
}

View File

@ -19,15 +19,16 @@ package v1alpha1
import (
"net/url"
"strings"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
kubeletconfigv1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
kubeproxyscheme "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/scheme"
kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
utilpointer "k8s.io/kubernetes/pkg/util/pointer"
)
const (
@ -38,7 +39,7 @@ const (
// DefaultClusterDNSIP defines default DNS IP
DefaultClusterDNSIP = "10.96.0.10"
// DefaultKubernetesVersion defines default kubernetes version
DefaultKubernetesVersion = "stable-1.9"
DefaultKubernetesVersion = "stable-1.11"
// DefaultAPIBindPort defines default API port
DefaultAPIBindPort = 6443
// DefaultAuthorizationModes defines default authorization modes
@ -51,6 +52,8 @@ const (
DefaultManifestsDir = "/etc/kubernetes/manifests"
// DefaultCRISocket defines the default cri socket
DefaultCRISocket = "/var/run/dockershim.sock"
// DefaultClusterName defines the default cluster name
DefaultClusterName = "kubernetes"
// DefaultEtcdDataDir defines default location of etcd where static pods will save data to
DefaultEtcdDataDir = "/var/lib/etcd"
@ -68,6 +71,9 @@ const (
DefaultProxyBindAddressv6 = "::"
// KubeproxyKubeConfigFileName defines the file name for the kube-proxy's KubeConfig file
KubeproxyKubeConfigFileName = "/var/lib/kube-proxy/kubeconfig.conf"
// DefaultDiscoveryTimeout specifies the default discovery timeout for kubeadm (used unless one is specified in the NodeConfiguration)
DefaultDiscoveryTimeout = 5 * time.Minute
)
var (
@ -132,10 +138,12 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
obj.Etcd.DataDir = DefaultEtcdDataDir
}
SetDefaultsEtcdSelfHosted(obj)
if features.Enabled(obj.FeatureGates, features.DynamicKubeletConfig) {
SetDefaults_KubeletConfiguration(obj)
if obj.ClusterName == "" {
obj.ClusterName = DefaultClusterName
}
SetDefaultsEtcdSelfHosted(obj)
SetDefaults_KubeletConfiguration(obj)
SetDefaults_ProxyConfiguration(obj)
SetDefaults_AuditPolicyConfiguration(obj)
}
@ -177,6 +185,14 @@ func SetDefaults_NodeConfiguration(obj *NodeConfiguration) {
obj.DiscoveryFile = u.Path
}
}
if obj.DiscoveryTimeout == nil {
obj.DiscoveryTimeout = &metav1.Duration{
Duration: DefaultDiscoveryTimeout,
}
}
if obj.ClusterName == "" {
obj.ClusterName = DefaultClusterName
}
}
// SetDefaultsEtcdSelfHosted sets defaults for self-hosted etcd if used
@ -217,15 +233,32 @@ func SetDefaults_KubeletConfiguration(obj *MasterConfiguration) {
}
}
if obj.KubeletConfiguration.BaseConfig.ClusterDomain == "" {
obj.KubeletConfiguration.BaseConfig.ClusterDomain = DefaultServiceDNSDomain
}
if obj.KubeletConfiguration.BaseConfig.Authorization.Mode == "" {
obj.KubeletConfiguration.BaseConfig.Authorization.Mode = kubeletconfigv1beta1.KubeletAuthorizationModeWebhook
}
if obj.KubeletConfiguration.BaseConfig.Authentication.X509.ClientCAFile == "" {
obj.KubeletConfiguration.BaseConfig.Authentication.X509.ClientCAFile = DefaultCACertPath
obj.KubeletConfiguration.BaseConfig.ClusterDomain = obj.Networking.DNSDomain
}
// Enforce security-related kubelet options
// Require all clients to the kubelet API to have client certs signed by the cluster CA
obj.KubeletConfiguration.BaseConfig.Authentication.X509.ClientCAFile = DefaultCACertPath
obj.KubeletConfiguration.BaseConfig.Authentication.Anonymous.Enabled = utilpointer.BoolPtr(false)
// On every client request to the kubelet API, execute a webhook (SubjectAccessReview request) to the API server
// and ask it whether the client is authorized to access the kubelet API
obj.KubeletConfiguration.BaseConfig.Authorization.Mode = kubeletconfigv1beta1.KubeletAuthorizationModeWebhook
// Let clients using other authentication methods like ServiceAccount tokens also access the kubelet API
obj.KubeletConfiguration.BaseConfig.Authentication.Webhook.Enabled = utilpointer.BoolPtr(true)
// Disable the readonly port of the kubelet, in order to not expose unnecessary information
obj.KubeletConfiguration.BaseConfig.ReadOnlyPort = 0
// Enables client certificate rotation for the kubelet
obj.KubeletConfiguration.BaseConfig.RotateCertificates = true
// Serve a /healthz webserver on localhost:10248 that kubeadm can talk to
obj.KubeletConfiguration.BaseConfig.HealthzBindAddress = "127.0.0.1"
obj.KubeletConfiguration.BaseConfig.HealthzPort = utilpointer.Int32Ptr(10248)
scheme, _, _ := kubeletscheme.NewSchemeAndCodecs()
if scheme != nil {
scheme.Default(obj.KubeletConfiguration.BaseConfig)

View File

@ -43,7 +43,7 @@ func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs, addConversionFuncs)
}
// Kind takes an unqualified kind and returns a Group qualified GroupKind

View File

@ -116,13 +116,26 @@ type MasterConfiguration struct {
// FeatureGates enabled by the user.
FeatureGates map[string]bool `json:"featureGates,omitempty"`
// The cluster name
ClusterName string `json:"clusterName,omitempty"`
}
// API struct contains elements of API server address.
type API struct {
// AdvertiseAddress sets the IP address for the API server to advertise.
AdvertiseAddress string `json:"advertiseAddress"`
// ControlPlaneEndpoint sets the DNS address for the API server
// ControlPlaneEndpoint sets a stable IP address or DNS name for the control plane; it
// can be a valid IP address or a RFC-1123 DNS subdomain, both with optional TCP port.
// In case the ControlPlaneEndpoint is not specified, the AdvertiseAddress + BindPort
// are used; in case the ControlPlaneEndpoint is specified but without a TCP port,
// the BindPort is used.
// Possible usages are:
// e.g. In an cluster with more than one control plane instances, this field should be
// assigned the address of the external load balancer in front of the
// control plane instances.
// e.g. in environments with enforced node recycling, the ControlPlaneEndpoint
// could be used for assigning a stable DNS to the control plane.
ControlPlaneEndpoint string `json:"controlPlaneEndpoint"`
// BindPort sets the secure port for the API Server to bind to.
// Defaults to 6443.
@ -214,6 +227,8 @@ type NodeConfiguration struct {
// will be fetched. Currently we only pay attention to one API server but
// hope to support >1 in the future.
DiscoveryTokenAPIServers []string `json:"discoveryTokenAPIServers,omitempty"`
// DiscoveryTimeout modifies the discovery timeout
DiscoveryTimeout *metav1.Duration `json:"discoveryTimeout,omitempty"`
// NodeName is the name of the node to join the cluster. Defaults
// to the name of the host.
NodeName string `json:"nodeName"`
@ -224,6 +239,8 @@ type NodeConfiguration struct {
Token string `json:"token"`
// CRISocket is used to retrieve container runtime info.
CRISocket string `json:"criSocket,omitempty"`
// ClusterName is the name for the cluster in kubeconfig.
ClusterName string `json:"clusterName,omitempty"`
// DiscoveryTokenCACertHashes specifies a set of public key pins to verify
// when token-based discovery is used. The root CA found during discovery
@ -259,6 +276,10 @@ type HostPathMount struct {
HostPath string `json:"hostPath"`
// MountPath is the path inside the pod where hostPath will be mounted.
MountPath string `json:"mountPath"`
// Writable controls write access to the volume
Writable bool `json:"writable,omitempty"`
// PathType is the type of the HostPath.
PathType v1.HostPathType `json:"pathType,omitempty"`
}
// KubeProxy contains elements describing the proxy configuration.

View File

@ -0,0 +1,98 @@
/*
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 v1alpha1
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
"github.com/ugorji/go/codec"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
)
type configMutationFunc func(map[string]interface{}) error
// These migrations are a stop-gap until we get a properly-versioned configuration file for MasterConfiguration.
// https://github.com/kubernetes/kubeadm/issues/750
var migrations = map[string][]configMutationFunc{
"MasterConfiguration": {
proxyFeatureListToMap,
},
}
// Migrate takes a map representing a config file and an object to decode into.
// The map is transformed into a format suitable for encoding into the supplied object, then serialised and decoded.
func Migrate(in map[string]interface{}, obj runtime.Object, codecs serializer.CodecFactory) error {
kind := reflect.TypeOf(obj).Elem().Name()
migrationsForKind := migrations[kind]
for _, m := range migrationsForKind {
err := m(in)
if err != nil {
return err
}
}
// Use codec instead of encoding/json to handle map[interface{}]interface{}
handle := &codec.JsonHandle{}
buf := new(bytes.Buffer)
if err := codec.NewEncoder(buf, handle).Encode(in); err != nil {
return fmt.Errorf("couldn't json encode object: %v", err)
}
return runtime.DecodeInto(codecs.UniversalDecoder(), buf.Bytes(), obj)
}
func proxyFeatureListToMap(m map[string]interface{}) error {
featureGatePath := []string{"kubeProxy", "config", "featureGates"}
// If featureGatePath is already a map, we don't need to do anything.
_, _, err := unstructured.NestedMap(m, featureGatePath...)
if err == nil {
return nil
}
gates, _, err := unstructured.NestedString(m, featureGatePath...)
if err != nil {
return fmt.Errorf("couldn't get featureGates: %v", err)
}
gateMap := make(map[string]interface{})
for _, gate := range strings.Split(gates, ",") {
if gate == "" {
continue
}
parts := strings.SplitN(gate, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("unparsable kubeproxy feature gate %q", gate)
}
val, err := strconv.ParseBool(parts[1])
if err != nil {
return fmt.Errorf("unparsable kubeproxy feature gate %q: %v", gate, err)
}
gateMap[parts[0]] = val
}
unstructured.SetNestedMap(m, gateMap, featureGatePath...)
return nil
}

View File

@ -0,0 +1,117 @@
/*
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 v1alpha1
import (
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestProxyFeatureListToMap(t *testing.T) {
cases := []struct {
name string
featureGates interface{}
expected map[string]interface{}
shouldError bool
}{
{
name: "multiple features",
featureGates: "feature1=true,feature2=false",
expected: map[string]interface{}{
"feature1": true,
"feature2": false,
},
},
{
name: "single feature",
featureGates: "feature1=true",
expected: map[string]interface{}{
"feature1": true,
},
},
{
name: "already a map",
featureGates: map[string]interface{}{
"feature1": true,
},
expected: map[string]interface{}{
"feature1": true,
},
},
{
name: "single feature",
featureGates: "",
expected: map[string]interface{}{},
},
{
name: "malformed string",
featureGates: "test,",
shouldError: true,
},
}
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
cfg := map[string]interface{}{
"kubeProxy": map[string]interface{}{
"config": map[string]interface{}{
"featureGates": testCase.featureGates,
},
},
}
err := proxyFeatureListToMap(cfg)
if testCase.shouldError {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
gates, ok, err := unstructured.NestedMap(cfg, "kubeProxy", "config", "featureGates")
if !ok {
t.Errorf("missing map keys in nested map")
}
if err != nil {
t.Errorf("unexpected error in map: %v", err)
}
if len(testCase.expected) != len(gates) {
t.Errorf("expected feature gate size %d, got %d", len(testCase.expected), len(gates))
}
for k, v := range testCase.expected {
gateVal, ok := gates[k]
if !ok {
t.Errorf("featureGates missing key %q", k)
continue
}
if v != gateVal {
t.Errorf("expected value %v, got %v", v, gateVal)
}
}
})
}
}

View File

@ -1,7 +1,7 @@
// +build !ignore_autogenerated
/*
Copyright 2018 The Kubernetes Authors.
Copyright 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.
@ -23,8 +23,8 @@ package v1alpha1
import (
unsafe "unsafe"
core_v1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/api/core/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
@ -58,10 +58,6 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_kubeadm_Networking_To_v1alpha1_Networking,
Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration,
Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration,
Convert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd,
Convert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd,
Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery,
Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery,
)
}
@ -114,47 +110,31 @@ func Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha1_AuditPolicyConfigurati
}
func autoConvert_v1alpha1_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conversion.Scope) error {
out.Endpoints = *(*[]string)(unsafe.Pointer(&in.Endpoints))
out.CAFile = in.CAFile
out.CertFile = in.CertFile
out.KeyFile = in.KeyFile
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.Image = in.Image
out.SelfHosted = (*kubeadm.SelfHostedEtcd)(unsafe.Pointer(in.SelfHosted))
out.ServerCertSANs = *(*[]string)(unsafe.Pointer(&in.ServerCertSANs))
out.PeerCertSANs = *(*[]string)(unsafe.Pointer(&in.PeerCertSANs))
// WARNING: in.Endpoints requires manual conversion: does not exist in peer-type
// WARNING: in.CAFile requires manual conversion: does not exist in peer-type
// WARNING: in.CertFile requires manual conversion: does not exist in peer-type
// WARNING: in.KeyFile requires manual conversion: does not exist in peer-type
// WARNING: in.DataDir requires manual conversion: does not exist in peer-type
// WARNING: in.ExtraArgs requires manual conversion: does not exist in peer-type
// WARNING: in.Image requires manual conversion: does not exist in peer-type
// WARNING: in.SelfHosted requires manual conversion: does not exist in peer-type
// WARNING: in.ServerCertSANs requires manual conversion: does not exist in peer-type
// WARNING: in.PeerCertSANs requires manual conversion: does not exist in peer-type
return nil
}
// Convert_v1alpha1_Etcd_To_kubeadm_Etcd is an autogenerated conversion function.
func Convert_v1alpha1_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conversion.Scope) error {
return autoConvert_v1alpha1_Etcd_To_kubeadm_Etcd(in, out, s)
}
func autoConvert_kubeadm_Etcd_To_v1alpha1_Etcd(in *kubeadm.Etcd, out *Etcd, s conversion.Scope) error {
out.Endpoints = *(*[]string)(unsafe.Pointer(&in.Endpoints))
out.CAFile = in.CAFile
out.CertFile = in.CertFile
out.KeyFile = in.KeyFile
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.Image = in.Image
out.SelfHosted = (*SelfHostedEtcd)(unsafe.Pointer(in.SelfHosted))
out.ServerCertSANs = *(*[]string)(unsafe.Pointer(&in.ServerCertSANs))
out.PeerCertSANs = *(*[]string)(unsafe.Pointer(&in.PeerCertSANs))
// WARNING: in.Local requires manual conversion: does not exist in peer-type
// WARNING: in.External requires manual conversion: does not exist in peer-type
return nil
}
// Convert_kubeadm_Etcd_To_v1alpha1_Etcd is an autogenerated conversion function.
func Convert_kubeadm_Etcd_To_v1alpha1_Etcd(in *kubeadm.Etcd, out *Etcd, s conversion.Scope) error {
return autoConvert_kubeadm_Etcd_To_v1alpha1_Etcd(in, out, s)
}
func autoConvert_v1alpha1_HostPathMount_To_kubeadm_HostPathMount(in *HostPathMount, out *kubeadm.HostPathMount, s conversion.Scope) error {
out.Name = in.Name
out.HostPath = in.HostPath
out.MountPath = in.MountPath
out.Writable = in.Writable
out.PathType = v1.HostPathType(in.PathType)
return nil
}
@ -167,6 +147,8 @@ func autoConvert_kubeadm_HostPathMount_To_v1alpha1_HostPathMount(in *kubeadm.Hos
out.Name = in.Name
out.HostPath = in.HostPath
out.MountPath = in.MountPath
out.Writable = in.Writable
out.PathType = v1.HostPathType(in.PathType)
return nil
}
@ -232,16 +214,16 @@ func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in
return err
}
out.KubernetesVersion = in.KubernetesVersion
out.CloudProvider = in.CloudProvider
out.NodeName = in.NodeName
out.AuthorizationModes = *(*[]string)(unsafe.Pointer(&in.AuthorizationModes))
out.NoTaintMaster = in.NoTaintMaster
out.PrivilegedPods = in.PrivilegedPods
out.Token = in.Token
out.TokenTTL = (*v1.Duration)(unsafe.Pointer(in.TokenTTL))
out.TokenUsages = *(*[]string)(unsafe.Pointer(&in.TokenUsages))
out.TokenGroups = *(*[]string)(unsafe.Pointer(&in.TokenGroups))
out.CRISocket = in.CRISocket
// WARNING: in.CloudProvider requires manual conversion: does not exist in peer-type
// WARNING: in.NodeName requires manual conversion: does not exist in peer-type
// WARNING: in.AuthorizationModes requires manual conversion: does not exist in peer-type
// WARNING: in.NoTaintMaster requires manual conversion: does not exist in peer-type
// WARNING: in.PrivilegedPods requires manual conversion: does not exist in peer-type
// WARNING: in.Token requires manual conversion: does not exist in peer-type
// WARNING: in.TokenTTL requires manual conversion: does not exist in peer-type
// WARNING: in.TokenUsages requires manual conversion: does not exist in peer-type
// WARNING: in.TokenGroups requires manual conversion: does not exist in peer-type
// WARNING: in.CRISocket requires manual conversion: does not exist in peer-type
out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs))
out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs))
out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs))
@ -251,21 +233,19 @@ func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in
out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs))
out.CertificatesDir = in.CertificatesDir
out.ImageRepository = in.ImageRepository
out.ImagePullPolicy = core_v1.PullPolicy(in.ImagePullPolicy)
// WARNING: in.ImagePullPolicy requires manual conversion: does not exist in peer-type
out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage
if err := Convert_v1alpha1_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration(&in.AuditPolicyConfiguration, &out.AuditPolicyConfiguration, s); err != nil {
return err
}
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
out.ClusterName = in.ClusterName
return nil
}
// Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration is an autogenerated conversion function.
func Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in *MasterConfiguration, out *kubeadm.MasterConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in, out, s)
}
func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in *kubeadm.MasterConfiguration, out *MasterConfiguration, s conversion.Scope) error {
// WARNING: in.BootstrapTokens requires manual conversion: does not exist in peer-type
// WARNING: in.NodeRegistration requires manual conversion: does not exist in peer-type
if err := Convert_kubeadm_API_To_v1alpha1_API(&in.API, &out.API, s); err != nil {
return err
}
@ -282,16 +262,6 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in
return err
}
out.KubernetesVersion = in.KubernetesVersion
out.CloudProvider = in.CloudProvider
out.NodeName = in.NodeName
out.AuthorizationModes = *(*[]string)(unsafe.Pointer(&in.AuthorizationModes))
out.NoTaintMaster = in.NoTaintMaster
out.PrivilegedPods = in.PrivilegedPods
out.Token = in.Token
out.TokenTTL = (*v1.Duration)(unsafe.Pointer(in.TokenTTL))
out.TokenUsages = *(*[]string)(unsafe.Pointer(&in.TokenUsages))
out.TokenGroups = *(*[]string)(unsafe.Pointer(&in.TokenGroups))
out.CRISocket = in.CRISocket
out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs))
out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs))
out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs))
@ -300,7 +270,6 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in
out.SchedulerExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.SchedulerExtraVolumes))
out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs))
out.CertificatesDir = in.CertificatesDir
out.ImagePullPolicy = core_v1.PullPolicy(in.ImagePullPolicy)
out.ImageRepository = in.ImageRepository
// INFO: in.CIImageRepository opted out of conversion generation
out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage
@ -308,14 +277,10 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in
return err
}
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
out.ClusterName = in.ClusterName
return nil
}
// Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration is an autogenerated conversion function.
func Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in *kubeadm.MasterConfiguration, out *MasterConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in, out, s)
}
func autoConvert_v1alpha1_Networking_To_kubeadm_Networking(in *Networking, out *kubeadm.Networking, s conversion.Scope) error {
out.ServiceSubnet = in.ServiceSubnet
out.PodSubnet = in.PodSubnet
@ -345,85 +310,30 @@ func autoConvert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in *Nod
out.DiscoveryFile = in.DiscoveryFile
out.DiscoveryToken = in.DiscoveryToken
out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers))
out.NodeName = in.NodeName
out.DiscoveryTimeout = (*meta_v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout))
// WARNING: in.NodeName requires manual conversion: does not exist in peer-type
out.TLSBootstrapToken = in.TLSBootstrapToken
out.Token = in.Token
out.CRISocket = in.CRISocket
// WARNING: in.CRISocket requires manual conversion: does not exist in peer-type
out.ClusterName = in.ClusterName
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}
// Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration is an autogenerated conversion function.
func Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in *NodeConfiguration, out *kubeadm.NodeConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in, out, s)
}
func autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kubeadm.NodeConfiguration, out *NodeConfiguration, s conversion.Scope) error {
// WARNING: in.NodeRegistration requires manual conversion: does not exist in peer-type
out.CACertPath = in.CACertPath
out.DiscoveryFile = in.DiscoveryFile
out.DiscoveryToken = in.DiscoveryToken
out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers))
out.NodeName = in.NodeName
out.DiscoveryTimeout = (*meta_v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout))
out.TLSBootstrapToken = in.TLSBootstrapToken
out.Token = in.Token
out.CRISocket = in.CRISocket
out.ClusterName = in.ClusterName
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}
// Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration is an autogenerated conversion function.
func Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kubeadm.NodeConfiguration, out *NodeConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in, out, s)
}
func autoConvert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd(in *SelfHostedEtcd, out *kubeadm.SelfHostedEtcd, s conversion.Scope) error {
out.CertificatesDir = in.CertificatesDir
out.ClusterServiceName = in.ClusterServiceName
out.EtcdVersion = in.EtcdVersion
out.OperatorVersion = in.OperatorVersion
return nil
}
// Convert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd is an autogenerated conversion function.
func Convert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd(in *SelfHostedEtcd, out *kubeadm.SelfHostedEtcd, s conversion.Scope) error {
return autoConvert_v1alpha1_SelfHostedEtcd_To_kubeadm_SelfHostedEtcd(in, out, s)
}
func autoConvert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd(in *kubeadm.SelfHostedEtcd, out *SelfHostedEtcd, s conversion.Scope) error {
out.CertificatesDir = in.CertificatesDir
out.ClusterServiceName = in.ClusterServiceName
out.EtcdVersion = in.EtcdVersion
out.OperatorVersion = in.OperatorVersion
return nil
}
// Convert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd is an autogenerated conversion function.
func Convert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd(in *kubeadm.SelfHostedEtcd, out *SelfHostedEtcd, s conversion.Scope) error {
return autoConvert_kubeadm_SelfHostedEtcd_To_v1alpha1_SelfHostedEtcd(in, out, s)
}
func autoConvert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in *TokenDiscovery, out *kubeadm.TokenDiscovery, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
return nil
}
// Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery is an autogenerated conversion function.
func Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in *TokenDiscovery, out *kubeadm.TokenDiscovery, s conversion.Scope) error {
return autoConvert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in, out, s)
}
func autoConvert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery(in *kubeadm.TokenDiscovery, out *TokenDiscovery, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
return nil
}
// Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery is an autogenerated conversion function.
func Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery(in *kubeadm.TokenDiscovery, out *TokenDiscovery, s conversion.Scope) error {
return autoConvert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery(in, out, s)
}

View File

@ -1,7 +1,7 @@
// +build !ignore_autogenerated
/*
Copyright 2018 The Kubernetes Authors.
Copyright 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.
@ -309,6 +309,15 @@ func (in *NodeConfiguration) DeepCopyInto(out *NodeConfiguration) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DiscoveryTimeout != nil {
in, out := &in.DiscoveryTimeout, &out.DiscoveryTimeout
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
**out = **in
}
}
if in.DiscoveryTokenCACertHashes != nil {
in, out := &in.DiscoveryTokenCACertHashes, &out.DiscoveryTokenCACertHashes
*out = make([]string, len(*in))

View File

@ -1,7 +1,7 @@
// +build !ignore_autogenerated
/*
Copyright 2018 The Kubernetes Authors.
Copyright 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.

View File

@ -0,0 +1,88 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"bootstraptokenstring.go",
"defaults.go",
"doc.go",
"register.go",
"types.go",
"zz_generated.conversion.go",
"zz_generated.deepcopy.go",
"zz_generated.defaults.go",
] + select({
"@io_bazel_rules_go//go/platform:android": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:darwin": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:linux": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:nacl": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:plan9": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:solaris": [
"defaults_unix.go",
],
"@io_bazel_rules_go//go/platform:windows": [
"defaults_windows.go",
],
"//conditions:default": [],
}),
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2",
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library",
"//pkg/kubelet/apis/kubeletconfig/v1beta1:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library",
"//pkg/util/pointer: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/conversion: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/client-go/tools/bootstrap/token/api:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/util: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"],
)
go_test(
name = "go_default_test",
srcs = ["bootstraptokenstring_test.go"],
embed = [":go_default_library"],
)

View File

@ -0,0 +1,90 @@
/*
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 v1alpha2 holds the external kubeadm API types of version v1alpha2
// Note: This file should be kept in sync with the similar one for the internal API
// TODO: The BootstrapTokenString object should move out to either k8s.io/client-go or k8s.io/api in the future
// (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now.
package v1alpha2
import (
"fmt"
"strings"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
)
// BootstrapTokenString is a token of the format abcdef.abcdef0123456789 that is used
// for both validation of the practically of the API server from a joining node's point
// of view and as an authentication method for the node in the bootstrap phase of
// "kubeadm join". This token is and should be short-lived
type BootstrapTokenString struct {
ID string
Secret string
}
// MarshalJSON implements the json.Marshaler interface.
func (bts BootstrapTokenString) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, bts.String())), nil
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (bts *BootstrapTokenString) UnmarshalJSON(b []byte) error {
// If the token is represented as "", just return quickly without an error
if len(b) == 0 {
return nil
}
// Remove unnecessary " characters coming from the JSON parser
token := strings.Replace(string(b), `"`, ``, -1)
// Convert the string Token to a BootstrapTokenString object
newbts, err := NewBootstrapTokenString(token)
if err != nil {
return err
}
bts.ID = newbts.ID
bts.Secret = newbts.Secret
return nil
}
// String returns the string representation of the BootstrapTokenString
func (bts BootstrapTokenString) String() string {
if len(bts.ID) > 0 && len(bts.Secret) > 0 {
return bootstraputil.TokenFromIDAndSecret(bts.ID, bts.Secret)
}
return ""
}
// NewBootstrapTokenString converts the given Bootstrap Token as a string
// to the BootstrapTokenString object used for serialization/deserialization
// and internal usage. It also automatically validates that the given token
// is of the right format
func NewBootstrapTokenString(token string) (*BootstrapTokenString, error) {
substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token)
// TODO: Add a constant for the 3 value here, and explain better why it's needed (other than because how the regexp parsin works)
if len(substrs) != 3 {
return nil, fmt.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern)
}
return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil
}
// NewBootstrapTokenStringFromIDAndSecret is a wrapper around NewBootstrapTokenString
// that allows the caller to specify the ID and Secret separately
func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) {
return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret))
}

View File

@ -0,0 +1,236 @@
/*
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 v1alpha2
import (
"encoding/json"
"fmt"
"reflect"
"testing"
)
func TestMarshalJSON(t *testing.T) {
var tests = []struct {
bts BootstrapTokenString
expected string
}{
{BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, `"abcdef.abcdef0123456789"`},
{BootstrapTokenString{ID: "foo", Secret: "bar"}, `"foo.bar"`},
{BootstrapTokenString{ID: "h", Secret: "b"}, `"h.b"`},
}
for _, rt := range tests {
b, err := json.Marshal(rt.bts)
if err != nil {
t.Fatalf("json.Marshal returned an unexpected error: %v", err)
}
if string(b) != rt.expected {
t.Errorf(
"failed BootstrapTokenString.MarshalJSON:\n\texpected: %s\n\t actual: %s",
rt.expected,
string(b),
)
}
}
}
func TestUnmarshalJSON(t *testing.T) {
var tests = []struct {
input string
bts *BootstrapTokenString
expectedError bool
}{
{`"f.s"`, &BootstrapTokenString{}, true},
{`"abcdef."`, &BootstrapTokenString{}, true},
{`"abcdef:abcdef0123456789"`, &BootstrapTokenString{}, true},
{`abcdef.abcdef0123456789`, &BootstrapTokenString{}, true},
{`"abcdef.abcdef0123456789`, &BootstrapTokenString{}, true},
{`"abcdef.ABCDEF0123456789"`, &BootstrapTokenString{}, true},
{`"abcdef.abcdef0123456789"`, &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, false},
{`"123456.aabbccddeeffgghh"`, &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}, false},
}
for _, rt := range tests {
newbts := &BootstrapTokenString{}
err := json.Unmarshal([]byte(rt.input), newbts)
if (err != nil) != rt.expectedError {
t.Errorf("failed BootstrapTokenString.UnmarshalJSON:\n\texpected error: %t\n\t actual error: %v", rt.expectedError, err)
} else if !reflect.DeepEqual(rt.bts, newbts) {
t.Errorf(
"failed BootstrapTokenString.UnmarshalJSON:\n\texpected: %v\n\t actual: %v",
rt.bts,
newbts,
)
}
}
}
func TestJSONRoundtrip(t *testing.T) {
var tests = []struct {
input string
bts *BootstrapTokenString
}{
{`"abcdef.abcdef0123456789"`, nil},
{"", &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}},
}
for _, rt := range tests {
if err := roundtrip(rt.input, rt.bts); err != nil {
t.Errorf("failed BootstrapTokenString JSON roundtrip with error: %v", err)
}
}
}
func roundtrip(input string, bts *BootstrapTokenString) error {
var b []byte
var err error
newbts := &BootstrapTokenString{}
// If string input was specified, roundtrip like this: string -> (unmarshal) -> object -> (marshal) -> string
if len(input) > 0 {
if err := json.Unmarshal([]byte(input), newbts); err != nil {
return fmt.Errorf("expected no unmarshal error, got error: %v", err)
}
if b, err = json.Marshal(newbts); err != nil {
return fmt.Errorf("expected no marshal error, got error: %v", err)
}
if input != string(b) {
return fmt.Errorf(
"expected token: %s\n\t actual: %s",
input,
string(b),
)
}
} else { // Otherwise, roundtrip like this: object -> (marshal) -> string -> (unmarshal) -> object
if b, err = json.Marshal(bts); err != nil {
return fmt.Errorf("expected no marshal error, got error: %v", err)
}
if err := json.Unmarshal(b, newbts); err != nil {
return fmt.Errorf("expected no unmarshal error, got error: %v", err)
}
if !reflect.DeepEqual(bts, newbts) {
return fmt.Errorf(
"expected object: %v\n\t actual: %v",
bts,
newbts,
)
}
}
return nil
}
func TestTokenFromIDAndSecret(t *testing.T) {
var tests = []struct {
bts BootstrapTokenString
expected string
}{
{BootstrapTokenString{ID: "foo", Secret: "bar"}, "foo.bar"},
{BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, "abcdef.abcdef0123456789"},
{BootstrapTokenString{ID: "h", Secret: "b"}, "h.b"},
}
for _, rt := range tests {
actual := rt.bts.String()
if actual != rt.expected {
t.Errorf(
"failed BootstrapTokenString.String():\n\texpected: %s\n\t actual: %s",
rt.expected,
actual,
)
}
}
}
func TestNewBootstrapTokenString(t *testing.T) {
var tests = []struct {
token string
expectedError bool
bts *BootstrapTokenString
}{
{token: "", expectedError: true, bts: nil},
{token: ".", expectedError: true, bts: nil},
{token: "1234567890123456789012", expectedError: true, bts: nil}, // invalid parcel size
{token: "12345.1234567890123456", expectedError: true, bts: nil}, // invalid parcel size
{token: ".1234567890123456", expectedError: true, bts: nil}, // invalid parcel size
{token: "123456.", expectedError: true, bts: nil}, // invalid parcel size
{token: "123456:1234567890.123456", expectedError: true, bts: nil}, // invalid separation
{token: "abcdef:1234567890123456", expectedError: true, bts: nil}, // invalid separation
{token: "Abcdef.1234567890123456", expectedError: true, bts: nil}, // invalid token id
{token: "123456.AABBCCDDEEFFGGHH", expectedError: true, bts: nil}, // invalid token secret
{token: "123456.AABBCCD-EEFFGGHH", expectedError: true, bts: nil}, // invalid character
{token: "abc*ef.1234567890123456", expectedError: true, bts: nil}, // invalid character
{token: "abcdef.1234567890123456", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "1234567890123456"}},
{token: "123456.aabbccddeeffgghh", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}},
{token: "abcdef.abcdef0123456789", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}},
{token: "123456.1234560123456789", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "1234560123456789"}},
}
for _, rt := range tests {
actual, err := NewBootstrapTokenString(rt.token)
if (err != nil) != rt.expectedError {
t.Errorf(
"failed NewBootstrapTokenString for the token %q\n\texpected error: %t\n\t actual error: %v",
rt.token,
rt.expectedError,
err,
)
} else if !reflect.DeepEqual(actual, rt.bts) {
t.Errorf(
"failed NewBootstrapTokenString for the token %q\n\texpected: %v\n\t actual: %v",
rt.token,
rt.bts,
actual,
)
}
}
}
func TestNewBootstrapTokenStringFromIDAndSecret(t *testing.T) {
var tests = []struct {
id, secret string
expectedError bool
bts *BootstrapTokenString
}{
{id: "", secret: "", expectedError: true, bts: nil},
{id: "1234567890123456789012", secret: "", expectedError: true, bts: nil}, // invalid parcel size
{id: "12345", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid parcel size
{id: "", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid parcel size
{id: "123456", secret: "", expectedError: true, bts: nil}, // invalid parcel size
{id: "Abcdef", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid token id
{id: "123456", secret: "AABBCCDDEEFFGGHH", expectedError: true, bts: nil}, // invalid token secret
{id: "123456", secret: "AABBCCD-EEFFGGHH", expectedError: true, bts: nil}, // invalid character
{id: "abc*ef", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid character
{id: "abcdef", secret: "1234567890123456", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "1234567890123456"}},
{id: "123456", secret: "aabbccddeeffgghh", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}},
{id: "abcdef", secret: "abcdef0123456789", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}},
{id: "123456", secret: "1234560123456789", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "1234560123456789"}},
}
for _, rt := range tests {
actual, err := NewBootstrapTokenStringFromIDAndSecret(rt.id, rt.secret)
if (err != nil) != rt.expectedError {
t.Errorf(
"failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected error: %t\n\t actual error: %v",
rt.id,
rt.secret,
rt.expectedError,
err,
)
} else if !reflect.DeepEqual(actual, rt.bts) {
t.Errorf(
"failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected: %v\n\t actual: %v",
rt.id,
rt.secret,
rt.bts,
actual,
)
}
}
}

View File

@ -0,0 +1,269 @@
/*
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 v1alpha2
import (
"net/url"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
kubeletconfigv1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
kubeproxyscheme "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/scheme"
kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
utilpointer "k8s.io/kubernetes/pkg/util/pointer"
)
const (
// DefaultServiceDNSDomain defines default cluster-internal domain name for Services and Pods
DefaultServiceDNSDomain = "cluster.local"
// DefaultServicesSubnet defines default service subnet range
DefaultServicesSubnet = "10.96.0.0/12"
// DefaultClusterDNSIP defines default DNS IP
DefaultClusterDNSIP = "10.96.0.10"
// DefaultKubernetesVersion defines default kubernetes version
DefaultKubernetesVersion = "stable-1.11"
// DefaultAPIBindPort defines default API port
DefaultAPIBindPort = 6443
// DefaultCertificatesDir defines default certificate directory
DefaultCertificatesDir = "/etc/kubernetes/pki"
// DefaultImageRepository defines default image registry
DefaultImageRepository = "k8s.gcr.io"
// DefaultManifestsDir defines default manifests directory
DefaultManifestsDir = "/etc/kubernetes/manifests"
// DefaultCRISocket defines the default cri socket
DefaultCRISocket = "/var/run/dockershim.sock"
// DefaultClusterName defines the default cluster name
DefaultClusterName = "kubernetes"
// DefaultEtcdDataDir defines default location of etcd where static pods will save data to
DefaultEtcdDataDir = "/var/lib/etcd"
// DefaultProxyBindAddressv4 is the default bind address when the advertise address is v4
DefaultProxyBindAddressv4 = "0.0.0.0"
// DefaultProxyBindAddressv6 is the default bind address when the advertise address is v6
DefaultProxyBindAddressv6 = "::"
// KubeproxyKubeConfigFileName defines the file name for the kube-proxy's KubeConfig file
KubeproxyKubeConfigFileName = "/var/lib/kube-proxy/kubeconfig.conf"
// DefaultDiscoveryTimeout specifies the default discovery timeout for kubeadm (used unless one is specified in the NodeConfiguration)
DefaultDiscoveryTimeout = 5 * time.Minute
)
var (
// DefaultAuditPolicyLogMaxAge is defined as a var so its address can be taken
// It is the number of days to store audit logs
DefaultAuditPolicyLogMaxAge = int32(2)
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}
// SetDefaults_MasterConfiguration assigns default values to Master node
func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
if obj.KubernetesVersion == "" {
obj.KubernetesVersion = DefaultKubernetesVersion
}
if obj.API.BindPort == 0 {
obj.API.BindPort = DefaultAPIBindPort
}
if obj.Networking.ServiceSubnet == "" {
obj.Networking.ServiceSubnet = DefaultServicesSubnet
}
if obj.Networking.DNSDomain == "" {
obj.Networking.DNSDomain = DefaultServiceDNSDomain
}
if obj.CertificatesDir == "" {
obj.CertificatesDir = DefaultCertificatesDir
}
if obj.ImageRepository == "" {
obj.ImageRepository = DefaultImageRepository
}
if obj.ClusterName == "" {
obj.ClusterName = DefaultClusterName
}
SetDefaults_NodeRegistrationOptions(&obj.NodeRegistration)
SetDefaults_BootstrapTokens(obj)
SetDefaults_KubeletConfiguration(obj)
SetDefaults_Etcd(obj)
SetDefaults_ProxyConfiguration(obj)
SetDefaults_AuditPolicyConfiguration(obj)
}
// SetDefaults_Etcd assigns default values for the Proxy
func SetDefaults_Etcd(obj *MasterConfiguration) {
if obj.Etcd.External == nil && obj.Etcd.Local == nil {
obj.Etcd.Local = &LocalEtcd{}
}
if obj.Etcd.Local != nil {
if obj.Etcd.Local.DataDir == "" {
obj.Etcd.Local.DataDir = DefaultEtcdDataDir
}
}
}
// SetDefaults_ProxyConfiguration assigns default values for the Proxy
func SetDefaults_ProxyConfiguration(obj *MasterConfiguration) {
if obj.KubeProxy.Config == nil {
obj.KubeProxy.Config = &kubeproxyconfigv1alpha1.KubeProxyConfiguration{}
}
if obj.KubeProxy.Config.ClusterCIDR == "" && obj.Networking.PodSubnet != "" {
obj.KubeProxy.Config.ClusterCIDR = obj.Networking.PodSubnet
}
if obj.KubeProxy.Config.ClientConnection.KubeConfigFile == "" {
obj.KubeProxy.Config.ClientConnection.KubeConfigFile = KubeproxyKubeConfigFileName
}
kubeproxyscheme.Scheme.Default(obj.KubeProxy.Config)
}
// SetDefaults_NodeConfiguration assigns default values to a regular node
func SetDefaults_NodeConfiguration(obj *NodeConfiguration) {
if obj.CACertPath == "" {
obj.CACertPath = DefaultCACertPath
}
if len(obj.TLSBootstrapToken) == 0 {
obj.TLSBootstrapToken = obj.Token
}
if len(obj.DiscoveryToken) == 0 && len(obj.DiscoveryFile) == 0 {
obj.DiscoveryToken = obj.Token
}
// Make sure file URLs become paths
if len(obj.DiscoveryFile) != 0 {
u, err := url.Parse(obj.DiscoveryFile)
if err == nil && u.Scheme == "file" {
obj.DiscoveryFile = u.Path
}
}
if obj.DiscoveryTimeout == nil {
obj.DiscoveryTimeout = &metav1.Duration{
Duration: DefaultDiscoveryTimeout,
}
}
if obj.ClusterName == "" {
obj.ClusterName = DefaultClusterName
}
SetDefaults_NodeRegistrationOptions(&obj.NodeRegistration)
}
// SetDefaults_KubeletConfiguration assigns default values to kubelet
func SetDefaults_KubeletConfiguration(obj *MasterConfiguration) {
if obj.KubeletConfiguration.BaseConfig == nil {
obj.KubeletConfiguration.BaseConfig = &kubeletconfigv1beta1.KubeletConfiguration{}
}
if obj.KubeletConfiguration.BaseConfig.StaticPodPath == "" {
obj.KubeletConfiguration.BaseConfig.StaticPodPath = DefaultManifestsDir
}
if obj.KubeletConfiguration.BaseConfig.ClusterDNS == nil {
dnsIP, err := constants.GetDNSIP(obj.Networking.ServiceSubnet)
if err != nil {
obj.KubeletConfiguration.BaseConfig.ClusterDNS = []string{DefaultClusterDNSIP}
} else {
obj.KubeletConfiguration.BaseConfig.ClusterDNS = []string{dnsIP.String()}
}
}
if obj.KubeletConfiguration.BaseConfig.ClusterDomain == "" {
obj.KubeletConfiguration.BaseConfig.ClusterDomain = obj.Networking.DNSDomain
}
// Enforce security-related kubelet options
// Require all clients to the kubelet API to have client certs signed by the cluster CA
obj.KubeletConfiguration.BaseConfig.Authentication.X509.ClientCAFile = DefaultCACertPath
obj.KubeletConfiguration.BaseConfig.Authentication.Anonymous.Enabled = utilpointer.BoolPtr(false)
// On every client request to the kubelet API, execute a webhook (SubjectAccessReview request) to the API server
// and ask it whether the client is authorized to access the kubelet API
obj.KubeletConfiguration.BaseConfig.Authorization.Mode = kubeletconfigv1beta1.KubeletAuthorizationModeWebhook
// Let clients using other authentication methods like ServiceAccount tokens also access the kubelet API
obj.KubeletConfiguration.BaseConfig.Authentication.Webhook.Enabled = utilpointer.BoolPtr(true)
// Disable the readonly port of the kubelet, in order to not expose unnecessary information
obj.KubeletConfiguration.BaseConfig.ReadOnlyPort = 0
// Enables client certificate rotation for the kubelet
obj.KubeletConfiguration.BaseConfig.RotateCertificates = true
// Serve a /healthz webserver on localhost:10248 that kubeadm can talk to
obj.KubeletConfiguration.BaseConfig.HealthzBindAddress = "127.0.0.1"
obj.KubeletConfiguration.BaseConfig.HealthzPort = utilpointer.Int32Ptr(10248)
scheme, _, _ := kubeletscheme.NewSchemeAndCodecs()
if scheme != nil {
scheme.Default(obj.KubeletConfiguration.BaseConfig)
}
}
func SetDefaults_NodeRegistrationOptions(obj *NodeRegistrationOptions) {
if obj.CRISocket == "" {
obj.CRISocket = DefaultCRISocket
}
}
// SetDefaults_AuditPolicyConfiguration sets default values for the AuditPolicyConfiguration
func SetDefaults_AuditPolicyConfiguration(obj *MasterConfiguration) {
if obj.AuditPolicyConfiguration.LogDir == "" {
obj.AuditPolicyConfiguration.LogDir = constants.StaticPodAuditPolicyLogDir
}
if obj.AuditPolicyConfiguration.LogMaxAge == nil {
obj.AuditPolicyConfiguration.LogMaxAge = &DefaultAuditPolicyLogMaxAge
}
}
// SetDefaults_BootstrapTokens sets the defaults for the .BootstrapTokens field
// If the slice is empty, it's defaulted with one token. Otherwise it just loops
// through the slice and sets the defaults for the omitempty fields that are TTL,
// Usages and Groups. Token is NOT defaulted with a random one in the API defaulting
// layer, but set to a random value later at runtime if not set before.
func SetDefaults_BootstrapTokens(obj *MasterConfiguration) {
if obj.BootstrapTokens == nil || len(obj.BootstrapTokens) == 0 {
obj.BootstrapTokens = []BootstrapToken{{}}
}
for _, bt := range obj.BootstrapTokens {
SetDefaults_BootstrapToken(&bt)
}
}
// SetDefaults_BootstrapToken sets the defaults for an individual Bootstrap Token
func SetDefaults_BootstrapToken(bt *BootstrapToken) {
if bt.TTL == nil {
bt.TTL = &metav1.Duration{
Duration: constants.DefaultTokenDuration,
}
}
if len(bt.Usages) == 0 {
bt.Usages = constants.DefaultTokenUsages
}
if len(bt.Groups) == 0 {
bt.Groups = constants.DefaultTokenGroups
}
}

View File

@ -1,5 +1,7 @@
// +build !windows
/*
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.
@ -14,6 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:deepcopy-gen=package
package v1alpha2
package spec
// DefaultCACertPath defines default location of CA certificate on Linux
const DefaultCACertPath = "/etc/kubernetes/pki/ca.crt"

View File

@ -1,5 +1,7 @@
// +build windows
/*
Copyright 2016 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.
@ -14,4 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package install // import "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
package v1alpha2
// DefaultCACertPath defines default location of CA certificate on Windows
const DefaultCACertPath = "C:/etc/kubernetes/pki/ca.crt"

View File

@ -0,0 +1,22 @@
/*
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 v1alpha2 is the package that contains the libraries that drive the kubeadm binary.
// +k8s:defaulter-gen=TypeMeta
// +groupName=kubeadm.k8s.io
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm
package v1alpha2 // import "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"

View File

@ -0,0 +1,66 @@
/*
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 v1alpha2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "kubeadm.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
var (
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
// SchemeBuilder points to a list of functions added to Scheme.
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
// AddToScheme applies all the stored functions to the scheme.
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
}
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&MasterConfiguration{},
&NodeConfiguration{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@ -0,0 +1,319 @@
/*
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 v1alpha2
import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeletconfigv1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// MasterConfiguration contains a list of elements which make up master's
// configuration object.
type MasterConfiguration struct {
metav1.TypeMeta `json:",inline"`
// `kubeadm init`-only information. These fields are solely used the first time `kubeadm init` runs.
// After that, the information in the fields ARE NOT uploaded to the `kubeadm-config` ConfigMap
// that is used by `kubeadm upgrade` for instance. These fields must be omitempty.
// BootstrapTokens is respected at `kubeadm init` time and describes a set of Bootstrap Tokens to create.
// This information IS NOT uploaded to the kubeadm cluster configmap, partly because of its sensitive nature
BootstrapTokens []BootstrapToken `json:"bootstrapTokens,omitempty"`
// NodeRegistration holds fields that relate to registering the new master node to the cluster
NodeRegistration NodeRegistrationOptions `json:"nodeRegistration,omitempty"`
// Cluster-wide configuration
// TODO: Move these fields under some kind of ClusterConfiguration or similar struct that describes
// one cluster. Eventually we want this kind of spec to align well with the Cluster API spec.
// API holds configuration for the k8s apiserver.
API API `json:"api"`
// KubeProxy holds configuration for the k8s service proxy.
KubeProxy KubeProxy `json:"kubeProxy"`
// Etcd holds configuration for etcd.
Etcd Etcd `json:"etcd"`
// KubeletConfiguration holds configuration for the kubelet.
KubeletConfiguration KubeletConfiguration `json:"kubeletConfiguration"`
// Networking holds configuration for the networking topology of the cluster.
Networking Networking `json:"networking"`
// KubernetesVersion is the target version of the control plane.
KubernetesVersion string `json:"kubernetesVersion"`
// APIServerExtraArgs is a set of extra flags to pass to the API Server or override
// default ones in form of <flagname>=<value>.
// TODO: This is temporary and ideally we would like to switch all components to
// use ComponentConfig + ConfigMaps.
APIServerExtraArgs map[string]string `json:"apiServerExtraArgs,omitempty"`
// ControllerManagerExtraArgs is a set of extra flags to pass to the Controller Manager
// or override default ones in form of <flagname>=<value>
// TODO: This is temporary and ideally we would like to switch all components to
// use ComponentConfig + ConfigMaps.
ControllerManagerExtraArgs map[string]string `json:"controllerManagerExtraArgs,omitempty"`
// SchedulerExtraArgs is a set of extra flags to pass to the Scheduler or override
// default ones in form of <flagname>=<value>
// TODO: This is temporary and ideally we would like to switch all components to
// use ComponentConfig + ConfigMaps.
SchedulerExtraArgs map[string]string `json:"schedulerExtraArgs,omitempty"`
// APIServerExtraVolumes is an extra set of host volumes mounted to the API server.
APIServerExtraVolumes []HostPathMount `json:"apiServerExtraVolumes,omitempty"`
// ControllerManagerExtraVolumes is an extra set of host volumes mounted to the
// Controller Manager.
ControllerManagerExtraVolumes []HostPathMount `json:"controllerManagerExtraVolumes,omitempty"`
// SchedulerExtraVolumes is an extra set of host volumes mounted to the scheduler.
SchedulerExtraVolumes []HostPathMount `json:"schedulerExtraVolumes,omitempty"`
// APIServerCertSANs sets extra Subject Alternative Names for the API Server signing cert.
APIServerCertSANs []string `json:"apiServerCertSANs,omitempty"`
// CertificatesDir specifies where to store or look for all required certificates.
CertificatesDir string `json:"certificatesDir"`
// ImageRepository what container registry to pull control plane images from
ImageRepository string `json:"imageRepository"`
// UnifiedControlPlaneImage specifies if a specific container image should
// be used for all control plane components.
UnifiedControlPlaneImage string `json:"unifiedControlPlaneImage"`
// AuditPolicyConfiguration defines the options for the api server audit system
AuditPolicyConfiguration AuditPolicyConfiguration `json:"auditPolicy"`
// FeatureGates enabled by the user.
FeatureGates map[string]bool `json:"featureGates,omitempty"`
// The cluster name
ClusterName string `json:"clusterName,omitempty"`
}
// API struct contains elements of API server address.
type API struct {
// AdvertiseAddress sets the IP address for the API server to advertise.
AdvertiseAddress string `json:"advertiseAddress"`
// ControlPlaneEndpoint sets a stable IP address or DNS name for the control plane; it
// can be a valid IP address or a RFC-1123 DNS subdomain, both with optional TCP port.
// In case the ControlPlaneEndpoint is not specified, the AdvertiseAddress + BindPort
// are used; in case the ControlPlaneEndpoint is specified but without a TCP port,
// the BindPort is used.
// Possible usages are:
// e.g. In an cluster with more than one control plane instances, this field should be
// assigned the address of the external load balancer in front of the
// control plane instances.
// e.g. in environments with enforced node recycling, the ControlPlaneEndpoint
// could be used for assigning a stable DNS to the control plane.
ControlPlaneEndpoint string `json:"controlPlaneEndpoint"`
// BindPort sets the secure port for the API Server to bind to.
// Defaults to 6443.
BindPort int32 `json:"bindPort"`
}
// NodeRegistrationOptions holds fields that relate to registering a new master or node to the cluster, either via "kubeadm init" or "kubeadm join"
type NodeRegistrationOptions struct {
// Name is the `.Metadata.Name` field of the Node API object that will be created in this `kubeadm init` or `kubeadm joiń` operation.
// This field is also used in the CommonName field of the kubelet's client certificate to the API server.
// Defaults to the hostname of the node if not provided.
Name string `json:"name,omitempty"`
// CRISocket is used to retrieve container runtime info. This information will be annotated to the Node API object, for later re-use
CRISocket string `json:"criSocket,omitempty"`
// Taints specifies the taints the Node API object should be registered with. If this field is unset, i.e. nil, in the `kubeadm init` process
// it will be defaulted to []v1.Taint{'node-role.kubernetes.io/master=""'}. If you don't want to taint your master node, set this field to an
// empty slice, i.e. `taints: {}` in the YAML file. This field is solely used for Node registration.
Taints []v1.Taint `json:"taints,omitempty"`
// KubeletExtraArgs passes through extra arguments to the kubelet. The arguments here are passed to the kubelet command line via the environment file
// kubeadm writes at runtime for the kubelet to source. This overrides the generic base-level configuration in the kubelet-config-1.X ConfigMap
// Flags have higher higher priority when parsing. These values are local and specific to the node kubeadm is executing on.
KubeletExtraArgs map[string]string `json:"kubeletExtraArgs,omitempty"`
}
// Networking contains elements describing cluster's networking configuration
type Networking struct {
// ServiceSubnet is the subnet used by k8s services. Defaults to "10.96.0.0/12".
ServiceSubnet string `json:"serviceSubnet"`
// PodSubnet is the subnet used by pods.
PodSubnet string `json:"podSubnet"`
// DNSDomain is the dns domain used by k8s services. Defaults to "cluster.local".
DNSDomain string `json:"dnsDomain"`
}
// BootstrapToken describes one bootstrap token, stored as a Secret in the cluster
type BootstrapToken struct {
// Token is used for establishing bidirectional trust between nodes and masters.
// Used for joining nodes in the cluster.
Token *BootstrapTokenString `json:"token"`
// Description sets a human-friendly message why this token exists and what it's used
// for, so other administrators can know its purpose.
Description string `json:"description,omitempty"`
// TTL defines the time to live for this token. Defaults to 24h.
// Expires and TTL are mutually exclusive.
TTL *metav1.Duration `json:"ttl,omitempty"`
// Expires specifies the timestamp when this token expires. Defaults to being set
// dynamically at runtime based on the TTL. Expires and TTL are mutually exclusive.
Expires *metav1.Time `json:"expires,omitempty"`
// Usages describes the ways in which this token can be used. Can by default be used
// for establishing bidirectional trust, but that can be changed here.
Usages []string `json:"usages,omitempty"`
// Groups specifies the extra groups that this token will authenticate as when/if
// used for authentication
Groups []string `json:"groups,omitempty"`
}
// Etcd contains elements describing Etcd configuration.
type Etcd struct {
// Local provides configuration knobs for configuring the local etcd instance
// Local and External are mutually exclusive
Local *LocalEtcd `json:"local,omitempty"`
// External describes how to connect to an external etcd cluster
// Local and External are mutually exclusive
External *ExternalEtcd `json:"external,omitempty"`
}
// LocalEtcd describes that kubeadm should run an etcd cluster locally
type LocalEtcd struct {
// Image specifies which container image to use for running etcd.
// If empty, automatically populated by kubeadm using the image
// repository and default etcd version.
Image string `json:"image"`
// DataDir is the directory etcd will place its data.
// Defaults to "/var/lib/etcd".
DataDir string `json:"dataDir"`
// ExtraArgs are extra arguments provided to the etcd binary
// when run inside a static pod.
ExtraArgs map[string]string `json:"extraArgs,omitempty"`
// ServerCertSANs sets extra Subject Alternative Names for the etcd server signing cert.
ServerCertSANs []string `json:"serverCertSANs,omitempty"`
// PeerCertSANs sets extra Subject Alternative Names for the etcd peer signing cert.
PeerCertSANs []string `json:"peerCertSANs,omitempty"`
}
// ExternalEtcd describes an external etcd cluster
type ExternalEtcd struct {
// Endpoints of etcd members. Useful for using external etcd.
// If not provided, kubeadm will run etcd in a static pod.
Endpoints []string `json:"endpoints"`
// CAFile is an SSL Certificate Authority file used to secure etcd communication.
CAFile string `json:"caFile"`
// CertFile is an SSL certification file used to secure etcd communication.
CertFile string `json:"certFile"`
// KeyFile is an SSL key file used to secure etcd communication.
KeyFile string `json:"keyFile"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// NodeConfiguration contains elements describing a particular node.
// TODO: This struct should be replaced by dynamic kubelet configuration.
type NodeConfiguration struct {
metav1.TypeMeta `json:",inline"`
// NodeRegistration holds fields that relate to registering the new master node to the cluster
NodeRegistration NodeRegistrationOptions `json:"nodeRegistration"`
// CACertPath is the path to the SSL certificate authority used to
// secure comunications between node and master.
// Defaults to "/etc/kubernetes/pki/ca.crt".
CACertPath string `json:"caCertPath"`
// DiscoveryFile is a file or url to a kubeconfig file from which to
// load cluster information.
DiscoveryFile string `json:"discoveryFile"`
// DiscoveryToken is a token used to validate cluster information
// fetched from the master.
DiscoveryToken string `json:"discoveryToken"`
// DiscoveryTokenAPIServers is a set of IPs to API servers from which info
// will be fetched. Currently we only pay attention to one API server but
// hope to support >1 in the future.
DiscoveryTokenAPIServers []string `json:"discoveryTokenAPIServers,omitempty"`
// DiscoveryTimeout modifies the discovery timeout
DiscoveryTimeout *metav1.Duration `json:"discoveryTimeout,omitempty"`
// TLSBootstrapToken is a token used for TLS bootstrapping.
// Defaults to Token.
TLSBootstrapToken string `json:"tlsBootstrapToken"`
// Token is used for both discovery and TLS bootstrapping.
Token string `json:"token"`
// ClusterName is the name for the cluster in kubeconfig.
ClusterName string `json:"clusterName,omitempty"`
// DiscoveryTokenCACertHashes specifies a set of public key pins to verify
// when token-based discovery is used. The root CA found during discovery
// must match one of these values. Specifying an empty set disables root CA
// pinning, which can be unsafe. Each hash is specified as "<type>:<value>",
// where the only currently supported type is "sha256". This is a hex-encoded
// SHA-256 hash of the Subject Public Key Info (SPKI) object in DER-encoded
// ASN.1. These hashes can be calculated using, for example, OpenSSL:
// openssl x509 -pubkey -in ca.crt openssl rsa -pubin -outform der 2>&/dev/null | openssl dgst -sha256 -hex
DiscoveryTokenCACertHashes []string `json:"discoveryTokenCACertHashes,omitempty"`
// DiscoveryTokenUnsafeSkipCAVerification allows token-based discovery
// without CA verification via DiscoveryTokenCACertHashes. This can weaken
// the security of kubeadm since other nodes can impersonate the master.
DiscoveryTokenUnsafeSkipCAVerification bool `json:"discoveryTokenUnsafeSkipCAVerification"`
// FeatureGates enabled by the user.
FeatureGates map[string]bool `json:"featureGates,omitempty"`
}
// KubeletConfiguration contains elements describing initial remote configuration of kubelet.
type KubeletConfiguration struct {
BaseConfig *kubeletconfigv1beta1.KubeletConfiguration `json:"baseConfig,omitempty"`
}
// HostPathMount contains elements describing volumes that are mounted from the
// host.
type HostPathMount struct {
// Name of the volume inside the pod template.
Name string `json:"name"`
// HostPath is the path in the host that will be mounted inside
// the pod.
HostPath string `json:"hostPath"`
// MountPath is the path inside the pod where hostPath will be mounted.
MountPath string `json:"mountPath"`
// Writable controls write access to the volume
Writable bool `json:"writable,omitempty"`
// PathType is the type of the HostPath.
PathType v1.HostPathType `json:"pathType,omitempty"`
}
// KubeProxy contains elements describing the proxy configuration.
type KubeProxy struct {
Config *kubeproxyconfigv1alpha1.KubeProxyConfiguration `json:"config,omitempty"`
}
// AuditPolicyConfiguration holds the options for configuring the api server audit policy.
type AuditPolicyConfiguration struct {
// Path is the local path to an audit policy.
Path string `json:"path"`
// LogDir is the local path to the directory where logs should be stored.
LogDir string `json:"logDir"`
// LogMaxAge is the number of days logs will be stored for. 0 indicates forever.
LogMaxAge *int32 `json:"logMaxAge,omitempty"`
//TODO(chuckha) add other options for audit policy.
}

View File

@ -0,0 +1,501 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha2
import (
unsafe "unsafe"
core_v1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
v1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
v1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(scheme *runtime.Scheme) error {
return scheme.AddGeneratedConversionFuncs(
Convert_v1alpha2_API_To_kubeadm_API,
Convert_kubeadm_API_To_v1alpha2_API,
Convert_v1alpha2_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration,
Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha2_AuditPolicyConfiguration,
Convert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken,
Convert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken,
Convert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString,
Convert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString,
Convert_v1alpha2_Etcd_To_kubeadm_Etcd,
Convert_kubeadm_Etcd_To_v1alpha2_Etcd,
Convert_v1alpha2_ExternalEtcd_To_kubeadm_ExternalEtcd,
Convert_kubeadm_ExternalEtcd_To_v1alpha2_ExternalEtcd,
Convert_v1alpha2_HostPathMount_To_kubeadm_HostPathMount,
Convert_kubeadm_HostPathMount_To_v1alpha2_HostPathMount,
Convert_v1alpha2_KubeProxy_To_kubeadm_KubeProxy,
Convert_kubeadm_KubeProxy_To_v1alpha2_KubeProxy,
Convert_v1alpha2_KubeletConfiguration_To_kubeadm_KubeletConfiguration,
Convert_kubeadm_KubeletConfiguration_To_v1alpha2_KubeletConfiguration,
Convert_v1alpha2_LocalEtcd_To_kubeadm_LocalEtcd,
Convert_kubeadm_LocalEtcd_To_v1alpha2_LocalEtcd,
Convert_v1alpha2_MasterConfiguration_To_kubeadm_MasterConfiguration,
Convert_kubeadm_MasterConfiguration_To_v1alpha2_MasterConfiguration,
Convert_v1alpha2_Networking_To_kubeadm_Networking,
Convert_kubeadm_Networking_To_v1alpha2_Networking,
Convert_v1alpha2_NodeConfiguration_To_kubeadm_NodeConfiguration,
Convert_kubeadm_NodeConfiguration_To_v1alpha2_NodeConfiguration,
Convert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions,
Convert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions,
)
}
func autoConvert_v1alpha2_API_To_kubeadm_API(in *API, out *kubeadm.API, s conversion.Scope) error {
out.AdvertiseAddress = in.AdvertiseAddress
out.ControlPlaneEndpoint = in.ControlPlaneEndpoint
out.BindPort = in.BindPort
return nil
}
// Convert_v1alpha2_API_To_kubeadm_API is an autogenerated conversion function.
func Convert_v1alpha2_API_To_kubeadm_API(in *API, out *kubeadm.API, s conversion.Scope) error {
return autoConvert_v1alpha2_API_To_kubeadm_API(in, out, s)
}
func autoConvert_kubeadm_API_To_v1alpha2_API(in *kubeadm.API, out *API, s conversion.Scope) error {
out.AdvertiseAddress = in.AdvertiseAddress
out.ControlPlaneEndpoint = in.ControlPlaneEndpoint
out.BindPort = in.BindPort
return nil
}
// Convert_kubeadm_API_To_v1alpha2_API is an autogenerated conversion function.
func Convert_kubeadm_API_To_v1alpha2_API(in *kubeadm.API, out *API, s conversion.Scope) error {
return autoConvert_kubeadm_API_To_v1alpha2_API(in, out, s)
}
func autoConvert_v1alpha2_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration(in *AuditPolicyConfiguration, out *kubeadm.AuditPolicyConfiguration, s conversion.Scope) error {
out.Path = in.Path
out.LogDir = in.LogDir
out.LogMaxAge = (*int32)(unsafe.Pointer(in.LogMaxAge))
return nil
}
// Convert_v1alpha2_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration is an autogenerated conversion function.
func Convert_v1alpha2_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration(in *AuditPolicyConfiguration, out *kubeadm.AuditPolicyConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha2_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration(in, out, s)
}
func autoConvert_kubeadm_AuditPolicyConfiguration_To_v1alpha2_AuditPolicyConfiguration(in *kubeadm.AuditPolicyConfiguration, out *AuditPolicyConfiguration, s conversion.Scope) error {
out.Path = in.Path
out.LogDir = in.LogDir
out.LogMaxAge = (*int32)(unsafe.Pointer(in.LogMaxAge))
return nil
}
// Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha2_AuditPolicyConfiguration is an autogenerated conversion function.
func Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha2_AuditPolicyConfiguration(in *kubeadm.AuditPolicyConfiguration, out *AuditPolicyConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_AuditPolicyConfiguration_To_v1alpha2_AuditPolicyConfiguration(in, out, s)
}
func autoConvert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken(in *BootstrapToken, out *kubeadm.BootstrapToken, s conversion.Scope) error {
out.Token = (*kubeadm.BootstrapTokenString)(unsafe.Pointer(in.Token))
out.Description = in.Description
out.TTL = (*v1.Duration)(unsafe.Pointer(in.TTL))
out.Expires = (*v1.Time)(unsafe.Pointer(in.Expires))
out.Usages = *(*[]string)(unsafe.Pointer(&in.Usages))
out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups))
return nil
}
// Convert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken is an autogenerated conversion function.
func Convert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken(in *BootstrapToken, out *kubeadm.BootstrapToken, s conversion.Scope) error {
return autoConvert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken(in, out, s)
}
func autoConvert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken(in *kubeadm.BootstrapToken, out *BootstrapToken, s conversion.Scope) error {
out.Token = (*BootstrapTokenString)(unsafe.Pointer(in.Token))
out.Description = in.Description
out.TTL = (*v1.Duration)(unsafe.Pointer(in.TTL))
out.Expires = (*v1.Time)(unsafe.Pointer(in.Expires))
out.Usages = *(*[]string)(unsafe.Pointer(&in.Usages))
out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups))
return nil
}
// Convert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken is an autogenerated conversion function.
func Convert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken(in *kubeadm.BootstrapToken, out *BootstrapToken, s conversion.Scope) error {
return autoConvert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken(in, out, s)
}
func autoConvert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in *BootstrapTokenString, out *kubeadm.BootstrapTokenString, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
return nil
}
// Convert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString is an autogenerated conversion function.
func Convert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in *BootstrapTokenString, out *kubeadm.BootstrapTokenString, s conversion.Scope) error {
return autoConvert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in, out, s)
}
func autoConvert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString(in *kubeadm.BootstrapTokenString, out *BootstrapTokenString, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
return nil
}
// Convert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString is an autogenerated conversion function.
func Convert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString(in *kubeadm.BootstrapTokenString, out *BootstrapTokenString, s conversion.Scope) error {
return autoConvert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString(in, out, s)
}
func autoConvert_v1alpha2_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conversion.Scope) error {
out.Local = (*kubeadm.LocalEtcd)(unsafe.Pointer(in.Local))
out.External = (*kubeadm.ExternalEtcd)(unsafe.Pointer(in.External))
return nil
}
// Convert_v1alpha2_Etcd_To_kubeadm_Etcd is an autogenerated conversion function.
func Convert_v1alpha2_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conversion.Scope) error {
return autoConvert_v1alpha2_Etcd_To_kubeadm_Etcd(in, out, s)
}
func autoConvert_kubeadm_Etcd_To_v1alpha2_Etcd(in *kubeadm.Etcd, out *Etcd, s conversion.Scope) error {
out.Local = (*LocalEtcd)(unsafe.Pointer(in.Local))
out.External = (*ExternalEtcd)(unsafe.Pointer(in.External))
return nil
}
// Convert_kubeadm_Etcd_To_v1alpha2_Etcd is an autogenerated conversion function.
func Convert_kubeadm_Etcd_To_v1alpha2_Etcd(in *kubeadm.Etcd, out *Etcd, s conversion.Scope) error {
return autoConvert_kubeadm_Etcd_To_v1alpha2_Etcd(in, out, s)
}
func autoConvert_v1alpha2_ExternalEtcd_To_kubeadm_ExternalEtcd(in *ExternalEtcd, out *kubeadm.ExternalEtcd, s conversion.Scope) error {
out.Endpoints = *(*[]string)(unsafe.Pointer(&in.Endpoints))
out.CAFile = in.CAFile
out.CertFile = in.CertFile
out.KeyFile = in.KeyFile
return nil
}
// Convert_v1alpha2_ExternalEtcd_To_kubeadm_ExternalEtcd is an autogenerated conversion function.
func Convert_v1alpha2_ExternalEtcd_To_kubeadm_ExternalEtcd(in *ExternalEtcd, out *kubeadm.ExternalEtcd, s conversion.Scope) error {
return autoConvert_v1alpha2_ExternalEtcd_To_kubeadm_ExternalEtcd(in, out, s)
}
func autoConvert_kubeadm_ExternalEtcd_To_v1alpha2_ExternalEtcd(in *kubeadm.ExternalEtcd, out *ExternalEtcd, s conversion.Scope) error {
out.Endpoints = *(*[]string)(unsafe.Pointer(&in.Endpoints))
out.CAFile = in.CAFile
out.CertFile = in.CertFile
out.KeyFile = in.KeyFile
return nil
}
// Convert_kubeadm_ExternalEtcd_To_v1alpha2_ExternalEtcd is an autogenerated conversion function.
func Convert_kubeadm_ExternalEtcd_To_v1alpha2_ExternalEtcd(in *kubeadm.ExternalEtcd, out *ExternalEtcd, s conversion.Scope) error {
return autoConvert_kubeadm_ExternalEtcd_To_v1alpha2_ExternalEtcd(in, out, s)
}
func autoConvert_v1alpha2_HostPathMount_To_kubeadm_HostPathMount(in *HostPathMount, out *kubeadm.HostPathMount, s conversion.Scope) error {
out.Name = in.Name
out.HostPath = in.HostPath
out.MountPath = in.MountPath
out.Writable = in.Writable
out.PathType = core_v1.HostPathType(in.PathType)
return nil
}
// Convert_v1alpha2_HostPathMount_To_kubeadm_HostPathMount is an autogenerated conversion function.
func Convert_v1alpha2_HostPathMount_To_kubeadm_HostPathMount(in *HostPathMount, out *kubeadm.HostPathMount, s conversion.Scope) error {
return autoConvert_v1alpha2_HostPathMount_To_kubeadm_HostPathMount(in, out, s)
}
func autoConvert_kubeadm_HostPathMount_To_v1alpha2_HostPathMount(in *kubeadm.HostPathMount, out *HostPathMount, s conversion.Scope) error {
out.Name = in.Name
out.HostPath = in.HostPath
out.MountPath = in.MountPath
out.Writable = in.Writable
out.PathType = core_v1.HostPathType(in.PathType)
return nil
}
// Convert_kubeadm_HostPathMount_To_v1alpha2_HostPathMount is an autogenerated conversion function.
func Convert_kubeadm_HostPathMount_To_v1alpha2_HostPathMount(in *kubeadm.HostPathMount, out *HostPathMount, s conversion.Scope) error {
return autoConvert_kubeadm_HostPathMount_To_v1alpha2_HostPathMount(in, out, s)
}
func autoConvert_v1alpha2_KubeProxy_To_kubeadm_KubeProxy(in *KubeProxy, out *kubeadm.KubeProxy, s conversion.Scope) error {
out.Config = (*v1alpha1.KubeProxyConfiguration)(unsafe.Pointer(in.Config))
return nil
}
// Convert_v1alpha2_KubeProxy_To_kubeadm_KubeProxy is an autogenerated conversion function.
func Convert_v1alpha2_KubeProxy_To_kubeadm_KubeProxy(in *KubeProxy, out *kubeadm.KubeProxy, s conversion.Scope) error {
return autoConvert_v1alpha2_KubeProxy_To_kubeadm_KubeProxy(in, out, s)
}
func autoConvert_kubeadm_KubeProxy_To_v1alpha2_KubeProxy(in *kubeadm.KubeProxy, out *KubeProxy, s conversion.Scope) error {
out.Config = (*v1alpha1.KubeProxyConfiguration)(unsafe.Pointer(in.Config))
return nil
}
// Convert_kubeadm_KubeProxy_To_v1alpha2_KubeProxy is an autogenerated conversion function.
func Convert_kubeadm_KubeProxy_To_v1alpha2_KubeProxy(in *kubeadm.KubeProxy, out *KubeProxy, s conversion.Scope) error {
return autoConvert_kubeadm_KubeProxy_To_v1alpha2_KubeProxy(in, out, s)
}
func autoConvert_v1alpha2_KubeletConfiguration_To_kubeadm_KubeletConfiguration(in *KubeletConfiguration, out *kubeadm.KubeletConfiguration, s conversion.Scope) error {
out.BaseConfig = (*v1beta1.KubeletConfiguration)(unsafe.Pointer(in.BaseConfig))
return nil
}
// Convert_v1alpha2_KubeletConfiguration_To_kubeadm_KubeletConfiguration is an autogenerated conversion function.
func Convert_v1alpha2_KubeletConfiguration_To_kubeadm_KubeletConfiguration(in *KubeletConfiguration, out *kubeadm.KubeletConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha2_KubeletConfiguration_To_kubeadm_KubeletConfiguration(in, out, s)
}
func autoConvert_kubeadm_KubeletConfiguration_To_v1alpha2_KubeletConfiguration(in *kubeadm.KubeletConfiguration, out *KubeletConfiguration, s conversion.Scope) error {
out.BaseConfig = (*v1beta1.KubeletConfiguration)(unsafe.Pointer(in.BaseConfig))
return nil
}
// Convert_kubeadm_KubeletConfiguration_To_v1alpha2_KubeletConfiguration is an autogenerated conversion function.
func Convert_kubeadm_KubeletConfiguration_To_v1alpha2_KubeletConfiguration(in *kubeadm.KubeletConfiguration, out *KubeletConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_KubeletConfiguration_To_v1alpha2_KubeletConfiguration(in, out, s)
}
func autoConvert_v1alpha2_LocalEtcd_To_kubeadm_LocalEtcd(in *LocalEtcd, out *kubeadm.LocalEtcd, s conversion.Scope) error {
out.Image = in.Image
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.ServerCertSANs = *(*[]string)(unsafe.Pointer(&in.ServerCertSANs))
out.PeerCertSANs = *(*[]string)(unsafe.Pointer(&in.PeerCertSANs))
return nil
}
// Convert_v1alpha2_LocalEtcd_To_kubeadm_LocalEtcd is an autogenerated conversion function.
func Convert_v1alpha2_LocalEtcd_To_kubeadm_LocalEtcd(in *LocalEtcd, out *kubeadm.LocalEtcd, s conversion.Scope) error {
return autoConvert_v1alpha2_LocalEtcd_To_kubeadm_LocalEtcd(in, out, s)
}
func autoConvert_kubeadm_LocalEtcd_To_v1alpha2_LocalEtcd(in *kubeadm.LocalEtcd, out *LocalEtcd, s conversion.Scope) error {
out.Image = in.Image
out.DataDir = in.DataDir
out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs))
out.ServerCertSANs = *(*[]string)(unsafe.Pointer(&in.ServerCertSANs))
out.PeerCertSANs = *(*[]string)(unsafe.Pointer(&in.PeerCertSANs))
return nil
}
// Convert_kubeadm_LocalEtcd_To_v1alpha2_LocalEtcd is an autogenerated conversion function.
func Convert_kubeadm_LocalEtcd_To_v1alpha2_LocalEtcd(in *kubeadm.LocalEtcd, out *LocalEtcd, s conversion.Scope) error {
return autoConvert_kubeadm_LocalEtcd_To_v1alpha2_LocalEtcd(in, out, s)
}
func autoConvert_v1alpha2_MasterConfiguration_To_kubeadm_MasterConfiguration(in *MasterConfiguration, out *kubeadm.MasterConfiguration, s conversion.Scope) error {
out.BootstrapTokens = *(*[]kubeadm.BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens))
if err := Convert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(&in.NodeRegistration, &out.NodeRegistration, s); err != nil {
return err
}
if err := Convert_v1alpha2_API_To_kubeadm_API(&in.API, &out.API, s); err != nil {
return err
}
if err := Convert_v1alpha2_KubeProxy_To_kubeadm_KubeProxy(&in.KubeProxy, &out.KubeProxy, s); err != nil {
return err
}
if err := Convert_v1alpha2_Etcd_To_kubeadm_Etcd(&in.Etcd, &out.Etcd, s); err != nil {
return err
}
if err := Convert_v1alpha2_KubeletConfiguration_To_kubeadm_KubeletConfiguration(&in.KubeletConfiguration, &out.KubeletConfiguration, s); err != nil {
return err
}
if err := Convert_v1alpha2_Networking_To_kubeadm_Networking(&in.Networking, &out.Networking, s); err != nil {
return err
}
out.KubernetesVersion = in.KubernetesVersion
out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs))
out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs))
out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs))
out.APIServerExtraVolumes = *(*[]kubeadm.HostPathMount)(unsafe.Pointer(&in.APIServerExtraVolumes))
out.ControllerManagerExtraVolumes = *(*[]kubeadm.HostPathMount)(unsafe.Pointer(&in.ControllerManagerExtraVolumes))
out.SchedulerExtraVolumes = *(*[]kubeadm.HostPathMount)(unsafe.Pointer(&in.SchedulerExtraVolumes))
out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs))
out.CertificatesDir = in.CertificatesDir
out.ImageRepository = in.ImageRepository
out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage
if err := Convert_v1alpha2_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration(&in.AuditPolicyConfiguration, &out.AuditPolicyConfiguration, s); err != nil {
return err
}
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
out.ClusterName = in.ClusterName
return nil
}
// Convert_v1alpha2_MasterConfiguration_To_kubeadm_MasterConfiguration is an autogenerated conversion function.
func Convert_v1alpha2_MasterConfiguration_To_kubeadm_MasterConfiguration(in *MasterConfiguration, out *kubeadm.MasterConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha2_MasterConfiguration_To_kubeadm_MasterConfiguration(in, out, s)
}
func autoConvert_kubeadm_MasterConfiguration_To_v1alpha2_MasterConfiguration(in *kubeadm.MasterConfiguration, out *MasterConfiguration, s conversion.Scope) error {
out.BootstrapTokens = *(*[]BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens))
if err := Convert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions(&in.NodeRegistration, &out.NodeRegistration, s); err != nil {
return err
}
if err := Convert_kubeadm_API_To_v1alpha2_API(&in.API, &out.API, s); err != nil {
return err
}
if err := Convert_kubeadm_KubeProxy_To_v1alpha2_KubeProxy(&in.KubeProxy, &out.KubeProxy, s); err != nil {
return err
}
if err := Convert_kubeadm_Etcd_To_v1alpha2_Etcd(&in.Etcd, &out.Etcd, s); err != nil {
return err
}
if err := Convert_kubeadm_KubeletConfiguration_To_v1alpha2_KubeletConfiguration(&in.KubeletConfiguration, &out.KubeletConfiguration, s); err != nil {
return err
}
if err := Convert_kubeadm_Networking_To_v1alpha2_Networking(&in.Networking, &out.Networking, s); err != nil {
return err
}
out.KubernetesVersion = in.KubernetesVersion
out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs))
out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs))
out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs))
out.APIServerExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.APIServerExtraVolumes))
out.ControllerManagerExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.ControllerManagerExtraVolumes))
out.SchedulerExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.SchedulerExtraVolumes))
out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs))
out.CertificatesDir = in.CertificatesDir
out.ImageRepository = in.ImageRepository
// INFO: in.CIImageRepository opted out of conversion generation
out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage
if err := Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha2_AuditPolicyConfiguration(&in.AuditPolicyConfiguration, &out.AuditPolicyConfiguration, s); err != nil {
return err
}
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
out.ClusterName = in.ClusterName
return nil
}
// Convert_kubeadm_MasterConfiguration_To_v1alpha2_MasterConfiguration is an autogenerated conversion function.
func Convert_kubeadm_MasterConfiguration_To_v1alpha2_MasterConfiguration(in *kubeadm.MasterConfiguration, out *MasterConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_MasterConfiguration_To_v1alpha2_MasterConfiguration(in, out, s)
}
func autoConvert_v1alpha2_Networking_To_kubeadm_Networking(in *Networking, out *kubeadm.Networking, s conversion.Scope) error {
out.ServiceSubnet = in.ServiceSubnet
out.PodSubnet = in.PodSubnet
out.DNSDomain = in.DNSDomain
return nil
}
// Convert_v1alpha2_Networking_To_kubeadm_Networking is an autogenerated conversion function.
func Convert_v1alpha2_Networking_To_kubeadm_Networking(in *Networking, out *kubeadm.Networking, s conversion.Scope) error {
return autoConvert_v1alpha2_Networking_To_kubeadm_Networking(in, out, s)
}
func autoConvert_kubeadm_Networking_To_v1alpha2_Networking(in *kubeadm.Networking, out *Networking, s conversion.Scope) error {
out.ServiceSubnet = in.ServiceSubnet
out.PodSubnet = in.PodSubnet
out.DNSDomain = in.DNSDomain
return nil
}
// Convert_kubeadm_Networking_To_v1alpha2_Networking is an autogenerated conversion function.
func Convert_kubeadm_Networking_To_v1alpha2_Networking(in *kubeadm.Networking, out *Networking, s conversion.Scope) error {
return autoConvert_kubeadm_Networking_To_v1alpha2_Networking(in, out, s)
}
func autoConvert_v1alpha2_NodeConfiguration_To_kubeadm_NodeConfiguration(in *NodeConfiguration, out *kubeadm.NodeConfiguration, s conversion.Scope) error {
if err := Convert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(&in.NodeRegistration, &out.NodeRegistration, s); err != nil {
return err
}
out.CACertPath = in.CACertPath
out.DiscoveryFile = in.DiscoveryFile
out.DiscoveryToken = in.DiscoveryToken
out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers))
out.DiscoveryTimeout = (*v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout))
out.TLSBootstrapToken = in.TLSBootstrapToken
out.Token = in.Token
out.ClusterName = in.ClusterName
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}
// Convert_v1alpha2_NodeConfiguration_To_kubeadm_NodeConfiguration is an autogenerated conversion function.
func Convert_v1alpha2_NodeConfiguration_To_kubeadm_NodeConfiguration(in *NodeConfiguration, out *kubeadm.NodeConfiguration, s conversion.Scope) error {
return autoConvert_v1alpha2_NodeConfiguration_To_kubeadm_NodeConfiguration(in, out, s)
}
func autoConvert_kubeadm_NodeConfiguration_To_v1alpha2_NodeConfiguration(in *kubeadm.NodeConfiguration, out *NodeConfiguration, s conversion.Scope) error {
if err := Convert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions(&in.NodeRegistration, &out.NodeRegistration, s); err != nil {
return err
}
out.CACertPath = in.CACertPath
out.DiscoveryFile = in.DiscoveryFile
out.DiscoveryToken = in.DiscoveryToken
out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers))
out.DiscoveryTimeout = (*v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout))
out.TLSBootstrapToken = in.TLSBootstrapToken
out.Token = in.Token
out.ClusterName = in.ClusterName
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}
// Convert_kubeadm_NodeConfiguration_To_v1alpha2_NodeConfiguration is an autogenerated conversion function.
func Convert_kubeadm_NodeConfiguration_To_v1alpha2_NodeConfiguration(in *kubeadm.NodeConfiguration, out *NodeConfiguration, s conversion.Scope) error {
return autoConvert_kubeadm_NodeConfiguration_To_v1alpha2_NodeConfiguration(in, out, s)
}
func autoConvert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(in *NodeRegistrationOptions, out *kubeadm.NodeRegistrationOptions, s conversion.Scope) error {
out.Name = in.Name
out.CRISocket = in.CRISocket
out.Taints = *(*[]core_v1.Taint)(unsafe.Pointer(&in.Taints))
out.KubeletExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.KubeletExtraArgs))
return nil
}
// Convert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions is an autogenerated conversion function.
func Convert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(in *NodeRegistrationOptions, out *kubeadm.NodeRegistrationOptions, s conversion.Scope) error {
return autoConvert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(in, out, s)
}
func autoConvert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions(in *kubeadm.NodeRegistrationOptions, out *NodeRegistrationOptions, s conversion.Scope) error {
out.Name = in.Name
out.CRISocket = in.CRISocket
out.Taints = *(*[]core_v1.Taint)(unsafe.Pointer(&in.Taints))
out.KubeletExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.KubeletExtraArgs))
return nil
}
// Convert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions is an autogenerated conversion function.
func Convert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions(in *kubeadm.NodeRegistrationOptions, out *NodeRegistrationOptions, s conversion.Scope) error {
return autoConvert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions(in, out, s)
}

View File

@ -0,0 +1,477 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha2
import (
core_v1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
v1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
v1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *API) DeepCopyInto(out *API) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new API.
func (in *API) DeepCopy() *API {
if in == nil {
return nil
}
out := new(API)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AuditPolicyConfiguration) DeepCopyInto(out *AuditPolicyConfiguration) {
*out = *in
if in.LogMaxAge != nil {
in, out := &in.LogMaxAge, &out.LogMaxAge
if *in == nil {
*out = nil
} else {
*out = new(int32)
**out = **in
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuditPolicyConfiguration.
func (in *AuditPolicyConfiguration) DeepCopy() *AuditPolicyConfiguration {
if in == nil {
return nil
}
out := new(AuditPolicyConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BootstrapToken) DeepCopyInto(out *BootstrapToken) {
*out = *in
if in.Token != nil {
in, out := &in.Token, &out.Token
if *in == nil {
*out = nil
} else {
*out = new(BootstrapTokenString)
**out = **in
}
}
if in.TTL != nil {
in, out := &in.TTL, &out.TTL
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
**out = **in
}
}
if in.Expires != nil {
in, out := &in.Expires, &out.Expires
if *in == nil {
*out = nil
} else {
*out = (*in).DeepCopy()
}
}
if in.Usages != nil {
in, out := &in.Usages, &out.Usages
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Groups != nil {
in, out := &in.Groups, &out.Groups
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapToken.
func (in *BootstrapToken) DeepCopy() *BootstrapToken {
if in == nil {
return nil
}
out := new(BootstrapToken)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BootstrapTokenString) DeepCopyInto(out *BootstrapTokenString) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapTokenString.
func (in *BootstrapTokenString) DeepCopy() *BootstrapTokenString {
if in == nil {
return nil
}
out := new(BootstrapTokenString)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Etcd) DeepCopyInto(out *Etcd) {
*out = *in
if in.Local != nil {
in, out := &in.Local, &out.Local
if *in == nil {
*out = nil
} else {
*out = new(LocalEtcd)
(*in).DeepCopyInto(*out)
}
}
if in.External != nil {
in, out := &in.External, &out.External
if *in == nil {
*out = nil
} else {
*out = new(ExternalEtcd)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Etcd.
func (in *Etcd) DeepCopy() *Etcd {
if in == nil {
return nil
}
out := new(Etcd)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalEtcd) DeepCopyInto(out *ExternalEtcd) {
*out = *in
if in.Endpoints != nil {
in, out := &in.Endpoints, &out.Endpoints
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalEtcd.
func (in *ExternalEtcd) DeepCopy() *ExternalEtcd {
if in == nil {
return nil
}
out := new(ExternalEtcd)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostPathMount) DeepCopyInto(out *HostPathMount) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPathMount.
func (in *HostPathMount) DeepCopy() *HostPathMount {
if in == nil {
return nil
}
out := new(HostPathMount)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeProxy) DeepCopyInto(out *KubeProxy) {
*out = *in
if in.Config != nil {
in, out := &in.Config, &out.Config
if *in == nil {
*out = nil
} else {
*out = new(v1alpha1.KubeProxyConfiguration)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeProxy.
func (in *KubeProxy) DeepCopy() *KubeProxy {
if in == nil {
return nil
}
out := new(KubeProxy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
*out = *in
if in.BaseConfig != nil {
in, out := &in.BaseConfig, &out.BaseConfig
if *in == nil {
*out = nil
} else {
*out = new(v1beta1.KubeletConfiguration)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeletConfiguration.
func (in *KubeletConfiguration) DeepCopy() *KubeletConfiguration {
if in == nil {
return nil
}
out := new(KubeletConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LocalEtcd) DeepCopyInto(out *LocalEtcd) {
*out = *in
if in.ExtraArgs != nil {
in, out := &in.ExtraArgs, &out.ExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.ServerCertSANs != nil {
in, out := &in.ServerCertSANs, &out.ServerCertSANs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.PeerCertSANs != nil {
in, out := &in.PeerCertSANs, &out.PeerCertSANs
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalEtcd.
func (in *LocalEtcd) DeepCopy() *LocalEtcd {
if in == nil {
return nil
}
out := new(LocalEtcd)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.BootstrapTokens != nil {
in, out := &in.BootstrapTokens, &out.BootstrapTokens
*out = make([]BootstrapToken, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.NodeRegistration.DeepCopyInto(&out.NodeRegistration)
out.API = in.API
in.KubeProxy.DeepCopyInto(&out.KubeProxy)
in.Etcd.DeepCopyInto(&out.Etcd)
in.KubeletConfiguration.DeepCopyInto(&out.KubeletConfiguration)
out.Networking = in.Networking
if in.APIServerExtraArgs != nil {
in, out := &in.APIServerExtraArgs, &out.APIServerExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.ControllerManagerExtraArgs != nil {
in, out := &in.ControllerManagerExtraArgs, &out.ControllerManagerExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.SchedulerExtraArgs != nil {
in, out := &in.SchedulerExtraArgs, &out.SchedulerExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.APIServerExtraVolumes != nil {
in, out := &in.APIServerExtraVolumes, &out.APIServerExtraVolumes
*out = make([]HostPathMount, len(*in))
copy(*out, *in)
}
if in.ControllerManagerExtraVolumes != nil {
in, out := &in.ControllerManagerExtraVolumes, &out.ControllerManagerExtraVolumes
*out = make([]HostPathMount, len(*in))
copy(*out, *in)
}
if in.SchedulerExtraVolumes != nil {
in, out := &in.SchedulerExtraVolumes, &out.SchedulerExtraVolumes
*out = make([]HostPathMount, len(*in))
copy(*out, *in)
}
if in.APIServerCertSANs != nil {
in, out := &in.APIServerCertSANs, &out.APIServerCertSANs
*out = make([]string, len(*in))
copy(*out, *in)
}
in.AuditPolicyConfiguration.DeepCopyInto(&out.AuditPolicyConfiguration)
if in.FeatureGates != nil {
in, out := &in.FeatureGates, &out.FeatureGates
*out = make(map[string]bool, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MasterConfiguration.
func (in *MasterConfiguration) DeepCopy() *MasterConfiguration {
if in == nil {
return nil
}
out := new(MasterConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MasterConfiguration) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Networking) DeepCopyInto(out *Networking) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Networking.
func (in *Networking) DeepCopy() *Networking {
if in == nil {
return nil
}
out := new(Networking)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeConfiguration) DeepCopyInto(out *NodeConfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
in.NodeRegistration.DeepCopyInto(&out.NodeRegistration)
if in.DiscoveryTokenAPIServers != nil {
in, out := &in.DiscoveryTokenAPIServers, &out.DiscoveryTokenAPIServers
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DiscoveryTimeout != nil {
in, out := &in.DiscoveryTimeout, &out.DiscoveryTimeout
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
**out = **in
}
}
if in.DiscoveryTokenCACertHashes != nil {
in, out := &in.DiscoveryTokenCACertHashes, &out.DiscoveryTokenCACertHashes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.FeatureGates != nil {
in, out := &in.FeatureGates, &out.FeatureGates
*out = make(map[string]bool, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeConfiguration.
func (in *NodeConfiguration) DeepCopy() *NodeConfiguration {
if in == nil {
return nil
}
out := new(NodeConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NodeConfiguration) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeRegistrationOptions) DeepCopyInto(out *NodeRegistrationOptions) {
*out = *in
if in.Taints != nil {
in, out := &in.Taints, &out.Taints
*out = make([]core_v1.Taint, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.KubeletExtraArgs != nil {
in, out := &in.KubeletExtraArgs, &out.KubeletExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeRegistrationOptions.
func (in *NodeRegistrationOptions) DeepCopy() *NodeRegistrationOptions {
if in == nil {
return nil
}
out := new(NodeRegistrationOptions)
in.DeepCopyInto(out)
return out
}

View File

@ -0,0 +1,56 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1alpha2
import (
runtime "k8s.io/apimachinery/pkg/runtime"
v1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
v1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
scheme.AddTypeDefaultingFunc(&MasterConfiguration{}, func(obj interface{}) { SetObjectDefaults_MasterConfiguration(obj.(*MasterConfiguration)) })
scheme.AddTypeDefaultingFunc(&NodeConfiguration{}, func(obj interface{}) { SetObjectDefaults_NodeConfiguration(obj.(*NodeConfiguration)) })
return nil
}
func SetObjectDefaults_MasterConfiguration(in *MasterConfiguration) {
SetDefaults_MasterConfiguration(in)
for i := range in.BootstrapTokens {
a := &in.BootstrapTokens[i]
SetDefaults_BootstrapToken(a)
}
SetDefaults_NodeRegistrationOptions(&in.NodeRegistration)
if in.KubeProxy.Config != nil {
v1alpha1.SetDefaults_KubeProxyConfiguration(in.KubeProxy.Config)
}
if in.KubeletConfiguration.BaseConfig != nil {
v1beta1.SetDefaults_KubeletConfiguration(in.KubeletConfiguration.BaseConfig)
}
}
func SetObjectDefaults_NodeConfiguration(in *NodeConfiguration) {
SetDefaults_NodeConfiguration(in)
SetDefaults_NodeRegistrationOptions(&in.NodeRegistration)
}

View File

@ -10,17 +10,15 @@ go_library(
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/kubeapiserver/authorizer/modes:go_default_library",
"//pkg/kubelet/apis/kubeletconfig:go_default_library",
"//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library",
"//pkg/kubelet/apis/kubeletconfig/validation:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/validation:go_default_library",
"//pkg/registry/core/service/ipallocator:go_default_library",
"//pkg/util/node:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",

View File

@ -35,71 +35,42 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
kubeletvalidation "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/validation"
"k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig"
kubeproxyscheme "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/scheme"
kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
proxyvalidation "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/validation"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
"k8s.io/kubernetes/pkg/util/node"
)
// TODO: Break out the cloudprovider functionality out of core and only support the new flow
// described in https://github.com/kubernetes/community/pull/128
var cloudproviders = []string{
"aws",
"azure",
"cloudstack",
"gce",
"external", // Support for out-of-tree cloud providers
"openstack",
"ovirt",
"photon",
"vsphere",
}
// Describes the authorization modes that are enforced by kubeadm
var requiredAuthzModes = []string{
authzmodes.ModeRBAC,
authzmodes.ModeNode,
}
// ValidateMasterConfiguration validates master configuration and collects all encountered errors
func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateCloudProvider(c.CloudProvider, field.NewPath("cloudprovider"))...)
allErrs = append(allErrs, ValidateAuthorizationModes(c.AuthorizationModes, field.NewPath("authorization-modes"))...)
allErrs = append(allErrs, ValidateNetworking(&c.Networking, field.NewPath("networking"))...)
allErrs = append(allErrs, ValidateCertSANs(c.APIServerCertSANs, field.NewPath("api-server-cert-altnames"))...)
allErrs = append(allErrs, ValidateCertSANs(c.Etcd.ServerCertSANs, field.NewPath("etcd-server-cert-altnames"))...)
allErrs = append(allErrs, ValidateCertSANs(c.Etcd.PeerCertSANs, field.NewPath("etcd-peer-cert-altnames"))...)
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificates-dir"))...)
allErrs = append(allErrs, ValidateNodeName(c.NodeName, field.NewPath("node-name"))...)
allErrs = append(allErrs, ValidateToken(c.Token, field.NewPath("token"))...)
allErrs = append(allErrs, ValidateTokenUsages(c.TokenUsages, field.NewPath("tokenUsages"))...)
allErrs = append(allErrs, ValidateTokenGroups(c.TokenUsages, c.TokenGroups, field.NewPath("tokenGroups"))...)
allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("feature-gates"))...)
allErrs = append(allErrs, ValidateAPIEndpoint(c, field.NewPath("api-endpoint"))...)
allErrs = append(allErrs, ValidateProxy(c, field.NewPath("kube-proxy"))...)
if features.Enabled(c.FeatureGates, features.DynamicKubeletConfig) {
allErrs = append(allErrs, ValidateKubeletConfiguration(&c.KubeletConfiguration, field.NewPath("kubeletConfiguration"))...)
}
allErrs = append(allErrs, ValidateCertSANs(c.APIServerCertSANs, field.NewPath("apiServerCertSANs"))...)
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificatesDir"))...)
allErrs = append(allErrs, ValidateNodeRegistrationOptions(&c.NodeRegistration, field.NewPath("nodeRegistration"))...)
allErrs = append(allErrs, ValidateBootstrapTokens(c.BootstrapTokens, field.NewPath("bootstrapTokens"))...)
allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("featureGates"))...)
allErrs = append(allErrs, ValidateAPIEndpoint(&c.API, field.NewPath("api"))...)
allErrs = append(allErrs, ValidateProxy(c.KubeProxy.Config, field.NewPath("kubeProxy").Child("config"))...)
allErrs = append(allErrs, ValidateEtcd(&c.Etcd, field.NewPath("etcd"))...)
allErrs = append(allErrs, ValidateKubeletConfiguration(&c.KubeletConfiguration, field.NewPath("kubeletConfiguration"))...)
return allErrs
}
// ValidateProxy validates proxy configuration and collects all encountered errors
func ValidateProxy(c *kubeadm.MasterConfiguration, fldPath *field.Path) field.ErrorList {
func ValidateProxy(c *kubeproxyconfigv1alpha1.KubeProxyConfiguration, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// Convert to the internal version
internalcfg := &kubeproxyconfig.KubeProxyConfiguration{}
err := kubeproxyscheme.Scheme.Convert(c.KubeProxy.Config, internalcfg, nil)
err := kubeproxyscheme.Scheme.Convert(c, internalcfg, nil)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, "KubeProxy.Config", err.Error()))
allErrs = append(allErrs, field.Invalid(fldPath, "", err.Error()))
return allErrs
}
return proxyvalidation.Validate(internalcfg)
@ -108,49 +79,40 @@ func ValidateProxy(c *kubeadm.MasterConfiguration, fldPath *field.Path) field.Er
// ValidateNodeConfiguration validates node configuration and collects all encountered errors
func ValidateNodeConfiguration(c *kubeadm.NodeConfiguration) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateDiscovery(c, field.NewPath("discovery"))...)
allErrs = append(allErrs, ValidateDiscovery(c)...)
allErrs = append(allErrs, ValidateNodeRegistrationOptions(&c.NodeRegistration, field.NewPath("nodeRegistration"))...)
if !filepath.IsAbs(c.CACertPath) || !strings.HasSuffix(c.CACertPath, ".crt") {
allErrs = append(allErrs, field.Invalid(field.NewPath("ca-cert-path"), c.CACertPath, "the ca certificate path must be an absolute path"))
allErrs = append(allErrs, field.Invalid(field.NewPath("caCertPath"), c.CACertPath, "the ca certificate path must be an absolute path"))
}
return allErrs
}
// ValidateAuthorizationModes validates authorization modes and collects all encountered errors
func ValidateAuthorizationModes(authzModes []string, fldPath *field.Path) field.ErrorList {
// ValidateNodeRegistrationOptions validates the NodeRegistrationOptions object
func ValidateNodeRegistrationOptions(nro *kubeadm.NodeRegistrationOptions, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
found := map[string]bool{}
for _, authzMode := range authzModes {
if !authzmodes.IsValidAuthorizationMode(authzMode) {
allErrs = append(allErrs, field.Invalid(fldPath, authzMode, "invalid authorization mode"))
}
if found[authzMode] {
allErrs = append(allErrs, field.Invalid(fldPath, authzMode, "duplicate authorization mode"))
continue
}
found[authzMode] = true
}
for _, requiredMode := range requiredAuthzModes {
if !found[requiredMode] {
allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf("authorization mode %s must be enabled", requiredMode)))
}
if len(nro.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath, "--node-name or .nodeRegistration.name in the config file is a required value. It seems like this value couldn't be automatically detected in your environment, please specify the desired value using the CLI or config file."))
} else {
allErrs = append(allErrs, apivalidation.ValidateDNS1123Subdomain(nro.Name, field.NewPath("name"))...)
}
allErrs = append(allErrs, ValidateAbsolutePath(nro.CRISocket, fldPath.Child("criSocket"))...)
// TODO: Maybe validate .Taints as well in the future using something like validateNodeTaints() in pkg/apis/core/validation
return allErrs
}
// ValidateDiscovery validates discovery related configuration and collects all encountered errors
func ValidateDiscovery(c *kubeadm.NodeConfiguration, fldPath *field.Path) field.ErrorList {
func ValidateDiscovery(c *kubeadm.NodeConfiguration) field.ErrorList {
allErrs := field.ErrorList{}
if len(c.DiscoveryToken) != 0 {
allErrs = append(allErrs, ValidateToken(c.DiscoveryToken, fldPath)...)
allErrs = append(allErrs, ValidateToken(c.DiscoveryToken, field.NewPath("discoveryToken"))...)
}
if len(c.DiscoveryFile) != 0 {
allErrs = append(allErrs, ValidateDiscoveryFile(c.DiscoveryFile, fldPath)...)
allErrs = append(allErrs, ValidateDiscoveryFile(c.DiscoveryFile, field.NewPath("discoveryFile"))...)
}
allErrs = append(allErrs, ValidateArgSelection(c, fldPath)...)
allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, fldPath)...)
allErrs = append(allErrs, ValidateJoinDiscoveryTokenAPIServer(c, fldPath)...)
allErrs = append(allErrs, ValidateArgSelection(c, field.NewPath("discovery"))...)
allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, field.NewPath("tlsBootstrapToken"))...)
allErrs = append(allErrs, ValidateJoinDiscoveryTokenAPIServer(c.DiscoveryTokenAPIServers, field.NewPath("discoveryTokenAPIServers"))...)
return allErrs
}
@ -159,22 +121,22 @@ func ValidateDiscovery(c *kubeadm.NodeConfiguration, fldPath *field.Path) field.
func ValidateArgSelection(cfg *kubeadm.NodeConfiguration, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(cfg.DiscoveryToken) != 0 && len(cfg.DiscoveryFile) != 0 {
allErrs = append(allErrs, field.Invalid(fldPath, "", "DiscoveryToken and DiscoveryFile cannot both be set"))
allErrs = append(allErrs, field.Invalid(fldPath, "", "discoveryToken and discoveryFile cannot both be set"))
}
if len(cfg.DiscoveryToken) == 0 && len(cfg.DiscoveryFile) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, "", "DiscoveryToken or DiscoveryFile must be set"))
allErrs = append(allErrs, field.Invalid(fldPath, "", "discoveryToken or discoveryFile must be set"))
}
if len(cfg.DiscoveryTokenAPIServers) < 1 && len(cfg.DiscoveryToken) != 0 {
allErrs = append(allErrs, field.Required(fldPath, "DiscoveryTokenAPIServers not set"))
allErrs = append(allErrs, field.Required(fldPath, "discoveryTokenAPIServers not set"))
}
if len(cfg.DiscoveryFile) != 0 && len(cfg.DiscoveryTokenCACertHashes) != 0 {
allErrs = append(allErrs, field.Invalid(fldPath, "", "DiscoveryTokenCACertHashes cannot be used with DiscoveryFile"))
allErrs = append(allErrs, field.Invalid(fldPath, "", "discoveryTokenCACertHashes cannot be used with discoveryFile"))
}
if len(cfg.DiscoveryFile) == 0 && len(cfg.DiscoveryToken) != 0 &&
len(cfg.DiscoveryTokenCACertHashes) == 0 && !cfg.DiscoveryTokenUnsafeSkipCAVerification {
allErrs = append(allErrs, field.Invalid(fldPath, "", "using token-based discovery without DiscoveryTokenCACertHashes can be unsafe. set --discovery-token-unsafe-skip-ca-verification to continue"))
allErrs = append(allErrs, field.Invalid(fldPath, "", "using token-based discovery without discoveryTokenCACertHashes can be unsafe. set --discovery-token-unsafe-skip-ca-verification to continue"))
}
// TODO remove once we support multiple api servers
@ -185,9 +147,9 @@ func ValidateArgSelection(cfg *kubeadm.NodeConfiguration, fldPath *field.Path) f
}
// ValidateJoinDiscoveryTokenAPIServer validates discovery token for API server
func ValidateJoinDiscoveryTokenAPIServer(c *kubeadm.NodeConfiguration, fldPath *field.Path) field.ErrorList {
func ValidateJoinDiscoveryTokenAPIServer(apiServers []string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for _, m := range c.DiscoveryTokenAPIServers {
for _, m := range apiServers {
_, _, err := net.SplitHostPort(m)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, m, err.Error()))
@ -214,23 +176,35 @@ func ValidateDiscoveryFile(discoveryFile string, fldPath *field.Path) field.Erro
}
if u.Scheme != "https" {
allErrs = append(allErrs, field.Invalid(fldPath, discoveryFile, "if an URL is used, the scheme must be https"))
allErrs = append(allErrs, field.Invalid(fldPath, discoveryFile, "if a URL is used, the scheme must be https"))
}
return allErrs
}
// ValidateToken validates token
func ValidateToken(t string, fldPath *field.Path) field.ErrorList {
// ValidateBootstrapTokens validates a slice of BootstrapToken objects
func ValidateBootstrapTokens(bts []kubeadm.BootstrapToken, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for i, bt := range bts {
btPath := fldPath.Child(fmt.Sprintf("%d", i))
allErrs = append(allErrs, ValidateToken(bt.Token.String(), btPath.Child("token"))...)
allErrs = append(allErrs, ValidateTokenUsages(bt.Usages, btPath.Child("usages"))...)
allErrs = append(allErrs, ValidateTokenGroups(bt.Usages, bt.Groups, btPath.Child("groups"))...)
if bt.Expires != nil && bt.TTL != nil {
allErrs = append(allErrs, field.Invalid(btPath, "", "the BootstrapToken .TTL and .Expires fields are mutually exclusive"))
}
}
return allErrs
}
// ValidateToken validates a Bootstrap Token
func ValidateToken(token string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
id, secret, err := tokenutil.ParseToken(t)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, t, err.Error()))
if !bootstraputil.IsValidBootstrapToken(token) {
allErrs = append(allErrs, field.Invalid(fldPath, token, "the bootstrap token is invalid"))
}
if len(id) == 0 || len(secret) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, t, "token must be of form '[a-z0-9]{6}.[a-z0-9]{16}'"))
}
return allErrs
}
@ -267,6 +241,54 @@ func ValidateTokenUsages(usages []string, fldPath *field.Path) field.ErrorList {
return allErrs
}
// ValidateEtcd validates the .Etcd sub-struct.
func ValidateEtcd(e *kubeadm.Etcd, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
localPath := fldPath.Child("local")
externalPath := fldPath.Child("external")
if e.Local == nil && e.External == nil {
allErrs = append(allErrs, field.Invalid(fldPath, "", "either .Etcd.Local or .Etcd.External is required"))
return allErrs
}
if e.Local != nil && e.External != nil {
allErrs = append(allErrs, field.Invalid(fldPath, "", ".Etcd.Local and .Etcd.External are mutually exclusive"))
return allErrs
}
if e.Local != nil {
allErrs = append(allErrs, ValidateAbsolutePath(e.Local.DataDir, localPath.Child("dataDir"))...)
allErrs = append(allErrs, ValidateCertSANs(e.Local.ServerCertSANs, localPath.Child("serverCertSANs"))...)
allErrs = append(allErrs, ValidateCertSANs(e.Local.PeerCertSANs, localPath.Child("peerCertSANs"))...)
}
if e.External != nil {
requireHTTPS := true
// Only allow the http scheme if no certs/keys are passed
if e.External.CAFile == "" && e.External.CertFile == "" && e.External.KeyFile == "" {
requireHTTPS = false
}
// Require either none or both of the cert/key pair
if (e.External.CertFile == "" && e.External.KeyFile != "") || (e.External.CertFile != "" && e.External.KeyFile == "") {
allErrs = append(allErrs, field.Invalid(externalPath, "", "either both or none of .Etcd.External.CertFile and .Etcd.External.KeyFile must be set"))
}
// If the cert and key are specified, require the VA as well
if e.External.CertFile != "" && e.External.KeyFile != "" && e.External.CAFile == "" {
allErrs = append(allErrs, field.Invalid(externalPath, "", "setting .Etcd.External.CertFile and .Etcd.External.KeyFile requires .Etcd.External.CAFile"))
}
allErrs = append(allErrs, ValidateURLs(e.External.Endpoints, requireHTTPS, externalPath.Child("endpoints"))...)
if e.External.CAFile != "" {
allErrs = append(allErrs, ValidateAbsolutePath(e.External.CAFile, externalPath.Child("caFile"))...)
}
if e.External.CertFile != "" {
allErrs = append(allErrs, ValidateAbsolutePath(e.External.CertFile, externalPath.Child("certFile"))...)
}
if e.External.KeyFile != "" {
allErrs = append(allErrs, ValidateAbsolutePath(e.External.KeyFile, externalPath.Child("keyFile"))...)
}
}
return allErrs
}
// ValidateCertSANs validates alternative names
func ValidateCertSANs(altnames []string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
@ -278,6 +300,21 @@ func ValidateCertSANs(altnames []string, fldPath *field.Path) field.ErrorList {
return allErrs
}
// ValidateURLs validates the URLs given in the string slice, makes sure they are parseable. Optionally, it can enforcs HTTPS usage.
func ValidateURLs(urls []string, requireHTTPS bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for _, urlstr := range urls {
u, err := url.Parse(urlstr)
if err != nil || u.Scheme == "" {
allErrs = append(allErrs, field.Invalid(fldPath, urlstr, "not a valid URL"))
}
if requireHTTPS && u.Scheme != "https" {
allErrs = append(allErrs, field.Invalid(fldPath, urlstr, "the URL must be using the HTTPS scheme"))
}
}
return allErrs
}
// ValidateIPFromString validates ip address
func ValidateIPFromString(ipaddr string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
@ -305,10 +342,10 @@ func ValidateIPNetFromString(subnet string, minAddrs int64, fldPath *field.Path)
// ValidateNetworking validates networking configuration
func ValidateNetworking(c *kubeadm.Networking, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateDNS1123Subdomain(c.DNSDomain, field.NewPath("dns-domain"))...)
allErrs = append(allErrs, ValidateIPNetFromString(c.ServiceSubnet, constants.MinimumAddressesInServiceSubnet, field.NewPath("service-subnet"))...)
allErrs = append(allErrs, apivalidation.ValidateDNS1123Subdomain(c.DNSDomain, field.NewPath("dnsDomain"))...)
allErrs = append(allErrs, ValidateIPNetFromString(c.ServiceSubnet, constants.MinimumAddressesInServiceSubnet, field.NewPath("serviceSubnet"))...)
if len(c.PodSubnet) != 0 {
allErrs = append(allErrs, ValidateIPNetFromString(c.PodSubnet, constants.MinimumAddressesInServiceSubnet, field.NewPath("pod-subnet"))...)
allErrs = append(allErrs, ValidateIPNetFromString(c.PodSubnet, constants.MinimumAddressesInServiceSubnet, field.NewPath("podSubnet"))...)
}
return allErrs
}
@ -322,30 +359,6 @@ func ValidateAbsolutePath(path string, fldPath *field.Path) field.ErrorList {
return allErrs
}
// ValidateNodeName validates the name of a node
func ValidateNodeName(nodename string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if node.GetHostname(nodename) != nodename {
allErrs = append(allErrs, field.Invalid(fldPath, nodename, "nodename is not valid, must be lower case"))
}
return allErrs
}
// ValidateCloudProvider validates if cloud provider is supported
func ValidateCloudProvider(provider string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(provider) == 0 {
return allErrs
}
for _, supported := range cloudproviders {
if provider == supported {
return allErrs
}
}
allErrs = append(allErrs, field.Invalid(fldPath, provider, "cloudprovider not supported"))
return allErrs
}
// ValidateMixedArguments validates passed arguments
func ValidateMixedArguments(flag *pflag.FlagSet) error {
// If --config isn't set, we have nothing to validate
@ -355,7 +368,7 @@ func ValidateMixedArguments(flag *pflag.FlagSet) error {
mixedInvalidFlags := []string{}
flag.Visit(func(f *pflag.Flag) {
if f.Name == "config" || f.Name == "ignore-preflight-errors" || strings.HasPrefix(f.Name, "skip-") || f.Name == "dry-run" || f.Name == "kubeconfig" {
if f.Name == "config" || f.Name == "ignore-preflight-errors" || strings.HasPrefix(f.Name, "skip-") || f.Name == "dry-run" || f.Name == "kubeconfig" || f.Name == "v" {
// "--skip-*" flags or other whitelisted flags can be set with --config
return
}
@ -385,7 +398,7 @@ func ValidateFeatureGates(featureGates map[string]bool, fldPath *field.Path) fie
}
// ValidateAPIEndpoint validates API server's endpoint
func ValidateAPIEndpoint(c *kubeadm.MasterConfiguration, fldPath *field.Path) field.ErrorList {
func ValidateAPIEndpoint(c *kubeadm.API, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
endpoint, err := kubeadmutil.GetMasterEndpoint(c)
@ -420,6 +433,10 @@ func ValidateIgnorePreflightErrors(ignorePreflightErrors []string, skipPreflight
func ValidateKubeletConfiguration(c *kubeadm.KubeletConfiguration, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if c.BaseConfig == nil {
return allErrs
}
scheme, _, err := kubeletscheme.NewSchemeAndCodecs()
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, "kubeletConfiguration", err.Error()))

View File

@ -33,7 +33,7 @@ import (
utilpointer "k8s.io/kubernetes/pkg/util/pointer"
)
func TestValidateTokenDiscovery(t *testing.T) {
func TestValidateToken(t *testing.T) {
var tests = []struct {
c *kubeadm.NodeConfiguration
f *field.Path
@ -51,7 +51,7 @@ func TestValidateTokenDiscovery(t *testing.T) {
err := ValidateToken(rt.c.Token, rt.f).ToAggregate()
if (err == nil) != rt.expected {
t.Errorf(
"failed ValidateTokenDiscovery:\n\texpected: %t\n\t actual: %t",
"failed ValidateToken:\n\texpected: %t\n\t actual: %t",
rt.expected,
(err == nil),
)
@ -104,76 +104,31 @@ func TestValidateTokenGroups(t *testing.T) {
}
}
func TestValidateAuthorizationModes(t *testing.T) {
func TestValidateNodeRegistrationOptions(t *testing.T) {
var tests = []struct {
s []string
f *field.Path
expected bool
nodeName string
criSocket string
expectedErrors bool
}{
{[]string{""}, nil, false},
{[]string{"rBAC"}, nil, false}, // mode not supported
{[]string{"rBAC", "Webhook"}, nil, false}, // mode not supported
{[]string{"RBAC", "Webhook"}, nil, false}, // mode Node required
{[]string{"Node", "RBAC", "Webhook", "Webhook"}, nil, false}, // no duplicates allowed
{[]string{"not valid"}, nil, false}, // invalid mode
{[]string{"Node", "RBAC"}, nil, true}, // supported
{[]string{"RBAC", "Node"}, nil, true}, // supported
{[]string{"Node", "RBAC", "Webhook", "ABAC"}, nil, true}, // supported
{"", "/some/path", true}, // node name can't be empty
{"valid-nodename", "", true}, // crisocket can't be empty
{"INVALID-NODENAME", "/some/path", true}, // Upper cases is invalid
{"invalid-nodename-", "/some/path", true}, // Can't have trailing dashes
{"invalid-node?name", "/some/path", true}, // Unsupported characters
{"valid-nodename", "relative/path", true}, // crisocket must be an absolute path
{"valid-nodename", "/some/path", false}, // supported
{"valid-nodename-with-numbers01234", "/some/path/with/numbers/01234/", false}, // supported, with numbers as well
}
for _, rt := range tests {
actual := ValidateAuthorizationModes(rt.s, rt.f)
if (len(actual) == 0) != rt.expected {
nro := kubeadm.NodeRegistrationOptions{Name: rt.nodeName, CRISocket: rt.criSocket}
actual := ValidateNodeRegistrationOptions(&nro, field.NewPath("nodeRegistration"))
actualErrors := len(actual) > 0
if actualErrors != rt.expectedErrors {
t.Errorf(
"failed ValidateAuthorizationModes:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
)
}
}
}
func TestValidateNodeName(t *testing.T) {
var tests = []struct {
s string
f *field.Path
expected bool
}{
{"", nil, false}, // ok if not provided
{"1234", nil, true}, // supported
{"valid-nodename", nil, true}, // supported
{"INVALID-NODENAME", nil, false}, // Upper cases is invalid
}
for _, rt := range tests {
actual := ValidateNodeName(rt.s, rt.f)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"failed ValidateNodeName:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
)
}
}
}
func TestValidateCloudProvider(t *testing.T) {
var tests = []struct {
s string
f *field.Path
expected bool
}{
{"", nil, true}, // if not provided, ok, it's optional
{"1234", nil, false}, // not supported
{"awws", nil, false}, // not supported
{"aws", nil, true}, // supported
{"gce", nil, true}, // supported
}
for _, rt := range tests {
actual := ValidateCloudProvider(rt.s, rt.f)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"failed ValidateCloudProvider:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
"failed ValidateNodeRegistrationOptions: value: %v\n\texpected: %t\n\t actual: %t",
nro,
rt.expectedErrors,
actualErrors,
)
}
}
@ -275,7 +230,73 @@ func TestValidateAPIEndpoint(t *testing.T) {
expected: false,
},
{
name: "Valid IPv4 address and default port",
name: "Valid DNS ControlPlaneEndpoint (with port), AdvertiseAddress and default port",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "cp.k8s.io:8081",
AdvertiseAddress: "4.5.6.7",
BindPort: 6443,
},
},
expected: true,
},
{
name: "Valid IPv4 ControlPlaneEndpoint (with port), AdvertiseAddress and default port",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "1.2.3.4:8081",
AdvertiseAddress: "4.5.6.7",
BindPort: 6443,
},
},
expected: true,
},
{
name: "Valid IPv6 ControlPlaneEndpoint (with port), ControlPlaneEndpoint and port",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "[2001:db7::1]:8081",
AdvertiseAddress: "2001:db7::2",
BindPort: 6443,
},
},
expected: true,
},
{
name: "Valid DNS ControlPlaneEndpoint (without port), AdvertiseAddress and default port",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "cp.k8s.io",
AdvertiseAddress: "4.5.6.7",
BindPort: 6443,
},
},
expected: true,
},
{
name: "Valid IPv4 ControlPlaneEndpoint (without port), AdvertiseAddress and default port",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "1.2.3.4",
AdvertiseAddress: "4.5.6.7",
BindPort: 6443,
},
},
expected: true,
},
{
name: "Valid IPv6 ControlPlaneEndpoint (without port), ControlPlaneEndpoint and port",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "2001:db7::1",
AdvertiseAddress: "2001:db7::2",
BindPort: 6443,
},
},
expected: true,
},
{
name: "Valid IPv4 AdvertiseAddress and default port",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
AdvertiseAddress: "1.2.3.4",
@ -285,7 +306,7 @@ func TestValidateAPIEndpoint(t *testing.T) {
expected: true,
},
{
name: "Valid IPv6 address and port",
name: "Valid IPv6 AdvertiseAddress and port",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
AdvertiseAddress: "2001:db7::1",
@ -295,7 +316,7 @@ func TestValidateAPIEndpoint(t *testing.T) {
expected: true,
},
{
name: "Invalid IPv4 address",
name: "Invalid IPv4 AdvertiseAddress",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
AdvertiseAddress: "1.2.34",
@ -305,7 +326,7 @@ func TestValidateAPIEndpoint(t *testing.T) {
expected: false,
},
{
name: "Invalid IPv6 address",
name: "Invalid IPv6 AdvertiseAddress",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
AdvertiseAddress: "2001:db7:1",
@ -314,9 +335,55 @@ func TestValidateAPIEndpoint(t *testing.T) {
},
expected: false,
},
{
name: "Invalid BindPort",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
AdvertiseAddress: "1.2.3.4",
BindPort: 0,
},
},
expected: false,
},
{
name: "Invalid DNS ControlPlaneEndpoint",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "bad!!.k8s.io",
},
},
expected: false,
},
{
name: "Invalid ipv4 ControlPlaneEndpoint",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "1..3.4",
},
},
expected: false,
},
{
name: "Invalid ipv6 ControlPlaneEndpoint",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "1200::AB00:1234::2552:7777:1313",
},
},
expected: false,
},
{
name: "Invalid ControlPlaneEndpoint port",
s: &kubeadm.MasterConfiguration{
API: kubeadm.API{
ControlPlaneEndpoint: "1.2.3.4:0",
},
},
expected: false,
},
}
for _, rt := range tests {
actual := ValidateAPIEndpoint(rt.s, nil)
actual := ValidateAPIEndpoint(&rt.s.API, nil)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"%s test case failed:\n\texpected: %t\n\t actual: %t",
@ -343,13 +410,12 @@ func TestValidateMasterConfiguration(t *testing.T) {
AdvertiseAddress: "1.2.3.4",
BindPort: 6443,
},
AuthorizationModes: []string{"Node", "RBAC"},
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/cert/dir",
NodeName: nodename,
CertificatesDir: "/some/cert/dir",
NodeRegistration: kubeadm.NodeRegistrationOptions{Name: nodename, CRISocket: "/some/path"},
}, false},
{"invalid missing token with IPv6 service subnet",
&kubeadm.MasterConfiguration{
@ -357,13 +423,12 @@ func TestValidateMasterConfiguration(t *testing.T) {
AdvertiseAddress: "1.2.3.4",
BindPort: 6443,
},
AuthorizationModes: []string{"Node", "RBAC"},
Networking: kubeadm.Networking{
ServiceSubnet: "2001:db8::1/98",
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/cert/dir",
NodeName: nodename,
CertificatesDir: "/some/cert/dir",
NodeRegistration: kubeadm.NodeRegistrationOptions{Name: nodename, CRISocket: "/some/path"},
}, false},
{"invalid missing node name",
&kubeadm.MasterConfiguration{
@ -371,13 +436,11 @@ func TestValidateMasterConfiguration(t *testing.T) {
AdvertiseAddress: "1.2.3.4",
BindPort: 6443,
},
AuthorizationModes: []string{"Node", "RBAC"},
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/other/cert/dir",
Token: "abcdef.0123456789abcdef",
}, false},
{"valid master configuration with incorrect IPv4 pod subnet",
&kubeadm.MasterConfiguration{
@ -385,15 +448,13 @@ func TestValidateMasterConfiguration(t *testing.T) {
AdvertiseAddress: "1.2.3.4",
BindPort: 6443,
},
AuthorizationModes: []string{"Node", "RBAC"},
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
PodSubnet: "10.0.1.15",
},
CertificatesDir: "/some/other/cert/dir",
Token: "abcdef.0123456789abcdef",
NodeName: nodename,
CertificatesDir: "/some/other/cert/dir",
NodeRegistration: kubeadm.NodeRegistrationOptions{Name: nodename, CRISocket: "/some/path"},
}, false},
{"valid master configuration with IPv4 service subnet",
&kubeadm.MasterConfiguration{
@ -401,6 +462,11 @@ func TestValidateMasterConfiguration(t *testing.T) {
AdvertiseAddress: "1.2.3.4",
BindPort: 6443,
},
Etcd: kubeadm.Etcd{
Local: &kubeadm.LocalEtcd{
DataDir: "/some/path",
},
},
KubeProxy: kubeadm.KubeProxy{
Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{
BindAddress: "192.168.59.103",
@ -427,15 +493,13 @@ func TestValidateMasterConfiguration(t *testing.T) {
},
},
},
AuthorizationModes: []string{"Node", "RBAC"},
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
DNSDomain: "cluster.local",
PodSubnet: "10.0.1.15/16",
},
CertificatesDir: "/some/other/cert/dir",
Token: "abcdef.0123456789abcdef",
NodeName: nodename,
CertificatesDir: "/some/other/cert/dir",
NodeRegistration: kubeadm.NodeRegistrationOptions{Name: nodename, CRISocket: "/some/path"},
}, true},
{"valid master configuration using IPv6 service subnet",
&kubeadm.MasterConfiguration{
@ -443,6 +507,11 @@ func TestValidateMasterConfiguration(t *testing.T) {
AdvertiseAddress: "1:2:3::4",
BindPort: 3446,
},
Etcd: kubeadm.Etcd{
Local: &kubeadm.LocalEtcd{
DataDir: "/some/path",
},
},
KubeProxy: kubeadm.KubeProxy{
Config: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{
BindAddress: "192.168.59.103",
@ -469,14 +538,12 @@ func TestValidateMasterConfiguration(t *testing.T) {
},
},
},
AuthorizationModes: []string{"Node", "RBAC"},
Networking: kubeadm.Networking{
ServiceSubnet: "2001:db8::1/98",
DNSDomain: "cluster.local",
},
CertificatesDir: "/some/other/cert/dir",
Token: "abcdef.0123456789abcdef",
NodeName: nodename,
CertificatesDir: "/some/other/cert/dir",
NodeRegistration: kubeadm.NodeRegistrationOptions{Name: nodename, CRISocket: "/some/path"},
}, true},
}
for _, rt := range tests {
@ -705,7 +772,7 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
}
for _, successCase := range successCases {
if errs := ValidateProxy(&successCase, nil); len(errs) != 0 {
if errs := ValidateProxy(successCase.KubeProxy.Config, nil); len(errs) != 0 {
t.Errorf("failed ValidateProxy: expect no errors but got %v", errs)
}
}
@ -909,7 +976,7 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
}
for i, errorCase := range errorCases {
if errs := ValidateProxy(&errorCase.masterConfig, nil); len(errs) == 0 {
if errs := ValidateProxy(errorCase.masterConfig.KubeProxy.Config, nil); len(errs) == 0 {
t.Errorf("%d failed ValidateProxy: expected error for %s, but got no error", i, errorCase.msg)
} else if !strings.Contains(errs[0].Error(), errorCase.msg) {
t.Errorf("%d failed ValidateProxy: unexpected error: %v, expected: %s", i, errs[0], errorCase.msg)
@ -1040,7 +1107,7 @@ func TestValidateJoinDiscoveryTokenAPIServer(t *testing.T) {
},
}
for _, rt := range tests {
actual := ValidateJoinDiscoveryTokenAPIServer(rt.s, nil)
actual := ValidateJoinDiscoveryTokenAPIServer(rt.s.DiscoveryTokenAPIServers, nil)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"failed ValidateJoinDiscoveryTokenAPIServer:\n\texpected: %t\n\t actual: %t",

View File

@ -1,7 +1,7 @@
// +build !ignore_autogenerated
/*
Copyright 2018 The Kubernetes Authors.
Copyright 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.
@ -21,6 +21,7 @@ limitations under the License.
package kubeadm
import (
core_v1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
v1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
@ -69,42 +70,97 @@ func (in *AuditPolicyConfiguration) DeepCopy() *AuditPolicyConfiguration {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Etcd) DeepCopyInto(out *Etcd) {
func (in *BootstrapToken) DeepCopyInto(out *BootstrapToken) {
*out = *in
if in.Endpoints != nil {
in, out := &in.Endpoints, &out.Endpoints
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ExtraArgs != nil {
in, out := &in.ExtraArgs, &out.ExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.SelfHosted != nil {
in, out := &in.SelfHosted, &out.SelfHosted
if in.Token != nil {
in, out := &in.Token, &out.Token
if *in == nil {
*out = nil
} else {
*out = new(SelfHostedEtcd)
*out = new(BootstrapTokenString)
**out = **in
}
}
if in.ServerCertSANs != nil {
in, out := &in.ServerCertSANs, &out.ServerCertSANs
if in.TTL != nil {
in, out := &in.TTL, &out.TTL
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
**out = **in
}
}
if in.Expires != nil {
in, out := &in.Expires, &out.Expires
if *in == nil {
*out = nil
} else {
*out = (*in).DeepCopy()
}
}
if in.Usages != nil {
in, out := &in.Usages, &out.Usages
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.PeerCertSANs != nil {
in, out := &in.PeerCertSANs, &out.PeerCertSANs
if in.Groups != nil {
in, out := &in.Groups, &out.Groups
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapToken.
func (in *BootstrapToken) DeepCopy() *BootstrapToken {
if in == nil {
return nil
}
out := new(BootstrapToken)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BootstrapTokenString) DeepCopyInto(out *BootstrapTokenString) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapTokenString.
func (in *BootstrapTokenString) DeepCopy() *BootstrapTokenString {
if in == nil {
return nil
}
out := new(BootstrapTokenString)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Etcd) DeepCopyInto(out *Etcd) {
*out = *in
if in.Local != nil {
in, out := &in.Local, &out.Local
if *in == nil {
*out = nil
} else {
*out = new(LocalEtcd)
(*in).DeepCopyInto(*out)
}
}
if in.External != nil {
in, out := &in.External, &out.External
if *in == nil {
*out = nil
} else {
*out = new(ExternalEtcd)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Etcd.
func (in *Etcd) DeepCopy() *Etcd {
if in == nil {
@ -115,6 +171,27 @@ func (in *Etcd) DeepCopy() *Etcd {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalEtcd) DeepCopyInto(out *ExternalEtcd) {
*out = *in
if in.Endpoints != nil {
in, out := &in.Endpoints, &out.Endpoints
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalEtcd.
func (in *ExternalEtcd) DeepCopy() *ExternalEtcd {
if in == nil {
return nil
}
out := new(ExternalEtcd)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostPathMount) DeepCopyInto(out *HostPathMount) {
*out = *in
@ -181,39 +258,56 @@ func (in *KubeletConfiguration) DeepCopy() *KubeletConfiguration {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LocalEtcd) DeepCopyInto(out *LocalEtcd) {
*out = *in
if in.ExtraArgs != nil {
in, out := &in.ExtraArgs, &out.ExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.ServerCertSANs != nil {
in, out := &in.ServerCertSANs, &out.ServerCertSANs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.PeerCertSANs != nil {
in, out := &in.PeerCertSANs, &out.PeerCertSANs
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalEtcd.
func (in *LocalEtcd) DeepCopy() *LocalEtcd {
if in == nil {
return nil
}
out := new(LocalEtcd)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.BootstrapTokens != nil {
in, out := &in.BootstrapTokens, &out.BootstrapTokens
*out = make([]BootstrapToken, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.NodeRegistration.DeepCopyInto(&out.NodeRegistration)
out.API = in.API
in.KubeProxy.DeepCopyInto(&out.KubeProxy)
in.Etcd.DeepCopyInto(&out.Etcd)
in.KubeletConfiguration.DeepCopyInto(&out.KubeletConfiguration)
out.Networking = in.Networking
if in.AuthorizationModes != nil {
in, out := &in.AuthorizationModes, &out.AuthorizationModes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.TokenTTL != nil {
in, out := &in.TokenTTL, &out.TokenTTL
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
**out = **in
}
}
if in.TokenUsages != nil {
in, out := &in.TokenUsages, &out.TokenUsages
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.TokenGroups != nil {
in, out := &in.TokenGroups, &out.TokenGroups
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.APIServerExtraArgs != nil {
in, out := &in.APIServerExtraArgs, &out.APIServerExtraArgs
*out = make(map[string]string, len(*in))
@ -304,11 +398,21 @@ func (in *Networking) DeepCopy() *Networking {
func (in *NodeConfiguration) DeepCopyInto(out *NodeConfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
in.NodeRegistration.DeepCopyInto(&out.NodeRegistration)
if in.DiscoveryTokenAPIServers != nil {
in, out := &in.DiscoveryTokenAPIServers, &out.DiscoveryTokenAPIServers
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DiscoveryTimeout != nil {
in, out := &in.DiscoveryTimeout, &out.DiscoveryTimeout
if *in == nil {
*out = nil
} else {
*out = new(v1.Duration)
**out = **in
}
}
if in.DiscoveryTokenCACertHashes != nil {
in, out := &in.DiscoveryTokenCACertHashes, &out.DiscoveryTokenCACertHashes
*out = make([]string, len(*in))
@ -343,33 +447,31 @@ func (in *NodeConfiguration) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SelfHostedEtcd) DeepCopyInto(out *SelfHostedEtcd) {
func (in *NodeRegistrationOptions) DeepCopyInto(out *NodeRegistrationOptions) {
*out = *in
if in.Taints != nil {
in, out := &in.Taints, &out.Taints
*out = make([]core_v1.Taint, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.KubeletExtraArgs != nil {
in, out := &in.KubeletExtraArgs, &out.KubeletExtraArgs
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelfHostedEtcd.
func (in *SelfHostedEtcd) DeepCopy() *SelfHostedEtcd {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeRegistrationOptions.
func (in *NodeRegistrationOptions) DeepCopy() *NodeRegistrationOptions {
if in == nil {
return nil
}
out := new(SelfHostedEtcd)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TokenDiscovery) DeepCopyInto(out *TokenDiscovery) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TokenDiscovery.
func (in *TokenDiscovery) DeepCopy() *TokenDiscovery {
if in == nil {
return nil
}
out := new(TokenDiscovery)
out := new(NodeRegistrationOptions)
in.DeepCopyInto(out)
return out
}

View File

@ -21,9 +21,11 @@ go_library(
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/install:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/cmd/options:go_default_library",
"//cmd/kubeadm/app/cmd/phases:go_default_library",
"//cmd/kubeadm/app/cmd/upgrade:go_default_library",
"//cmd/kubeadm/app/cmd/util:go_default_library",
@ -41,6 +43,7 @@ go_library(
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
"//cmd/kubeadm/app/phases/kubelet:go_default_library",
"//cmd/kubeadm/app/phases/markmaster:go_default_library",
"//cmd/kubeadm/app/phases/patchnode:go_default_library",
"//cmd/kubeadm/app/phases/selfhosting:go_default_library",
"//cmd/kubeadm/app/phases/uploadconfig:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library",
@ -50,28 +53,24 @@ go_library(
"//cmd/kubeadm/app/util/config:go_default_library",
"//cmd/kubeadm/app/util/dryrun:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/util/initsystem:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/version:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/renstrom/dedent:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag: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/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/duration:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/util:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
@ -80,20 +79,30 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"completion_test.go",
"join_test.go",
"reset_test.go",
"token_test.go",
"version_test.go",
],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/spf13/cobra: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/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
],
@ -110,9 +119,22 @@ filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//cmd/kubeadm/app/cmd/options:all-srcs",
"//cmd/kubeadm/app/cmd/phases:all-srcs",
"//cmd/kubeadm/app/cmd/upgrade:all-srcs",
"//cmd/kubeadm/app/cmd/util:all-srcs",
],
tags = ["automanaged"],
)
go_test(
name = "go_default_xtest",
srcs = ["config_test.go"],
deps = [
":go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//cmd/kubeadm/app/util/config:go_default_library",
"//vendor/github.com/renstrom/dedent:go_default_library",
],
)

View File

@ -22,17 +22,14 @@ import (
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
"k8s.io/apiserver/pkg/util/flag"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/upgrade"
// Register the kubeadm configuration types because CLI flag generation
// depends on the generated defaults.
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
)
// NewKubeadmCommand return cobra.Command to run kubeadm command
func NewKubeadmCommand(_ io.Reader, out, err io.Writer) *cobra.Command {
func NewKubeadmCommand(in io.Reader, out, err io.Writer) *cobra.Command {
cmds := &cobra.Command{
Use: "kubeadm",
Short: "kubeadm: easily bootstrap a secure Kubernetes cluster",
@ -71,13 +68,12 @@ func NewKubeadmCommand(_ io.Reader, out, err io.Writer) *cobra.Command {
}
cmds.ResetFlags()
cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc)
cmds.AddCommand(NewCmdCompletion(out, ""))
cmds.AddCommand(NewCmdConfig(out))
cmds.AddCommand(NewCmdInit(out))
cmds.AddCommand(NewCmdJoin(out))
cmds.AddCommand(NewCmdReset(out))
cmds.AddCommand(NewCmdReset(in, out))
cmds.AddCommand(NewCmdVersion(out))
cmds.AddCommand(NewCmdToken(out, err))
cmds.AddCommand(upgrade.NewCmdUpgrade(out))

View File

@ -21,6 +21,7 @@ import (
"fmt"
"io"
"github.com/golang/glog"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
@ -50,17 +51,18 @@ var (
The shell code must be evaluated to provide interactive
completion of kubeadm commands. This can be done by sourcing it from
the .bash_profile.
Note: this requires the bash-completion framework.
Note: this requires the bash-completion framework, which is not installed
by default on Mac. This can be installed by using homebrew:
To install it on Mac use homebrew:
$ brew install bash-completion
Once installed, bash_completion must be evaluated. This can be done by adding the
following line to the .bash_profile
$ source $(brew --prefix)/etc/bash_completion
If bash-completion is not installed on Linux, please install the 'bash-completion' package
via your distribution's package manager.
Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2`)
completionExample = dedent.Dedent(`
@ -88,13 +90,17 @@ var (
}
)
// NewCmdCompletion return command for executing "kubeadm completion" command
func NewCmdCompletion(out io.Writer, boilerPlate string) *cobra.Command {
// GetSupportedShells returns a list of supported shells
func GetSupportedShells() []string {
shells := []string{}
for s := range completionShells {
shells = append(shells, s)
}
return shells
}
// NewCmdCompletion returns the "kubeadm completion" command
func NewCmdCompletion(out io.Writer, boilerPlate string) *cobra.Command {
cmd := &cobra.Command{
Use: "completion SHELL",
Short: i18n.T("Output shell completion code for the specified shell (bash or zsh)."),
@ -104,7 +110,7 @@ func NewCmdCompletion(out io.Writer, boilerPlate string) *cobra.Command {
err := RunCompletion(out, boilerPlate, cmd, args)
kubeadmutil.CheckErr(err)
},
ValidArgs: shells,
ValidArgs: GetSupportedShells(),
}
return cmd
@ -133,6 +139,7 @@ func RunCompletion(out io.Writer, boilerPlate string, cmd *cobra.Command, args [
}
func runCompletionBash(out io.Writer, kubeadm *cobra.Command) error {
glog.V(1).Infoln("[completion] writing completion code for Bash")
return kubeadm.GenBashCompletion(out)
}
@ -210,7 +217,7 @@ __kubeadm_get_comp_words_by_ref() {
__kubeadm_filedir() {
local RET OLD_IFS w qw
__debug "_filedir $@ cur=$cur"
__kubectl_debug "_filedir $@ cur=$cur"
if [[ "$1" = \~* ]]; then
# somehow does not work. Maybe, zsh does not call this at all
eval echo "$1"
@ -227,7 +234,7 @@ __kubeadm_filedir() {
fi
IFS="$OLD_IFS"
IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"
IFS="," __kubectl_debug "RET=${RET[@]} len=${#RET[@]}"
for w in ${RET[@]}; do
if [[ ! "${w}" = "${cur}"* ]]; then
@ -278,10 +285,12 @@ __kubeadm_convert_bash_to_zsh() {
-e "s/\\\$(type${RWORD}/\$(__kubeadm_type/g" \
<<'BASH_COMPLETION_EOF'
`
glog.V(1).Infoln("[completion] writing completion code for Zsh")
out.Write([]byte(zshInitialization))
buf := new(bytes.Buffer)
kubeadm.GenBashCompletion(buf)
glog.V(1).Infoln("[completion] writing completion code for Bash")
out.Write(buf.Bytes())
zshTail := `

View File

@ -0,0 +1,95 @@
/*
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 cmd
import (
"bytes"
"testing"
"github.com/spf13/cobra"
)
const shellsError = "Unexpected empty completion shells list"
func TestNewCmdCompletion(t *testing.T) {
var out bytes.Buffer
shells := GetSupportedShells()
if len(shells) == 0 {
t.Errorf(shellsError)
}
// test NewCmdCompletion with a valid shell.
// use a dummy parent command as NewCmdCompletion needs it.
parentCmd := &cobra.Command{}
args := []string{"completion", shells[0]}
parentCmd.SetArgs(args)
cmd := NewCmdCompletion(&out, "")
parentCmd.AddCommand(cmd)
if err := parentCmd.Execute(); err != nil {
t.Errorf("Cannot exectute NewCmdCompletion: %v", err)
}
}
func TestRunCompletion(t *testing.T) {
var out bytes.Buffer
type TestCase struct {
name string
args []string
expectedError bool
}
testCases := []TestCase{
{
name: "invalid: missing argument",
args: []string{},
expectedError: true,
},
{
name: "invalid: too many arguments",
args: []string{"", ""},
expectedError: true,
},
{
name: "invalid: unsupported shell name",
args: []string{"unsupported"},
expectedError: true,
},
}
// test all supported shells
shells := GetSupportedShells()
if len(shells) == 0 {
t.Errorf(shellsError)
}
for _, shell := range shells {
test := TestCase{
name: "valid: test shell " + shell,
args: []string{shell},
}
testCases = append(testCases, test)
}
// use dummy cobra commands
parentCmd := &cobra.Command{}
cmd := &cobra.Command{}
parentCmd.AddCommand(cmd)
for _, tc := range testCases {
if err := RunCompletion(&out, "", cmd, tc.args); (err != nil) != tc.expectedError {
t.Errorf("Test case %q: TestRunCompletion expected error: %v, saw: %v", tc.name, tc.expectedError, (err != nil))
}
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016 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.
@ -19,21 +19,46 @@ package cmd
import (
"fmt"
"io"
"io/ioutil"
"strings"
"github.com/golang/glog"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
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"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/kubernetes/cmd/kubeadm/app/images"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/pkg/api/legacyscheme"
utilsexec "k8s.io/utils/exec"
)
const (
// TODO: Figure out how to get these constants from the API machinery
masterConfig = "MasterConfiguration"
nodeConfig = "NodeConfiguration"
)
var (
availableAPIObjects = []string{masterConfig, nodeConfig}
// sillyToken is only set statically to make kubeadm not randomize the token on every run
sillyToken = kubeadmapiv1alpha2.BootstrapToken{
Token: &kubeadmapiv1alpha2.BootstrapTokenString{
ID: "abcdef",
Secret: "0123456789abcdef",
},
}
)
// NewCmdConfig returns cobra.Command for "kubeadm config" command
@ -59,9 +84,134 @@ func NewCmdConfig(out io.Writer) *cobra.Command {
cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster.")
cmd.AddCommand(NewCmdConfigPrintDefault(out))
cmd.AddCommand(NewCmdConfigMigrate(out))
cmd.AddCommand(NewCmdConfigUpload(out, &kubeConfigFile))
cmd.AddCommand(NewCmdConfigView(out, &kubeConfigFile))
cmd.AddCommand(NewCmdConfigImages(out))
return cmd
}
// NewCmdConfigPrintDefault returns cobra.Command for "kubeadm config print-default" command
func NewCmdConfigPrintDefault(out io.Writer) *cobra.Command {
apiObjects := []string{}
cmd := &cobra.Command{
Use: "print-default",
Aliases: []string{"print-defaults"},
Short: "Print the default values for a kubeadm configuration object.",
Long: fmt.Sprintf(dedent.Dedent(`
This command prints the default MasterConfiguration object that is used for 'kubeadm init' and 'kubeadm upgrade',
and the default NodeConfiguration object that is used for 'kubeadm join'.
Note that sensitive values like the Bootstrap Token fields are replaced with silly values like %q in order to pass validation but
not perform the real computation for creating a token.
`), sillyToken),
Run: func(cmd *cobra.Command, args []string) {
if len(apiObjects) == 0 {
apiObjects = availableAPIObjects
}
for i, apiObject := range apiObjects {
if i > 0 {
fmt.Fprintln(out, "---")
}
cfgBytes, err := getDefaultAPIObjectBytes(apiObject)
kubeadmutil.CheckErr(err)
// Print the API object byte array
fmt.Fprintf(out, "%s", cfgBytes)
}
},
}
cmd.Flags().StringSliceVar(&apiObjects, "api-objects", apiObjects,
fmt.Sprintf("A comma-separated list for API objects to print the default values for. Available values: %v. This flag unset means 'print all known objects'", availableAPIObjects))
return cmd
}
func getDefaultAPIObjectBytes(apiObject string) ([]byte, error) {
if apiObject == masterConfig {
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha2.MasterConfiguration{
BootstrapTokens: []kubeadmapiv1alpha2.BootstrapToken{sillyToken},
})
kubeadmutil.CheckErr(err)
return kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmscheme.Codecs)
}
if apiObject == nodeConfig {
internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha2.NodeConfiguration{
Token: sillyToken.Token.String(),
DiscoveryTokenAPIServers: []string{"kube-apiserver:6443"},
DiscoveryTokenUnsafeSkipCAVerification: true,
})
kubeadmutil.CheckErr(err)
return kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmscheme.Codecs)
}
return []byte{}, fmt.Errorf("--api-object needs to be one of %v", availableAPIObjects)
}
// NewCmdConfigMigrate returns cobra.Command for "kubeadm config migrate" command
func NewCmdConfigMigrate(out io.Writer) *cobra.Command {
var oldCfgPath, newCfgPath string
cmd := &cobra.Command{
Use: "migrate",
Short: "Read an older version of the kubeadm configuration API types from a file, and output the similar config object for the newer version.",
Long: fmt.Sprintf(dedent.Dedent(`
This command lets you convert configuration objects of older versions to the latest supported version,
locally in the CLI tool without ever touching anything in the cluster.
In this version of kubeadm, the following API versions are supported:
- %s
- %s
Further, kubeadm can only write out config of version %q, but read both types.
So regardless of what version you pass to the --old-config parameter here, the API object will be
read, deserialized, defaulted, converted, validated, and re-serialized when written to stdout or
--new-config if specified.
In other words, the output of this command is what kubeadm actually would read internally if you
submitted this file to "kubeadm init"
`), kubeadmapiv1alpha2.SchemeGroupVersion.String(), kubeadmapiv1alpha1.SchemeGroupVersion.String(), kubeadmapiv1alpha2.SchemeGroupVersion.String()),
Run: func(cmd *cobra.Command, args []string) {
if len(oldCfgPath) == 0 {
kubeadmutil.CheckErr(fmt.Errorf("The --old-config flag is mandatory"))
}
b, err := ioutil.ReadFile(oldCfgPath)
kubeadmutil.CheckErr(err)
var outputBytes []byte
gvk, err := kubeadmutil.GroupVersionKindFromBytes(b, kubeadmscheme.Codecs)
kubeadmutil.CheckErr(err)
switch gvk.Kind {
case masterConfig:
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(oldCfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
kubeadmutil.CheckErr(err)
outputBytes, err = kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmscheme.Codecs)
kubeadmutil.CheckErr(err)
case nodeConfig:
internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig(oldCfgPath, &kubeadmapiv1alpha2.NodeConfiguration{})
kubeadmutil.CheckErr(err)
// TODO: In the future we might not want to duplicate these two lines of code for every case here.
outputBytes, err = kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmscheme.Codecs)
kubeadmutil.CheckErr(err)
default:
kubeadmutil.CheckErr(fmt.Errorf("Didn't recognize type with GroupVersionKind: %v", gvk))
}
if newCfgPath == "" {
fmt.Fprint(out, string(outputBytes))
} else {
if err := ioutil.WriteFile(newCfgPath, outputBytes, 0644); err != nil {
kubeadmutil.CheckErr(fmt.Errorf("failed to write the new configuration to the file %q: %v", newCfgPath, err))
}
}
},
}
cmd.Flags().StringVar(&oldCfgPath, "old-config", "", "Path to the kubeadm config file that is using an old API version and should be converted. This flag is mandatory.")
cmd.Flags().StringVar(&newCfgPath, "new-config", "", "Path to the resulting equivalent kubeadm config file using the new API version. Optional, if not specified output will be sent to STDOUT.")
return cmd
}
@ -89,6 +239,7 @@ func NewCmdConfigView(out io.Writer, kubeConfigFile *string) *cobra.Command {
The configuration is located in the %q namespace in the %q ConfigMap.
`), metav1.NamespaceSystem, constants.MasterConfigurationConfigMap),
Run: func(cmd *cobra.Command, args []string) {
glog.V(1).Infoln("[config] retrieving ClientSet from file")
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
kubeadmutil.CheckErr(err)
@ -117,12 +268,15 @@ func NewCmdConfigUploadFromFile(out io.Writer, kubeConfigFile *string) *cobra.Co
kubeadmutil.CheckErr(fmt.Errorf("The --config flag is mandatory"))
}
glog.V(1).Infoln("[config] retrieving ClientSet from file")
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
kubeadmutil.CheckErr(err)
// The default configuration is empty; everything should come from the file on disk
defaultcfg := &kubeadmapiext.MasterConfiguration{}
glog.V(1).Infoln("[config] creating empty default configuration")
defaultcfg := &kubeadmapiv1alpha2.MasterConfiguration{}
// Upload the configuration using the file; don't care about the defaultcfg really
glog.V(1).Infof("[config] uploading configuration")
err = uploadConfiguration(client, cfgPath, defaultcfg)
kubeadmutil.CheckErr(err)
},
@ -133,8 +287,8 @@ func NewCmdConfigUploadFromFile(out io.Writer, kubeConfigFile *string) *cobra.Co
// NewCmdConfigUploadFromFlags returns cobra.Command for "kubeadm config upload from-flags" command
func NewCmdConfigUploadFromFlags(out io.Writer, kubeConfigFile *string) *cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{}
legacyscheme.Scheme.Default(cfg)
cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
kubeadmscheme.Scheme.Default(cfg)
var featureGatesString string
@ -150,15 +304,17 @@ func NewCmdConfigUploadFromFlags(out io.Writer, kubeConfigFile *string) *cobra.C
`), metav1.NamespaceSystem, constants.MasterConfigurationConfigMap),
Run: func(cmd *cobra.Command, args []string) {
var err error
glog.V(1).Infoln("[config] creating new FeatureGates")
if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil {
kubeadmutil.CheckErr(err)
}
glog.V(1).Infoln("[config] retrieving ClientSet from file")
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
kubeadmutil.CheckErr(err)
// Default both statically and dynamically, convert to internal API type, and validate everything
// The cfgPath argument is unset here as we shouldn't load a config file from disk, just go with cfg
glog.V(1).Infof("[config] uploading configuration")
err = uploadConfiguration(client, "", cfg)
kubeadmutil.CheckErr(err)
},
@ -170,6 +326,7 @@ func NewCmdConfigUploadFromFlags(out io.Writer, kubeConfigFile *string) *cobra.C
// RunConfigView gets the configuration persisted in the cluster
func RunConfigView(out io.Writer, client clientset.Interface) error {
glog.V(1).Infoln("[config] getting the cluster configuration")
cfgConfigMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(constants.MasterConfigurationConfigMap, metav1.GetOptions{})
if err != nil {
return err
@ -180,10 +337,11 @@ func RunConfigView(out io.Writer, client clientset.Interface) error {
}
// uploadConfiguration handles the uploading of the configuration internally
func uploadConfiguration(client clientset.Interface, cfgPath string, defaultcfg *kubeadmapiext.MasterConfiguration) error {
func uploadConfiguration(client clientset.Interface, cfgPath string, defaultcfg *kubeadmapiv1alpha2.MasterConfiguration) error {
// Default both statically and dynamically, convert to internal API type, and validate everything
// First argument is unset here as we shouldn't load a config file from disk
glog.V(1).Infoln("[config] converting to internal API type")
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, defaultcfg)
if err != nil {
return err
@ -192,3 +350,139 @@ func uploadConfiguration(client clientset.Interface, cfgPath string, defaultcfg
// Then just call the uploadconfig phase to do the rest of the work
return uploadconfig.UploadConfiguration(internalcfg, client)
}
// NewCmdConfigImages returns the "kubeadm config images" command
func NewCmdConfigImages(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "images",
Short: "Interact with container images used by kubeadm.",
RunE: cmdutil.SubCmdRunE("images"),
}
cmd.AddCommand(NewCmdConfigImagesList(out, nil))
cmd.AddCommand(NewCmdConfigImagesPull())
return cmd
}
// NewCmdConfigImagesPull returns the `kubeadm config images pull` command
func NewCmdConfigImagesPull() *cobra.Command {
cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, featureGatesString string
var err error
cmd := &cobra.Command{
Use: "pull",
Short: "Pull images used by kubeadm.",
Run: func(_ *cobra.Command, _ []string) {
cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString)
kubeadmutil.CheckErr(err)
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
kubeadmutil.CheckErr(err)
puller, err := images.NewCRInterfacer(utilsexec.New(), internalcfg.GetCRISocket())
kubeadmutil.CheckErr(err)
imagesPull := NewImagesPull(puller, images.GetAllImages(internalcfg))
kubeadmutil.CheckErr(imagesPull.PullAll())
},
}
AddImagesCommonConfigFlags(cmd.PersistentFlags(), cfg, &cfgPath, &featureGatesString)
AddImagesPullFlags(cmd.PersistentFlags(), cfg)
return cmd
}
// ImagesPull is the struct used to hold information relating to image pulling
type ImagesPull struct {
puller images.Puller
images []string
}
// NewImagesPull initializes and returns the `kubeadm config images pull` command
func NewImagesPull(puller images.Puller, images []string) *ImagesPull {
return &ImagesPull{
puller: puller,
images: images,
}
}
// PullAll pulls all images that the ImagesPull knows about
func (ip *ImagesPull) PullAll() error {
for _, image := range ip.images {
if err := ip.puller.Pull(image); err != nil {
return fmt.Errorf("failed to pull image %q: %v", image, err)
}
fmt.Printf("[config/images] Pulled %s\n", image)
}
return nil
}
// NewCmdConfigImagesList returns the "kubeadm config images list" command
func NewCmdConfigImagesList(out io.Writer, mockK8sVersion *string) *cobra.Command {
cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, featureGatesString string
var err error
// This just sets the kubernetes version for unit testing so kubeadm won't try to
// lookup the latest release from the internet.
if mockK8sVersion != nil {
cfg.KubernetesVersion = *mockK8sVersion
}
cmd := &cobra.Command{
Use: "list",
Short: "Print a list of images kubeadm will use. The configuration file is used in case any images or image repositories are customized.",
Run: func(_ *cobra.Command, _ []string) {
cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString)
kubeadmutil.CheckErr(err)
imagesList, err := NewImagesList(cfgPath, cfg)
kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(imagesList.Run(out))
},
}
AddImagesCommonConfigFlags(cmd.PersistentFlags(), cfg, &cfgPath, &featureGatesString)
return cmd
}
// NewImagesList returns the underlying struct for the "kubeadm config images list" command
func NewImagesList(cfgPath string, cfg *kubeadmapiv1alpha2.MasterConfiguration) (*ImagesList, error) {
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
if err != nil {
return nil, fmt.Errorf("could not convert cfg to an internal cfg: %v", err)
}
return &ImagesList{
cfg: internalcfg,
}, nil
}
// ImagesList defines the struct used for "kubeadm config images list"
type ImagesList struct {
cfg *kubeadmapi.MasterConfiguration
}
// Run runs the images command and writes the result to the io.Writer passed in
func (i *ImagesList) Run(out io.Writer) error {
imgs := images.GetAllImages(i.cfg)
for _, img := range imgs {
fmt.Fprintln(out, img)
}
return nil
}
// AddImagesCommonConfigFlags adds the flags that configure kubeadm (and affect the images kubeadm will use)
func AddImagesCommonConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha2.MasterConfiguration, cfgPath *string, featureGatesString *string) {
flagSet.StringVar(
&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion,
`Choose a specific Kubernetes version for the control plane.`,
)
flagSet.StringVar(featureGatesString, "feature-gates", *featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))
flagSet.StringVar(cfgPath, "config", *cfgPath, "Path to kubeadm config file.")
}
// AddImagesPullFlags adds flags related to the `kubeadm config images pull` command
func AddImagesPullFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha2.MasterConfiguration) {
flagSet.StringVar(&cfg.NodeRegistration.CRISocket, "cri-socket-path", cfg.NodeRegistration.CRISocket, "Path to the CRI socket.")
}

View File

@ -0,0 +1,246 @@
/*
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 cmd_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/renstrom/dedent"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/kubernetes/cmd/kubeadm/app/util/config"
)
const (
defaultNumberOfImages = 8
// dummyKubernetesVersion is just used for unit testing, in order to not make
// kubeadm lookup dl.k8s.io to resolve what the latest stable release is
dummyKubernetesVersion = "v1.10.0"
)
func TestNewCmdConfigImagesList(t *testing.T) {
var output bytes.Buffer
mockK8sVersion := dummyKubernetesVersion
images := cmd.NewCmdConfigImagesList(&output, &mockK8sVersion)
images.Run(nil, nil)
actual := strings.Split(output.String(), "\n")
if len(actual) != defaultNumberOfImages {
t.Fatalf("Expected %v but found %v images", defaultNumberOfImages, len(actual))
}
}
func TestImagesListRunWithCustomConfigPath(t *testing.T) {
testcases := []struct {
name string
expectedImageCount int
// each string provided here must appear in at least one image returned by Run
expectedImageSubstrings []string
configContents []byte
}{
{
name: "set k8s version",
expectedImageCount: defaultNumberOfImages,
expectedImageSubstrings: []string{
":v1.10.1",
},
configContents: []byte(dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.10.1
`)),
},
{
name: "use coredns",
expectedImageCount: defaultNumberOfImages,
expectedImageSubstrings: []string{
"coredns",
},
configContents: []byte(dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
featureGates:
CoreDNS: True
`)),
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "kubeadm-images-test")
if err != nil {
t.Fatalf("Unable to create temporary directory: %v", err)
}
defer os.RemoveAll(tmpDir)
configFilePath := filepath.Join(tmpDir, "test-config-file")
err = ioutil.WriteFile(configFilePath, tc.configContents, 0644)
if err != nil {
t.Fatalf("Failed writing a config file: %v", err)
}
i, err := cmd.NewImagesList(configFilePath, &kubeadmapiv1alpha2.MasterConfiguration{
KubernetesVersion: dummyKubernetesVersion,
})
if err != nil {
t.Fatalf("Failed getting the kubeadm images command: %v", err)
}
var output bytes.Buffer
if i.Run(&output) != nil {
t.Fatalf("Error from running the images command: %v", err)
}
actual := strings.Split(output.String(), "\n")
if len(actual) != tc.expectedImageCount {
t.Fatalf("did not get the same number of images: actual: %v expected: %v. Actual value: %v", len(actual), tc.expectedImageCount, actual)
}
for _, substring := range tc.expectedImageSubstrings {
if !strings.Contains(output.String(), substring) {
t.Errorf("Expected to find %v but did not in this list of images: %v", substring, actual)
}
}
})
}
}
func TestConfigImagesListRunWithoutPath(t *testing.T) {
testcases := []struct {
name string
cfg kubeadmapiv1alpha2.MasterConfiguration
expectedImages int
}{
{
name: "empty config",
expectedImages: defaultNumberOfImages,
cfg: kubeadmapiv1alpha2.MasterConfiguration{
KubernetesVersion: dummyKubernetesVersion,
},
},
{
name: "external etcd configuration",
cfg: kubeadmapiv1alpha2.MasterConfiguration{
Etcd: kubeadmapiv1alpha2.Etcd{
External: &kubeadmapiv1alpha2.ExternalEtcd{
Endpoints: []string{"https://some.etcd.com:2379"},
},
},
KubernetesVersion: dummyKubernetesVersion,
},
expectedImages: defaultNumberOfImages - 1,
},
{
name: "coredns enabled",
cfg: kubeadmapiv1alpha2.MasterConfiguration{
FeatureGates: map[string]bool{
features.CoreDNS: true,
},
KubernetesVersion: dummyKubernetesVersion,
},
expectedImages: defaultNumberOfImages,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
i, err := cmd.NewImagesList("", &tc.cfg)
if err != nil {
t.Fatalf("did not expect an error while creating the Images command: %v", err)
}
var output bytes.Buffer
if i.Run(&output) != nil {
t.Fatalf("did not expect an error running the Images command: %v", err)
}
actual := strings.Split(output.String(), "\n")
if len(actual) != tc.expectedImages {
t.Fatalf("expected %v images but got %v", tc.expectedImages, actual)
}
})
}
}
type fakePuller struct {
count map[string]int
}
func (f *fakePuller) Pull(image string) error {
f.count[image]++
return nil
}
func TestImagesPull(t *testing.T) {
puller := &fakePuller{
count: make(map[string]int),
}
images := []string{"a", "b", "c", "d", "a"}
ip := cmd.NewImagesPull(puller, images)
err := ip.PullAll()
if err != nil {
t.Fatalf("expected nil but found %v", err)
}
if puller.count["a"] != 2 {
t.Fatalf("expected 2 but found %v", puller.count["a"])
}
}
func TestMigrate(t *testing.T) {
cfg := []byte(dedent.Dedent(`
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.10.0
`))
configFile, cleanup := tempConfig(t, cfg)
defer cleanup()
var output bytes.Buffer
command := cmd.NewCmdConfigMigrate(&output)
err := command.Flags().Set("old-config", configFile)
if err != nil {
t.Fatalf("failed to set old-config flag")
}
command.Run(nil, nil)
_, err = config.BytesToInternalConfig(output.Bytes())
if err != nil {
t.Fatalf("Could not read output back into internal type: %v", err)
}
}
// Returns the name of the file created and a cleanup callback
func tempConfig(t *testing.T, config []byte) (string, func()) {
t.Helper()
tmpDir, err := ioutil.TempDir("", "kubeadm-migration-test")
if err != nil {
t.Fatalf("Unable to create temporary directory: %v", err)
}
configFilePath := filepath.Join(tmpDir, "test-config-file")
err = ioutil.WriteFile(configFilePath, config, 0644)
if err != nil {
os.RemoveAll(tmpDir)
t.Fatalf("Failed writing a config file: %v", err)
}
return configFilePath, func() {
os.RemoveAll(tmpDir)
}
}

View File

@ -26,17 +26,18 @@ import (
"text/template"
"time"
"github.com/golang/glog"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
@ -51,6 +52,7 @@ import (
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster"
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting"
uploadconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
@ -60,7 +62,6 @@ import (
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/pkg/api/legacyscheme"
utilsexec "k8s.io/utils/exec"
)
@ -92,23 +93,31 @@ var (
This error is likely caused by:
- The kubelet is not running
- The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)
- Either there is no internet connection, or imagePullPolicy is set to "Never",
so the kubelet cannot pull or find the following control plane images:
- No internet connection is available so the kubelet cannot pull or find the following control plane images:
- {{ .APIServerImage }}
- {{ .ControllerManagerImage }}
- {{ .SchedulerImage }}
- {{ .EtcdImage }} (only if no external etcd endpoints are configured)
{{ .EtcdImage }}
- You can check or miligate this in beforehand with "kubeadm config images pull" to make sure the images
are downloaded locally and cached.
If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
- 'systemctl status kubelet'
- 'journalctl -xeu kubelet'
Additionally, a control plane component may have crashed or exited when started by the container runtime.
To troubleshoot, list all containers using your preferred container runtimes CLI, e.g. docker.
Here is one example how you may list all Kubernetes containers running in docker:
- 'docker ps -a | grep kube | grep -v pause'
Once you have found the failing container, you can inspect its logs with:
- 'docker logs CONTAINERID'
`)))
)
// NewCmdInit returns "kubeadm init" command.
func NewCmdInit(out io.Writer) *cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{}
legacyscheme.Scheme.Default(cfg)
externalcfg := &kubeadmapiv1alpha2.MasterConfiguration{}
kubeadmscheme.Scheme.Default(externalcfg)
var cfgPath string
var skipPreFlight bool
@ -116,38 +125,47 @@ func NewCmdInit(out io.Writer) *cobra.Command {
var dryRun bool
var featureGatesString string
var ignorePreflightErrors []string
// Create the options object for the bootstrap token-related flags, and override the default value for .Description
bto := options.NewBootstrapTokenOptions()
bto.Description = "The default bootstrap token generated by 'kubeadm init'."
cmd := &cobra.Command{
Use: "init",
Short: "Run this command in order to set up the Kubernetes master.",
Run: func(cmd *cobra.Command, args []string) {
kubeadmscheme.Scheme.Default(externalcfg)
var err error
if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil {
if externalcfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil {
kubeadmutil.CheckErr(err)
}
legacyscheme.Scheme.Default(cfg)
internalcfg := &kubeadmapi.MasterConfiguration{}
legacyscheme.Scheme.Convert(cfg, internalcfg, nil)
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight)
kubeadmutil.CheckErr(err)
i, err := NewInit(cfgPath, internalcfg, ignorePreflightErrorsSet, skipTokenPrint, dryRun)
err = validation.ValidateMixedArguments(cmd.Flags())
kubeadmutil.CheckErr(err)
err = bto.ApplyTo(externalcfg)
kubeadmutil.CheckErr(err)
i, err := NewInit(cfgPath, externalcfg, ignorePreflightErrorsSet, skipTokenPrint, dryRun)
kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(i.Validate(cmd))
kubeadmutil.CheckErr(i.Run(out))
},
}
AddInitConfigFlags(cmd.PersistentFlags(), cfg, &featureGatesString)
AddInitConfigFlags(cmd.PersistentFlags(), externalcfg, &featureGatesString)
AddInitOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &skipTokenPrint, &dryRun, &ignorePreflightErrors)
bto.AddTokenFlag(cmd.PersistentFlags())
bto.AddTTLFlag(cmd.PersistentFlags())
return cmd
}
// AddInitConfigFlags adds init flags bound to the config to the specified flagset
func AddInitConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.MasterConfiguration, featureGatesString *string) {
func AddInitConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha2.MasterConfiguration, featureGatesString *string) {
flagSet.StringVar(
&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress,
"The IP address the API Server will advertise it's listening on. Specify '0.0.0.0' to use the address of the default network interface.",
@ -181,24 +199,15 @@ func AddInitConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.MasterConfigur
`Optional extra Subject Alternative Names (SANs) to use for the API Server serving certificate. Can be both IP addresses and DNS names.`,
)
flagSet.StringVar(
&cfg.NodeName, "node-name", cfg.NodeName,
&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name,
`Specify the node name.`,
)
flagSet.StringVar(
&cfg.Token, "token", cfg.Token,
"The token to use for establishing bidirectional trust between nodes and masters.",
)
flagSet.DurationVar(
&cfg.TokenTTL.Duration, "token-ttl", cfg.TokenTTL.Duration,
"The duration before the bootstrap token is automatically deleted. If set to '0', the token will never expire.",
)
flagSet.StringVar(
&cfg.CRISocket, "cri-socket", cfg.CRISocket,
&cfg.NodeRegistration.CRISocket, "cri-socket", cfg.NodeRegistration.CRISocket,
`Specify the CRI socket to connect to.`,
)
flagSet.StringVar(featureGatesString, "feature-gates", *featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))
}
// AddInitOtherFlags adds init flags that are not bound to a configuration file to the given flagset
@ -230,72 +239,85 @@ func AddInitOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipPreFlight, sk
}
// NewInit validates given arguments and instantiates Init struct with provided information.
func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, ignorePreflightErrors sets.String, skipTokenPrint, dryRun bool) (*Init, error) {
func NewInit(cfgPath string, externalcfg *kubeadmapiv1alpha2.MasterConfiguration, ignorePreflightErrors sets.String, skipTokenPrint, dryRun bool) (*Init, error) {
if cfgPath != "" {
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
}
if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), b, cfg); err != nil {
return nil, fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err)
}
}
// Set defaults dynamically that the API group defaulting can't (by fetching information from the internet, looking up network interfaces, etc.)
err := configutil.SetInitDynamicDefaults(cfg)
// Either use the config file if specified, or convert the defaults in the external to an internal cfg representation
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, externalcfg)
if err != nil {
return nil, err
}
glog.V(1).Infof("[init] validating feature gates")
if err := features.ValidateVersion(features.InitFeatureGates, cfg.FeatureGates, cfg.KubernetesVersion); err != nil {
return nil, err
}
fmt.Printf("[init] Using Kubernetes version: %s\n", cfg.KubernetesVersion)
fmt.Printf("[init] Using Authorization modes: %v\n", cfg.AuthorizationModes)
fmt.Printf("[init] using Kubernetes version: %s\n", cfg.KubernetesVersion)
// Warn about the limitations with the current cloudprovider solution.
if cfg.CloudProvider != "" {
fmt.Println("[init] WARNING: For cloudprovider integrations to work --cloud-provider must be set for all kubelets in the cluster.")
fmt.Println("\t(/etc/systemd/system/kubelet.service.d/10-kubeadm.conf should be edited for this purpose)")
}
fmt.Println("[preflight] Running pre-flight checks.")
fmt.Println("[preflight] running pre-flight checks")
if err := preflight.RunInitMasterChecks(utilsexec.New(), cfg, ignorePreflightErrors); err != nil {
return nil, err
}
// Try to start the kubelet service in case it's inactive
preflight.TryStartKubelet(ignorePreflightErrors)
if !dryRun {
fmt.Println("[preflight/images] Pulling images required for setting up a Kubernetes cluster")
fmt.Println("[preflight/images] This might take a minute or two, depending on the speed of your internet connection")
fmt.Println("[preflight/images] You can also perform this action in beforehand using 'kubeadm config images pull'")
if err := preflight.RunPullImagesCheck(utilsexec.New(), cfg, ignorePreflightErrors); err != nil {
return nil, err
}
} else {
fmt.Println("[preflight/images] Would pull the required images (like 'kubeadm config images pull')")
}
return &Init{cfg: cfg, skipTokenPrint: skipTokenPrint, dryRun: dryRun}, nil
return &Init{cfg: cfg, skipTokenPrint: skipTokenPrint, dryRun: dryRun, ignorePreflightErrors: ignorePreflightErrors}, nil
}
// Init defines struct used by "kubeadm init" command
type Init struct {
cfg *kubeadmapi.MasterConfiguration
skipTokenPrint bool
dryRun bool
}
// Validate validates configuration passed to "kubeadm init"
func (i *Init) Validate(cmd *cobra.Command) error {
if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil {
return err
}
return validation.ValidateMasterConfiguration(i.cfg).ToAggregate()
cfg *kubeadmapi.MasterConfiguration
skipTokenPrint bool
dryRun bool
ignorePreflightErrors sets.String
}
// Run executes master node provisioning, including certificates, needed static pod manifests, etc.
func (i *Init) Run(out io.Writer) error {
// Get directories to write files to; can be faked if we're dry-running
glog.V(1).Infof("[init] Getting certificates directory from configuration")
realCertsDir := i.cfg.CertificatesDir
certsDirToWriteTo, kubeConfigDir, manifestDir, err := getDirectoriesToUse(i.dryRun, i.cfg.CertificatesDir)
certsDirToWriteTo, kubeConfigDir, manifestDir, kubeletDir, err := getDirectoriesToUse(i.dryRun, i.cfg.CertificatesDir)
if err != nil {
return fmt.Errorf("error getting directories to use: %v", err)
}
// First off, configure the kubelet. In this short timeframe, kubeadm is trying to stop/restart the kubelet
// Try to stop the kubelet service so no race conditions occur when configuring it
if !i.dryRun {
glog.V(1).Infof("Stopping the kubelet")
preflight.TryStopKubelet()
}
// Write env file with flags for the kubelet to use. We do not need to write the --register-with-taints for the master,
// as we handle that ourselves in the markmaster phase
// TODO: Maybe we want to do that some time in the future, in order to remove some logic from the markmaster phase?
if err := kubeletphase.WriteKubeletDynamicEnvFile(&i.cfg.NodeRegistration, i.cfg.FeatureGates, false, kubeletDir); err != nil {
return fmt.Errorf("error writing a dynamic environment file for the kubelet: %v", err)
}
// Write the kubelet configuration file to disk.
if err := kubeletphase.WriteConfigToDisk(i.cfg.KubeletConfiguration.BaseConfig, kubeletDir); err != nil {
return fmt.Errorf("error writing kubelet configuration to disk: %v", err)
}
if !i.dryRun {
// Try to start the kubelet service in case it's inactive
glog.V(1).Infof("Starting the kubelet")
preflight.TryStartKubelet()
}
// certsDirToWriteTo is gonna equal cfg.CertificatesDir in the normal case, but gonna be a temp directory if dryrunning
i.cfg.CertificatesDir = certsDirToWriteTo
@ -304,17 +326,19 @@ func (i *Init) Run(out io.Writer) error {
if res, _ := certsphase.UsingExternalCA(i.cfg); !res {
// PHASE 1: Generate certificates
glog.V(1).Infof("[init] creating PKI Assets")
if err := certsphase.CreatePKIAssets(i.cfg); err != nil {
return err
}
// PHASE 2: Generate kubeconfig files for the admin and the kubelet
glog.V(2).Infof("[init] generating kubeconfig files")
if err := kubeconfigphase.CreateInitKubeConfigFiles(kubeConfigDir, i.cfg); err != nil {
return err
}
} else {
fmt.Println("[externalca] The file 'ca.key' was not found, yet all other certificates are present. Using external CA mode - certificates or kubeconfig will not be generated.")
fmt.Println("[externalca] the file 'ca.key' was not found, yet all other certificates are present. Using external CA mode - certificates or kubeconfig will not be generated")
}
if features.Enabled(i.cfg.FeatureGates, features.Auditing) {
@ -337,11 +361,14 @@ func (i *Init) Run(out io.Writer) error {
i.cfg.CertificatesDir = realCertsDir
// PHASE 3: Bootstrap the control plane
glog.V(1).Infof("[init] bootstraping the control plane")
glog.V(1).Infof("[init] creating static pod manifest")
if err := controlplanephase.CreateInitStaticPodManifestFiles(manifestDir, i.cfg); err != nil {
return fmt.Errorf("error creating init static pod manifest files: %v", err)
}
// Add etcd static pod spec only if external etcd is not configured
if len(i.cfg.Etcd.Endpoints) == 0 {
if i.cfg.Etcd.External == nil {
glog.V(1).Infof("[init] no external etcd found. Creating manifest for local etcd static pod")
if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(manifestDir, i.cfg); err != nil {
return fmt.Errorf("error creating local etcd static pod manifest file: %v", err)
}
@ -355,30 +382,32 @@ func (i *Init) Run(out io.Writer) error {
return fmt.Errorf("error printing files on dryrun: %v", err)
}
// NOTE: flag "--dynamic-config-dir" should be specified in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
if features.Enabled(i.cfg.FeatureGates, features.DynamicKubeletConfig) {
// Write base kubelet configuration for dynamic kubelet configuration feature.
if err := kubeletphase.WriteInitKubeletConfigToDiskOnMaster(i.cfg); err != nil {
return fmt.Errorf("error writing base kubelet configuration to disk: %v", err)
}
}
// Create a kubernetes client and wait for the API server to be healthy (if not dryrunning)
glog.V(1).Infof("creating Kubernetes client")
client, err := createClient(i.cfg, i.dryRun)
if err != nil {
return fmt.Errorf("error creating client: %v", err)
}
// waiter holds the apiclient.Waiter implementation of choice, responsible for querying the API server in various ways and waiting for conditions to be fulfilled
glog.V(1).Infof("[init] waiting for the API server to be healthy")
waiter := getWaiter(i, client)
if err := waitForAPIAndKubelet(waiter); err != nil {
fmt.Printf("[init] waiting for the kubelet to boot up the control plane as Static Pods from directory %q \n", kubeadmconstants.GetStaticPodDirectory())
fmt.Println("[init] this might take a minute or longer if the control plane images have to be pulled")
if err := waitForKubeletAndFunc(waiter, waiter.WaitForAPI); err != nil {
ctx := map[string]string{
"Error": fmt.Sprintf("%v", err),
"APIServerImage": images.GetCoreImage(kubeadmconstants.KubeAPIServer, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage),
"ControllerManagerImage": images.GetCoreImage(kubeadmconstants.KubeControllerManager, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage),
"SchedulerImage": images.GetCoreImage(kubeadmconstants.KubeScheduler, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage),
"EtcdImage": images.GetCoreImage(kubeadmconstants.Etcd, i.cfg.ImageRepository, i.cfg.KubernetesVersion, i.cfg.Etcd.Image),
}
// Set .EtcdImage conditionally
if i.cfg.Etcd.Local != nil {
ctx["EtcdImage"] = fmt.Sprintf(" - %s", images.GetCoreImage(kubeadmconstants.Etcd, i.cfg.ImageRepository, i.cfg.KubernetesVersion, i.cfg.Etcd.Local.Image))
} else {
ctx["EtcdImage"] = ""
}
kubeletFailTempl.Execute(out, ctx)
@ -386,71 +415,104 @@ func (i *Init) Run(out io.Writer) error {
return fmt.Errorf("couldn't initialize a Kubernetes cluster")
}
// NOTE: flag "--dynamic-config-dir" should be specified in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
if features.Enabled(i.cfg.FeatureGates, features.DynamicKubeletConfig) {
// Create base kubelet configuration for dynamic kubelet configuration feature.
if err := kubeletphase.CreateBaseKubeletConfiguration(i.cfg, client); err != nil {
return fmt.Errorf("error creating base kubelet configuration: %v", err)
}
}
// Upload currently used configuration to the cluster
// Note: This is done right in the beginning of cluster initialization; as we might want to make other phases
// depend on centralized information from this source in the future
glog.V(1).Infof("[init] uploading currently used configuration to the cluster")
if err := uploadconfigphase.UploadConfiguration(i.cfg, client); err != nil {
return fmt.Errorf("error uploading configuration: %v", err)
}
glog.V(1).Infof("[init] creating kubelet configuration configmap")
if err := kubeletphase.CreateConfigMap(i.cfg, client); err != nil {
return fmt.Errorf("error creating kubelet configuration ConfigMap: %v", err)
}
// PHASE 4: Mark the master with the right label/taint
if err := markmasterphase.MarkMaster(client, i.cfg.NodeName, !i.cfg.NoTaintMaster); err != nil {
glog.V(1).Infof("[init] marking the master with right label")
if err := markmasterphase.MarkMaster(client, i.cfg.NodeRegistration.Name, i.cfg.NodeRegistration.Taints); err != nil {
return fmt.Errorf("error marking master: %v", err)
}
glog.V(1).Infof("[init] preserving the crisocket information for the master")
if err := patchnodephase.AnnotateCRISocket(client, i.cfg.NodeRegistration.Name, i.cfg.NodeRegistration.CRISocket); err != nil {
return fmt.Errorf("error uploading crisocket: %v", err)
}
// This feature is disabled by default
if features.Enabled(i.cfg.FeatureGates, features.DynamicKubeletConfig) {
kubeletVersion, err := preflight.GetKubeletVersion(utilsexec.New())
if err != nil {
return err
}
// Enable dynamic kubelet configuration for the node.
if err := kubeletphase.EnableDynamicConfigForNode(client, i.cfg.NodeRegistration.Name, kubeletVersion); err != nil {
return fmt.Errorf("error enabling dynamic kubelet configuration: %v", err)
}
}
// PHASE 5: Set up the node bootstrap tokens
tokens := []string{}
for _, bt := range i.cfg.BootstrapTokens {
tokens = append(tokens, bt.Token.String())
}
if !i.skipTokenPrint {
fmt.Printf("[bootstraptoken] Using token: %s\n", i.cfg.Token)
if len(tokens) == 1 {
fmt.Printf("[bootstraptoken] using token: %s\n", tokens[0])
} else if len(tokens) > 1 {
fmt.Printf("[bootstraptoken] using tokens: %v\n", tokens)
}
}
// Create the default node bootstrap token
tokenDescription := "The default bootstrap token generated by 'kubeadm init'."
if err := nodebootstraptokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL.Duration, i.cfg.TokenUsages, i.cfg.TokenGroups, tokenDescription); err != nil {
glog.V(1).Infof("[init] creating RBAC rules to generate default bootstrap token")
if err := nodebootstraptokenphase.UpdateOrCreateTokens(client, false, i.cfg.BootstrapTokens); err != nil {
return fmt.Errorf("error updating or creating token: %v", err)
}
// Create RBAC rules that makes the bootstrap tokens able to post CSRs
glog.V(1).Infof("[init] creating RBAC rules to allow bootstrap tokens to post CSR")
if err := nodebootstraptokenphase.AllowBootstrapTokensToPostCSRs(client); err != nil {
return fmt.Errorf("error allowing bootstrap tokens to post CSRs: %v", err)
}
// Create RBAC rules that makes the bootstrap tokens able to get their CSRs approved automatically
glog.V(1).Infof("[init] creating RBAC rules to automatic approval of CSRs automatically")
if err := nodebootstraptokenphase.AutoApproveNodeBootstrapTokens(client); err != nil {
return fmt.Errorf("error auto-approving node bootstrap tokens: %v", err)
}
// Create/update RBAC rules that makes the nodes to rotate certificates and get their CSRs approved automatically
glog.V(1).Infof("[init] creating/updating RBAC rules for rotating certificate")
if err := nodebootstraptokenphase.AutoApproveNodeCertificateRotation(client); err != nil {
return err
}
// Create the cluster-info ConfigMap with the associated RBAC rules
glog.V(1).Infof("[init] creating bootstrap configmap")
if err := clusterinfophase.CreateBootstrapConfigMapIfNotExists(client, adminKubeConfigPath); err != nil {
return fmt.Errorf("error creating bootstrap configmap: %v", err)
}
glog.V(1).Infof("[init] creating ClusterInfo RBAC rules")
if err := clusterinfophase.CreateClusterInfoRBACRules(client); err != nil {
return fmt.Errorf("error creating clusterinfo RBAC rules: %v", err)
}
glog.V(1).Infof("[init] ensuring DNS addon")
if err := dnsaddonphase.EnsureDNSAddon(i.cfg, client); err != nil {
return fmt.Errorf("error ensuring dns addon: %v", err)
}
glog.V(1).Infof("[init] ensuring proxy addon")
if err := proxyaddonphase.EnsureProxyAddon(i.cfg, client); err != nil {
return fmt.Errorf("error ensuring proxy addon: %v", err)
}
// PHASE 7: Make the control plane self-hosted if feature gate is enabled
if features.Enabled(i.cfg.FeatureGates, features.SelfHosting) {
glog.V(1).Infof("[init] feature gate is enabled. Making control plane self-hosted")
// Temporary control plane is up, now we create our self hosted control
// plane components and remove the static manifests:
fmt.Println("[self-hosted] Creating self-hosted control plane.")
fmt.Println("[self-hosted] creating self-hosted control plane")
if err := selfhostingphase.CreateSelfHostedControlPlane(manifestDir, kubeConfigDir, i.cfg, client, waiter, i.dryRun); err != nil {
return fmt.Errorf("error creating self hosted control plane: %v", err)
}
@ -458,14 +520,23 @@ func (i *Init) Run(out io.Writer) error {
// Exit earlier if we're dryrunning
if i.dryRun {
fmt.Println("[dryrun] Finished dry-running successfully. Above are the resources that would be created.")
fmt.Println("[dryrun] finished dry-running successfully. Above are the resources that would be created")
return nil
}
// Gets the join command
joinCommand, err := cmdutil.GetJoinCommand(kubeadmconstants.GetAdminKubeConfigPath(), i.cfg.Token, i.skipTokenPrint)
// Prints the join command, multiple times in case the user has multiple tokens
for _, token := range tokens {
if err := printJoinCommand(out, adminKubeConfigPath, token, i.skipTokenPrint); err != nil {
return fmt.Errorf("failed to print join command: %v", err)
}
}
return nil
}
func printJoinCommand(out io.Writer, adminKubeConfigPath, token string, skipTokenPrint bool) error {
joinCommand, err := cmdutil.GetJoinCommand(adminKubeConfigPath, token, skipTokenPrint)
if err != nil {
return fmt.Errorf("failed to get join command: %v", err)
return err
}
ctx := map[string]string{
@ -480,7 +551,7 @@ func (i *Init) Run(out io.Writer) error {
func createClient(cfg *kubeadmapi.MasterConfiguration, dryRun bool) (clientset.Interface, error) {
if dryRun {
// If we're dry-running; we should create a faked client that answers some GETs in order to be able to do the full init flow and just logs the rest of requests
dryRunGetter := apiclient.NewInitDryRunGetter(cfg.NodeName, cfg.Networking.ServiceSubnet)
dryRunGetter := apiclient.NewInitDryRunGetter(cfg.NodeRegistration.Name, cfg.Networking.ServiceSubnet)
return apiclient.NewDryRunClient(dryRunGetter, os.Stdout), nil
}
@ -490,17 +561,17 @@ func createClient(cfg *kubeadmapi.MasterConfiguration, dryRun bool) (clientset.I
// getDirectoriesToUse returns the (in order) certificates, kubeconfig and Static Pod manifest directories, followed by a possible error
// This behaves differently when dry-running vs the normal flow
func getDirectoriesToUse(dryRun bool, defaultPkiDir string) (string, string, string, error) {
func getDirectoriesToUse(dryRun bool, defaultPkiDir string) (string, string, string, string, error) {
if dryRun {
dryRunDir, err := ioutil.TempDir("", "kubeadm-init-dryrun")
if err != nil {
return "", "", "", fmt.Errorf("couldn't create a temporary directory: %v", err)
return "", "", "", "", fmt.Errorf("couldn't create a temporary directory: %v", err)
}
// Use the same temp dir for all
return dryRunDir, dryRunDir, dryRunDir, nil
return dryRunDir, dryRunDir, dryRunDir, dryRunDir, nil
}
return defaultPkiDir, kubeadmconstants.KubernetesDir, kubeadmconstants.GetStaticPodDirectory(), nil
return defaultPkiDir, kubeadmconstants.KubernetesDir, kubeadmconstants.GetStaticPodDirectory(), kubeadmconstants.KubeletRunDirectory, nil
}
// printFilesIfDryRunning prints the Static Pod manifests to stdout and informs about the temporary directory to go and lookup
@ -509,17 +580,25 @@ func printFilesIfDryRunning(dryRun bool, manifestDir string) error {
return nil
}
fmt.Printf("[dryrun] Wrote certificates, kubeconfig files and control plane manifests to the %q directory.\n", manifestDir)
fmt.Println("[dryrun] The certificates or kubeconfig files would not be printed due to their sensitive nature.")
fmt.Printf("[dryrun] Please examine the %q directory for details about what would be written.\n", manifestDir)
fmt.Printf("[dryrun] wrote certificates, kubeconfig files and control plane manifests to the %q directory\n", manifestDir)
fmt.Println("[dryrun] the certificates or kubeconfig files would not be printed due to their sensitive nature")
fmt.Printf("[dryrun] please examine the %q directory for details about what would be written\n", manifestDir)
// Print the contents of the upgraded manifests and pretend like they were in /etc/kubernetes/manifests
files := []dryrunutil.FileToPrint{}
// Print static pod manifests
for _, component := range kubeadmconstants.MasterComponents {
realPath := kubeadmconstants.GetStaticPodFilepath(component, manifestDir)
outputPath := kubeadmconstants.GetStaticPodFilepath(component, kubeadmconstants.GetStaticPodDirectory())
files = append(files, dryrunutil.NewFileToPrint(realPath, outputPath))
}
// Print kubelet config manifests
kubeletConfigFiles := []string{kubeadmconstants.KubeletConfigurationFileName, kubeadmconstants.KubeletEnvFileName}
for _, filename := range kubeletConfigFiles {
realPath := filepath.Join(manifestDir, filename)
outputPath := filepath.Join(kubeadmconstants.KubeletRunDirectory, filename)
files = append(files, dryrunutil.NewFileToPrint(realPath, outputPath))
}
return dryrunutil.PrintDryRunFiles(files, os.Stdout)
}
@ -530,42 +609,30 @@ func getWaiter(i *Init, client clientset.Interface) apiclient.Waiter {
return dryrunutil.NewWaiter()
}
timeout := 30 * time.Minute
// No need for a large timeout if we don't expect downloads
if i.cfg.ImagePullPolicy == v1.PullNever {
timeout = 60 * time.Second
}
// We know that the images should be cached locally already as we have pulled them using
// crictl in the preflight checks. Hence we can have a pretty short timeout for the kubelet
// to start creating Static Pods.
timeout := 4 * time.Minute
return apiclient.NewKubeWaiter(client, timeout, os.Stdout)
}
// waitForAPIAndKubelet waits primarily for the API server to come up. If that takes a long time, and the kubelet
// /healthz and /healthz/syncloop endpoints continuously are unhealthy, kubeadm will error out after a period of
// backoffing exponentially
func waitForAPIAndKubelet(waiter apiclient.Waiter) error {
// 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 waitForKubeletAndFunc(waiter apiclient.Waiter, f func() error) error {
errorChan := make(chan error)
fmt.Printf("[init] Waiting for the kubelet to boot up the control plane as Static Pods from directory %q.\n", kubeadmconstants.GetStaticPodDirectory())
fmt.Println("[init] This might take a minute or longer if the control plane images have to be pulled.")
go func(errC chan error, waiter apiclient.Waiter) {
// This goroutine can only make kubeadm init fail. If this check succeeds, it won't do anything special
if err := waiter.WaitForHealthyKubelet(40*time.Second, "http://localhost:10255/healthz"); err != nil {
// TODO: Make 10248 a constant somewhere
if err := waiter.WaitForHealthyKubelet(40*time.Second, "http://localhost:10248/healthz"); err != nil {
errC <- err
}
}(errorChan, waiter)
go func(errC chan error, waiter apiclient.Waiter) {
// This goroutine can only make kubeadm init fail. If this check succeeds, it won't do anything special
if err := waiter.WaitForHealthyKubelet(60*time.Second, "http://localhost:10255/healthz/syncloop"); err != nil {
errC <- err
}
}(errorChan, waiter)
go func(errC chan error, waiter apiclient.Waiter) {
// This main goroutine sends whatever WaitForAPI returns (error or not) to the channel
// This in order to continue on success (nil error), or just fail if
errC <- waiter.WaitForAPI()
// 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, waiter)
// This call is blocking until one of the goroutines sends to errorChan

View File

@ -19,29 +19,32 @@ package cmd
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/golang/glog"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
certutil "k8s.io/client-go/util/cert"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
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"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/pkg/api/legacyscheme"
nodeutil "k8s.io/kubernetes/pkg/util/node"
utilsexec "k8s.io/utils/exec"
)
@ -97,12 +100,25 @@ var (
Often times the same token is used for both parts. In this case, the
--token flag can be used instead of specifying each token individually.
`)
kubeadmJoinFailMsgf = dedent.Dedent(`
Unfortunately, an error has occurred:
%v
This error is likely caused by:
- The kubelet is not running
- The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)
If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
- 'systemctl status kubelet'
- 'journalctl -xeu kubelet'
`)
)
// NewCmdJoin returns "kubeadm join" command.
func NewCmdJoin(out io.Writer) *cobra.Command {
cfg := &kubeadmapiext.NodeConfiguration{}
legacyscheme.Scheme.Default(cfg)
cfg := &kubeadmapiv1alpha2.NodeConfiguration{}
kubeadmscheme.Scheme.Default(cfg)
var skipPreFlight bool
var cfgPath string
@ -114,23 +130,8 @@ func NewCmdJoin(out io.Writer) *cobra.Command {
Short: "Run this on any machine you wish to join an existing cluster",
Long: joinLongDescription,
Run: func(cmd *cobra.Command, args []string) {
cfg.DiscoveryTokenAPIServers = args
var err error
if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil {
kubeadmutil.CheckErr(err)
}
legacyscheme.Scheme.Default(cfg)
internalcfg := &kubeadmapi.NodeConfiguration{}
legacyscheme.Scheme.Convert(cfg, internalcfg, nil)
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight)
j, err := NewValidJoin(cmd.PersistentFlags(), cfg, args, skipPreFlight, cfgPath, featureGatesString, ignorePreflightErrors)
kubeadmutil.CheckErr(err)
j, err := NewJoin(cfgPath, args, internalcfg, ignorePreflightErrorsSet)
kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(j.Validate(cmd))
kubeadmutil.CheckErr(j.Run(out))
},
}
@ -141,8 +142,29 @@ func NewCmdJoin(out io.Writer) *cobra.Command {
return cmd
}
// NewValidJoin validates the command line that are passed to the cobra command
func NewValidJoin(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha2.NodeConfiguration, args []string, skipPreFlight bool, cfgPath, featureGatesString string, ignorePreflightErrors []string) (*Join, error) {
cfg.DiscoveryTokenAPIServers = args
var err error
if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil {
return nil, err
}
if err := validation.ValidateMixedArguments(flagSet); err != nil {
return nil, err
}
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight)
if err != nil {
return nil, err
}
return NewJoin(cfgPath, args, cfg, ignorePreflightErrorsSet)
}
// AddJoinConfigFlags adds join flags bound to the config to the specified flagset
func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.NodeConfiguration, featureGatesString *string) {
func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha2.NodeConfiguration, featureGatesString *string) {
flagSet.StringVar(
&cfg.DiscoveryFile, "discovery-file", "",
"A file or url from which to load cluster information.")
@ -150,7 +172,7 @@ func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.NodeConfigurat
&cfg.DiscoveryToken, "discovery-token", "",
"A token used to validate cluster information fetched from the master.")
flagSet.StringVar(
&cfg.NodeName, "node-name", "",
&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name,
"Specify the node name.")
flagSet.StringVar(
&cfg.TLSBootstrapToken, "tls-bootstrap-token", "",
@ -169,7 +191,7 @@ func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.NodeConfigurat
"A set of key=value pairs that describe feature gates for various features. "+
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))
flagSet.StringVar(
&cfg.CRISocket, "cri-socket", cfg.CRISocket,
&cfg.NodeRegistration.CRISocket, "cri-socket", cfg.NodeRegistration.CRISocket,
`Specify the CRI socket to connect to.`,
)
}
@ -193,71 +215,111 @@ func AddJoinOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipPreFlight *bo
// Join defines struct used by kubeadm join command
type Join struct {
cfg *kubeadmapi.NodeConfiguration
cfg *kubeadmapi.NodeConfiguration
ignorePreflightErrors sets.String
}
// NewJoin instantiates Join struct with given arguments
func NewJoin(cfgPath string, args []string, cfg *kubeadmapi.NodeConfiguration, ignorePreflightErrors sets.String) (*Join, error) {
func NewJoin(cfgPath string, args []string, defaultcfg *kubeadmapiv1alpha2.NodeConfiguration, ignorePreflightErrors sets.String) (*Join, error) {
if cfg.NodeName == "" {
cfg.NodeName = nodeutil.GetHostname("")
if defaultcfg.NodeRegistration.Name == "" {
glog.V(1).Infoln("[join] found NodeName empty")
glog.V(1).Infoln("[join] considered OS hostname as NodeName")
}
if cfgPath != "" {
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
}
if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), b, cfg); err != nil {
return nil, fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err)
}
}
fmt.Println("[preflight] Running pre-flight checks.")
// Then continue with the others...
if err := preflight.RunJoinNodeChecks(utilsexec.New(), cfg, ignorePreflightErrors); err != nil {
internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig(cfgPath, defaultcfg)
if err != nil {
return nil, err
}
// Try to start the kubelet service in case it's inactive
preflight.TryStartKubelet(ignorePreflightErrors)
fmt.Println("[preflight] running pre-flight checks")
return &Join{cfg: cfg}, nil
}
// Validate validates mixed arguments passed to cobra.Command
func (j *Join) Validate(cmd *cobra.Command) error {
if err := validation.ValidateMixedArguments(cmd.PersistentFlags()); err != nil {
return err
// Then continue with the others...
glog.V(1).Infoln("[preflight] running various checks on all nodes")
if err := preflight.RunJoinNodeChecks(utilsexec.New(), internalcfg, ignorePreflightErrors); err != nil {
return nil, err
}
return validation.ValidateNodeConfiguration(j.cfg).ToAggregate()
return &Join{cfg: internalcfg, ignorePreflightErrors: ignorePreflightErrors}, nil
}
// Run executes worker node provisioning and tries to join an existing cluster.
func (j *Join) Run(out io.Writer) error {
// Perform the Discovery, which turns a Bootstrap Token and optionally (and preferably) a CA cert hash into a KubeConfig
// file that may be used for the TLS Bootstrapping process the kubelet performs using the Certificates API.
glog.V(1).Infoln("[join] retrieving KubeConfig objects")
cfg, err := discovery.For(j.cfg)
if err != nil {
return err
}
kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletBootstrapKubeConfigFileName)
bootstrapKubeConfigFile := kubeadmconstants.GetBootstrapKubeletKubeConfigPath()
// Write the bootstrap kubelet config file or the TLS-Boostrapped kubelet config file down to disk
if err := kubeconfigutil.WriteToDisk(kubeconfigFile, cfg); err != nil {
glog.V(1).Infoln("[join] writing bootstrap kubelet config file at", bootstrapKubeConfigFile)
if err := kubeconfigutil.WriteToDisk(bootstrapKubeConfigFile, cfg); err != nil {
return fmt.Errorf("couldn't save bootstrap-kubelet.conf to disk: %v", err)
}
// Write the ca certificate to disk so kubelet can use it for authentication
cluster := cfg.Contexts[cfg.CurrentContext].Cluster
err = certutil.WriteCert(j.cfg.CACertPath, cfg.Clusters[cluster].CertificateAuthorityData)
if err != nil {
if err := certutil.WriteCert(j.cfg.CACertPath, cfg.Clusters[cluster].CertificateAuthorityData); err != nil {
return fmt.Errorf("couldn't save the CA certificate to disk: %v", err)
}
// NOTE: flag "--dynamic-config-dir" should be specified in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
kubeletVersion, err := preflight.GetKubeletVersion(utilsexec.New())
if err != nil {
return err
}
bootstrapClient, err := kubeconfigutil.ClientSetFromFile(bootstrapKubeConfigFile)
if err != nil {
return fmt.Errorf("couldn't create client from kubeconfig file %q", bootstrapKubeConfigFile)
}
// Configure the kubelet. In this short timeframe, kubeadm is trying to stop/restart the kubelet
// Try to stop the kubelet service so no race conditions occur when configuring it
glog.V(1).Infof("Stopping the kubelet")
preflight.TryStopKubelet()
// Write the configuration for the kubelet (using the bootstrap token credentials) to disk so the kubelet can start
if err := kubeletphase.DownloadConfig(bootstrapClient, kubeletVersion, kubeadmconstants.KubeletRunDirectory); err != nil {
return err
}
// Write env file with flags for the kubelet to use. Also register taints
if err := kubeletphase.WriteKubeletDynamicEnvFile(&j.cfg.NodeRegistration, j.cfg.FeatureGates, true, kubeadmconstants.KubeletRunDirectory); err != nil {
return err
}
// Try to start the kubelet service in case it's inactive
glog.V(1).Infof("Starting the kubelet")
preflight.TryStartKubelet()
// Now the kubelet will perform the TLS Bootstrap, transforming /etc/kubernetes/bootstrap-kubelet.conf to /etc/kubernetes/kubelet.conf
// Wait for the kubelet to create the /etc/kubernetes/kubelet.conf KubeConfig file. If this process
// times out, display a somewhat user-friendly message.
waiter := apiclient.NewKubeWaiter(nil, kubeadmconstants.TLSBootstrapTimeout, os.Stdout)
if err := waitForKubeletAndFunc(waiter, waitForTLSBootstrappedClient); err != nil {
fmt.Printf(kubeadmJoinFailMsgf, err)
return err
}
// When we know the /etc/kubernetes/kubelet.conf file is available, get the client
client, err := kubeconfigutil.ClientSetFromFile(kubeadmconstants.GetKubeletKubeConfigPath())
if err != nil {
return err
}
glog.V(1).Infof("[join] preserving the crisocket information for the node")
if err := patchnodephase.AnnotateCRISocket(client, j.cfg.NodeRegistration.Name, j.cfg.NodeRegistration.CRISocket); err != nil {
return fmt.Errorf("error uploading crisocket: %v", err)
}
// This feature is disabled by default in kubeadm
if features.Enabled(j.cfg.FeatureGates, features.DynamicKubeletConfig) {
if err := kubeletphase.ConsumeBaseKubeletConfiguration(j.cfg.NodeName); err != nil {
if err := kubeletphase.EnableDynamicConfigForNode(client, j.cfg.NodeRegistration.Name, kubeletVersion); err != nil {
return fmt.Errorf("error consuming base kubelet configuration: %v", err)
}
}
@ -265,3 +327,15 @@ func (j *Join) Run(out io.Writer) error {
fmt.Fprintf(out, joinDoneMsgf)
return nil
}
// waitForTLSBootstrappedClient waits for the /etc/kubernetes/kubelet.conf file to be available
func waitForTLSBootstrappedClient() error {
fmt.Println("[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...")
kubeletKubeConfig := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName)
// Loop on every falsy return. Return with an error if raised. Exit successfully if true is returned.
return wait.PollImmediate(kubeadmconstants.APICallRetryInterval, kubeadmconstants.TLSBootstrapTimeout, func() (bool, error) {
_, err := os.Stat(kubeletKubeConfig)
return (err == nil), nil
})
}

View File

@ -0,0 +1,183 @@
/*
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 cmd
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
)
const (
testConfig = `apiVersion: v1
clusters:
- cluster:
certificate-authority-data:
server: localhost:9008
name: prod
contexts:
- context:
cluster: prod
namespace: default
user: default-service-account
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data:
client-key-data:
`
)
func TestNewValidJoin(t *testing.T) {
// create temp directory
tmpDir, err := ioutil.TempDir("", "kubeadm-join-test")
if err != nil {
t.Errorf("Unable to create temporary directory: %v", err)
}
defer os.RemoveAll(tmpDir)
// create config file
configFilePath := filepath.Join(tmpDir, "test-config-file")
cfgFile, err := os.Create(configFilePath)
if err != nil {
t.Errorf("Unable to create file %q: %v", configFilePath, err)
}
defer cfgFile.Close()
testCases := []struct {
name string
args []string
skipPreFlight bool
cfgPath string
configToWrite string
featureGatesString string
ignorePreflightErrors []string
testJoinValidate bool
testJoinRun bool
cmdPersistentFlags map[string]string
nodeConfig *kubeadm.NodeConfiguration
expectedError bool
}{
{
name: "invalid: missing config file",
skipPreFlight: true,
cfgPath: "missing-path-to-a-config",
expectedError: true,
},
{
name: "invalid: incorrect config file",
skipPreFlight: true,
cfgPath: configFilePath,
configToWrite: "bad-config-contents",
expectedError: true,
},
{
name: "invalid: fail at preflight.RunJoinNodeChecks()",
skipPreFlight: false,
cfgPath: configFilePath,
configToWrite: testConfig,
expectedError: true,
},
{
name: "invalid: incorrect ignorePreflight argument",
skipPreFlight: true,
cfgPath: configFilePath,
configToWrite: testConfig,
ignorePreflightErrors: []string{"some-unsupported-preflight-arg"},
expectedError: true,
},
{
name: "invalid: incorrect featureGatesString",
featureGatesString: "bad-feature-gate-string",
expectedError: true,
},
{
name: "invalid: fail Join.Validate() with wrong flags",
skipPreFlight: true,
cfgPath: configFilePath,
configToWrite: testConfig,
testJoinValidate: true,
cmdPersistentFlags: map[string]string{
"config": "some-config",
"node-name": "some-node-name",
},
expectedError: true,
},
{
name: "invalid: fail Join.Validate() with wrong node configuration",
skipPreFlight: true,
cfgPath: configFilePath,
configToWrite: testConfig,
testJoinValidate: true,
expectedError: true,
},
{
name: "invalid: fail Join.Run() with invalid node config",
skipPreFlight: true,
cfgPath: configFilePath,
configToWrite: testConfig,
testJoinRun: true,
expectedError: true,
},
}
var out bytes.Buffer
cfg := &kubeadmapiv1alpha2.NodeConfiguration{}
kubeadmscheme.Scheme.Default(cfg)
errorFormat := "Test case %q: NewValidJoin expected error: %v, saw: %v, error: %v"
for _, tc := range testCases {
if _, err = cfgFile.WriteString(tc.configToWrite); err != nil {
t.Fatalf("Unable to write file %q: %v", tc.cfgPath, err)
}
cmd := NewCmdJoin(&out)
if tc.cmdPersistentFlags != nil {
for key, value := range tc.cmdPersistentFlags {
cmd.PersistentFlags().Set(key, value)
}
}
join, err := NewValidJoin(cmd.PersistentFlags(), cfg, tc.args, tc.skipPreFlight, tc.cfgPath, tc.featureGatesString, tc.ignorePreflightErrors)
if tc.nodeConfig != nil {
join.cfg = tc.nodeConfig
}
// test Join.Run()
if err == nil && tc.testJoinRun {
err = join.Run(&out)
if (err != nil) != tc.expectedError {
t.Fatalf(errorFormat, tc.name, tc.expectedError, (err != nil), err)
}
// check error for NewValidJoin()
} else if (err != nil) != tc.expectedError {
t.Fatalf(errorFormat, tc.name, tc.expectedError, (err != nil), err)
}
}
}

View File

@ -0,0 +1,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"generic.go",
"token.go",
],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options",
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/api: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,29 @@
/*
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 options
import "github.com/spf13/pflag"
// AddKubeConfigFlag adds the --kubeconfig flag to the given flagset
func AddKubeConfigFlag(fs *pflag.FlagSet, kubeConfigFile *string) {
fs.StringVar(kubeConfigFile, "kubeconfig", *kubeConfigFile, "The KubeConfig file to use when talking to the cluster")
}
// AddConfigFlag adds the --config flag to the given flagset
func AddConfigFlag(fs *pflag.FlagSet, cfgPath *string) {
fs.StringVar(cfgPath, "config", *cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
}

View File

@ -0,0 +1,104 @@
/*
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 options
import (
"fmt"
"strings"
"github.com/spf13/pflag"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
// NewBootstrapTokenOptions creates a new BootstrapTokenOptions object with the default values
func NewBootstrapTokenOptions() *BootstrapTokenOptions {
bto := &BootstrapTokenOptions{&kubeadmapiv1alpha2.BootstrapToken{}, ""}
kubeadmapiv1alpha2.SetDefaults_BootstrapToken(bto.BootstrapToken)
return bto
}
// BootstrapTokenOptions is a wrapper struct for adding bootstrap token-related flags to a FlagSet
// and applying the parsed flags to a MasterConfiguration object later at runtime
// TODO: In the future, we might want to group the flags in a better way than adding them all individually like this
type BootstrapTokenOptions struct {
*kubeadmapiv1alpha2.BootstrapToken
TokenStr string
}
// AddTokenFlag adds the --token flag to the given flagset
func (bto *BootstrapTokenOptions) AddTokenFlag(fs *pflag.FlagSet) {
fs.StringVar(
&bto.TokenStr, "token", "",
"The token to use for establishing bidirectional trust between nodes and masters. The format is [a-z0-9]{6}\\.[a-z0-9]{16} - e.g. abcdef.0123456789abcdef",
)
}
// AddTTLFlag adds the --token-ttl flag to the given flagset
func (bto *BootstrapTokenOptions) AddTTLFlag(fs *pflag.FlagSet) {
bto.AddTTLFlagWithName(fs, "token-ttl")
}
// AddTTLFlagWithName adds the --token-ttl flag with a custom flag name given flagset
func (bto *BootstrapTokenOptions) AddTTLFlagWithName(fs *pflag.FlagSet, flagName string) {
fs.DurationVar(
&bto.TTL.Duration, flagName, bto.TTL.Duration,
"The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire",
)
}
// AddUsagesFlag adds the --usages flag to the given flagset
func (bto *BootstrapTokenOptions) AddUsagesFlag(fs *pflag.FlagSet) {
fs.StringSliceVar(
&bto.Usages, "usages", bto.Usages,
fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s]", strings.Join(kubeadmconstants.DefaultTokenUsages, ",")),
)
}
// AddGroupsFlag adds the --groups flag to the given flagset
func (bto *BootstrapTokenOptions) AddGroupsFlag(fs *pflag.FlagSet) {
fs.StringSliceVar(
&bto.Groups, "groups", bto.Groups,
fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q", bootstrapapi.BootstrapGroupPattern),
)
}
// AddDescriptionFlag adds the --description flag to the given flagset
func (bto *BootstrapTokenOptions) AddDescriptionFlag(fs *pflag.FlagSet) {
fs.StringVar(
&bto.Description, "description", bto.Description,
"A human friendly description of how this token is used.",
)
}
// ApplyTo applies the values set internally in the BootstrapTokenOptions object to a MasterConfiguration object at runtime
// If --token was specified in the CLI (as a string), it's parsed and validated before it's added to the BootstrapToken object.
func (bto *BootstrapTokenOptions) ApplyTo(cfg *kubeadmapiv1alpha2.MasterConfiguration) error {
if len(bto.TokenStr) > 0 {
var err error
bto.Token, err = kubeadmapiv1alpha2.NewBootstrapTokenString(bto.TokenStr)
if err != nil {
return err
}
}
// Set the token specified by the flags as the first and only token to create in case --config is not specified
cfg.BootstrapTokens = []kubeadmapiv1alpha2.BootstrapToken{*bto.BootstrapToken}
return nil
}

View File

@ -1,10 +1,4 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
@ -15,6 +9,7 @@ go_library(
"controlplane.go",
"etcd.go",
"kubeconfig.go",
"kubelet.go",
"markmaster.go",
"phase.go",
"preflight.go",
@ -23,10 +18,13 @@ go_library(
"util.go",
],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases",
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/cmd/options:go_default_library",
"//cmd/kubeadm/app/cmd/util:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
@ -38,6 +36,7 @@ go_library(
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
"//cmd/kubeadm/app/phases/etcd:go_default_library",
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
"//cmd/kubeadm/app/phases/kubelet:go_default_library",
"//cmd/kubeadm/app/phases/markmaster:go_default_library",
"//cmd/kubeadm/app/phases/selfhosting:go_default_library",
"//cmd/kubeadm/app/phases/uploadconfig:go_default_library",
@ -46,8 +45,9 @@ go_library(
"//cmd/kubeadm/app/util/apiclient:go_default_library",
"//cmd/kubeadm/app/util/config:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/util/normalizer:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
@ -67,17 +67,18 @@ go_test(
"controlplane_test.go",
"etcd_test.go",
"kubeconfig_test.go",
"kubelet_test.go",
],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/install:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
"//cmd/kubeadm/test:go_default_library",
"//cmd/kubeadm/test/cmd:go_default_library",
"//cmd/kubeadm/test/kubeconfig:go_default_library",
"//pkg/util/node:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
],
)
@ -93,4 +94,5 @@ filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -19,11 +19,13 @@ package phases
import (
"strings"
"github.com/golang/glog"
"github.com/spf13/cobra"
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
@ -32,30 +34,29 @@ import (
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/normalizer"
)
var (
allAddonsLongDesc = normalizer.LongDesc(`
Installs the kube-dns and the kube-proxys addons components via the API server.
Installs the CoreDNS and the kube-proxys addons components via the API server.
Please note that although the DNS server is deployed, it will not be scheduled until CNI is installed.
` + cmdutil.AlphaDisclaimer)
allAddonsExample = normalizer.Examples(`
# Installs the kube-dns and the kube-proxys addons components via the API server,
# Installs the CoreDNS and the kube-proxys addons components via the API server,
# functionally equivalent to what installed by kubeadm init.
kubeadm alpha phase selfhosting from-staticpods
`)
kubednsAddonsLongDesc = normalizer.LongDesc(`
Installs the kube-dns addon components via the API server.
corednsAddonsLongDesc = normalizer.LongDesc(`
Installs the CoreDNS addon components via the API server.
Please note that although the DNS server is deployed, it will not be scheduled until CNI is installed.
` + cmdutil.AlphaDisclaimer)
kubeproxyAddonsLongDesc = normalizer.LongDesc(`
Installs the kube-proxy addon components via the API server.
Installs the kube-proxy addon components via the API server.
` + cmdutil.AlphaDisclaimer)
)
@ -80,6 +81,7 @@ func EnsureAllAddons(cfg *kubeadmapi.MasterConfiguration, client clientset.Inter
proxyaddon.EnsureProxyAddon,
}
glog.V(1).Infoln("[addon] installing all addons")
for _, action := range addonActions {
err := action(cfg, client)
if err != nil {
@ -92,9 +94,9 @@ func EnsureAllAddons(cfg *kubeadmapi.MasterConfiguration, client clientset.Inter
// getAddonsSubCommands returns sub commands for addons phase
func getAddonsSubCommands() []*cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{}
cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, kubeConfigFile, featureGatesString string
var subCmds []*cobra.Command
@ -114,9 +116,9 @@ func getAddonsSubCommands() []*cobra.Command {
cmdFunc: EnsureAllAddons,
},
{
use: "kube-dns",
short: "Installs the kube-dns addon to a Kubernetes cluster",
long: kubednsAddonsLongDesc,
use: "coredns",
short: "Installs the CoreDNS addon to a Kubernetes cluster",
long: corednsAddonsLongDesc,
cmdFunc: dnsaddon.EnsureDNSAddon,
},
{
@ -139,17 +141,17 @@ func getAddonsSubCommands() []*cobra.Command {
// Add flags to the command
cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster")
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental!")
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental")
cmd.Flags().StringVar(&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion, `Choose a specific Kubernetes version for the control plane`)
cmd.Flags().StringVar(&cfg.ImageRepository, "image-repository", cfg.ImageRepository, `Choose a container registry to pull control plane images from`)
if properties.use == "all" || properties.use == "kube-proxy" {
cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, `The IP address or DNS name the API server is accessible on`)
cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, `The IP address the API server is accessible on`)
cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, `The port the API server is accessible on`)
cmd.Flags().StringVar(&cfg.Networking.PodSubnet, "pod-network-cidr", cfg.Networking.PodSubnet, `The range of IP addresses used for the Pod network`)
}
if properties.use == "all" || properties.use == "kube-dns" {
if properties.use == "all" || properties.use == "coredns" {
cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, `Alternative domain for services`)
cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, `The range of IP address used for service VIPs`)
cmd.Flags().StringVar(&featureGatesString, "feature-gates", featureGatesString, "A set of key=value pairs that describe feature gates for various features."+
@ -162,7 +164,7 @@ func getAddonsSubCommands() []*cobra.Command {
}
// runAddonsCmdFunc creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters)
func runAddonsCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error, cfg *kubeadmapiext.MasterConfiguration, kubeConfigFile *string, cfgPath *string, featureGatesString *string) func(cmd *cobra.Command, args []string) {
func runAddonsCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error, cfg *kubeadmapiv1alpha2.MasterConfiguration, kubeConfigFile *string, cfgPath *string, featureGatesString *string) func(cmd *cobra.Command, args []string) {
// the following statement build a clousure that wraps a call to a cmdFunc, binding
// the function itself with the specific parameters of each sub command.
@ -180,7 +182,7 @@ func runAddonsCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration, client c
}
internalcfg := &kubeadmapi.MasterConfiguration{}
legacyscheme.Scheme.Convert(cfg, internalcfg, nil)
kubeadmscheme.Scheme.Convert(cfg, internalcfg, nil)
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
kubeadmutil.CheckErr(err)
internalcfg, err = configutil.ConfigFileAndDefaultsToInternalConfig(*cfgPath, cfg)

View File

@ -19,9 +19,6 @@ package phases
import (
"testing"
// required for triggering api machinery startup when running unit tests
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd"
)
@ -59,7 +56,7 @@ func TestAddonsSubCommandsHasFlags(t *testing.T) {
},
},
{
command: "kube-dns",
command: "coredns",
additionalFlags: []string{
"service-dns-domain",
"service-cidr",

View File

@ -18,30 +18,30 @@ package phases
import (
"fmt"
"strings"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/normalizer"
)
var (
allTokenLongDesc = normalizer.LongDesc(`
Bootstrap tokens are used for establishing bidirectional trust between a node joining
Bootstrap tokens are used for establishing bidirectional trust between a node joining
the cluster and a the master node.
This command makes all the configurations required to make bootstrap tokens works
@ -49,21 +49,21 @@ var (
` + cmdutil.AlphaDisclaimer)
allTokenExamples = normalizer.Examples(`
# Makes all the bootstrap token configurations and creates an initial token, functionally
# Makes all the bootstrap token configurations and creates an initial token, functionally
# equivalent to what generated by kubeadm init.
kubeadm alpha phase bootstrap-token all
`)
createTokenLongDesc = normalizer.LongDesc(`
Creates a bootstrap token. If no token value is given, kubeadm will generate a random token instead.
Alternatively, you can use kubeadm token.
` + cmdutil.AlphaDisclaimer)
clusterInfoLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Uploads the %q ConfigMap in the %q namespace, populating it with cluster information extracted from the
given kubeconfig file. The ConfigMap is used for the node bootstrap process in its initial phases,
before the client trusts the API server.
Uploads the %q ConfigMap in the %q namespace, populating it with cluster information extracted from the
given kubeconfig file. The ConfigMap is used for the node bootstrap process in its initial phases,
before the client trusts the API server.
See online documentation about Authenticating with Bootstrap Tokens for more details.
`+cmdutil.AlphaDisclaimer), bootstrapapi.ConfigMapClusterInfo, metav1.NamespacePublic)
@ -76,10 +76,10 @@ var (
` + cmdutil.AlphaDisclaimer)
nodeAutoApproveLongDesc = normalizer.LongDesc(`
Configures RBAC rules to allow the csrapprover controller to automatically approve
Configures RBAC rules to allow the csrapprover controller to automatically approve
certificate signing requests generated by nodes joining the cluster.
It configures also RBAC rules for certificates rotation (with auto approval of new certificates).
See online documentation about TLS bootstrapping for more details.
` + cmdutil.AlphaDisclaimer)
)
@ -107,17 +107,18 @@ func NewCmdBootstrapToken() *cobra.Command {
// NewSubCmdBootstrapTokenAll returns the Cobra command for running the token all sub-phase
func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
// the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig
KubernetesVersion: "v1.9.0",
KubernetesVersion: "v1.10.0",
}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, description string
var cfgPath string
var skipTokenPrint bool
bto := options.NewBootstrapTokenOptions()
cmd := &cobra.Command{
Use: "all",
@ -128,11 +129,14 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command {
err := validation.ValidateMixedArguments(cmd.Flags())
kubeadmutil.CheckErr(err)
err = bto.ApplyTo(cfg)
kubeadmutil.CheckErr(err)
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
kubeadmutil.CheckErr(err)
// Creates the bootstap token
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, skipTokenPrint)
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, skipTokenPrint)
kubeadmutil.CheckErr(err)
// Create the cluster-info ConfigMap or update if it already exists
@ -158,24 +162,30 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command {
}
// Adds flags to the command
addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &skipTokenPrint)
addGenericFlags(cmd.Flags(), &cfgPath, &skipTokenPrint)
bto.AddTokenFlag(cmd.Flags())
bto.AddTTLFlag(cmd.Flags())
bto.AddUsagesFlag(cmd.Flags())
bto.AddGroupsFlag(cmd.Flags())
bto.AddDescriptionFlag(cmd.Flags())
return cmd
}
// NewSubCmdBootstrapToken returns the Cobra command for running the create token phase
func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
// the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig
KubernetesVersion: "v1.9.0",
KubernetesVersion: "v1.10.0",
}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, description string
var cfgPath string
var skipTokenPrint bool
bto := options.NewBootstrapTokenOptions()
cmd := &cobra.Command{
Use: "create",
@ -185,16 +195,24 @@ func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command {
err := validation.ValidateMixedArguments(cmd.Flags())
kubeadmutil.CheckErr(err)
err = bto.ApplyTo(cfg)
kubeadmutil.CheckErr(err)
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
kubeadmutil.CheckErr(err)
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, skipTokenPrint)
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, skipTokenPrint)
kubeadmutil.CheckErr(err)
},
}
// Adds flags to the command
addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &skipTokenPrint)
addGenericFlags(cmd.Flags(), &cfgPath, &skipTokenPrint)
bto.AddTokenFlag(cmd.Flags())
bto.AddTTLFlag(cmd.Flags())
bto.AddUsagesFlag(cmd.Flags())
bto.AddGroupsFlag(cmd.Flags())
bto.AddDescriptionFlag(cmd.Flags())
return cmd
}
@ -277,30 +295,10 @@ func NewSubCmdNodeBootstrapTokenAutoApprove(kubeConfigFile *string) *cobra.Comma
return cmd
}
func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterConfiguration, cfgPath, description *string, skipTokenPrint *bool) {
func addGenericFlags(flagSet *pflag.FlagSet, cfgPath *string, skipTokenPrint *bool) {
flagSet.StringVar(
cfgPath, "config", *cfgPath,
"Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)",
)
flagSet.StringVar(
&cfg.Token, "token", cfg.Token,
"The token to use for establishing bidirectional trust between nodes and masters",
)
flagSet.DurationVar(
&cfg.TokenTTL.Duration, "ttl", cfg.TokenTTL.Duration,
"The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire",
)
flagSet.StringSliceVar(
&cfg.TokenUsages, "usages", cfg.TokenUsages,
fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s]", strings.Join(kubeadmconstants.DefaultTokenUsages, ",")),
)
flagSet.StringSliceVar(
&cfg.TokenGroups, "groups", cfg.TokenGroups,
fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q", bootstrapapi.BootstrapGroupPattern),
)
flagSet.StringVar(
description, "description", "The default bootstrap token generated by 'kubeadm init'.",
"A human friendly description of how this token is used.",
"Path to kubeadm config file. WARNING: Usage of a configuration file is experimental",
)
flagSet.BoolVar(
skipTokenPrint, "skip-token-print", *skipTokenPrint,
@ -308,7 +306,7 @@ func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterCon
)
}
func createBootstrapToken(kubeConfigFile string, client clientset.Interface, cfgPath string, cfg *kubeadmapiext.MasterConfiguration, description string, skipTokenPrint bool) error {
func createBootstrapToken(kubeConfigFile string, client clientset.Interface, cfgPath string, cfg *kubeadmapiv1alpha2.MasterConfiguration, skipTokenPrint bool) error {
// This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
@ -316,19 +314,21 @@ func createBootstrapToken(kubeConfigFile string, client clientset.Interface, cfg
return err
}
glog.V(1).Infoln("[bootstraptoken] creating/updating token")
// Creates or updates the token
if err := node.UpdateOrCreateToken(client, internalcfg.Token, false, internalcfg.TokenTTL.Duration, internalcfg.TokenUsages, internalcfg.TokenGroups, description); err != nil {
if err := node.UpdateOrCreateTokens(client, false, internalcfg.BootstrapTokens); err != nil {
return err
}
fmt.Println("[bootstraptoken] Bootstrap token Created")
fmt.Println("[bootstraptoken] You can now join any number of machines by running:")
fmt.Println("[bootstraptoken] bootstrap token created")
fmt.Println("[bootstraptoken] you can now join any number of machines by running:")
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.Token, skipTokenPrint)
if err != nil {
return fmt.Errorf("failed to get join command: %v", err)
if len(internalcfg.BootstrapTokens) > 0 {
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.BootstrapTokens[0].Token.String(), skipTokenPrint)
if err != nil {
return fmt.Errorf("failed to get join command: %v", err)
}
fmt.Println(joinCommand)
}
fmt.Println(joinCommand)
return nil
}

View File

@ -22,14 +22,14 @@ import (
"github.com/spf13/cobra"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/normalizer"
)
@ -44,7 +44,7 @@ var (
allCertsExample = normalizer.Examples(`
# Creates all PKI assets necessary to establish the control plane,
# functionally equivalent to what generated by kubeadm init.
# functionally equivalent to what generated by kubeadm init.
kubeadm alpha phase certs all
# Creates all PKI assets using options read from a configuration file.
@ -52,7 +52,7 @@ var (
`)
caCertLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the self-signed certificate authority and related key, and saves them into %s and %s files.
Generates the self-signed kubernetes certificate authority and related key, and saves them into %s and %s files.
If both files already exist, kubeadm skips the generation step and existing files will be used.
`+cmdutil.AlphaDisclaimer), kubeadmconstants.CACertName, kubeadmconstants.CAKeyName)
@ -74,6 +74,12 @@ var (
If both files already exist, kubeadm skips the generation step and existing files will be used.
`+cmdutil.AlphaDisclaimer), kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName)
etcdCaCertLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the self-signed etcd certificate authority and related key and saves them into %s and %s files.
If both files already exist, kubeadm skips the generation step and existing files will be used.
`+cmdutil.AlphaDisclaimer), kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName)
etcdServerCertLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the etcd serving certificate and key and saves them into %s and %s files.
@ -92,6 +98,13 @@ var (
If both files already exist, kubeadm skips the generation step and existing files will be used.
`+cmdutil.AlphaDisclaimer), kubeadmconstants.EtcdPeerCertName, kubeadmconstants.EtcdPeerKeyName)
etcdHealthcheckClientCertLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the client certificate for liveness probes to healthcheck etcd and the respective key,
and saves them into %s and %s files.
If both files already exist, kubeadm skips the generation step and existing files will be used.
`+cmdutil.AlphaDisclaimer), kubeadmconstants.EtcdHealthcheckClientCertName, kubeadmconstants.EtcdHealthcheckClientKeyName)
apiServerEtcdServerCertLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the client certificate for the API server to connect to etcd securely and the respective key,
and saves them into %s and %s files.
@ -135,7 +148,7 @@ func NewCmdCerts() *cobra.Command {
// getCertsSubCommands returns sub commands for certs phase
func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{}
cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
// This is used for unit testing only...
// If we wouldn't set this to something, the code would dynamically look up the version from the internet
@ -145,7 +158,7 @@ func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command {
}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath string
var subCmds []*cobra.Command
@ -166,37 +179,49 @@ func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command {
},
{
use: "ca",
short: "Generates self-signed CA to provision identities for each component in the cluster",
short: "Generates a self-signed kubernetes CA to provision identities for components of the cluster",
long: caCertLongDesc,
cmdFunc: certsphase.CreateCACertAndKeyfiles,
cmdFunc: certsphase.CreateCACertAndKeyFiles,
},
{
use: "apiserver",
short: "Generates API server serving certificate and key",
short: "Generates an API server serving certificate and key",
long: apiServerCertLongDesc,
cmdFunc: certsphase.CreateAPIServerCertAndKeyFiles,
},
{
use: "apiserver-kubelet-client",
short: "Generates client certificate for the API server to connect to the kubelets securely",
short: "Generates a client certificate for the API server to connect to the kubelets securely",
long: apiServerKubeletCertLongDesc,
cmdFunc: certsphase.CreateAPIServerKubeletClientCertAndKeyFiles,
},
{
use: "etcd-ca",
short: "Generates a self-signed CA to provision identities for etcd",
long: etcdCaCertLongDesc,
cmdFunc: certsphase.CreateEtcdCACertAndKeyFiles,
},
{
use: "etcd-server",
short: "Generates etcd serving certificate and key",
short: "Generates an etcd serving certificate and key",
long: etcdServerCertLongDesc,
cmdFunc: certsphase.CreateEtcdServerCertAndKeyFiles,
},
{
use: "etcd-peer",
short: "Generates etcd peer certificate and key",
short: "Generates an etcd peer certificate and key",
long: etcdPeerCertLongDesc,
cmdFunc: certsphase.CreateEtcdPeerCertAndKeyFiles,
},
{
use: "etcd-healthcheck-client",
short: "Generates a client certificate for liveness probes to healthcheck etcd",
long: etcdHealthcheckClientCertLongDesc,
cmdFunc: certsphase.CreateEtcdHealthcheckClientCertAndKeyFiles,
},
{
use: "apiserver-etcd-client",
short: "Generates client certificate for the API server to connect to etcd securely",
short: "Generates a client certificate for the API server to connect to etcd securely",
long: apiServerEtcdServerCertLongDesc,
cmdFunc: certsphase.CreateAPIServerEtcdClientCertAndKeyFiles,
},
@ -208,13 +233,13 @@ func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command {
},
{
use: "front-proxy-ca",
short: "Generates front proxy CA certificate and key for a Kubernetes cluster",
short: "Generates a front proxy CA certificate and key for a Kubernetes cluster",
long: frontProxyCaCertLongDesc,
cmdFunc: certsphase.CreateFrontProxyCACertAndKeyFiles,
},
{
use: "front-proxy-client",
short: "Generates front proxy CA client certificate and key for a Kubernetes cluster",
short: "Generates a front proxy CA client certificate and key for a Kubernetes cluster",
long: frontProxyClientCertLongDesc,
cmdFunc: certsphase.CreateFrontProxyClientCertAndKeyFiles,
},
@ -231,12 +256,12 @@ func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command {
}
// Add flags to the command
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental")
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where to save the certificates")
if properties.use == "all" || properties.use == "apiserver" {
cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, "Alternative domain for services, to use for the API server serving cert")
cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Alternative range of IP address for service VIPs, from which derives the internal API server VIP that will be added to the API Server serving cert")
cmd.Flags().StringSliceVar(&cfg.APIServerCertSANs, "apiserver-cert-extra-sans", []string{}, "Optional extra altnames to use for the API server serving cert. Can be both IP addresses and dns names")
cmd.Flags().StringSliceVar(&cfg.APIServerCertSANs, "apiserver-cert-extra-sans", []string{}, "Optional extra altnames to use for the API server serving cert. Can be both IP addresses and DNS names")
cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API server is accessible on, to use for the API server serving cert")
}
@ -247,7 +272,7 @@ func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command {
}
// runCmdFunc creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters)
func runCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) {
func runCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error, cfgPath *string, cfg *kubeadmapiv1alpha2.MasterConfiguration) func(cmd *cobra.Command, args []string) {
// the following statement build a closure that wraps a call to a cmdFunc, binding
// the function itself with the specific parameters of each sub command.

View File

@ -21,9 +21,6 @@ import (
"os"
"testing"
// required for triggering api machinery startup when running unit tests
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
@ -34,7 +31,7 @@ import (
)
// phaseTestK8sVersion is a fake kubernetes version to use when testing
const phaseTestK8sVersion = "v1.9.0"
const phaseTestK8sVersion = "v1.10.0"
func TestCertsSubCommandsHasFlags(t *testing.T) {
@ -73,6 +70,21 @@ func TestCertsSubCommandsHasFlags(t *testing.T) {
{
command: "apiserver-kubelet-client",
},
{
command: "etcd-ca",
},
{
command: "etcd-server",
},
{
command: "etcd-peer",
},
{
command: "etcd-healthcheck-client",
},
{
command: "apiserver-etcd-client",
},
{
command: "sa",
},
@ -113,6 +125,16 @@ func TestSubCmdCertsCreateFilesWithFlags(t *testing.T) {
subCmds: []string{"ca", "apiserver", "apiserver-kubelet-client"},
expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName, kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName},
},
{
subCmds: []string{"etcd-ca", "etcd-server", "etcd-peer", "etcd-healthcheck-client", "apiserver-etcd-client"},
expectedFiles: []string{
kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName,
kubeadmconstants.EtcdServerCertName, kubeadmconstants.EtcdServerKeyName,
kubeadmconstants.EtcdPeerCertName, kubeadmconstants.EtcdPeerKeyName,
kubeadmconstants.EtcdHealthcheckClientCertName, kubeadmconstants.EtcdHealthcheckClientKeyName,
kubeadmconstants.APIServerEtcdClientCertName, kubeadmconstants.APIServerEtcdClientKeyName,
},
},
{
subCmds: []string{"sa"},
expectedFiles: []string{kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName},
@ -204,6 +226,16 @@ func TestSubCmdCertsCreateFilesWithConfigFile(t *testing.T) {
subCmds: []string{"ca", "apiserver", "apiserver-kubelet-client"},
expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName, kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName},
},
{
subCmds: []string{"etcd-ca", "etcd-server", "etcd-peer", "etcd-healthcheck-client", "apiserver-etcd-client"},
expectedFiles: []string{
kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName,
kubeadmconstants.EtcdServerCertName, kubeadmconstants.EtcdServerKeyName,
kubeadmconstants.EtcdPeerCertName, kubeadmconstants.EtcdPeerKeyName,
kubeadmconstants.EtcdHealthcheckClientCertName, kubeadmconstants.EtcdHealthcheckClientKeyName,
kubeadmconstants.APIServerEtcdClientCertName, kubeadmconstants.APIServerEtcdClientKeyName,
},
},
{
subCmds: []string{"front-proxy-ca", "front-proxy-client"},
expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName, kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName},
@ -222,9 +254,9 @@ func TestSubCmdCertsCreateFilesWithConfigFile(t *testing.T) {
certdir := tmpdir
cfg := &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
CertificatesDir: certdir,
NodeName: "valid-node-name",
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
CertificatesDir: certdir,
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-node-name"},
}
configPath := testutil.SetupMasterConfigurationFile(t, tmpdir, cfg)

View File

@ -24,7 +24,8 @@ import (
utilflag "k8s.io/apiserver/pkg/util/flag"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -32,7 +33,6 @@ import (
controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/normalizer"
)
@ -79,7 +79,7 @@ func NewCmdControlplane() *cobra.Command {
// getControlPlaneSubCommands returns sub commands for Controlplane phase
func getControlPlaneSubCommands(outDir, defaultKubernetesVersion string) []*cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{}
cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
// This is used for unit testing only...
// If we wouldn't set this to something, the code would dynamically look up the version from the internet
@ -89,7 +89,7 @@ func getControlPlaneSubCommands(outDir, defaultKubernetesVersion string) []*cobr
}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, featureGatesString string
var subCmds []*cobra.Command
@ -110,19 +110,19 @@ func getControlPlaneSubCommands(outDir, defaultKubernetesVersion string) []*cobr
},
{
use: "apiserver",
short: "Generates the API server static Pod manifest.",
short: "Generates the API server static Pod manifest",
long: apiServerControlplaneLongDesc,
cmdFunc: controlplanephase.CreateAPIServerStaticPodManifestFile,
},
{
use: "controller-manager",
short: "Generates the controller-manager static Pod manifest.",
short: "Generates the controller-manager static Pod manifest",
long: controllerManagerControlplaneLongDesc,
cmdFunc: controlplanephase.CreateControllerManagerStaticPodManifestFile,
},
{
use: "scheduler",
short: "Generates the scheduler static Pod manifest.",
short: "Generates the scheduler static Pod manifest",
long: schedulerControlplaneLongDesc,
cmdFunc: controlplanephase.CreateSchedulerStaticPodManifestFile,
},
@ -160,7 +160,7 @@ func getControlPlaneSubCommands(outDir, defaultKubernetesVersion string) []*cobr
cmd.Flags().Var(utilflag.NewMapStringString(&cfg.SchedulerExtraArgs), "scheduler-extra-args", "A set of extra flags to pass to the Scheduler or override default ones in form of <flagname>=<value>")
}
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental")
subCmds = append(subCmds, cmd)
}
@ -169,7 +169,7 @@ func getControlPlaneSubCommands(outDir, defaultKubernetesVersion string) []*cobr
}
// runCmdControlPlane creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters)
func runCmdControlPlane(cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error, outDir, cfgPath *string, featureGatesString *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) {
func runCmdControlPlane(cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error, outDir, cfgPath *string, featureGatesString *string, cfg *kubeadmapiv1alpha2.MasterConfiguration) func(cmd *cobra.Command, args []string) {
// the following statement build a closure that wraps a call to a cmdFunc, binding
// the function itself with the specific parameters of each sub command.

View File

@ -21,9 +21,6 @@ import (
"os"
"testing"
// required for triggering api machinery startup when running unit tests
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd"
)
@ -93,7 +90,7 @@ func TestControlPlaneCreateFilesWithFlags(t *testing.T) {
{
command: "all",
additionalFlags: []string{
"--kubernetes-version=v1.9.0",
"--kubernetes-version=v1.10.0",
"--apiserver-advertise-address=1.2.3.4",
"--apiserver-bind-port=6443",
"--service-cidr=1.2.3.4/16",
@ -108,7 +105,7 @@ func TestControlPlaneCreateFilesWithFlags(t *testing.T) {
{
command: "apiserver",
additionalFlags: []string{
"--kubernetes-version=v1.9.0",
"--kubernetes-version=v1.10.0",
"--apiserver-advertise-address=1.2.3.4",
"--apiserver-bind-port=6443",
"--service-cidr=1.2.3.4/16",
@ -118,7 +115,7 @@ func TestControlPlaneCreateFilesWithFlags(t *testing.T) {
{
command: "controller-manager",
additionalFlags: []string{
"--kubernetes-version=v1.9.0",
"--kubernetes-version=v1.10.0",
"--pod-network-cidr=1.2.3.4/16",
},
expectedFiles: []string{"kube-controller-manager.yaml"},
@ -126,7 +123,7 @@ func TestControlPlaneCreateFilesWithFlags(t *testing.T) {
{
command: "scheduler",
additionalFlags: []string{
"--kubernetes-version=v1.9.0",
"--kubernetes-version=v1.10.0",
},
expectedFiles: []string{"kube-scheduler.yaml"},
},

View File

@ -22,11 +22,11 @@ import (
"github.com/spf13/cobra"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/normalizer"
)
@ -61,7 +61,7 @@ func NewCmdEtcd() *cobra.Command {
// getEtcdSubCommands returns sub commands for etcd phase
func getEtcdSubCommands(outDir, defaultKubernetesVersion string) []*cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{}
cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
// This is used for unit testing only...
// If we wouldn't set this to something, the code would dynamically look up the version from the internet
@ -71,7 +71,7 @@ func getEtcdSubCommands(outDir, defaultKubernetesVersion string) []*cobra.Comman
}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath string
var subCmds []*cobra.Command
@ -101,7 +101,7 @@ func getEtcdSubCommands(outDir, defaultKubernetesVersion string) []*cobra.Comman
// Add flags to the command
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, `The path where certificates are stored`)
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental")
subCmds = append(subCmds, cmd)

View File

@ -21,9 +21,6 @@ import (
"os"
"testing"
// required for triggering api machinery startup when running unit tests
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd"
)

View File

@ -24,11 +24,11 @@ import (
"github.com/spf13/cobra"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/normalizer"
)
@ -51,9 +51,9 @@ var (
`+cmdutil.AlphaDisclaimer), kubeadmconstants.AdminKubeConfigFileName)
kubeletKubeconfigLongDesc = fmt.Sprintf(normalizer.LongDesc(`
Generates the kubeconfig file for the kubelet to use and saves it to %s file.
Generates the kubeconfig file for the kubelet to use and saves it to %s file.
Please note that this should *only* be used for bootstrapping purposes. After your control plane is up,
Please note that this should *only* be used for bootstrapping purposes. After your control plane is up,
you should request all kubelet credentials from the CSR API.
`+cmdutil.AlphaDisclaimer), filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName))
@ -90,7 +90,7 @@ func NewCmdKubeConfig(out io.Writer) *cobra.Command {
// getKubeConfigSubCommands returns sub commands for kubeconfig phase
func getKubeConfigSubCommands(out io.Writer, outDir, defaultKubernetesVersion string) []*cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{}
cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
// This is used for unit testing only...
// If we wouldn't set this to something, the code would dynamically look up the version from the internet
@ -100,9 +100,10 @@ func getKubeConfigSubCommands(out io.Writer, outDir, defaultKubernetesVersion st
}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, token, clientName string
var organizations []string
var subCmds []*cobra.Command
subCmdProperties := []struct {
@ -127,7 +128,7 @@ func getKubeConfigSubCommands(out io.Writer, outDir, defaultKubernetesVersion st
},
{
use: "kubelet",
short: "Generates a kubeconfig file for the kubelet to use. Please note that this should be used *only* for bootstrapping purposes.",
short: "Generates a kubeconfig file for the kubelet to use. Please note that this should be used *only* for bootstrapping purposes",
long: kubeletKubeconfigLongDesc,
cmdFunc: kubeconfigphase.CreateKubeletKubeConfigFile,
},
@ -159,7 +160,7 @@ func getKubeConfigSubCommands(out io.Writer, outDir, defaultKubernetesVersion st
}
// Otherwise, write a kubeconfig file with a generate client cert
return kubeconfigphase.WriteKubeConfigWithClientCert(out, cfg, clientName)
return kubeconfigphase.WriteKubeConfigWithClientCert(out, cfg, clientName, organizations)
},
},
}
@ -176,18 +177,19 @@ func getKubeConfigSubCommands(out io.Writer, outDir, defaultKubernetesVersion st
// Add flags to the command
if properties.use != "user" {
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental")
}
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where certificates are stored")
cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API server is accessible on")
cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, "The port the API server is accessible on")
cmd.Flags().StringVar(&outDir, "kubeconfig-dir", outDir, "The port where to save the kubeconfig file")
cmd.Flags().StringVar(&outDir, "kubeconfig-dir", outDir, "The path where to save the kubeconfig file")
if properties.use == "all" || properties.use == "kubelet" {
cmd.Flags().StringVar(&cfg.NodeName, "node-name", cfg.NodeName, `The node name that should be used for the kubelet client certificate`)
cmd.Flags().StringVar(&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name, `The node name that should be used for the kubelet client certificate`)
}
if properties.use == "user" {
cmd.Flags().StringVar(&token, "token", token, "The token that should be used as the authentication mechanism for this kubeconfig (instead of client certificates)")
cmd.Flags().StringVar(&token, "token", token, "The token that should be used as the authentication mechanism for this kubeconfig, instead of client certificates")
cmd.Flags().StringVar(&clientName, "client-name", clientName, "The name of user. It will be used as the CN if client certificates are created")
cmd.Flags().StringSliceVar(&organizations, "org", organizations, "The orgnizations of the client certificate. It will be used as the O if client certificates are created")
}
subCmds = append(subCmds, cmd)

View File

@ -23,9 +23,6 @@ import (
"path/filepath"
"testing"
// required for triggering api machinery startup when running unit tests
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
"k8s.io/client-go/tools/clientcmd"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -196,7 +193,7 @@ func TestKubeConfigSubCommandsThatCreateFilesWithFlags(t *testing.T) {
// reads generated files
config, err := clientcmd.LoadFromFile(filepath.Join(tmpdir, file))
if err != nil {
t.Errorf("Couldn't load generated kubeconfig file: %v", err)
t.Errorf("couldn't load generated kubeconfig file: %v", err)
}
// checks that CLI flags are properly propagated and kubeconfig properties are correct
@ -280,9 +277,9 @@ func TestKubeConfigSubCommandsThatCreateFilesWithConfigFile(t *testing.T) {
// Adds a master configuration file
cfg := &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
CertificatesDir: pkidir,
NodeName: "valid-node-name",
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
CertificatesDir: pkidir,
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-node-name"},
}
cfgPath := testutil.SetupMasterConfigurationFile(t, tmpdir, cfg)
@ -302,7 +299,7 @@ func TestKubeConfigSubCommandsThatCreateFilesWithConfigFile(t *testing.T) {
// reads generated files
config, err := clientcmd.LoadFromFile(filepath.Join(tmpdir, file))
if err != nil {
t.Errorf("Couldn't load generated kubeconfig file: %v", err)
t.Errorf("couldn't load generated kubeconfig file: %v", err)
}
// checks that config file properties are properly propagated and kubeconfig properties are correct
@ -371,7 +368,7 @@ func TestKubeConfigSubCommandsThatWritesToOut(t *testing.T) {
// reads kubeconfig written to stdout
config, err := clientcmd.Load(buf.Bytes())
if err != nil {
t.Errorf("Couldn't read kubeconfig file from buffer: %v", err)
t.Errorf("couldn't read kubeconfig file from buffer: %v", err)
continue
}

View File

@ -0,0 +1,334 @@
/*
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 phases
import (
"fmt"
"io/ioutil"
"github.com/spf13/cobra"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/pkg/util/normalizer"
"k8s.io/kubernetes/pkg/util/version"
utilsexec "k8s.io/utils/exec"
)
const (
// TODO: Figure out how to get these constants from the API machinery
masterConfig = "MasterConfiguration"
nodeConfig = "NodeConfiguration"
)
var (
kubeletWriteEnvFileLongDesc = normalizer.LongDesc(`
Writes an environment file with flags that should be passed to the kubelet executing on the master or node.
This --config flag can either consume a MasterConfiguration object or a NodeConfiguration one, as this
function is used for both "kubeadm init" and "kubeadm join".
` + cmdutil.AlphaDisclaimer)
kubeletWriteEnvFileExample = normalizer.Examples(`
# Writes a dynamic environment file with kubelet flags from a MasterConfiguration file.
kubeadm alpha phase kubelet write-env-file --config masterconfig.yaml
# Writes a dynamic environment file with kubelet flags from a NodeConfiguration file.
kubeadm alpha phase kubelet write-env-file --config nodeConfig.yaml
`)
kubeletConfigUploadLongDesc = normalizer.LongDesc(`
Uploads kubelet configuration extracted from the kubeadm MasterConfiguration object to a ConfigMap
of the form kubelet-config-1.X in the cluster, where X is the minor version of the current (API Server) Kubernetes version.
` + cmdutil.AlphaDisclaimer)
kubeletConfigUploadExample = normalizer.Examples(`
# Uploads the kubelet configuration from the kubeadm Config file to a ConfigMap in the cluster.
kubeadm alpha phase kubelet config upload --config kubeadm.yaml
`)
kubeletConfigDownloadLongDesc = normalizer.LongDesc(`
Downloads the kubelet configuration from a ConfigMap of the form "kubelet-config-1.X" in the cluster,
where X is the minor version of the kubelet. Either kubeadm autodetects the kubelet version by exec-ing
"kubelet --version" or respects the --kubelet-version parameter.
` + cmdutil.AlphaDisclaimer)
kubeletConfigDownloadExample = normalizer.Examples(`
# Downloads the kubelet configuration from the ConfigMap in the cluster. Autodetects the kubelet version.
kubeadm alpha phase kubelet config download
# Downloads the kubelet configuration from the ConfigMap in the cluster. Uses a specific desired kubelet version.
kubeadm alpha phase kubelet config download --kubelet-version v1.11.0
`)
kubeletConfigWriteToDiskLongDesc = normalizer.LongDesc(`
Writes kubelet configuration to disk, based on the kubeadm configuration passed via "--config".
` + cmdutil.AlphaDisclaimer)
kubeletConfigWriteToDiskExample = normalizer.Examples(`
# Extracts the kubelet configuration from a kubeadm configuration file
kubeadm alpha phase kubelet config write-to-disk --config kubeadm.yaml
`)
kubeletConfigEnableDynamicLongDesc = normalizer.LongDesc(`
Enables or updates dynamic kubelet configuration for a Node, against the kubelet-config-1.X ConfigMap in the cluster,
where X is the minor version of the desired kubelet version.
WARNING: This feature is still experimental, and disabled by default. Enable only if you know what you are doing, as it
may have surprising side-effects at this stage.
` + cmdutil.AlphaDisclaimer)
kubeletConfigEnableDynamicExample = normalizer.Examples(`
# Enables dynamic kubelet configuration for a Node.
kubeadm alpha phase kubelet enable-dynamic-config --node-name node-1 --kubelet-version v1.11.0
WARNING: This feature is still experimental, and disabled by default. Enable only if you know what you are doing, as it
may have surprising side-effects at this stage.
`)
)
// NewCmdKubelet returns command for `kubeadm phase kubelet`
func NewCmdKubelet() *cobra.Command {
cmd := &cobra.Command{
Use: "kubelet",
Short: "Commands related to handling the kubelet.",
Long: cmdutil.MacroCommandLongDescription,
}
cmd.AddCommand(NewCmdKubeletConfig())
cmd.AddCommand(NewCmdKubeletWriteEnvFile())
return cmd
}
// NewCmdKubeletWriteEnvFile calls cobra.Command for writing the dynamic kubelet env file based on a MasterConfiguration or NodeConfiguration object
func NewCmdKubeletWriteEnvFile() *cobra.Command {
var cfgPath string
cmd := &cobra.Command{
Use: "write-env-file",
Short: "Writes an environment file with runtime flags for the kubelet.",
Long: kubeletWriteEnvFileLongDesc,
Example: kubeletWriteEnvFileExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunKubeletWriteEnvFile(cfgPath)
kubeadmutil.CheckErr(err)
},
}
options.AddConfigFlag(cmd.Flags(), &cfgPath)
return cmd
}
// RunKubeletWriteEnvFile is the function that is run when "kubeadm phase kubelet write-env-file" is executed
func RunKubeletWriteEnvFile(cfgPath string) error {
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return err
}
gvk, err := kubeadmutil.GroupVersionKindFromBytes(b, kubeadmscheme.Codecs)
if err != nil {
return err
}
var nodeRegistrationObj *kubeadmapi.NodeRegistrationOptions
var featureGates map[string]bool
var registerWithTaints bool
switch gvk.Kind {
case masterConfig:
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
if err != nil {
return err
}
nodeRegistrationObj = &internalcfg.NodeRegistration
featureGates = internalcfg.FeatureGates
registerWithTaints = false
case nodeConfig:
internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1alpha2.NodeConfiguration{})
if err != nil {
return err
}
nodeRegistrationObj = &internalcfg.NodeRegistration
featureGates = internalcfg.FeatureGates
registerWithTaints = true
default:
if err != nil {
return fmt.Errorf("Didn't recognize type with GroupVersionKind: %v", gvk)
}
}
if nodeRegistrationObj == nil {
return fmt.Errorf("couldn't load nodeRegistration field from config file")
}
if err := kubeletphase.WriteKubeletDynamicEnvFile(nodeRegistrationObj, featureGates, registerWithTaints, constants.KubeletRunDirectory); err != nil {
return fmt.Errorf("error writing a dynamic environment file for the kubelet: %v", err)
}
return nil
}
// NewCmdKubeletConfig returns command for `kubeadm phase kubelet config`
func NewCmdKubeletConfig() *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Handles kubelet configuration.",
Long: cmdutil.MacroCommandLongDescription,
}
cmd.AddCommand(NewCmdKubeletConfigUpload())
cmd.AddCommand(NewCmdKubeletConfigDownload())
cmd.AddCommand(NewCmdKubeletConfigWriteToDisk())
cmd.AddCommand(NewCmdKubeletConfigEnableDynamic())
return cmd
}
// NewCmdKubeletConfigUpload calls cobra.Command for uploading dynamic kubelet configuration
func NewCmdKubeletConfigUpload() *cobra.Command {
var cfgPath string
kubeConfigFile := constants.GetAdminKubeConfigPath()
cmd := &cobra.Command{
Use: "upload",
Short: "Uploads kubelet configuration to a ConfigMap based on a kubeadm MasterConfiguration file.",
Long: kubeletConfigUploadLongDesc,
Example: kubeletConfigUploadExample,
Run: func(cmd *cobra.Command, args []string) {
if len(cfgPath) == 0 {
kubeadmutil.CheckErr(fmt.Errorf("The --config argument is required"))
}
// This call returns the ready-to-use configuration based on the configuration file
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
kubeadmutil.CheckErr(err)
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile)
kubeadmutil.CheckErr(err)
err = kubeletphase.CreateConfigMap(internalcfg, client)
kubeadmutil.CheckErr(err)
},
}
options.AddKubeConfigFlag(cmd.Flags(), &kubeConfigFile)
options.AddConfigFlag(cmd.Flags(), &cfgPath)
return cmd
}
// NewCmdKubeletConfigDownload calls cobra.Command for downloading the kubelet configuration from the kubelet-config-1.X ConfigMap in the cluster
func NewCmdKubeletConfigDownload() *cobra.Command {
var kubeletVersionStr string
// TODO: Be smarter about this and be able to load multiple kubeconfig files in different orders of precedence
kubeConfigFile := constants.GetKubeletKubeConfigPath()
cmd := &cobra.Command{
Use: "download",
Short: "Downloads the kubelet configuration from the cluster ConfigMap kubelet-config-1.X, where X is the minor version of the kubelet.",
Long: kubeletConfigDownloadLongDesc,
Example: kubeletConfigDownloadExample,
Run: func(cmd *cobra.Command, args []string) {
kubeletVersion, err := getKubeletVersion(kubeletVersionStr)
kubeadmutil.CheckErr(err)
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile)
kubeadmutil.CheckErr(err)
err = kubeletphase.DownloadConfig(client, kubeletVersion, constants.KubeletRunDirectory)
kubeadmutil.CheckErr(err)
},
}
options.AddKubeConfigFlag(cmd.Flags(), &kubeConfigFile)
cmd.Flags().StringVar(&kubeletVersionStr, "kubelet-version", kubeletVersionStr, "The desired version for the kubelet. Defaults to being autodetected from 'kubelet --version'.")
return cmd
}
func getKubeletVersion(kubeletVersionStr string) (*version.Version, error) {
if len(kubeletVersionStr) > 0 {
return version.ParseSemantic(kubeletVersionStr)
}
return preflight.GetKubeletVersion(utilsexec.New())
}
// NewCmdKubeletConfigWriteToDisk calls cobra.Command for writing init kubelet configuration
func NewCmdKubeletConfigWriteToDisk() *cobra.Command {
var cfgPath string
cmd := &cobra.Command{
Use: "write-to-disk",
Short: "Writes kubelet configuration to disk, either based on the --config argument.",
Long: kubeletConfigWriteToDiskLongDesc,
Example: kubeletConfigWriteToDiskExample,
Run: func(cmd *cobra.Command, args []string) {
if len(cfgPath) == 0 {
kubeadmutil.CheckErr(fmt.Errorf("The --config argument is required"))
}
// This call returns the ready-to-use configuration based on the configuration file
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
kubeadmutil.CheckErr(err)
err = kubeletphase.WriteConfigToDisk(internalcfg.KubeletConfiguration.BaseConfig, constants.KubeletRunDirectory)
kubeadmutil.CheckErr(err)
},
}
options.AddConfigFlag(cmd.Flags(), &cfgPath)
return cmd
}
// NewCmdKubeletConfigEnableDynamic calls cobra.Command for enabling dynamic kubelet configuration on node
// This feature is still in alpha and an experimental state
func NewCmdKubeletConfigEnableDynamic() *cobra.Command {
var nodeName, kubeletVersionStr string
kubeConfigFile := constants.GetAdminKubeConfigPath()
cmd := &cobra.Command{
Use: "enable-dynamic",
Short: "EXPERIMENTAL: Enables or updates dynamic kubelet configuration for a Node",
Long: kubeletConfigEnableDynamicLongDesc,
Example: kubeletConfigEnableDynamicExample,
Run: func(cmd *cobra.Command, args []string) {
if len(nodeName) == 0 {
kubeadmutil.CheckErr(fmt.Errorf("The --node-name argument is required"))
}
if len(kubeletVersionStr) == 0 {
kubeadmutil.CheckErr(fmt.Errorf("The --kubelet-version argument is required"))
}
kubeletVersion, err := version.ParseSemantic(kubeletVersionStr)
kubeadmutil.CheckErr(err)
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile)
kubeadmutil.CheckErr(err)
err = kubeletphase.EnableDynamicConfigForNode(client, nodeName, kubeletVersion)
kubeadmutil.CheckErr(err)
},
}
options.AddKubeConfigFlag(cmd.Flags(), &kubeConfigFile)
cmd.Flags().StringVar(&nodeName, "node-name", nodeName, "Name of the node that should enable the dynamic kubelet configuration")
cmd.Flags().StringVar(&kubeletVersionStr, "kubelet-version", kubeletVersionStr, "The desired version for the kubelet")
return cmd
}

View File

@ -0,0 +1,82 @@
/*
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 phases
import (
"testing"
"github.com/spf13/cobra"
cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd"
)
func TestKubeletSubCommandsHasFlags(t *testing.T) {
subCmds := []*cobra.Command{
NewCmdKubeletWriteEnvFile(),
NewCmdKubeletConfigUpload(),
NewCmdKubeletConfigDownload(),
NewCmdKubeletConfigWriteToDisk(),
NewCmdKubeletConfigEnableDynamic(),
}
commonFlags := []string{}
var tests = []struct {
command string
additionalFlags []string
}{
{
command: "write-env-file",
additionalFlags: []string{
"config",
},
},
{
command: "upload",
additionalFlags: []string{
"kubeconfig",
"config",
},
},
{
command: "download",
additionalFlags: []string{
"kubeconfig",
"kubelet-version",
},
},
{
command: "write-to-disk",
additionalFlags: []string{
"config",
},
},
{
command: "enable-dynamic",
additionalFlags: []string{
"kubeconfig",
"node-name",
"kubelet-version",
},
},
}
for _, test := range tests {
expectedFlags := append(commonFlags, test.additionalFlags...)
cmdtestutil.AssertSubCommandHasFlags(t, subCmds, test.command, expectedFlags...)
}
}

View File

@ -19,14 +19,14 @@ package phases
import (
"github.com/spf13/cobra"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/normalizer"
)
@ -47,14 +47,14 @@ var (
// NewCmdMarkMaster returns the Cobra command for running the mark-master phase
func NewCmdMarkMaster() *cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
// KubernetesVersion is not used by mark master, but we set this explicitly to avoid
// the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig
KubernetesVersion: "v1.9.0",
KubernetesVersion: "v1.10.0",
}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, kubeConfigFile string
cmd := &cobra.Command{
@ -75,14 +75,14 @@ func NewCmdMarkMaster() *cobra.Command {
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile)
kubeadmutil.CheckErr(err)
err = markmasterphase.MarkMaster(client, internalcfg.NodeName, !internalcfg.NoTaintMaster)
err = markmasterphase.MarkMaster(client, internalcfg.NodeRegistration.Name, internalcfg.NodeRegistration.Taints)
kubeadmutil.CheckErr(err)
},
}
cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster")
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
cmd.Flags().StringVar(&cfg.NodeName, "node-name", cfg.NodeName, `The node name to which label and taints should apply`)
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental")
cmd.Flags().StringVar(&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name, `The node name to which label and taints should apply`)
return cmd
}

View File

@ -36,6 +36,7 @@ func NewCmdPhase(out io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdCerts())
cmd.AddCommand(NewCmdControlplane())
cmd.AddCommand(NewCmdEtcd())
cmd.AddCommand(NewCmdKubelet())
cmd.AddCommand(NewCmdKubeConfig(out))
cmd.AddCommand(NewCmdMarkMaster())
cmd.AddCommand(NewCmdPreFlight())

View File

@ -23,7 +23,8 @@ import (
"github.com/spf13/cobra"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -33,7 +34,6 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/normalizer"
)
@ -70,9 +70,9 @@ func NewCmdSelfhosting() *cobra.Command {
// getSelfhostingSubCommand returns sub commands for Selfhosting phase
func getSelfhostingSubCommand() *cobra.Command {
cfg := &kubeadmapiext.MasterConfiguration{}
cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, kubeConfigFile, featureGatesString string
@ -111,7 +111,7 @@ func getSelfhostingSubCommand() *cobra.Command {
// Add flags to the command
// flags bound to the configuration object
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, `The path where certificates are stored`)
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental!")
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental")
cmd.Flags().StringVar(&featureGatesString, "feature-gates", featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))

View File

@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
@ -57,12 +57,12 @@ func NewCmdUploadConfig() *cobra.Command {
Aliases: []string{"uploadconfig"},
Run: func(_ *cobra.Command, args []string) {
if len(cfgPath) == 0 {
kubeadmutil.CheckErr(fmt.Errorf("The --config flag is mandatory"))
kubeadmutil.CheckErr(fmt.Errorf("the --config flag is mandatory"))
}
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile)
kubeadmutil.CheckErr(err)
defaultcfg := &kubeadmapiext.MasterConfiguration{}
defaultcfg := &kubeadmapiv1alpha2.MasterConfiguration{}
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, defaultcfg)
kubeadmutil.CheckErr(err)
@ -72,7 +72,7 @@ func NewCmdUploadConfig() *cobra.Command {
}
cmd.Flags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster")
cmd.Flags().StringVar(&cfgPath, "config", "", "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental!")
cmd.Flags().StringVar(&cfgPath, "config", "", "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental")
return cmd
}

View File

@ -20,14 +20,14 @@ import (
"github.com/spf13/cobra"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "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"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
)
// runCmdPhase creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters)
func runCmdPhase(cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error, outDir, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) {
func runCmdPhase(cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error, outDir, cfgPath *string, cfg *kubeadmapiv1alpha2.MasterConfiguration) func(cmd *cobra.Command, args []string) {
// the following statement build a closure that wraps a call to a cmdFunc, binding
// the function itself with the specific parameters of each sub command.

View File

@ -17,6 +17,8 @@ limitations under the License.
package cmd
import (
"bufio"
"errors"
"fmt"
"io"
"os"
@ -24,10 +26,11 @@ import (
"path/filepath"
"strings"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/util/sets"
kubeadmapiext "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"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
@ -36,18 +39,13 @@ import (
utilsexec "k8s.io/utils/exec"
)
var (
crictlSandboxesParamsFormat = "%s -r %s sandboxes --quiet | xargs -r"
crictlStopParamsFormat = "%s -r %s stops %s"
crictlRemoveParamsFormat = "%s -r %s rms %s"
)
// NewCmdReset returns the "kubeadm reset" command
func NewCmdReset(out io.Writer) *cobra.Command {
func NewCmdReset(in io.Reader, out io.Writer) *cobra.Command {
var skipPreFlight bool
var certsDir string
var criSocketPath string
var ignorePreflightErrors []string
var forceReset bool
cmd := &cobra.Command{
Use: "reset",
@ -56,7 +54,7 @@ func NewCmdReset(out io.Writer) *cobra.Command {
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight)
kubeadmutil.CheckErr(err)
r, err := NewReset(ignorePreflightErrorsSet, certsDir, criSocketPath)
r, err := NewReset(in, ignorePreflightErrorsSet, forceReset, certsDir, criSocketPath)
kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(r.Run(out))
},
@ -73,7 +71,7 @@ func NewCmdReset(out io.Writer) *cobra.Command {
cmd.PersistentFlags().MarkDeprecated("skip-preflight-checks", "it is now equivalent to --ignore-preflight-errors=all")
cmd.PersistentFlags().StringVar(
&certsDir, "cert-dir", kubeadmapiext.DefaultCertificatesDir,
&certsDir, "cert-dir", kubeadmapiv1alpha2.DefaultCertificatesDir,
"The path to the directory where the certificates are stored. If specified, clean this directory.",
)
@ -82,6 +80,11 @@ func NewCmdReset(out io.Writer) *cobra.Command {
"The path to the CRI socket to use with crictl when cleaning up containers.",
)
cmd.PersistentFlags().BoolVarP(
&forceReset, "force", "f", false,
"Reset the node without prompting for confirmation.",
)
return cmd
}
@ -92,9 +95,21 @@ type Reset struct {
}
// NewReset instantiate Reset struct
func NewReset(ignorePreflightErrors sets.String, certsDir, criSocketPath string) (*Reset, error) {
fmt.Println("[preflight] Running pre-flight checks.")
func NewReset(in io.Reader, ignorePreflightErrors sets.String, forceReset bool, certsDir, criSocketPath string) (*Reset, error) {
if !forceReset {
fmt.Println("[reset] WARNING: changes made to this host by 'kubeadm init' or 'kubeadm join' will be reverted.")
fmt.Print("[reset] are you sure you want to proceed? [y/N]: ")
s := bufio.NewScanner(in)
s.Scan()
if err := s.Err(); err != nil {
return nil, err
}
if strings.ToLower(s.Text()) != "y" {
return nil, errors.New("Aborted reset operation")
}
}
fmt.Println("[preflight] running pre-flight checks")
if err := preflight.RunRootCheckOnly(ignorePreflightErrors); err != nil {
return nil, err
}
@ -109,27 +124,30 @@ func NewReset(ignorePreflightErrors sets.String, certsDir, criSocketPath string)
func (r *Reset) Run(out io.Writer) error {
// Try to stop the kubelet service
glog.V(1).Infof("[reset] getting init system")
initSystem, err := initsystem.GetInitSystem()
if err != nil {
fmt.Println("[reset] WARNING: The kubelet service could not be stopped by kubeadm. Unable to detect a supported init system!")
fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
glog.Warningln("[reset] the kubelet service could not be stopped by kubeadm. Unable to detect a supported init system!")
glog.Warningln("[reset] please ensure kubelet is stopped manually")
} else {
fmt.Println("[reset] Stopping the kubelet service.")
fmt.Println("[reset] stopping the kubelet service")
if err := initSystem.ServiceStop("kubelet"); err != nil {
fmt.Printf("[reset] WARNING: The kubelet service could not be stopped by kubeadm: [%v]\n", err)
fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
glog.Warningf("[reset] the kubelet service could not be stopped by kubeadm: [%v]\n", err)
glog.Warningln("[reset] please ensure kubelet is stopped manually")
}
}
// Try to unmount mounted directories under /var/lib/kubelet in order to be able to remove the /var/lib/kubelet directory later
fmt.Printf("[reset] Unmounting mounted directories in %q\n", "/var/lib/kubelet")
fmt.Printf("[reset] unmounting mounted directories in %q\n", "/var/lib/kubelet")
umountDirsCmd := "awk '$2 ~ path {print $2}' path=/var/lib/kubelet /proc/mounts | xargs -r umount"
glog.V(1).Infof("[reset] executing command %q", umountDirsCmd)
umountOutputBytes, err := exec.Command("sh", "-c", umountDirsCmd).Output()
if err != nil {
fmt.Printf("[reset] Failed to unmount mounted directories in /var/lib/kubelet: %s\n", string(umountOutputBytes))
glog.Errorf("[reset] failed to unmount mounted directories in /var/lib/kubelet: %s\n", string(umountOutputBytes))
}
fmt.Println("[reset] Removing kubernetes-managed containers.")
fmt.Println("[reset] removing kubernetes-managed containers")
dockerCheck := preflight.ServiceCheck{Service: "docker", CheckIfActive: true}
execer := utilsexec.New()
@ -138,23 +156,27 @@ func (r *Reset) Run(out io.Writer) error {
dirsToClean := []string{"/var/lib/kubelet", "/etc/cni/net.d", "/var/lib/dockershim", "/var/run/kubernetes"}
// Only clear etcd data when the etcd manifest is found. In case it is not found, we must assume that the user
// provided external etcd endpoints. In that case, it is his own responsibility to reset etcd
// provided external etcd endpoints. In that case, it is their own responsibility to reset etcd
etcdManifestPath := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName, "etcd.yaml")
glog.V(1).Infof("[reset] checking for etcd manifest")
if _, err := os.Stat(etcdManifestPath); err == nil {
glog.V(1).Infof("Found one at %s", etcdManifestPath)
dirsToClean = append(dirsToClean, "/var/lib/etcd")
} else {
fmt.Printf("[reset] No etcd manifest found in %q. Assuming external etcd.\n", etcdManifestPath)
fmt.Printf("[reset] no etcd manifest found in %q. Assuming external etcd\n", etcdManifestPath)
}
// Then clean contents from the stateful kubelet, etcd and cni directories
fmt.Printf("[reset] Deleting contents of stateful directories: %v\n", dirsToClean)
fmt.Printf("[reset] deleting contents of stateful directories: %v\n", dirsToClean)
for _, dir := range dirsToClean {
glog.V(1).Infof("[reset] deleting content of %s", dir)
cleanDir(dir)
}
// Remove contents from the config and pki directories
if r.certsDir != kubeadmapiext.DefaultCertificatesDir {
fmt.Printf("[reset] WARNING: Cleaning a non-default certificates directory: %q\n", r.certsDir)
glog.V(1).Infoln("[reset] removing contents from the config and pki directories")
if r.certsDir != kubeadmapiv1alpha2.DefaultCertificatesDir {
glog.Warningf("[reset] WARNING: cleaning a non-default certificates directory: %q\n", r.certsDir)
}
resetConfigDir(kubeadmconstants.KubernetesDir, r.certsDir)
@ -173,40 +195,49 @@ func reset(execer utilsexec.Interface, dockerCheck preflight.Checker, criSocketP
func resetWithDocker(execer utilsexec.Interface, dockerCheck preflight.Checker) {
if _, errors := dockerCheck.Check(); len(errors) == 0 {
if err := execer.Command("sh", "-c", "docker ps -a --filter name=k8s_ -q | xargs -r docker rm --force --volumes").Run(); err != nil {
fmt.Println("[reset] Failed to stop the running containers.")
glog.Errorln("[reset] failed to stop the running containers")
}
} else {
fmt.Println("[reset] Docker doesn't seem to be running. Skipping the removal of running Kubernetes containers.")
fmt.Println("[reset] docker doesn't seem to be running. Skipping the removal of running Kubernetes containers")
}
}
func resetWithCrictl(execer utilsexec.Interface, dockerCheck preflight.Checker, criSocketPath, crictlPath string) {
if criSocketPath != "" {
fmt.Printf("[reset] Cleaning up running containers using crictl with socket %s\n", criSocketPath)
listcmd := fmt.Sprintf(crictlSandboxesParamsFormat, crictlPath, criSocketPath)
output, err := execer.Command(listcmd).CombinedOutput()
fmt.Printf("[reset] cleaning up running containers using crictl with socket %s\n", criSocketPath)
glog.V(1).Infoln("[reset] listing running pods using crictl")
params := []string{"-r", criSocketPath, "pods", "--quiet"}
glog.V(1).Infof("[reset] Executing command %s %s", crictlPath, strings.Join(params, " "))
output, err := execer.Command(crictlPath, params...).CombinedOutput()
if err != nil {
fmt.Println("[reset] Failed to list running pods using crictl. Trying using docker instead.")
fmt.Printf("[reset] failed to list running pods using crictl: %v. Trying to use docker instead", err)
resetWithDocker(execer, dockerCheck)
return
}
sandboxes := strings.Split(string(output), " ")
sandboxes := strings.Fields(string(output))
glog.V(1).Infoln("[reset] Stopping and removing running containers using crictl")
for _, s := range sandboxes {
stopcmd := fmt.Sprintf(crictlStopParamsFormat, crictlPath, criSocketPath, s)
if err := execer.Command(stopcmd).Run(); err != nil {
fmt.Println("[reset] Failed to stop the running containers using crictl. Trying using docker instead.")
if strings.TrimSpace(s) == "" {
continue
}
params = []string{"-r", criSocketPath, "stopp", s}
glog.V(1).Infof("[reset] Executing command %s %s", crictlPath, strings.Join(params, " "))
if err := execer.Command(crictlPath, params...).Run(); err != nil {
fmt.Printf("[reset] failed to stop the running containers using crictl: %v. Trying to use docker instead", err)
resetWithDocker(execer, dockerCheck)
return
}
removecmd := fmt.Sprintf(crictlRemoveParamsFormat, crictlPath, criSocketPath, s)
if err := execer.Command(removecmd).Run(); err != nil {
fmt.Println("[reset] Failed to remove the running containers using crictl. Trying using docker instead.")
params = []string{"-r", criSocketPath, "rmp", s}
glog.V(1).Infof("[reset] Executing command %s %s", crictlPath, strings.Join(params, " "))
if err := execer.Command(crictlPath, params...).Run(); err != nil {
fmt.Printf("[reset] failed to remove the running containers using crictl: %v. Trying to use docker instead", err)
resetWithDocker(execer, dockerCheck)
return
}
}
} else {
fmt.Println("[reset] CRI socket path not provided for crictl. Trying docker instead.")
fmt.Println("[reset] CRI socket path not provided for crictl. Trying to use docker instead")
resetWithDocker(execer, dockerCheck)
}
}
@ -242,10 +273,10 @@ func resetConfigDir(configPathDir, pkiPathDir string) {
filepath.Join(configPathDir, kubeadmconstants.ManifestsSubDirName),
pkiPathDir,
}
fmt.Printf("[reset] Deleting contents of config directories: %v\n", dirsToClean)
fmt.Printf("[reset] deleting contents of config directories: %v\n", dirsToClean)
for _, dir := range dirsToClean {
if err := cleanDir(dir); err != nil {
fmt.Printf("[reset] Failed to remove directory: %q [%v]\n", dir, err)
glog.Errorf("[reset] failed to remove directory: %q [%v]\n", dir, err)
}
}
@ -256,10 +287,10 @@ func resetConfigDir(configPathDir, pkiPathDir string) {
filepath.Join(configPathDir, kubeadmconstants.ControllerManagerKubeConfigFileName),
filepath.Join(configPathDir, kubeadmconstants.SchedulerKubeConfigFileName),
}
fmt.Printf("[reset] Deleting files: %v\n", filesToClean)
fmt.Printf("[reset] deleting files: %v\n", filesToClean)
for _, path := range filesToClean {
if err := os.RemoveAll(path); err != nil {
fmt.Printf("[reset] Failed to remove file: %q [%v]\n", path, err)
glog.Errorf("[reset] failed to remove file: %q [%v]\n", path, err)
}
}
}

View File

@ -18,12 +18,15 @@ package cmd
import (
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
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"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
"k8s.io/utils/exec"
@ -52,8 +55,41 @@ func assertDirEmpty(t *testing.T, path string) {
}
}
func TestNewReset(t *testing.T) {
var in io.Reader
certsDir := kubeadmapiv1alpha2.DefaultCertificatesDir
criSocketPath := "/var/run/dockershim.sock"
skipPreFlight := false
forceReset := true
ignorePreflightErrors := []string{"all"}
ignorePreflightErrorsSet, _ := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight)
NewReset(in, ignorePreflightErrorsSet, forceReset, certsDir, criSocketPath)
ignorePreflightErrors = []string{}
ignorePreflightErrorsSet, _ = validation.ValidateIgnorePreflightErrors(ignorePreflightErrors, skipPreFlight)
NewReset(in, ignorePreflightErrorsSet, forceReset, certsDir, criSocketPath)
}
func TestNewCmdReset(t *testing.T) {
var out io.Writer
var in io.Reader
cmd := NewCmdReset(in, out)
tmpDir, err := ioutil.TempDir("", "kubeadm-reset-test")
if err != nil {
t.Errorf("Unable to create temporary directory: %v", err)
}
args := []string{"--ignore-preflight-errors=all", "--cert-dir=" + tmpDir, "--force"}
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("Cannot execute reset command: %v", err)
}
}
func TestConfigDirCleaner(t *testing.T) {
tests := map[string]struct {
resetDir string
setupDirs []string
setupFiles []string
verifyExists []string
@ -91,7 +127,7 @@ func TestConfigDirCleaner(t *testing.T) {
"manifests",
},
},
"preserve cloud-config": {
"preserve unrelated file foo": {
setupDirs: []string{
"manifests",
"pki",
@ -102,12 +138,12 @@ func TestConfigDirCleaner(t *testing.T) {
"pki/ca.pem",
kubeadmconstants.AdminKubeConfigFileName,
kubeadmconstants.KubeletKubeConfigFileName,
"cloud-config",
"foo",
},
verifyExists: []string{
"manifests",
"pki",
"cloud-config",
"foo",
},
},
"preserve hidden files and directories": {
@ -122,13 +158,11 @@ func TestConfigDirCleaner(t *testing.T) {
"pki/ca.pem",
kubeadmconstants.AdminKubeConfigFileName,
kubeadmconstants.KubeletKubeConfigFileName,
".cloud-config",
".mydir/.myfile",
},
verifyExists: []string{
"manifests",
"pki",
".cloud-config",
".mydir",
".mydir/.myfile",
},
@ -139,6 +173,12 @@ func TestConfigDirCleaner(t *testing.T) {
"manifests",
},
},
"not a directory": {
resetDir: "test-path",
setupFiles: []string{
"test-path",
},
},
}
for name, test := range tests {
@ -149,7 +189,6 @@ func TestConfigDirCleaner(t *testing.T) {
if err != nil {
t.Errorf("Unable to create temporary directory: %s", err)
}
defer os.RemoveAll(tmpDir)
for _, createDir := range test.setupDirs {
err := os.Mkdir(filepath.Join(tmpDir, createDir), 0700)
@ -164,10 +203,13 @@ func TestConfigDirCleaner(t *testing.T) {
if err != nil {
t.Errorf("Unable to create test file: %s", err)
}
defer f.Close()
f.Close()
}
resetConfigDir(tmpDir, filepath.Join(tmpDir, "pki"))
if test.resetDir == "" {
test.resetDir = "pki"
}
resetConfigDir(tmpDir, filepath.Join(tmpDir, test.resetDir))
// Verify the files we cleanup implicitly in every test:
assertExists(t, tmpDir)
@ -183,6 +225,8 @@ func TestConfigDirCleaner(t *testing.T) {
for _, path := range test.verifyNotExists {
assertNotExists(t, filepath.Join(tmpDir, path))
}
os.RemoveAll(tmpDir)
}
}

View File

@ -20,35 +20,36 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
"text/tabwriter"
"time"
"github.com/golang/glog"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/duration"
clientset "k8s.io/client-go/kubernetes"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
"k8s.io/client-go/tools/clientcmd"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
)
const defaultKubeConfig = "/etc/kubernetes/admin.conf"
// NewCmdToken returns cobra.Command for token management
func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
var kubeConfigFile string
@ -84,21 +85,23 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
}
tokenCmd.PersistentFlags().StringVar(&kubeConfigFile,
"kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster")
"kubeconfig", defaultKubeConfig, "The KubeConfig file to use when talking to the cluster. If the flag is not set a set of standard locations are searched for an existing KubeConfig file")
tokenCmd.PersistentFlags().BoolVar(&dryRun,
"dry-run", dryRun, "Whether to enable dry-run mode or not")
cfg := &kubeadmapiext.MasterConfiguration{
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
// the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig
KubernetesVersion: "v1.9.0",
KubernetesVersion: "v1.10.0",
}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
kubeadmscheme.Scheme.Default(cfg)
var cfgPath, description string
var cfgPath string
var printJoinCommand bool
bto := options.NewBootstrapTokenOptions()
createCmd := &cobra.Command{
Use: "create [token]",
DisableFlagsInUseLine: true,
@ -112,35 +115,35 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
If no [token] is given, kubeadm will generate a random token instead.
`),
Run: func(tokenCmd *cobra.Command, args []string) {
if len(args) != 0 {
cfg.Token = args[0]
if len(args) > 0 {
bto.TokenStr = args[0]
}
glog.V(1).Infoln("[token] validating mixed arguments")
err := validation.ValidateMixedArguments(tokenCmd.Flags())
kubeadmutil.CheckErr(err)
err = bto.ApplyTo(cfg)
kubeadmutil.CheckErr(err)
glog.V(1).Infoln("[token] getting Clientsets from KubeConfig file")
kubeConfigFile = findExistingKubeConfig(kubeConfigFile)
client, err := getClientset(kubeConfigFile, dryRun)
kubeadmutil.CheckErr(err)
err = RunCreateToken(out, client, cfgPath, cfg, description, printJoinCommand, kubeConfigFile)
err = RunCreateToken(out, client, cfgPath, cfg, printJoinCommand, kubeConfigFile)
kubeadmutil.CheckErr(err)
},
}
createCmd.Flags().StringVar(&cfgPath,
"config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
createCmd.Flags().DurationVar(&cfg.TokenTTL.Duration,
"ttl", cfg.TokenTTL.Duration, "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire.")
createCmd.Flags().StringSliceVar(&cfg.TokenUsages,
"usages", cfg.TokenUsages, fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s].", strings.Join(kubeadmconstants.DefaultTokenUsages, ",")))
createCmd.Flags().StringSliceVar(&cfg.TokenGroups,
"groups", cfg.TokenGroups,
fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q.", bootstrapapi.BootstrapGroupPattern))
createCmd.Flags().StringVar(&description,
"description", "", "A human friendly description of how this token is used.")
createCmd.Flags().BoolVar(&printJoinCommand,
"print-join-command", false, "Instead of printing only the token, print the full 'kubeadm join' flag needed to join the cluster using the token.")
tokenCmd.AddCommand(createCmd)
bto.AddTTLFlagWithName(createCmd.Flags(), "ttl")
bto.AddUsagesFlag(createCmd.Flags())
bto.AddGroupsFlag(createCmd.Flags())
bto.AddDescriptionFlag(createCmd.Flags())
tokenCmd.AddCommand(createCmd)
tokenCmd.AddCommand(NewCmdTokenGenerate(out))
listCmd := &cobra.Command{
@ -150,6 +153,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
This command will list all bootstrap tokens for you.
`),
Run: func(tokenCmd *cobra.Command, args []string) {
kubeConfigFile = findExistingKubeConfig(kubeConfigFile)
client, err := getClientset(kubeConfigFile, dryRun)
kubeadmutil.CheckErr(err)
@ -171,8 +175,9 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
`),
Run: func(tokenCmd *cobra.Command, args []string) {
if len(args) < 1 {
kubeadmutil.CheckErr(fmt.Errorf("missing subcommand; 'token delete' is missing token of form [%q]", tokenutil.TokenIDRegexpString))
kubeadmutil.CheckErr(fmt.Errorf("missing subcommand; 'token delete' is missing token of form %q", bootstrapapi.BootstrapTokenIDPattern))
}
kubeConfigFile = findExistingKubeConfig(kubeConfigFile)
client, err := getClientset(kubeConfigFile, dryRun)
kubeadmutil.CheckErr(err)
@ -209,28 +214,29 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command {
}
// RunCreateToken generates a new bootstrap token and stores it as a secret on the server.
func RunCreateToken(out io.Writer, client clientset.Interface, cfgPath string, cfg *kubeadmapiext.MasterConfiguration, description string, printJoinCommand bool, kubeConfigFile string) error {
func RunCreateToken(out io.Writer, client clientset.Interface, cfgPath string, cfg *kubeadmapiv1alpha2.MasterConfiguration, printJoinCommand bool, kubeConfigFile string) error {
// This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags
glog.V(1).Infoln("[token] loading configurations")
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
if err != nil {
return err
}
err = tokenphase.CreateNewToken(client, internalcfg.Token, internalcfg.TokenTTL.Duration, internalcfg.TokenUsages, internalcfg.TokenGroups, description)
if err != nil {
glog.V(1).Infoln("[token] creating token")
if err := tokenphase.CreateNewTokens(client, internalcfg.BootstrapTokens); err != nil {
return err
}
// if --print-join-command was specified, print the full `kubeadm join` command
// otherwise, just print the token
if printJoinCommand {
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.Token, false)
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.BootstrapTokens[0].Token.String(), false)
if err != nil {
return fmt.Errorf("failed to get join command: %v", err)
}
fmt.Fprintln(out, joinCommand)
} else {
fmt.Fprintln(out, internalcfg.Token)
fmt.Fprintln(out, internalcfg.BootstrapTokens[0].Token.String())
}
return nil
@ -238,7 +244,8 @@ func RunCreateToken(out io.Writer, client clientset.Interface, cfgPath string, c
// RunGenerateToken just generates a random token for the user
func RunGenerateToken(out io.Writer) error {
token, err := tokenutil.GenerateToken()
glog.V(1).Infoln("[token] generating random token")
token, err := bootstraputil.GenerateBootstrapToken()
if err != nil {
return err
}
@ -250,15 +257,20 @@ func RunGenerateToken(out io.Writer) error {
// RunListTokens lists details on all existing bootstrap tokens on the server.
func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) error {
// First, build our selector for bootstrap tokens only
glog.V(1).Infoln("[token] preparing selector for bootstrap token")
tokenSelector := fields.SelectorFromSet(
map[string]string{
api.SecretTypeField: string(bootstrapapi.SecretTypeBootstrapToken),
// TODO: We hard-code "type" here until `field_constants.go` that is
// currently in `pkg/apis/core/` exists in the external API, i.e.
// k8s.io/api/v1. Should be v1.SecretTypeField
"type": string(bootstrapapi.SecretTypeBootstrapToken),
},
)
listOptions := metav1.ListOptions{
FieldSelector: tokenSelector.String(),
}
glog.V(1).Infoln("[token] retrieving list of bootstrap tokens")
secrets, err := client.CoreV1().Secrets(metav1.NamespaceSystem).List(listOptions)
if err != nil {
return fmt.Errorf("failed to list bootstrap tokens [%v]", err)
@ -267,68 +279,17 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) er
w := tabwriter.NewWriter(out, 10, 4, 3, ' ', 0)
fmt.Fprintln(w, "TOKEN\tTTL\tEXPIRES\tUSAGES\tDESCRIPTION\tEXTRA GROUPS")
for _, secret := range secrets.Items {
tokenID := getSecretString(&secret, bootstrapapi.BootstrapTokenIDKey)
if len(tokenID) == 0 {
fmt.Fprintf(errW, "bootstrap token has no token-id data: %s\n", secret.Name)
// Get the BootstrapToken struct representation from the Secret object
token, err := kubeadmapi.BootstrapTokenFromSecret(&secret)
if err != nil {
fmt.Fprintf(errW, "%v", err)
continue
}
// enforce the right naming convention
if secret.Name != fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID) {
fmt.Fprintf(errW, "bootstrap token name is not of the form '%s(token-id)': %s\n", bootstrapapi.BootstrapTokenSecretPrefix, secret.Name)
continue
}
tokenSecret := getSecretString(&secret, bootstrapapi.BootstrapTokenSecretKey)
if len(tokenSecret) == 0 {
fmt.Fprintf(errW, "bootstrap token has no token-secret data: %s\n", secret.Name)
continue
}
td := &kubeadmapi.TokenDiscovery{ID: tokenID, Secret: tokenSecret}
// Expiration time is optional, if not specified this implies the token
// never expires.
ttl := "<forever>"
expires := "<never>"
secretExpiration := getSecretString(&secret, bootstrapapi.BootstrapTokenExpirationKey)
if len(secretExpiration) > 0 {
expireTime, err := time.Parse(time.RFC3339, secretExpiration)
if err != nil {
fmt.Fprintf(errW, "can't parse expiration time of bootstrap token %s\n", secret.Name)
continue
}
ttl = duration.ShortHumanDuration(expireTime.Sub(time.Now()))
expires = expireTime.Format(time.RFC3339)
}
usages := []string{}
for k, v := range secret.Data {
// Skip all fields that don't include this prefix
if !strings.HasPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix) {
continue
}
// Skip those that don't have this usage set to true
if string(v) != "true" {
continue
}
usages = append(usages, strings.TrimPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix))
}
sort.Strings(usages)
usageString := strings.Join(usages, ",")
if len(usageString) == 0 {
usageString = "<none>"
}
description := getSecretString(&secret, bootstrapapi.BootstrapTokenDescriptionKey)
if len(description) == 0 {
description = "<none>"
}
groups := getSecretString(&secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
if len(groups) == 0 {
groups = "<none>"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", tokenutil.BearerToken(td), ttl, expires, usageString, description, groups)
// Get the human-friendly string representation for the token
humanFriendlyTokenOutput := humanReadableBootstrapToken(token)
fmt.Fprintln(w, humanFriendlyTokenOutput)
}
w.Flush()
return nil
@ -338,13 +299,18 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) er
func RunDeleteToken(out io.Writer, client clientset.Interface, tokenIDOrToken string) error {
// Assume the given first argument is a token id and try to parse it
tokenID := tokenIDOrToken
if err := tokenutil.ParseTokenID(tokenIDOrToken); err != nil {
if tokenID, _, err = tokenutil.ParseToken(tokenIDOrToken); err != nil {
return fmt.Errorf("given token or token id %q didn't match pattern [%q] or [%q]", tokenIDOrToken, tokenutil.TokenIDRegexpString, tokenutil.TokenRegexpString)
glog.V(1).Infoln("[token] parsing token ID")
if !bootstraputil.IsValidBootstrapTokenID(tokenIDOrToken) {
// Okay, the full token with both id and secret was probably passed. Parse it and extract the ID only
bts, err := kubeadmapiv1alpha2.NewBootstrapTokenString(tokenIDOrToken)
if err != nil {
return fmt.Errorf("given token or token id %q didn't match pattern %q or %q", tokenIDOrToken, bootstrapapi.BootstrapTokenIDPattern, bootstrapapi.BootstrapTokenIDPattern)
}
tokenID = bts.ID
}
tokenSecretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID)
tokenSecretName := bootstraputil.BootstrapTokenSecretName(tokenID)
glog.V(1).Infoln("[token] deleting token")
if err := client.CoreV1().Secrets(metav1.NamespaceSystem).Delete(tokenSecretName, nil); err != nil {
return fmt.Errorf("failed to delete bootstrap token [%v]", err)
}
@ -352,14 +318,30 @@ func RunDeleteToken(out io.Writer, client clientset.Interface, tokenIDOrToken st
return nil
}
func getSecretString(secret *v1.Secret, key string) string {
if secret.Data == nil {
return ""
func humanReadableBootstrapToken(token *kubeadmapi.BootstrapToken) string {
description := token.Description
if len(description) == 0 {
description = "<none>"
}
if val, ok := secret.Data[key]; ok {
return string(val)
ttl := "<forever>"
expires := "<never>"
if token.Expires != nil {
ttl = duration.ShortHumanDuration(token.Expires.Sub(time.Now()))
expires = token.Expires.Format(time.RFC3339)
}
return ""
usagesString := strings.Join(token.Usages, ",")
if len(usagesString) == 0 {
usagesString = "<none>"
}
groupsString := strings.Join(token.Groups, ",")
if len(groupsString) == 0 {
groupsString = "<none>"
}
return fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", token.Token.String(), ttl, expires, usagesString, description, groupsString)
}
func getClientset(file string, dryRun bool) (clientset.Interface, error) {
@ -372,3 +354,16 @@ func getClientset(file string, dryRun bool) (clientset.Interface, error) {
}
return kubeconfigutil.ClientSetFromFile(file)
}
func findExistingKubeConfig(file string) string {
// The user did provide a --kubeconfig flag. Respect that and threat it as an
// explicit path without building a DefaultClientConfigLoadingRules object.
if file != defaultKubeConfig {
return file
}
// The user did not provide a --kubeconfig flag. Find a config in the standard
// locations using DefaultClientConfigLoadingRules, but also consider `defaultKubeConfig`.
rules := clientcmd.NewDefaultClientConfigLoadingRules()
rules.Precedence = append(rules.Precedence, defaultKubeConfig)
return rules.GetDefaultFilename()
}

View File

@ -18,8 +18,17 @@ package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"sync/atomic"
"testing"
"time"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
@ -27,11 +36,48 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
"k8s.io/client-go/tools/clientcmd"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
)
const (
TokenExpectedRegex = "^\\S{6}\\.\\S{16}\n$"
tokenExpectedRegex = "^\\S{6}\\.\\S{16}\n$"
testConfigToken = `apiVersion: v1
clusters:
- cluster:
certificate-authority-data:
server: localhost:8000
name: prod
contexts:
- context:
cluster: prod
namespace: default
user: default-service-account
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data:
client-key-data:
`
testConfigTokenCertAuthorityData = "certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFM01USXhOREUxTlRFek1Gb1hEVEkzTVRJeE1qRTFOVEV6TUZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTlZrCnNkT0NjRDBIOG9ycXZ5djBEZ09jZEpjRGc4aTJPNGt3QVpPOWZUanJGRHJqbDZlVXRtdlMyZ1lZd0c4TGhPV2gKb0lkZ3AvbVkrbVlDakliUUJtTmE2Ums1V2JremhJRzM1c1lseE9NVUJJR0xXMzN0RTh4SlR1RVd3V0NmZnpLcQpyaU1UT1A3REF3MUxuM2xUNlpJNGRNM09NOE1IUk9Wd3lRMDVpbWo5eUx5R1lYdTlvSncwdTVXWVpFYmpUL3VpCjJBZ2QwVDMrZGFFb044aVBJOTlVQkQxMzRkc2VGSEJEY3hHcmsvVGlQdHBpSC9IOGoxRWZaYzRzTGlONzJmL2YKYUpacTROSHFiT2F5UkpITCtJejFNTW1DRkN3cjdHOHVENWVvWWp2dEdZN2xLc1pBTlUwK3VlUnJsTitxTzhQWQpxaTZNMDFBcmV1UzFVVHFuTkM4Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFNbXo4Nm9LMmFLa0owMnlLSC9ZcTlzaDZZcDEKYmhLS25mMFJCaTA1clRacWdhTi9oTnROdmQxSzJxZGRLNzhIT2pVdkpNRGp3NERieXF0Wll2V01XVFRCQnQrSgpPMGNyWkg5NXlqUW42YzRlcU1FTjFhOUFKNXRlclNnTDVhREJsK0FMTWxaNVpxTzBUOUJDdTJtNXV3dGNWaFZuCnh6cGpTT3V5WVdOQ3A5bW9mV2VPUTljNXhEcElWeUlMUkFvNmZ5Z2c3N25TSDN4ckVmd0VKUHFMd1RPYVk1bTcKeEZWWWJoR3dxUGU5V0I5aTR5cnNrZUFBWlpUSzdVbklKMXFkRmlHQk9aZlRtaDhYQ3BOTHZZcFBLQW9hWWlsRwpjOW1acVhpWVlESTV6R1IxMElpc2FWNXJUY2hDenNQVWRhQzRVbnpTZG01cTdKYTAyb0poQlU1TE1FMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
testConfigTokenNoCluster = `apiVersion: v1
clusters:
- cluster:
server:
name: prod
contexts:
- context:
namespace: default
user: default-service-account
name: default
kind: Config
preferences: {}
`
)
func TestRunGenerateToken(t *testing.T) {
@ -44,12 +90,12 @@ func TestRunGenerateToken(t *testing.T) {
output := buf.String()
matched, err := regexp.MatchString(TokenExpectedRegex, output)
matched, err := regexp.MatchString(tokenExpectedRegex, output)
if err != nil {
t.Fatalf("encountered an error while trying to match RunGenerateToken's output: %v", err)
t.Fatalf("Encountered an error while trying to match RunGenerateToken's output: %v", err)
}
if !matched {
t.Errorf("RunGenerateToken's output did not match expected regex; wanted: [%s], got: [%s]", TokenExpectedRegex, output)
t.Errorf("RunGenerateToken's output did not match expected regex; wanted: [%s], got: [%s]", tokenExpectedRegex, output)
}
}
@ -65,6 +111,7 @@ func TestRunCreateToken(t *testing.T) {
token string
usages []string
extraGroups []string
printJoin bool
expectedError bool
}{
{
@ -88,13 +135,6 @@ func TestRunCreateToken(t *testing.T) {
extraGroups: []string{},
expectedError: false,
},
{
name: "invalid: incorrect token",
token: "123456.AABBCCDDEEFFGGHH",
usages: []string{"signing", "authentication"},
extraGroups: []string{},
expectedError: true,
},
{
name: "invalid: incorrect extraGroups",
token: "abcdef.1234567890123456",
@ -123,22 +163,376 @@ func TestRunCreateToken(t *testing.T) {
extraGroups: []string{"system:bootstrappers:foo"},
expectedError: true,
},
{
name: "invalid: print join command",
token: "",
usages: []string{"signing", "authentication"},
extraGroups: []string{"system:bootstrappers:foo"},
printJoin: true,
expectedError: true,
},
}
for _, tc := range testCases {
cfg := &kubeadmapiext.MasterConfiguration{
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
// the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig
KubernetesVersion: "v1.9.0",
Token: tc.token,
TokenTTL: &metav1.Duration{Duration: 0},
TokenUsages: tc.usages,
TokenGroups: tc.extraGroups,
bts, err := kubeadmapiv1alpha2.NewBootstrapTokenString(tc.token)
if err != nil && len(tc.token) != 0 { // if tc.token is "" it's okay as it will be generated later at runtime
t.Fatalf("token couldn't be parsed for testing: %v", err)
}
err := RunCreateToken(&buf, fakeClient, "", cfg, "", false, "")
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
// the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig
KubernetesVersion: "v1.10.0",
BootstrapTokens: []kubeadmapiv1alpha2.BootstrapToken{
{
Token: bts,
TTL: &metav1.Duration{Duration: 0},
Usages: tc.usages,
Groups: tc.extraGroups,
},
},
}
err = RunCreateToken(&buf, fakeClient, "", cfg, tc.printJoin, "")
if (err != nil) != tc.expectedError {
t.Errorf("Test case %s: RunCreateToken expected error: %v, saw: %v", tc.name, tc.expectedError, (err != nil))
}
}
}
func TestNewCmdTokenGenerate(t *testing.T) {
var buf bytes.Buffer
args := []string{}
cmd := NewCmdTokenGenerate(&buf)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("Cannot execute token command: %v", err)
}
}
func TestNewCmdToken(t *testing.T) {
var buf, bufErr bytes.Buffer
testConfigTokenFile := "test-config-file"
tmpDir, err := ioutil.TempDir("", "kubeadm-token-test")
if err != nil {
t.Errorf("Unable to create temporary directory: %v", err)
}
defer os.RemoveAll(tmpDir)
fullPath := filepath.Join(tmpDir, testConfigTokenFile)
f, err := os.Create(fullPath)
if err != nil {
t.Errorf("Unable to create test file %q: %v", fullPath, err)
}
defer f.Close()
testCases := []struct {
name string
args []string
configToWrite string
kubeConfigEnv string
expectedError bool
}{
{
name: "valid: generate",
args: []string{"generate"},
configToWrite: "",
expectedError: false,
},
{
name: "valid: delete from --kubeconfig",
args: []string{"delete", "abcdef.1234567890123456", "--dry-run", "--kubeconfig=" + fullPath},
configToWrite: testConfigToken,
expectedError: false,
},
{
name: "valid: delete from " + clientcmd.RecommendedConfigPathEnvVar,
args: []string{"delete", "abcdef.1234567890123456", "--dry-run"},
configToWrite: testConfigToken,
kubeConfigEnv: fullPath,
expectedError: false,
},
}
for _, tc := range testCases {
// the command is created for each test so that the kubeConfigFile
// variable in NewCmdToken() is reset.
cmd := NewCmdToken(&buf, &bufErr)
if _, err = f.WriteString(tc.configToWrite); err != nil {
t.Errorf("Unable to write test file %q: %v", fullPath, err)
}
// store the current value of the environment variable.
storedEnv := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
if tc.kubeConfigEnv != "" {
os.Setenv(clientcmd.RecommendedConfigPathEnvVar, tc.kubeConfigEnv)
}
cmd.SetArgs(tc.args)
err := cmd.Execute()
if (err != nil) != tc.expectedError {
t.Errorf("Test case %q: NewCmdToken expected error: %v, saw: %v", tc.name, tc.expectedError, (err != nil))
}
// restore the environment variable.
os.Setenv(clientcmd.RecommendedConfigPathEnvVar, storedEnv)
}
}
func TestGetClientset(t *testing.T) {
testConfigTokenFile := "test-config-file"
tmpDir, err := ioutil.TempDir("", "kubeadm-token-test")
if err != nil {
t.Errorf("Unable to create temporary directory: %v", err)
}
defer os.RemoveAll(tmpDir)
fullPath := filepath.Join(tmpDir, testConfigTokenFile)
// test dryRun = false on a non-exisiting file
if _, err = getClientset(fullPath, false); err == nil {
t.Errorf("getClientset(); dry-run: false; did no fail for test file %q: %v", fullPath, err)
}
// test dryRun = true on a non-exisiting file
if _, err = getClientset(fullPath, true); err == nil {
t.Errorf("getClientset(); dry-run: true; did no fail for test file %q: %v", fullPath, err)
}
f, err := os.Create(fullPath)
if err != nil {
t.Errorf("Unable to create test file %q: %v", fullPath, err)
}
defer f.Close()
if _, err = f.WriteString(testConfigToken); err != nil {
t.Errorf("Unable to write test file %q: %v", fullPath, err)
}
// test dryRun = true on an exisiting file
if _, err = getClientset(fullPath, true); err != nil {
t.Errorf("getClientset(); dry-run: true; failed for test file %q: %v", fullPath, err)
}
}
func TestRunDeleteToken(t *testing.T) {
var buf bytes.Buffer
tmpDir, err := ioutil.TempDir("", "kubeadm-token-test")
if err != nil {
t.Errorf("Unable to create temporary directory: %v", err)
}
defer os.RemoveAll(tmpDir)
fullPath := filepath.Join(tmpDir, "test-config-file")
f, err := os.Create(fullPath)
if err != nil {
t.Errorf("Unable to create test file %q: %v", fullPath, err)
}
defer f.Close()
if _, err = f.WriteString(testConfigToken); err != nil {
t.Errorf("Unable to write test file %q: %v", fullPath, err)
}
client, err := getClientset(fullPath, true)
if err != nil {
t.Errorf("Unable to run getClientset() for test file %q: %v", fullPath, err)
}
// test valid; should not fail
// for some reason Secrets().Delete() does not fail even for this dummy config
if err = RunDeleteToken(&buf, client, "abcdef.1234567890123456"); err != nil {
t.Errorf("RunDeleteToken() failed for a valid token: %v", err)
}
// test invalid token; should fail
if err = RunDeleteToken(&buf, client, "invalid-token"); err == nil {
t.Errorf("RunDeleteToken() succeeded for an invalid token: %v", err)
}
}
var httpTestItr uint32
var httpSentResponse uint32 = 1
func TestRunListTokens(t *testing.T) {
var err error
var bufOut, bufErr bytes.Buffer
tmpDir, err := ioutil.TempDir("", "kubeadm-token-test")
if err != nil {
t.Errorf("Unable to create temporary directory: %v", err)
}
defer os.RemoveAll(tmpDir)
fullPath := filepath.Join(tmpDir, "test-config-file")
f, err := os.Create(fullPath)
if err != nil {
t.Errorf("Unable to create test file %q: %v", fullPath, err)
}
defer f.Close()
// test config without secrets; should fail
if _, err = f.WriteString(testConfigToken); err != nil {
t.Errorf("Unable to write test file %q: %v", fullPath, err)
}
client, err := getClientset(fullPath, true)
if err != nil {
t.Errorf("Unable to run getClientset() for test file %q: %v", fullPath, err)
}
if err = RunListTokens(&bufOut, &bufErr, client); err == nil {
t.Errorf("RunListTokens() did not fail for a config without secrets: %v", err)
}
// test config without secrets but use a dummy API server that returns secrets
portString := "9008"
http.HandleFunc("/", httpHandler)
httpServer := &http.Server{Addr: "localhost:" + portString}
go func() {
err := httpServer.ListenAndServe()
if err != nil {
t.Errorf("Failed to start dummy API server: localhost:%s", portString)
}
}()
fmt.Printf("dummy API server listening on localhost:%s\n", portString)
testConfigTokenOpenPort := strings.Replace(testConfigToken, "server: localhost:8000", "server: localhost:"+portString, -1)
if _, err = f.WriteString(testConfigTokenOpenPort); err != nil {
t.Errorf("Unable to write test file %q: %v", fullPath, err)
}
client, err = getClientset(fullPath, true)
if err != nil {
t.Errorf("Unable to run getClientset() for test file %q: %v", fullPath, err)
}
// the order of these tests should match the case check
// for httpTestItr in httpHandler
testCases := []struct {
name string
expectedError bool
}{
{
name: "token-id not defined",
expectedError: true,
},
{
name: "secret name not formatted correctly",
expectedError: true,
},
{
name: "token-secret not defined",
expectedError: true,
},
{
name: "token expiration not formatted correctly",
expectedError: true,
},
{
name: "token expiration formatted correctly",
expectedError: false,
},
{
name: "token usage constant not true",
expectedError: false,
},
{
name: "token usage constant set to true",
expectedError: false,
},
}
for _, tc := range testCases {
bufErr.Reset()
atomic.StoreUint32(&httpSentResponse, 0)
fmt.Printf("Running HTTP test case (%d) %q\n", atomic.LoadUint32(&httpTestItr), tc.name)
// should always return nil here if a valid list of secrets if fetched
err := RunListTokens(&bufOut, &bufErr, client)
if err != nil {
t.Errorf("HTTP test case %d: Was unable to fetch a list of secrets", atomic.LoadUint32(&httpTestItr))
}
// wait for a response from the dummy HTTP server
timeSpent := 0 * time.Millisecond
timeToSleep := 50 * time.Millisecond
timeMax := 2000 * time.Millisecond
for {
if atomic.LoadUint32(&httpSentResponse) == 1 {
break
}
if timeSpent >= timeMax {
t.Errorf("HTTP test case %d: The server did not respond within %d ms", atomic.LoadUint32(&httpTestItr), timeMax)
}
timeSpent += timeToSleep
time.Sleep(timeToSleep)
}
// check if an error is written in the error buffer
hasError := bufErr.Len() != 0
if hasError != tc.expectedError {
t.Errorf("HTTP test case %d: RunListTokens expected error: %v, saw: %v; %v", atomic.LoadUint32(&httpTestItr), tc.expectedError, hasError, bufErr.String())
}
}
}
// only one of these should run at a time in a goroutine
func httpHandler(w http.ResponseWriter, r *http.Request) {
tokenID := []byte("07401b")
tokenSecret := []byte("f395accd246ae52d")
tokenExpire := []byte("2012-11-01T22:08:41+00:00")
badValue := "bad-value"
name := bootstrapapi.BootstrapTokenSecretPrefix + string(tokenID)
tokenUsageKey := bootstrapapi.BootstrapTokenUsagePrefix + "test"
secret := v1.Secret{}
secret.Type = bootstrapapi.SecretTypeBootstrapToken
secret.TypeMeta = metav1.TypeMeta{APIVersion: "v1", Kind: "Secret"}
secret.Data = map[string][]byte{}
switch atomic.LoadUint32(&httpTestItr) {
case 0:
secret.Data[bootstrapapi.BootstrapTokenIDKey] = []byte("")
case 1:
secret.Data[bootstrapapi.BootstrapTokenIDKey] = tokenID
secret.ObjectMeta = metav1.ObjectMeta{Name: badValue}
case 2:
secret.Data[bootstrapapi.BootstrapTokenIDKey] = tokenID
secret.Data[bootstrapapi.BootstrapTokenSecretKey] = []byte("")
secret.ObjectMeta = metav1.ObjectMeta{Name: name}
case 3:
secret.Data[bootstrapapi.BootstrapTokenIDKey] = tokenID
secret.Data[bootstrapapi.BootstrapTokenSecretKey] = tokenSecret
secret.Data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(badValue)
secret.ObjectMeta = metav1.ObjectMeta{Name: name}
case 4:
secret.Data[bootstrapapi.BootstrapTokenIDKey] = tokenID
secret.Data[bootstrapapi.BootstrapTokenSecretKey] = tokenSecret
secret.Data[bootstrapapi.BootstrapTokenExpirationKey] = tokenExpire
secret.ObjectMeta = metav1.ObjectMeta{Name: name}
case 5:
secret.Data[bootstrapapi.BootstrapTokenIDKey] = tokenID
secret.Data[bootstrapapi.BootstrapTokenSecretKey] = tokenSecret
secret.Data[bootstrapapi.BootstrapTokenExpirationKey] = tokenExpire
secret.Data[tokenUsageKey] = []byte("false")
secret.ObjectMeta = metav1.ObjectMeta{Name: name}
case 6:
secret.Data[bootstrapapi.BootstrapTokenIDKey] = tokenID
secret.Data[bootstrapapi.BootstrapTokenSecretKey] = tokenSecret
secret.Data[bootstrapapi.BootstrapTokenExpirationKey] = tokenExpire
secret.Data[tokenUsageKey] = []byte("true")
secret.ObjectMeta = metav1.ObjectMeta{Name: name}
}
secretList := v1.SecretList{}
secretList.Items = []v1.Secret{secret}
secretList.TypeMeta = metav1.TypeMeta{APIVersion: "v1", Kind: "SecretList"}
output, err := json.Marshal(secretList)
if err == nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(output))
}
atomic.AddUint32(&httpTestItr, 1)
atomic.StoreUint32(&httpSentResponse, 1)
}

View File

@ -5,6 +5,8 @@ go_library(
srcs = [
"apply.go",
"common.go",
"diff.go",
"node.go",
"plan.go",
"upgrade.go",
],
@ -12,23 +14,31 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/cmd/util:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
"//cmd/kubeadm/app/phases/kubelet:go_default_library",
"//cmd/kubeadm/app/phases/upgrade:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/apiclient:go_default_library",
"//cmd/kubeadm/app/util/config:go_default_library",
"//cmd/kubeadm/app/util/dryrun:go_default_library",
"//cmd/kubeadm/app/util/etcd:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/util/normalizer:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/pmezard/go-difflib/difflib:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag: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/util/sets:go_default_library",
"//vendor/k8s.io/client-go/discovery/fake:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
@ -40,11 +50,14 @@ go_test(
srcs = [
"apply_test.go",
"common_test.go",
"diff_test.go",
"plan_test.go",
],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/phases/upgrade:go_default_library",
],
)

View File

@ -21,10 +21,12 @@ import (
"os"
"time"
"github.com/golang/glog"
"github.com/spf13/cobra"
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -35,24 +37,28 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
"k8s.io/kubernetes/pkg/api/legacyscheme"
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
"k8s.io/kubernetes/pkg/util/version"
)
const (
upgradeManifestTimeout = 1 * time.Minute
upgradeManifestTimeout = 5 * time.Minute
defaultImagePullTimeout = 15 * time.Minute
)
// applyFlags holds the information about the flags that can be passed to apply
type applyFlags struct {
*applyPlanFlags
nonInteractiveMode bool
force bool
dryRun bool
etcdUpgrade bool
criSocket string
newK8sVersionStr string
newK8sVersion *version.Version
imagePullTimeout time.Duration
parent *cmdUpgradeFlags
}
// SessionIsInteractive returns true if the session is of an interactive type (the default, can be opted out of with -y, -f or --dry-run)
@ -61,11 +67,12 @@ func (f *applyFlags) SessionIsInteractive() bool {
}
// NewCmdApply returns the cobra command for `kubeadm upgrade apply`
func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command {
func NewCmdApply(apf *applyPlanFlags) *cobra.Command {
flags := &applyFlags{
parent: parentFlags,
imagePullTimeout: 15 * time.Minute,
applyPlanFlags: apf,
imagePullTimeout: defaultImagePullTimeout,
etcdUpgrade: true,
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
}
cmd := &cobra.Command{
@ -74,16 +81,18 @@ func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command {
Short: "Upgrade your Kubernetes cluster to the specified version.",
Run: func(cmd *cobra.Command, args []string) {
var err error
flags.parent.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(flags.parent.ignorePreflightErrors, flags.parent.skipPreFlight)
flags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(flags.ignorePreflightErrors, flags.skipPreFlight)
kubeadmutil.CheckErr(err)
// Ensure the user is root
err = runPreflightChecks(flags.parent.ignorePreflightErrorsSet)
glog.V(1).Infof("running preflight checks")
err = runPreflightChecks(flags.ignorePreflightErrorsSet)
kubeadmutil.CheckErr(err)
// If the version is specified in config file, pick up that value.
if flags.parent.cfgPath != "" {
cfg, err := upgrade.FetchConfigurationFromFile(flags.parent.cfgPath)
if flags.cfgPath != "" {
glog.V(1).Infof("fetching configuration from file", flags.cfgPath)
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(flags.cfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
kubeadmutil.CheckErr(err)
if cfg.KubernetesVersion != "" {
@ -111,13 +120,16 @@ func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command {
},
}
// Register the common flags for apply and plan
addApplyPlanFlags(cmd.Flags(), flags.applyPlanFlags)
// Specify the valid flags specific for apply
cmd.Flags().BoolVarP(&flags.nonInteractiveMode, "yes", "y", flags.nonInteractiveMode, "Perform the upgrade and do not prompt for confirmation (non-interactive mode).")
cmd.Flags().BoolVarP(&flags.force, "force", "f", flags.force, "Force upgrading although some requirements might not be met. This also implies non-interactive mode.")
cmd.Flags().BoolVar(&flags.dryRun, "dry-run", flags.dryRun, "Do not change any state, just output what actions would be performed.")
cmd.Flags().BoolVar(&flags.etcdUpgrade, "etcd-upgrade", flags.etcdUpgrade, "Perform the upgrade of etcd.")
cmd.Flags().DurationVar(&flags.imagePullTimeout, "image-pull-timeout", flags.imagePullTimeout, "The maximum amount of time to wait for the control plane pods to be downloaded.")
// TODO: Register this flag in a generic place
cmd.Flags().StringVar(&flags.criSocket, "cri-socket", flags.criSocket, "Specify the CRI socket to connect to.")
return cmd
}
@ -136,33 +148,38 @@ func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command {
func RunApply(flags *applyFlags) error {
// Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap)
upgradeVars, err := enforceRequirements(flags.parent, flags.dryRun, flags.newK8sVersionStr)
glog.V(1).Infof("[upgrade/apply] verifying health of cluster")
glog.V(1).Infof("[upgrade/apply] retrieving configuration from cluster")
upgradeVars, err := enforceRequirements(flags.applyPlanFlags, flags.dryRun, flags.newK8sVersionStr)
if err != nil {
return err
}
// Grab the external, versioned configuration and convert it to the internal type for usage here later
internalcfg := &kubeadmapi.MasterConfiguration{}
legacyscheme.Scheme.Convert(upgradeVars.cfg, internalcfg, nil)
if len(flags.criSocket) != 0 {
fmt.Println("[upgrade/apply] Respecting the --cri-socket flag that is set with higher priority than the config file.")
upgradeVars.cfg.NodeRegistration.CRISocket = flags.criSocket
}
// Validate requested and validate actual version
if err := configutil.NormalizeKubernetesVersion(internalcfg); err != nil {
glog.V(1).Infof("[upgrade/apply] validating requested and actual version")
if err := configutil.NormalizeKubernetesVersion(upgradeVars.cfg); err != nil {
return err
}
// Use normalized version string in all following code.
flags.newK8sVersionStr = internalcfg.KubernetesVersion
flags.newK8sVersionStr = upgradeVars.cfg.KubernetesVersion
k8sVer, err := version.ParseSemantic(flags.newK8sVersionStr)
if err != nil {
return fmt.Errorf("unable to parse normalized version %q as a semantic version", flags.newK8sVersionStr)
}
flags.newK8sVersion = k8sVer
if err := features.ValidateVersion(features.InitFeatureGates, internalcfg.FeatureGates, internalcfg.KubernetesVersion); err != nil {
if err := features.ValidateVersion(features.InitFeatureGates, upgradeVars.cfg.FeatureGates, upgradeVars.cfg.KubernetesVersion); err != nil {
return err
}
// Enforce the version skew policies
glog.V(1).Infof("[upgrade/version] enforcing version skew policies")
if err := EnforceVersionPolicies(flags, upgradeVars.versionGetter); err != nil {
return fmt.Errorf("[upgrade/version] FATAL: %v", err)
}
@ -176,16 +193,19 @@ func RunApply(flags *applyFlags) error {
// Use a prepuller implementation based on creating DaemonSets
// and block until all DaemonSets are ready; then we know for sure that all control plane images are cached locally
prepuller := upgrade.NewDaemonSetPrepuller(upgradeVars.client, upgradeVars.waiter, internalcfg)
glog.V(1).Infof("[upgrade/apply] creating prepuller")
prepuller := upgrade.NewDaemonSetPrepuller(upgradeVars.client, upgradeVars.waiter, upgradeVars.cfg)
upgrade.PrepullImagesInParallel(prepuller, flags.imagePullTimeout)
// Now; perform the upgrade procedure
if err := PerformControlPlaneUpgrade(flags, upgradeVars.client, upgradeVars.waiter, internalcfg); err != nil {
glog.V(1).Infof("[upgrade/apply] performing upgrade")
if err := PerformControlPlaneUpgrade(flags, upgradeVars.client, upgradeVars.waiter, upgradeVars.cfg); err != nil {
return fmt.Errorf("[upgrade/apply] FATAL: %v", err)
}
// Upgrade RBAC rules and addons.
if err := upgrade.PerformPostUpgradeTasks(upgradeVars.client, internalcfg, flags.newK8sVersion, flags.dryRun); err != nil {
glog.V(1).Infof("[upgrade/postupgrade] upgrading RBAC rules and addons")
if err := upgrade.PerformPostUpgradeTasks(upgradeVars.client, upgradeVars.cfg, flags.newK8sVersion, flags.dryRun); err != nil {
return fmt.Errorf("[upgrade/postupgrade] FATAL post-upgrade error: %v", err)
}
@ -197,7 +217,7 @@ func RunApply(flags *applyFlags) error {
fmt.Println("")
fmt.Printf("[upgrade/successful] SUCCESS! Your cluster was upgraded to %q. Enjoy!\n", flags.newK8sVersionStr)
fmt.Println("")
fmt.Println("[upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets in turn.")
fmt.Println("[upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so.")
return nil
}
@ -221,7 +241,7 @@ func SetImplicitFlags(flags *applyFlags) error {
func EnforceVersionPolicies(flags *applyFlags, versionGetter upgrade.VersionGetter) error {
fmt.Printf("[upgrade/version] You have chosen to change the cluster version to %q\n", flags.newK8sVersionStr)
versionSkewErrs := upgrade.EnforceVersionPolicies(versionGetter, flags.newK8sVersionStr, flags.newK8sVersion, flags.parent.allowExperimentalUpgrades, flags.parent.allowRCUpgrades)
versionSkewErrs := upgrade.EnforceVersionPolicies(versionGetter, flags.newK8sVersionStr, flags.newK8sVersion, flags.allowExperimentalUpgrades, flags.allowRCUpgrades)
if versionSkewErrs != nil {
if len(versionSkewErrs.Mandatory) > 0 {
@ -244,10 +264,12 @@ func EnforceVersionPolicies(flags *applyFlags, versionGetter upgrade.VersionGett
func PerformControlPlaneUpgrade(flags *applyFlags, client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.MasterConfiguration) error {
// Check if the cluster is self-hosted and act accordingly
glog.V(1).Infoln("checking if cluster is self-hosted")
if upgrade.IsControlPlaneSelfHosted(client) {
fmt.Printf("[upgrade/apply] Upgrading your Self-Hosted control plane to version %q...\n", flags.newK8sVersionStr)
// Upgrade the self-hosted cluster
glog.V(1).Infoln("[upgrade/apply] ugrading self-hosted cluster")
return upgrade.SelfHostedControlPlane(client, waiter, internalcfg, flags.newK8sVersion)
}
@ -258,17 +280,25 @@ func PerformControlPlaneUpgrade(flags *applyFlags, client clientset.Interface, w
return DryRunStaticPodUpgrade(internalcfg)
}
// Don't save etcd backup directory if etcd is HA, as this could cause corruption
return PerformStaticPodUpgrade(client, waiter, internalcfg, flags.etcdUpgrade)
}
// GetPathManagerForUpgrade returns a path manager properly configured for the given MasterConfiguration.
func GetPathManagerForUpgrade(internalcfg *kubeadmapi.MasterConfiguration, etcdUpgrade bool) (upgrade.StaticPodPathManager, error) {
isHAEtcd := etcdutil.CheckConfigurationIsHA(&internalcfg.Etcd)
return upgrade.NewKubeStaticPodPathManagerUsingTempDirs(constants.GetStaticPodDirectory(), true, etcdUpgrade && !isHAEtcd)
}
// PerformStaticPodUpgrade performs the upgrade of the control plane components for a static pod hosted cluster
func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.MasterConfiguration, etcdUpgrade bool) error {
pathManager, err := upgrade.NewKubeStaticPodPathManagerUsingTempDirs(constants.GetStaticPodDirectory())
pathManager, err := GetPathManagerForUpgrade(internalcfg, etcdUpgrade)
if err != nil {
return err
}
return upgrade.StaticPodControlPlane(waiter, pathManager, internalcfg, etcdUpgrade)
// The arguments oldEtcdClient and newEtdClient, are uninitialized because passing in the clients allow for mocking the client during testing
return upgrade.StaticPodControlPlane(waiter, pathManager, internalcfg, etcdUpgrade, nil, nil)
}
// DryRunStaticPodUpgrade fakes an upgrade of the control plane

View File

@ -17,8 +17,13 @@ limitations under the License.
package upgrade
import (
"io/ioutil"
"os"
"reflect"
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
func TestSetImplicitFlags(t *testing.T) {
@ -145,3 +150,90 @@ func TestSetImplicitFlags(t *testing.T) {
}
}
}
func TestGetPathManagerForUpgrade(t *testing.T) {
haEtcd := &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
External: &kubeadmapi.ExternalEtcd{
Endpoints: []string{"10.100.0.1:2379", "10.100.0.2:2379", "10.100.0.3:2379"},
},
},
}
noHAEtcd := &kubeadmapi.MasterConfiguration{}
tests := []struct {
name string
cfg *kubeadmapi.MasterConfiguration
etcdUpgrade bool
shouldDeleteEtcd bool
}{
{
name: "ha etcd but no etcd upgrade",
cfg: haEtcd,
etcdUpgrade: false,
shouldDeleteEtcd: true,
},
{
name: "non-ha etcd with etcd upgrade",
cfg: noHAEtcd,
etcdUpgrade: true,
shouldDeleteEtcd: false,
},
{
name: "ha etcd and etcd upgrade",
cfg: haEtcd,
etcdUpgrade: true,
shouldDeleteEtcd: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Use a temporary directory
tmpdir, err := ioutil.TempDir("", "TestGetPathManagerForUpgrade")
if err != nil {
t.Fatalf("unexpected error making temporary directory: %v", err)
}
oldK8sDir := constants.KubernetesDir
constants.KubernetesDir = tmpdir
defer func() {
constants.KubernetesDir = oldK8sDir
os.RemoveAll(tmpdir)
}()
pathmgr, err := GetPathManagerForUpgrade(test.cfg, test.etcdUpgrade)
if err != nil {
t.Fatalf("unexpected error creating path manager: %v", err)
}
if _, err := os.Stat(pathmgr.BackupManifestDir()); os.IsNotExist(err) {
t.Errorf("expected manifest dir %s to exist, but it did not (%v)", pathmgr.BackupManifestDir(), err)
}
if _, err := os.Stat(pathmgr.BackupEtcdDir()); os.IsNotExist(err) {
t.Errorf("expected etcd dir %s to exist, but it did not (%v)", pathmgr.BackupEtcdDir(), err)
}
if err := pathmgr.CleanupDirs(); err != nil {
t.Fatalf("unexpected error cleaning up directories: %v", err)
}
if _, err := os.Stat(pathmgr.BackupManifestDir()); os.IsNotExist(err) {
t.Errorf("expected manifest dir %s to exist, but it did not (%v)", pathmgr.BackupManifestDir(), err)
}
if test.shouldDeleteEtcd {
if _, err := os.Stat(pathmgr.BackupEtcdDir()); !os.IsNotExist(err) {
t.Errorf("expected etcd dir %s not to exist, but it did (%v)", pathmgr.BackupEtcdDir(), err)
}
} else {
if _, err := os.Stat(pathmgr.BackupEtcdDir()); os.IsNotExist(err) {
t.Errorf("expected etcd dir %s to exist, but it did not", pathmgr.BackupEtcdDir())
}
}
})
}
}

View File

@ -24,16 +24,21 @@ import (
"os"
"strings"
"github.com/ghodss/yaml"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
fakediscovery "k8s.io/client-go/discovery/fake"
clientset "k8s.io/client-go/kubernetes"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
)
@ -42,13 +47,14 @@ import (
// TODO - Restructure or rename upgradeVariables
type upgradeVariables struct {
client clientset.Interface
cfg *kubeadmapiext.MasterConfiguration
cfg *kubeadmapi.MasterConfiguration
versionGetter upgrade.VersionGetter
waiter apiclient.Waiter
}
// enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure
func enforceRequirements(flags *cmdUpgradeFlags, dryRun bool, newK8sVersion string) (*upgradeVariables, error) {
func enforceRequirements(flags *applyPlanFlags, dryRun bool, newK8sVersion string) (*upgradeVariables, error) {
client, err := getClient(flags.kubeConfigPath, dryRun)
if err != nil {
return nil, fmt.Errorf("couldn't create a Kubernetes client from file %q: %v", flags.kubeConfigPath, err)
@ -60,8 +66,20 @@ func enforceRequirements(flags *cmdUpgradeFlags, dryRun bool, newK8sVersion stri
}
// Fetch the configuration from a file or ConfigMap and validate it
cfg, err := upgrade.FetchConfiguration(client, os.Stdout, flags.cfgPath)
fmt.Println("[upgrade/config] Making sure the configuration is correct:")
cfg, err := configutil.FetchConfigFromFileOrCluster(client, os.Stdout, "upgrade/config", flags.cfgPath)
if err != nil {
if apierrors.IsNotFound(err) {
fmt.Printf("[upgrade/config] In order to upgrade, a ConfigMap called %q in the %s namespace must exist.\n", constants.MasterConfigurationConfigMap, metav1.NamespaceSystem)
fmt.Println("[upgrade/config] Without this information, 'kubeadm upgrade' won't know how to configure your upgraded cluster.")
fmt.Println("")
fmt.Println("[upgrade/config] Next steps:")
fmt.Printf("\t- OPTION 1: Run 'kubeadm config upload from-flags' and specify the same CLI arguments you passed to 'kubeadm init' when you created your master.\n")
fmt.Printf("\t- OPTION 2: Run 'kubeadm config upload from-file' and specify the same config file you passed to 'kubeadm init' when you created your master.\n")
fmt.Printf("\t- OPTION 3: Pass a config file to 'kubeadm upgrade' using the --config flag.\n")
fmt.Println("")
err = fmt.Errorf("the ConfigMap %q in the %s namespace used for getting configuration information was not found", constants.MasterConfigurationConfigMap, metav1.NamespaceSystem)
}
return nil, fmt.Errorf("[upgrade/config] FATAL: %v", err)
}
@ -70,34 +88,40 @@ func enforceRequirements(flags *cmdUpgradeFlags, dryRun bool, newK8sVersion stri
cfg.KubernetesVersion = newK8sVersion
}
// If features gates are passed to the command line, use it (otherwise use featureGates from configuration)
if flags.featureGatesString != "" {
cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, flags.featureGatesString)
if err != nil {
return nil, fmt.Errorf("[upgrade/config] FATAL: %v", err)
}
}
// If the user told us to print this information out; do it!
if flags.printConfig {
printConfiguration(cfg, os.Stdout)
}
cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, flags.featureGatesString)
if err != nil {
return nil, fmt.Errorf("[upgrade/config] FATAL: %v", err)
}
return &upgradeVariables{
client: client,
cfg: cfg,
// Use a real version getter interface that queries the API server, the kubeadm client and the Kubernetes CI system for latest versions
versionGetter: upgrade.NewKubeVersionGetter(client, os.Stdout),
versionGetter: upgrade.NewOfflineVersionGetter(upgrade.NewKubeVersionGetter(client, os.Stdout), newK8sVersion),
// Use the waiter conditionally based on the dryrunning variable
waiter: getWaiter(dryRun, client),
}, nil
}
// printConfiguration prints the external version of the API to yaml
func printConfiguration(cfg *kubeadmapiext.MasterConfiguration, w io.Writer) {
func printConfiguration(cfg *kubeadmapi.MasterConfiguration, w io.Writer) {
// Short-circuit if cfg is nil, so we can safely get the value of the pointer below
if cfg == nil {
return
}
cfgYaml, err := yaml.Marshal(*cfg)
externalcfg := &kubeadmapiv1alpha2.MasterConfiguration{}
kubeadmscheme.Scheme.Convert(cfg, externalcfg, nil)
cfgYaml, err := kubeadmutil.MarshalToYamlForCodecs(externalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmscheme.Codecs)
if err == nil {
fmt.Fprintln(w, "[upgrade/config] Configuration used:")
@ -131,7 +155,10 @@ func getClient(file string, dryRun bool) (clientset.Interface, error) {
}
// Get the fake clientset
fakeclient := apiclient.NewDryRunClient(dryRunGetter, os.Stdout)
dryRunOpts := apiclient.GetDefaultDryRunClientOptions(dryRunGetter, os.Stdout)
// Print GET and LIST requests
dryRunOpts.PrintGETAndLIST = true
fakeclient := apiclient.NewDryRunClientWithOpts(dryRunOpts)
// As we know the return of Discovery() of the fake clientset is of type *fakediscovery.FakeDiscovery
// we can convert it to that struct.
fakeclientDiscovery, ok := fakeclient.Discovery().(*fakediscovery.FakeDiscovery)

View File

@ -20,12 +20,12 @@ import (
"bytes"
"testing"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func TestPrintConfiguration(t *testing.T) {
var tests = []struct {
cfg *kubeadmapiext.MasterConfiguration
cfg *kubeadmapi.MasterConfiguration
buf *bytes.Buffer
expectedBytes []byte
}{
@ -34,87 +34,11 @@ func TestPrintConfiguration(t *testing.T) {
expectedBytes: []byte(""),
},
{
cfg: &kubeadmapiext.MasterConfiguration{
cfg: &kubeadmapi.MasterConfiguration{
KubernetesVersion: "v1.7.1",
},
expectedBytes: []byte(`[upgrade/config] Configuration used:
api:
advertiseAddress: ""
bindPort: 0
controlPlaneEndpoint: ""
auditPolicy:
logDir: ""
path: ""
certificatesDir: ""
cloudProvider: ""
etcd:
caFile: ""
certFile: ""
dataDir: ""
endpoints: null
image: ""
keyFile: ""
imageRepository: ""
kubeProxy: {}
kubeletConfiguration: {}
kubernetesVersion: v1.7.1
networking:
dnsDomain: ""
podSubnet: ""
serviceSubnet: ""
nodeName: ""
privilegedPods: false
token: ""
unifiedControlPlaneImage: ""
`),
},
{
cfg: &kubeadmapiext.MasterConfiguration{
KubernetesVersion: "v1.7.1",
Networking: kubeadmapiext.Networking{
ServiceSubnet: "10.96.0.1/12",
},
},
expectedBytes: []byte(`[upgrade/config] Configuration used:
api:
advertiseAddress: ""
bindPort: 0
controlPlaneEndpoint: ""
auditPolicy:
logDir: ""
path: ""
certificatesDir: ""
cloudProvider: ""
etcd:
caFile: ""
certFile: ""
dataDir: ""
endpoints: null
image: ""
keyFile: ""
imageRepository: ""
kubeProxy: {}
kubeletConfiguration: {}
kubernetesVersion: v1.7.1
networking:
dnsDomain: ""
podSubnet: ""
serviceSubnet: 10.96.0.1/12
nodeName: ""
privilegedPods: false
token: ""
unifiedControlPlaneImage: ""
`),
},
{
cfg: &kubeadmapiext.MasterConfiguration{
KubernetesVersion: "v1.7.1",
Etcd: kubeadmapiext.Etcd{
SelfHosted: &kubeadmapiext.SelfHostedEtcd{
CertificatesDir: "/var/foo",
ClusterServiceName: "foo",
EtcdVersion: "v0.1.0",
OperatorVersion: "v0.1.0",
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
DataDir: "/some/path",
},
},
},
@ -123,24 +47,17 @@ func TestPrintConfiguration(t *testing.T) {
advertiseAddress: ""
bindPort: 0
controlPlaneEndpoint: ""
apiVersion: kubeadm.k8s.io/v1alpha2
auditPolicy:
logDir: ""
path: ""
certificatesDir: ""
cloudProvider: ""
etcd:
caFile: ""
certFile: ""
dataDir: ""
endpoints: null
image: ""
keyFile: ""
selfHosted:
certificatesDir: /var/foo
clusterServiceName: foo
etcdVersion: v0.1.0
operatorVersion: v0.1.0
local:
dataDir: /some/path
image: ""
imageRepository: ""
kind: MasterConfiguration
kubeProxy: {}
kubeletConfiguration: {}
kubernetesVersion: v1.7.1
@ -148,9 +65,49 @@ func TestPrintConfiguration(t *testing.T) {
dnsDomain: ""
podSubnet: ""
serviceSubnet: ""
nodeName: ""
privilegedPods: false
token: ""
nodeRegistration: {}
unifiedControlPlaneImage: ""
`),
},
{
cfg: &kubeadmapi.MasterConfiguration{
KubernetesVersion: "v1.7.1",
Networking: kubeadmapi.Networking{
ServiceSubnet: "10.96.0.1/12",
},
Etcd: kubeadmapi.Etcd{
External: &kubeadmapi.ExternalEtcd{
Endpoints: []string{"https://one-etcd-instance:2379"},
},
},
},
expectedBytes: []byte(`[upgrade/config] Configuration used:
api:
advertiseAddress: ""
bindPort: 0
controlPlaneEndpoint: ""
apiVersion: kubeadm.k8s.io/v1alpha2
auditPolicy:
logDir: ""
path: ""
certificatesDir: ""
etcd:
external:
caFile: ""
certFile: ""
endpoints:
- https://one-etcd-instance:2379
keyFile: ""
imageRepository: ""
kind: MasterConfiguration
kubeProxy: {}
kubeletConfiguration: {}
kubernetesVersion: v1.7.1
networking:
dnsDomain: ""
podSubnet: ""
serviceSubnet: 10.96.0.1/12
nodeRegistration: {}
unifiedControlPlaneImage: ""
`),
},

View File

@ -0,0 +1,147 @@
/*
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 upgrade
import (
"fmt"
"io"
"io/ioutil"
"github.com/golang/glog"
"github.com/pmezard/go-difflib/difflib"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
kubeadmv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
"k8s.io/kubernetes/pkg/util/version"
)
type diffFlags struct {
apiServerManifestPath string
controllerManagerManifestPath string
schedulerManifestPath string
newK8sVersionStr string
contextLines int
cfgPath string
out io.Writer
}
var (
defaultAPIServerManifestPath = constants.GetStaticPodFilepath(constants.KubeAPIServer, constants.GetStaticPodDirectory())
defaultControllerManagerManifestPath = constants.GetStaticPodFilepath(constants.KubeControllerManager, constants.GetStaticPodDirectory())
defaultSchedulerManifestPath = constants.GetStaticPodFilepath(constants.KubeScheduler, constants.GetStaticPodDirectory())
)
// NewCmdDiff returns the cobra command for `kubeadm upgrade diff`
func NewCmdDiff(out io.Writer) *cobra.Command {
flags := &diffFlags{
out: out,
}
cmd := &cobra.Command{
Use: "diff [version]",
Short: "Show what differences would be applied to existing static pod manifests. See also: kubeadm upgrade apply --dry-run",
Run: func(cmd *cobra.Command, args []string) {
// TODO: Run preflight checks for diff to check that the manifests already exist.
kubeadmutil.CheckErr(runDiff(flags, args))
},
}
// TODO: Use the generic options.AddConfigFlag method instead
cmd.Flags().StringVar(&flags.cfgPath, "config", flags.cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental!")
cmd.Flags().StringVar(&flags.apiServerManifestPath, "api-server-manifest", defaultAPIServerManifestPath, "path to API server manifest")
cmd.Flags().StringVar(&flags.controllerManagerManifestPath, "controller-manager-manifest", defaultControllerManagerManifestPath, "path to controller manifest")
cmd.Flags().StringVar(&flags.schedulerManifestPath, "scheduler-manifest", defaultSchedulerManifestPath, "path to scheduler manifest")
cmd.Flags().IntVarP(&flags.contextLines, "context-lines", "c", 3, "How many lines of context in the diff")
return cmd
}
func runDiff(flags *diffFlags, args []string) error {
// If the version is specified in config file, pick up that value.
glog.V(1).Infof("fetching configuration from file", flags.cfgPath)
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(flags.cfgPath, &kubeadmv1alpha2.MasterConfiguration{})
if err != nil {
return err
}
if cfg.KubernetesVersion != "" {
flags.newK8sVersionStr = cfg.KubernetesVersion
}
// If the new version is already specified in config file, version arg is optional.
if flags.newK8sVersionStr == "" {
if err := cmdutil.ValidateExactArgNumber(args, []string{"version"}); err != nil {
return err
}
}
// If option was specified in both args and config file, args will overwrite the config file.
if len(args) == 1 {
flags.newK8sVersionStr = args[0]
}
k8sVer, err := version.ParseSemantic(flags.newK8sVersionStr)
if err != nil {
return err
}
specs := controlplane.GetStaticPodSpecs(cfg, k8sVer)
for spec, pod := range specs {
var path string
switch spec {
case constants.KubeAPIServer:
path = flags.apiServerManifestPath
case constants.KubeControllerManager:
path = flags.controllerManagerManifestPath
case constants.KubeScheduler:
path = flags.schedulerManifestPath
default:
glog.Errorf("[diff] unknown spec %v", spec)
continue
}
newManifest, err := kubeadmutil.MarshalToYaml(&pod, corev1.SchemeGroupVersion)
if err != nil {
return err
}
if path == "" {
return fmt.Errorf("empty manifest path")
}
existingManifest, err := ioutil.ReadFile(path)
if err != nil {
return err
}
// Populated and write out the diff
diff := difflib.UnifiedDiff{
A: difflib.SplitLines(string(existingManifest)),
B: difflib.SplitLines(string(newManifest)),
FromFile: path,
ToFile: "new manifest",
Context: flags.contextLines,
}
difflib.WriteUnifiedDiff(flags.out, diff)
}
return nil
}

View File

@ -0,0 +1,90 @@
/*
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 upgrade
import (
"io/ioutil"
"testing"
)
const (
testUpgradeDiffConfig = `testdata/diff_master_config.yaml`
testUpgradeDiffManifest = `testdata/diff_dummy_manifest.yaml`
)
func TestRunDiff(t *testing.T) {
flags := &diffFlags{
cfgPath: "",
out: ioutil.Discard,
}
testCases := []struct {
name string
args []string
setManifestPath bool
manifestPath string
cfgPath string
expectedError bool
}{
{
name: "valid: run diff on valid manifest path",
cfgPath: testUpgradeDiffConfig,
setManifestPath: true,
manifestPath: testUpgradeDiffManifest,
expectedError: false,
},
{
name: "invalid: missing config file",
cfgPath: "missing-path-to-a-config",
expectedError: true,
},
{
name: "invalid: valid config but empty manifest path",
cfgPath: testUpgradeDiffConfig,
setManifestPath: true,
manifestPath: "",
expectedError: true,
},
{
name: "invalid: valid config but bad manifest path",
cfgPath: testUpgradeDiffConfig,
setManifestPath: true,
manifestPath: "bad-path",
expectedError: true,
},
{
name: "invalid: badly formatted version as argument",
cfgPath: testUpgradeDiffConfig,
args: []string{"bad-version"},
expectedError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
flags.cfgPath = tc.cfgPath
if tc.setManifestPath {
flags.apiServerManifestPath = tc.manifestPath
flags.controllerManagerManifestPath = tc.manifestPath
flags.schedulerManifestPath = tc.manifestPath
}
if err := runDiff(flags, tc.args); (err != nil) != tc.expectedError {
t.Fatalf("expected error: %v, saw: %v, error: %v", tc.expectedError, (err != nil), err)
}
})
}
}

View File

@ -0,0 +1,158 @@
/*
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 upgrade
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
"k8s.io/kubernetes/pkg/util/normalizer"
"k8s.io/kubernetes/pkg/util/version"
)
var (
upgradeNodeConfigLongDesc = normalizer.LongDesc(`
Downloads the kubelet configuration from a ConfigMap of the form "kubelet-config-1.X" in the cluster,
where X is the minor version of the kubelet. kubeadm uses the --kubelet-version parameter to determine
what the _desired_ kubelet version is. Give
`)
upgradeNodeConfigExample = normalizer.Examples(`
# Downloads the kubelet configuration from the ConfigMap in the cluster. Uses a specific desired kubelet version.
kubeadm upgrade node config --kubelet-version v1.11.0
# Simulates the downloading of the kubelet configuration from the ConfigMap in the cluster with a specific desired
# version. Does not change any state locally on the node.
kubeadm upgrade node config --kubelet-version v1.11.0 --dry-run
`)
)
type nodeUpgradeFlags struct {
kubeConfigPath string
kubeletVersionStr string
dryRun bool
}
// NewCmdNode returns the cobra command for `kubeadm upgrade node`
func NewCmdNode() *cobra.Command {
cmd := &cobra.Command{
Use: "node",
Short: "Upgrade commands for a node in the cluster. Currently only supports upgrading the configuration, not the kubelet itself.",
RunE: cmdutil.SubCmdRunE("node"),
}
cmd.AddCommand(NewCmdUpgradeNodeConfig())
return cmd
}
// NewCmdUpgradeNodeConfig returns the cobra.Command for downloading the new/upgrading the kubelet configuration from the kubelet-config-1.X
// ConfigMap in the cluster
func NewCmdUpgradeNodeConfig() *cobra.Command {
flags := &nodeUpgradeFlags{
kubeConfigPath: constants.GetKubeletKubeConfigPath(),
kubeletVersionStr: "",
dryRun: false,
}
cmd := &cobra.Command{
Use: "config",
Short: "Downloads the kubelet configuration from the cluster ConfigMap kubelet-config-1.X, where X is the minor version of the kubelet.",
Long: upgradeNodeConfigLongDesc,
Example: upgradeNodeConfigExample,
Run: func(cmd *cobra.Command, args []string) {
err := RunUpgradeNodeConfig(flags)
kubeadmutil.CheckErr(err)
},
}
// TODO: Unify the registration of common flags and e.g. use the generic options.AddKubeConfigFlag method instead
cmd.Flags().StringVar(&flags.kubeConfigPath, "kubeconfig", flags.kubeConfigPath, "The KubeConfig file to use when talking to the cluster.")
cmd.Flags().BoolVar(&flags.dryRun, "dry-run", flags.dryRun, "Do not change any state, just output the actions that would be performed.")
cmd.Flags().StringVar(&flags.kubeletVersionStr, "kubelet-version", flags.kubeletVersionStr, "The *desired* version for the kubelet after the upgrade.")
return cmd
}
// RunUpgradeNodeConfig is executed when `kubeadm upgrade node config` runs.
func RunUpgradeNodeConfig(flags *nodeUpgradeFlags) error {
if len(flags.kubeletVersionStr) == 0 {
return fmt.Errorf("The --kubelet-version argument is required")
}
// Set up the kubelet directory to use. If dry-running, use a fake directory
kubeletDir, err := getKubeletDir(flags.dryRun)
if err != nil {
return err
}
client, err := getClient(flags.kubeConfigPath, flags.dryRun)
if err != nil {
return fmt.Errorf("couldn't create a Kubernetes client from file %q: %v", flags.kubeConfigPath, err)
}
// Parse the desired kubelet version
kubeletVersion, err := version.ParseSemantic(flags.kubeletVersionStr)
if err != nil {
return err
}
// TODO: Checkpoint the current configuration first so that if something goes wrong it can be recovered
if err := kubeletphase.DownloadConfig(client, kubeletVersion, kubeletDir); err != nil {
return err
}
// If we're dry-running, print the generated manifests, otherwise do nothing
if err := printFilesIfDryRunning(flags.dryRun, kubeletDir); err != nil {
return fmt.Errorf("error printing files on dryrun: %v", err)
}
fmt.Println("[upgrade] The configuration for this node was successfully updated!")
fmt.Println("[upgrade] Now you should go ahead and upgrade the kubelet package using your package manager.")
return nil
}
// getKubeletDir gets the kubelet directory based on whether the user is dry-running this command or not.
func getKubeletDir(dryRun bool) (string, error) {
if dryRun {
dryRunDir, err := ioutil.TempDir("", "kubeadm-init-dryrun")
if err != nil {
return "", fmt.Errorf("couldn't create a temporary directory: %v", err)
}
return dryRunDir, nil
}
return constants.KubeletRunDirectory, nil
}
// printFilesIfDryRunning prints the Static Pod manifests to stdout and informs about the temporary directory to go and lookup
func printFilesIfDryRunning(dryRun bool, kubeletDir string) error {
if !dryRun {
return nil
}
// Print the contents of the upgraded file and pretend like they were in kubeadmconstants.KubeletRunDirectory
fileToPrint := dryrunutil.FileToPrint{
RealPath: filepath.Join(kubeletDir, constants.KubeletConfigurationFileName),
PrintPath: filepath.Join(constants.KubeletRunDirectory, constants.KubeletConfigurationFileName),
}
return dryrunutil.PrintDryRunFiles([]dryrunutil.FileToPrint{fileToPrint}, os.Stdout)
}

View File

@ -23,60 +23,118 @@ import (
"sort"
"text/tabwriter"
"github.com/golang/glog"
"github.com/spf13/cobra"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
)
type planFlags struct {
*applyPlanFlags
newK8sVersionStr string
}
// NewCmdPlan returns the cobra command for `kubeadm upgrade plan`
func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command {
func NewCmdPlan(apf *applyPlanFlags) *cobra.Command {
flags := &planFlags{
applyPlanFlags: apf,
}
cmd := &cobra.Command{
Use: "plan",
Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable.",
Run: func(_ *cobra.Command, _ []string) {
Use: "plan [version] [flags]",
Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable. To skip the internet check, pass in the optional [version] parameter.",
Run: func(_ *cobra.Command, args []string) {
var err error
parentFlags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(parentFlags.ignorePreflightErrors, parentFlags.skipPreFlight)
flags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(flags.ignorePreflightErrors, flags.skipPreFlight)
kubeadmutil.CheckErr(err)
// Ensure the user is root
err = runPreflightChecks(parentFlags.ignorePreflightErrorsSet)
err = runPreflightChecks(flags.ignorePreflightErrorsSet)
kubeadmutil.CheckErr(err)
err = RunPlan(parentFlags)
// If the version is specified in config file, pick up that value.
if flags.cfgPath != "" {
glog.V(1).Infof("fetching configuration from file", flags.cfgPath)
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(flags.cfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
kubeadmutil.CheckErr(err)
if cfg.KubernetesVersion != "" {
flags.newK8sVersionStr = cfg.KubernetesVersion
}
}
// If option was specified in both args and config file, args will overwrite the config file.
if len(args) == 1 {
flags.newK8sVersionStr = args[0]
}
err = RunPlan(flags)
kubeadmutil.CheckErr(err)
},
}
// Register the common flags for apply and plan
addApplyPlanFlags(cmd.Flags(), flags.applyPlanFlags)
return cmd
}
// RunPlan takes care of outputting available versions to upgrade to for the user
func RunPlan(parentFlags *cmdUpgradeFlags) error {
func RunPlan(flags *planFlags) error {
// Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never dry-run when planning.
upgradeVars, err := enforceRequirements(parentFlags, false, "")
glog.V(1).Infof("[upgrade/plan] verifying health of cluster")
glog.V(1).Infof("[upgrade/plan] retrieving configuration from cluster")
upgradeVars, err := enforceRequirements(flags.applyPlanFlags, false, flags.newK8sVersionStr)
if err != nil {
return err
}
// Define Local Etcd cluster to be able to retrieve information
etcdCluster := kubeadmutil.LocalEtcdCluster{}
var etcdClient etcdutil.ClusterInterrogator
// Currently this is the only method we have for distinguishing
// external etcd vs static pod etcd
isExternalEtcd := upgradeVars.cfg.Etcd.External != nil
if isExternalEtcd {
client, err := etcdutil.New(
upgradeVars.cfg.Etcd.External.Endpoints,
upgradeVars.cfg.Etcd.External.CAFile,
upgradeVars.cfg.Etcd.External.CertFile,
upgradeVars.cfg.Etcd.External.KeyFile)
if err != nil {
return err
}
etcdClient = client
} else {
client, err := etcdutil.NewFromStaticPod(
[]string{"localhost:2379"},
constants.GetStaticPodDirectory(),
upgradeVars.cfg.CertificatesDir,
)
if err != nil {
return err
}
etcdClient = client
}
// Compute which upgrade possibilities there are
availUpgrades, err := upgrade.GetAvailableUpgrades(upgradeVars.versionGetter, parentFlags.allowExperimentalUpgrades, parentFlags.allowRCUpgrades, etcdCluster, upgradeVars.cfg.FeatureGates)
glog.V(1).Infof("[upgrade/plan] computing upgrade possibilities")
availUpgrades, err := upgrade.GetAvailableUpgrades(upgradeVars.versionGetter, flags.allowExperimentalUpgrades, flags.allowRCUpgrades, etcdClient, upgradeVars.cfg.FeatureGates, upgradeVars.client)
if err != nil {
return fmt.Errorf("[upgrade/versions] FATAL: %v", err)
}
// Tell the user which upgrades are available
printAvailableUpgrades(availUpgrades, os.Stdout, upgradeVars.cfg.FeatureGates)
printAvailableUpgrades(availUpgrades, os.Stdout, isExternalEtcd)
return nil
}
// printAvailableUpgrades prints a UX-friendly overview of what versions are available to upgrade to
// TODO look into columnize or some other formatter when time permits instead of using the tabwriter
func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer, featureGates map[string]bool) {
func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer, isExternalEtcd bool) {
// Return quickly if no upgrades can be made
if len(upgrades) == 0 {
@ -89,6 +147,16 @@ func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer, featureGate
// Loop through the upgrade possibilities and output text to the command line
for _, upgrade := range upgrades {
if isExternalEtcd && upgrade.CanUpgradeEtcd() {
fmt.Fprintln(w, "External components that should be upgraded manually before you upgrade the control plane with 'kubeadm upgrade apply':")
fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tAVAILABLE")
fmt.Fprintf(tabw, "Etcd\t%s\t%s\n", upgrade.Before.EtcdVersion, upgrade.After.EtcdVersion)
// We should flush the writer here at this stage; as the columns will now be of the right size, adjusted to the above content
tabw.Flush()
fmt.Fprintln(w, "")
}
if upgrade.CanUpgradeKubelets() {
fmt.Fprintln(w, "Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':")
fmt.Fprintln(tabw, "COMPONENT\tCURRENT\tAVAILABLE")
@ -104,7 +172,7 @@ func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer, featureGate
firstPrinted = true
continue
}
fmt.Fprintf(tabw, "\t\t%d x %s\t%s\n", nodeCount, oldVersion, upgrade.After.KubeVersion)
fmt.Fprintf(tabw, "\t%d x %s\t%s\n", nodeCount, oldVersion, upgrade.After.KubeVersion)
}
// We should flush the writer here at this stage; as the columns will now be of the right size, adjusted to the above content
tabw.Flush()
@ -118,12 +186,40 @@ func printAvailableUpgrades(upgrades []upgrade.Upgrade, w io.Writer, featureGate
fmt.Fprintf(tabw, "Controller Manager\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion)
fmt.Fprintf(tabw, "Scheduler\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion)
fmt.Fprintf(tabw, "Kube Proxy\t%s\t%s\n", upgrade.Before.KubeVersion, upgrade.After.KubeVersion)
if features.Enabled(featureGates, features.CoreDNS) {
fmt.Fprintf(tabw, "CoreDNS\t%s\t%s\n", upgrade.Before.DNSVersion, upgrade.After.DNSVersion)
} else {
fmt.Fprintf(tabw, "Kube DNS\t%s\t%s\n", upgrade.Before.DNSVersion, upgrade.After.DNSVersion)
// TODO There is currently no way to cleanly output upgrades that involve adding, removing, or changing components
// https://github.com/kubernetes/kubeadm/issues/810 was created to track addressing this.
printCoreDNS, printKubeDNS := false, false
coreDNSBeforeVersion, coreDNSAfterVersion, kubeDNSBeforeVersion, kubeDNSAfterVersion := "", "", "", ""
switch upgrade.Before.DNSType {
case constants.CoreDNS:
printCoreDNS = true
coreDNSBeforeVersion = upgrade.Before.DNSVersion
case constants.KubeDNS:
printKubeDNS = true
kubeDNSBeforeVersion = upgrade.Before.DNSVersion
}
switch upgrade.After.DNSType {
case constants.CoreDNS:
printCoreDNS = true
coreDNSAfterVersion = upgrade.After.DNSVersion
case constants.KubeDNS:
printKubeDNS = true
kubeDNSAfterVersion = upgrade.After.DNSVersion
}
if printCoreDNS {
fmt.Fprintf(tabw, "CoreDNS\t%s\t%s\n", coreDNSBeforeVersion, coreDNSAfterVersion)
}
if printKubeDNS {
fmt.Fprintf(tabw, "Kube DNS\t%s\t%s\n", kubeDNSBeforeVersion, kubeDNSAfterVersion)
}
if !isExternalEtcd {
fmt.Fprintf(tabw, "Etcd\t%s\t%s\n", upgrade.Before.EtcdVersion, upgrade.After.EtcdVersion)
}
fmt.Fprintf(tabw, "Etcd\t%s\t%s\n", upgrade.Before.EtcdVersion, upgrade.After.EtcdVersion)
// The tabwriter should be flushed at this stage as we have now put in all the required content for this time. This is required for the tabs' size to be correct.
tabw.Flush()

View File

@ -60,18 +60,28 @@ func TestSortedSliceFromStringIntMap(t *testing.T) {
// TODO Think about modifying this test to be less verbose checking b/c it can be brittle.
func TestPrintAvailableUpgrades(t *testing.T) {
featureGates := make(map[string]bool)
var tests = []struct {
name string
upgrades []upgrade.Upgrade
buf *bytes.Buffer
expectedBytes []byte
externalEtcd bool
}{
{
name: "Up to date",
upgrades: []upgrade.Upgrade{},
expectedBytes: []byte(`Awesome, you're up-to-date! Enjoy!
`),
},
{
name: "Up to date external etcd",
externalEtcd: true,
upgrades: []upgrade.Upgrade{},
expectedBytes: []byte(`Awesome, you're up-to-date! Enjoy!
`),
},
{
name: "Patch version available",
upgrades: []upgrade.Upgrade{
{
Description: "version in the v1.8 series",
@ -81,12 +91,14 @@ func TestPrintAvailableUpgrades(t *testing.T) {
"v1.8.1": 1,
},
KubeadmVersion: "v1.8.2",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
After: upgrade.ClusterState{
KubeVersion: "v1.8.3",
KubeadmVersion: "v1.8.3",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
@ -117,6 +129,7 @@ _____________________________________________________________________
`),
},
{
name: "minor version available",
upgrades: []upgrade.Upgrade{
{
Description: "stable version",
@ -126,14 +139,16 @@ _____________________________________________________________________
"v1.8.3": 1,
},
KubeadmVersion: "v1.9.0",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
After: upgrade.ClusterState{
KubeVersion: "v1.9.0",
KubeadmVersion: "v1.9.0",
DNSVersion: "1.14.8",
EtcdVersion: "3.1.11",
DNSType: "kube-dns",
DNSVersion: "1.14.10",
EtcdVersion: "3.1.12",
},
},
},
@ -148,8 +163,8 @@ API Server v1.8.3 v1.9.0
Controller Manager v1.8.3 v1.9.0
Scheduler v1.8.3 v1.9.0
Kube Proxy v1.8.3 v1.9.0
Kube DNS 1.14.5 1.14.8
Etcd 3.0.17 3.1.11
Kube DNS 1.14.5 1.14.10
Etcd 3.0.17 3.1.12
You can now apply the upgrade by executing the following command:
@ -160,6 +175,7 @@ _____________________________________________________________________
`),
},
{
name: "patch and minor version available",
upgrades: []upgrade.Upgrade{
{
Description: "version in the v1.8 series",
@ -169,12 +185,14 @@ _____________________________________________________________________
"v1.8.3": 1,
},
KubeadmVersion: "v1.8.3",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
After: upgrade.ClusterState{
KubeVersion: "v1.8.5",
KubeadmVersion: "v1.8.3",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
@ -187,14 +205,16 @@ _____________________________________________________________________
"v1.8.3": 1,
},
KubeadmVersion: "v1.8.3",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
After: upgrade.ClusterState{
KubeVersion: "v1.9.0",
KubeadmVersion: "v1.9.0",
DNSVersion: "1.14.8",
EtcdVersion: "3.1.11",
DNSType: "kube-dns",
DNSVersion: "1.14.10",
EtcdVersion: "3.1.12",
},
},
},
@ -229,8 +249,8 @@ API Server v1.8.3 v1.9.0
Controller Manager v1.8.3 v1.9.0
Scheduler v1.8.3 v1.9.0
Kube Proxy v1.8.3 v1.9.0
Kube DNS 1.14.5 1.14.8
Etcd 3.0.17 3.1.11
Kube DNS 1.14.5 1.14.10
Etcd 3.0.17 3.1.12
You can now apply the upgrade by executing the following command:
@ -243,6 +263,7 @@ _____________________________________________________________________
`),
},
{
name: "experimental version available",
upgrades: []upgrade.Upgrade{
{
Description: "experimental version",
@ -252,14 +273,16 @@ _____________________________________________________________________
"v1.8.5": 1,
},
KubeadmVersion: "v1.8.5",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
After: upgrade.ClusterState{
KubeVersion: "v1.9.0-beta.1",
KubeadmVersion: "v1.9.0-beta.1",
DNSVersion: "1.14.8",
EtcdVersion: "3.1.11",
DNSType: "kube-dns",
DNSVersion: "1.14.10",
EtcdVersion: "3.1.12",
},
},
},
@ -274,8 +297,8 @@ API Server v1.8.5 v1.9.0-beta.1
Controller Manager v1.8.5 v1.9.0-beta.1
Scheduler v1.8.5 v1.9.0-beta.1
Kube Proxy v1.8.5 v1.9.0-beta.1
Kube DNS 1.14.5 1.14.8
Etcd 3.0.17 3.1.11
Kube DNS 1.14.5 1.14.10
Etcd 3.0.17 3.1.12
You can now apply the upgrade by executing the following command:
@ -288,6 +311,7 @@ _____________________________________________________________________
`),
},
{
name: "release candidate available",
upgrades: []upgrade.Upgrade{
{
Description: "release candidate version",
@ -297,14 +321,16 @@ _____________________________________________________________________
"v1.8.5": 1,
},
KubeadmVersion: "v1.8.5",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
After: upgrade.ClusterState{
KubeVersion: "v1.9.0-rc.1",
KubeadmVersion: "v1.9.0-rc.1",
DNSVersion: "1.14.8",
EtcdVersion: "3.1.11",
DNSType: "kube-dns",
DNSVersion: "1.14.10",
EtcdVersion: "3.1.12",
},
},
},
@ -319,8 +345,8 @@ API Server v1.8.5 v1.9.0-rc.1
Controller Manager v1.8.5 v1.9.0-rc.1
Scheduler v1.8.5 v1.9.0-rc.1
Kube Proxy v1.8.5 v1.9.0-rc.1
Kube DNS 1.14.5 1.14.8
Etcd 3.0.17 3.1.11
Kube DNS 1.14.5 1.14.10
Etcd 3.0.17 3.1.12
You can now apply the upgrade by executing the following command:
@ -330,19 +356,264 @@ Note: Before you can perform this upgrade, you have to update kubeadm to v1.9.0-
_____________________________________________________________________
`),
},
{
name: "multiple kubelet versions",
upgrades: []upgrade.Upgrade{
{
Description: "version in the v1.9 series",
Before: upgrade.ClusterState{
KubeVersion: "v1.9.2",
KubeletVersions: map[string]uint16{
"v1.9.2": 1,
"v1.9.3": 2,
},
KubeadmVersion: "v1.9.2",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
After: upgrade.ClusterState{
KubeVersion: "v1.9.3",
KubeadmVersion: "v1.9.3",
DNSType: "kube-dns",
DNSVersion: "1.14.8",
EtcdVersion: "3.1.12",
},
},
},
expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.9.2 v1.9.3
2 x v1.9.3 v1.9.3
Upgrade to the latest version in the v1.9 series:
COMPONENT CURRENT AVAILABLE
API Server v1.9.2 v1.9.3
Controller Manager v1.9.2 v1.9.3
Scheduler v1.9.2 v1.9.3
Kube Proxy v1.9.2 v1.9.3
Kube DNS 1.14.5 1.14.8
Etcd 3.0.17 3.1.12
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.9.3
Note: Before you can perform this upgrade, you have to update kubeadm to v1.9.3.
_____________________________________________________________________
`),
},
{
name: "external etcd upgrade available",
upgrades: []upgrade.Upgrade{
{
Description: "version in the v1.9 series",
Before: upgrade.ClusterState{
KubeVersion: "v1.9.2",
KubeletVersions: map[string]uint16{
"v1.9.2": 1,
},
KubeadmVersion: "v1.9.2",
DNSType: "kube-dns",
DNSVersion: "1.14.5",
EtcdVersion: "3.0.17",
},
After: upgrade.ClusterState{
KubeVersion: "v1.9.3",
KubeadmVersion: "v1.9.3",
DNSType: "kube-dns",
DNSVersion: "1.14.8",
EtcdVersion: "3.1.12",
},
},
},
externalEtcd: true,
expectedBytes: []byte(`External components that should be upgraded manually before you upgrade the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Etcd 3.0.17 3.1.12
Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.9.2 v1.9.3
Upgrade to the latest version in the v1.9 series:
COMPONENT CURRENT AVAILABLE
API Server v1.9.2 v1.9.3
Controller Manager v1.9.2 v1.9.3
Scheduler v1.9.2 v1.9.3
Kube Proxy v1.9.2 v1.9.3
Kube DNS 1.14.5 1.14.8
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.9.3
Note: Before you can perform this upgrade, you have to update kubeadm to v1.9.3.
_____________________________________________________________________
`),
},
{
name: "kubedns to coredns",
upgrades: []upgrade.Upgrade{
{
Description: "kubedns to coredns",
Before: upgrade.ClusterState{
KubeVersion: "v1.10.2",
KubeletVersions: map[string]uint16{
"v1.10.2": 1,
},
KubeadmVersion: "v1.11.0",
DNSType: "kube-dns",
DNSVersion: "1.14.7",
EtcdVersion: "3.1.11",
},
After: upgrade.ClusterState{
KubeVersion: "v1.11.0",
KubeadmVersion: "v1.11.0",
DNSType: "coredns",
DNSVersion: "1.0.6",
EtcdVersion: "3.2.18",
},
},
},
expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.10.2 v1.11.0
Upgrade to the latest kubedns to coredns:
COMPONENT CURRENT AVAILABLE
API Server v1.10.2 v1.11.0
Controller Manager v1.10.2 v1.11.0
Scheduler v1.10.2 v1.11.0
Kube Proxy v1.10.2 v1.11.0
CoreDNS 1.0.6
Kube DNS 1.14.7
Etcd 3.1.11 3.2.18
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.11.0
_____________________________________________________________________
`),
},
{
name: "coredns",
upgrades: []upgrade.Upgrade{
{
Description: "coredns",
Before: upgrade.ClusterState{
KubeVersion: "v1.10.2",
KubeletVersions: map[string]uint16{
"v1.10.2": 1,
},
KubeadmVersion: "v1.11.0",
DNSType: "coredns",
DNSVersion: "1.0.5",
EtcdVersion: "3.1.11",
},
After: upgrade.ClusterState{
KubeVersion: "v1.11.0",
KubeadmVersion: "v1.11.0",
DNSType: "coredns",
DNSVersion: "1.0.6",
EtcdVersion: "3.2.18",
},
},
},
expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.10.2 v1.11.0
Upgrade to the latest coredns:
COMPONENT CURRENT AVAILABLE
API Server v1.10.2 v1.11.0
Controller Manager v1.10.2 v1.11.0
Scheduler v1.10.2 v1.11.0
Kube Proxy v1.10.2 v1.11.0
CoreDNS 1.0.5 1.0.6
Etcd 3.1.11 3.2.18
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.11.0
_____________________________________________________________________
`),
},
{
name: "coredns to kubedns",
upgrades: []upgrade.Upgrade{
{
Description: "coredns to kubedns",
Before: upgrade.ClusterState{
KubeVersion: "v1.10.2",
KubeletVersions: map[string]uint16{
"v1.10.2": 1,
},
KubeadmVersion: "v1.11.0",
DNSType: "coredns",
DNSVersion: "1.0.6",
EtcdVersion: "3.1.11",
},
After: upgrade.ClusterState{
KubeVersion: "v1.11.0",
KubeadmVersion: "v1.11.0",
DNSType: "kube-dns",
DNSVersion: "1.14.9",
EtcdVersion: "3.2.18",
},
},
},
expectedBytes: []byte(`Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT CURRENT AVAILABLE
Kubelet 1 x v1.10.2 v1.11.0
Upgrade to the latest coredns to kubedns:
COMPONENT CURRENT AVAILABLE
API Server v1.10.2 v1.11.0
Controller Manager v1.10.2 v1.11.0
Scheduler v1.10.2 v1.11.0
Kube Proxy v1.10.2 v1.11.0
CoreDNS 1.0.6
Kube DNS 1.14.9
Etcd 3.1.11 3.2.18
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.11.0
_____________________________________________________________________
`),
},
}
for _, rt := range tests {
rt.buf = bytes.NewBufferString("")
printAvailableUpgrades(rt.upgrades, rt.buf, featureGates)
actualBytes := rt.buf.Bytes()
if !bytes.Equal(actualBytes, rt.expectedBytes) {
t.Errorf(
"failed PrintAvailableUpgrades:\n\texpected: %q\n\t actual: %q",
string(rt.expectedBytes),
string(actualBytes),
)
}
t.Run(rt.name, func(t *testing.T) {
rt.buf = bytes.NewBufferString("")
printAvailableUpgrades(rt.upgrades, rt.buf, rt.externalEtcd)
actualBytes := rt.buf.Bytes()
if !bytes.Equal(actualBytes, rt.expectedBytes) {
t.Errorf(
"failed PrintAvailableUpgrades:\n\texpected: %q\n\t actual: %q",
string(rt.expectedBytes),
string(actualBytes),
)
}
})
}
}

View File

@ -0,0 +1 @@
some-empty-file-to-diff

View File

@ -0,0 +1,3 @@
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
kubernetesVersion: 1.11.0

View File

@ -21,13 +21,15 @@ import (
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/sets"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
)
// cmdUpgradeFlags holds the values for the common flags in `kubeadm upgrade`
type cmdUpgradeFlags struct {
// applyPlanFlags holds the values for the common flags in `kubeadm upgrade apply` and `kubeadm upgrade plan`
type applyPlanFlags struct {
kubeConfigPath string
cfgPath string
featureGatesString string
@ -37,11 +39,12 @@ type cmdUpgradeFlags struct {
skipPreFlight bool
ignorePreflightErrors []string
ignorePreflightErrorsSet sets.String
out io.Writer
}
// NewCmdUpgrade returns the cobra command for `kubeadm upgrade`
func NewCmdUpgrade(out io.Writer) *cobra.Command {
flags := &cmdUpgradeFlags{
flags := &applyPlanFlags{
kubeConfigPath: "/etc/kubernetes/admin.conf",
cfgPath: "",
featureGatesString: "",
@ -50,6 +53,7 @@ func NewCmdUpgrade(out io.Writer) *cobra.Command {
printConfig: false,
skipPreFlight: false,
ignorePreflightErrorsSet: sets.NewString(),
out: out,
}
cmd := &cobra.Command{
@ -58,19 +62,24 @@ func NewCmdUpgrade(out io.Writer) *cobra.Command {
RunE: cmdutil.SubCmdRunE("upgrade"),
}
cmd.PersistentFlags().StringVar(&flags.kubeConfigPath, "kubeconfig", flags.kubeConfigPath, "The KubeConfig file to use when talking to the cluster.")
cmd.PersistentFlags().StringVar(&flags.cfgPath, "config", flags.cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental!")
cmd.PersistentFlags().BoolVar(&flags.allowExperimentalUpgrades, "allow-experimental-upgrades", flags.allowExperimentalUpgrades, "Show unstable versions of Kubernetes as an upgrade alternative and allow upgrading to an alpha/beta/release candidate versions of Kubernetes.")
cmd.PersistentFlags().BoolVar(&flags.allowRCUpgrades, "allow-release-candidate-upgrades", flags.allowRCUpgrades, "Show release candidate versions of Kubernetes as an upgrade alternative and allow upgrading to a release candidate versions of Kubernetes.")
cmd.PersistentFlags().BoolVar(&flags.printConfig, "print-config", flags.printConfig, "Specifies whether the configuration file that will be used in the upgrade should be printed or not.")
cmd.PersistentFlags().StringSliceVar(&flags.ignorePreflightErrors, "ignore-preflight-errors", flags.ignorePreflightErrors, "A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.")
cmd.PersistentFlags().BoolVar(&flags.skipPreFlight, "skip-preflight-checks", flags.skipPreFlight, "Skip preflight checks that normally run before modifying the system.")
cmd.PersistentFlags().MarkDeprecated("skip-preflight-checks", "it is now equivalent to --ignore-preflight-errors=all")
cmd.PersistentFlags().StringVar(&flags.featureGatesString, "feature-gates", flags.featureGatesString, "A set of key=value pairs that describe feature gates for various features."+
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))
cmd.AddCommand(NewCmdApply(flags))
cmd.AddCommand(NewCmdPlan(flags))
cmd.AddCommand(NewCmdDiff(out))
cmd.AddCommand(NewCmdNode())
return cmd
}
func addApplyPlanFlags(fs *pflag.FlagSet, flags *applyPlanFlags) {
// TODO: Use the generic options.AddKubeConfigFlag and options.AddConfigFlag methods instead
fs.StringVar(&flags.kubeConfigPath, "kubeconfig", flags.kubeConfigPath, "The KubeConfig file to use when talking to the cluster.")
fs.StringVar(&flags.cfgPath, "config", flags.cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental!")
fs.BoolVar(&flags.allowExperimentalUpgrades, "allow-experimental-upgrades", flags.allowExperimentalUpgrades, "Show unstable versions of Kubernetes as an upgrade alternative and allow upgrading to an alpha/beta/release candidate versions of Kubernetes.")
fs.BoolVar(&flags.allowRCUpgrades, "allow-release-candidate-upgrades", flags.allowRCUpgrades, "Show release candidate versions of Kubernetes as an upgrade alternative and allow upgrading to a release candidate versions of Kubernetes.")
fs.BoolVar(&flags.printConfig, "print-config", flags.printConfig, "Specifies whether the configuration file that will be used in the upgrade should be printed or not.")
fs.StringSliceVar(&flags.ignorePreflightErrors, "ignore-preflight-errors", flags.ignorePreflightErrors, "A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.")
fs.BoolVar(&flags.skipPreFlight, "skip-preflight-checks", flags.skipPreFlight, "Skip preflight checks that normally run before modifying the system.")
fs.MarkDeprecated("skip-preflight-checks", "it is now equivalent to --ignore-preflight-errors=all")
fs.StringVar(&flags.featureGatesString, "feature-gates", flags.featureGatesString, "A set of key=value pairs that describe feature gates for various features."+
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))
}

View File

@ -22,11 +22,11 @@ import (
"io"
"github.com/ghodss/yaml"
"github.com/golang/glog"
"github.com/spf13/cobra"
apimachineryversion "k8s.io/apimachinery/pkg/version"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/version"
)
@ -53,12 +53,19 @@ func NewCmdVersion(out io.Writer) *cobra.Command {
// RunVersion provides the version information of kubeadm in format depending on arguments
// specified in cobra.Command.
func RunVersion(out io.Writer, cmd *cobra.Command) error {
glog.V(1).Infoln("[version] retrieving version info")
clientVersion := version.Get()
v := Version{
ClientVersion: &clientVersion,
}
switch of := cmdutil.GetFlagString(cmd, "output"); of {
const flag = "output"
of, err := cmd.Flags().GetString(flag)
if err != nil {
glog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
}
switch of {
case "":
fmt.Fprintf(out, "kubeadm version: %#v\n", v.ClientVersion)
case "short":

View File

@ -0,0 +1,96 @@
/*
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 cmd
import (
"bytes"
"encoding/json"
"fmt"
"github.com/ghodss/yaml"
"testing"
)
func TestNewCmdVersion(t *testing.T) {
var buf bytes.Buffer
cmd := NewCmdVersion(&buf)
if err := cmd.Execute(); err != nil {
t.Errorf("Cannot execute version command: %v", err)
}
}
func TestRunVersion(t *testing.T) {
var buf bytes.Buffer
iface := make(map[string]interface{})
flagNameOutput := "output"
cmd := NewCmdVersion(&buf)
testCases := []struct {
name string
flag string
expectedError bool
shouldBeValidYAML bool
shouldBeValidJSON bool
}{
{
name: "valid: run without flags",
},
{
name: "valid: run with flag 'short'",
flag: "short",
},
{
name: "valid: run with flag 'yaml'",
flag: "yaml",
shouldBeValidYAML: true,
},
{
name: "valid: run with flag 'json'",
flag: "json",
shouldBeValidJSON: true,
},
{
name: "invalid: run with unsupported flag",
flag: "unsupported-flag",
expectedError: true,
},
}
for _, tc := range testCases {
var err error
if len(tc.flag) > 0 {
if err = cmd.Flags().Set(flagNameOutput, tc.flag); err != nil {
goto error
}
}
buf.Reset()
if err = RunVersion(&buf, cmd); err != nil {
goto error
}
if buf.String() == "" {
err = fmt.Errorf("empty output")
goto error
}
if tc.shouldBeValidYAML {
err = yaml.Unmarshal(buf.Bytes(), &iface)
} else if tc.shouldBeValidJSON {
err = json.Unmarshal(buf.Bytes(), &iface)
}
error:
if (err != nil) != tc.expectedError {
t.Errorf("Test case %q: RunVersion expected error: %v, saw: %v; %v", tc.name, tc.expectedError, err != nil, err)
}
}
}

View File

@ -21,6 +21,7 @@ import (
"io/ioutil"
"net"
"os"
"path"
"path/filepath"
"time"
@ -38,7 +39,8 @@ const (
// ManifestsSubDirName defines directory name to store manifests
ManifestsSubDirName = "manifests"
// TempDirForKubeadm defines temporary directory for kubeadm
TempDirForKubeadm = "/etc/kubernetes/tmp"
// should be joined with KubernetesDir.
TempDirForKubeadm = "tmp"
// CACertAndKeyBaseName defines certificate authority base name
CACertAndKeyBaseName = "ca"
@ -65,14 +67,19 @@ const (
// APIServerKubeletClientCertCommonName defines kubelet client certificate common name (CN)
APIServerKubeletClientCertCommonName = "kube-apiserver-kubelet-client"
// EtcdCACertAndKeyBaseName defines etcd's CA certificate and key base name
EtcdCACertAndKeyBaseName = "etcd/ca"
// EtcdCACertName defines etcd's CA certificate name
EtcdCACertName = "etcd/ca.crt"
// EtcdCAKeyName defines etcd's CA key name
EtcdCAKeyName = "etcd/ca.key"
// EtcdServerCertAndKeyBaseName defines etcd's server certificate and key base name
EtcdServerCertAndKeyBaseName = "etcd/server"
// EtcdServerCertName defines etcd's server certificate name
EtcdServerCertName = "etcd/server.crt"
// EtcdServerKeyName defines etcd's server key name
EtcdServerKeyName = "etcd/server.key"
// EtcdServerCertCommonName defines etcd's server certificate common name (CN)
EtcdServerCertCommonName = "kube-etcd"
// EtcdPeerCertAndKeyBaseName defines etcd's peer certificate and key base name
EtcdPeerCertAndKeyBaseName = "etcd/peer"
@ -80,16 +87,23 @@ const (
EtcdPeerCertName = "etcd/peer.crt"
// EtcdPeerKeyName defines etcd's peer key name
EtcdPeerKeyName = "etcd/peer.key"
// EtcdPeerCertCommonName defines etcd's peer certificate common name (CN)
EtcdPeerCertCommonName = "kube-etcd-peer"
// APIServerEtcdClientCertAndKeyBaseName defines etcd client certificate and key base name
// EtcdHealthcheckClientCertAndKeyBaseName defines etcd's healthcheck client certificate and key base name
EtcdHealthcheckClientCertAndKeyBaseName = "etcd/healthcheck-client"
// EtcdHealthcheckClientCertName defines etcd's healthcheck client certificate name
EtcdHealthcheckClientCertName = "etcd/healthcheck-client.crt"
// EtcdHealthcheckClientKeyName defines etcd's healthcheck client key name
EtcdHealthcheckClientKeyName = "etcd/healthcheck-client.key"
// EtcdHealthcheckClientCertCommonName defines etcd's healthcheck client certificate common name (CN)
EtcdHealthcheckClientCertCommonName = "kube-etcd-healthcheck-client"
// APIServerEtcdClientCertAndKeyBaseName defines apiserver's etcd client certificate and key base name
APIServerEtcdClientCertAndKeyBaseName = "apiserver-etcd-client"
// APIServerEtcdClientCertName defines etcd client certificate name
// APIServerEtcdClientCertName defines apiserver's etcd client certificate name
APIServerEtcdClientCertName = "apiserver-etcd-client.crt"
// APIServerEtcdClientKeyName defines etcd client key name
// APIServerEtcdClientKeyName defines apiserver's etcd client key name
APIServerEtcdClientKeyName = "apiserver-etcd-client.key"
// APIServerEtcdClientCertCommonName defines etcd client certificate common name (CN)
// APIServerEtcdClientCertCommonName defines apiserver's etcd client certificate common name (CN)
APIServerEtcdClientCertCommonName = "kube-apiserver-etcd-client"
// ServiceAccountKeyBaseName defines SA key base name
@ -145,17 +159,16 @@ const (
// system:nodes group subject is removed if present.
NodesClusterRoleBinding = "system:node"
// KubeletBaseConfigMapRoleName defines the base kubelet configuration ConfigMap.
KubeletBaseConfigMapRoleName = "kubeadm:kubelet-base-configmap"
// APICallRetryInterval defines how long kubeadm should wait before retrying a failed API operation
APICallRetryInterval = 500 * time.Millisecond
// DiscoveryRetryInterval specifies how long kubeadm should wait before retrying to connect to the master when doing discovery
DiscoveryRetryInterval = 5 * time.Second
// MarkMasterTimeout specifies how long kubeadm should wait for applying the label and taint on the master before timing out
MarkMasterTimeout = 2 * time.Minute
// PatchNodeTimeout specifies how long kubeadm should wait for applying the label and taint on the master before timing out
PatchNodeTimeout = 2 * time.Minute
// UpdateNodeTimeout specifies how long kubeadm should wait for updating node with the initial remote configuration of kubelet before timing out
UpdateNodeTimeout = 2 * time.Minute
// TLSBootstrapTimeout specifies how long kubeadm should wait for the kubelet to perform the TLS Bootstrap
TLSBootstrapTimeout = 2 * time.Minute
// MinimumAddressesInServiceSubnet defines minimum amount of nodes the Service subnet should allow.
// We need at least ten, because the DNS service is always at the tenth cluster clusterIP
@ -169,33 +182,51 @@ const (
// This is a duplicate definition of the constant in pkg/controller/service/service_controller.go
LabelNodeRoleMaster = "node-role.kubernetes.io/master"
// AnnotationKubeadmCRISocket specifies the annotation kubeadm uses to preserve the crisocket information given to kubeadm at
// init/join time for use later. kubeadm annotates the node object with this information
AnnotationKubeadmCRISocket = "kubeadm.alpha.kubernetes.io/cri-socket"
// MasterConfigurationConfigMap specifies in what ConfigMap in the kube-system namespace the `kubeadm init` configuration should be stored
MasterConfigurationConfigMap = "kubeadm-config"
// MasterConfigurationConfigMapKey specifies in what ConfigMap key the master configuration should be stored
MasterConfigurationConfigMapKey = "MasterConfiguration"
// KubeletBaseConfigurationConfigMap specifies in what ConfigMap in the kube-system namespace the initial remote configuration of kubelet should be stored
KubeletBaseConfigurationConfigMap = "kubelet-base-config-1.9"
// KubeletBaseConfigurationConfigMapPrefix specifies in what ConfigMap in the kube-system namespace the initial remote configuration of kubelet should be stored
KubeletBaseConfigurationConfigMapPrefix = "kubelet-config-"
// KubeletBaseConfigurationConfigMapKey specifies in what ConfigMap key the initial remote configuration of kubelet should be stored
// TODO: Use the constant ("kubelet.config.k8s.io") defined in pkg/kubelet/kubeletconfig/util/keys/keys.go
// after https://github.com/kubernetes/kubernetes/pull/53833 being merged.
KubeletBaseConfigurationConfigMapKey = "kubelet"
// KubeletBaseConfigurationDir specifies the directory on the node where stores the initial remote configuration of kubelet
KubeletBaseConfigurationDir = "/var/lib/kubelet/config/init"
// KubeletBaseConfigMapRolePrefix defines the base kubelet configuration ConfigMap.
KubeletBaseConfigMapRolePrefix = "kubeadm:kubelet-config-"
// KubeletBaseConfigurationFile specifies the file name on the node which stores initial remote configuration of kubelet
// TODO: Use the constant ("kubelet.config.k8s.io") defined in pkg/kubelet/kubeletconfig/util/keys/keys.go
// after https://github.com/kubernetes/kubernetes/pull/53833 being merged.
KubeletBaseConfigurationFile = "kubelet"
// KubeletRunDirectory specifies the directory where the kubelet runtime information is stored.
// TODO: Make hard-coded "/var/lib/kubelet" strings reference this constant.
KubeletRunDirectory = "/var/lib/kubelet"
// KubeletConfigurationFileName specifies the file name on the node which stores initial remote configuration of kubelet
// This file should exist under KubeletRunDirectory
KubeletConfigurationFileName = "config.yaml"
// DynamicKubeletConfigurationDirectoryName specifies the directory which stores the dynamic configuration checkpoints for the kubelet
// This directory should exist under KubeletRunDirectory
DynamicKubeletConfigurationDirectoryName = "dynamic-config"
// KubeletEnvFileName is a file "kubeadm init" writes at runtime. Using that interface, kubeadm can customize certain
// kubelet flags conditionally based on the environment at runtime. Also, parameters given to the configuration file
// might be passed through this file. "kubeadm init" writes one variable, with the name ${KubeletEnvFileVariableName}.
// This file should exist under KubeletRunDirectory
KubeletEnvFileName = "kubeadm-flags.env"
// KubeletEnvFileVariableName specifies the shell script variable name "kubeadm init" should write a value to in KubeletEnvFile
KubeletEnvFileVariableName = "KUBELET_KUBEADM_ARGS"
// MinExternalEtcdVersion indicates minimum external etcd version which kubeadm supports
MinExternalEtcdVersion = "3.1.11"
MinExternalEtcdVersion = "3.2.17"
// DefaultEtcdVersion indicates the default etcd version that kubeadm uses
DefaultEtcdVersion = "3.2.14"
DefaultEtcdVersion = "3.2.18"
// Etcd defines variable used internally when referring to etcd component
Etcd = "etcd"
@ -243,6 +274,17 @@ const (
KubeAuditPolicyLogVolumeName = "audit-log"
// StaticPodAuditPolicyLogDir is the name of the directory in the static pod that will have the audit logs
StaticPodAuditPolicyLogDir = "/var/log/kubernetes/audit"
// LeaseEndpointReconcilerType will select a storage based reconciler
// Copied from pkg/master/reconcilers to avoid pulling extra dependencies
// TODO: Import this constant from a consts only package, that does not pull any further dependencies.
LeaseEndpointReconcilerType = "lease"
// KubeDNSVersion is the version of kube-dns to be deployed if it is used
KubeDNSVersion = "1.14.10"
// CoreDNSVersion is the version of CoreDNS to be deployed if it is used
CoreDNSVersion = "1.1.3"
)
var (
@ -258,11 +300,6 @@ var (
Effect: v1.TaintEffectNoSchedule,
}
// AuthorizationPolicyPath defines the supported location of authorization policy file
AuthorizationPolicyPath = filepath.Join(KubernetesDir, "abac_policy.json")
// AuthorizationWebhookConfigPath defines the supported location of webhook config file
AuthorizationWebhookConfigPath = filepath.Join(KubernetesDir, "webhook_authz.conf")
// DefaultTokenUsages specifies the default functions a token will get
DefaultTokenUsages = bootstrapapi.KnownTokenUsages
@ -273,16 +310,16 @@ var (
MasterComponents = []string{KubeAPIServer, KubeControllerManager, KubeScheduler}
// MinimumControlPlaneVersion specifies the minimum control plane version kubeadm can deploy
MinimumControlPlaneVersion = version.MustParseSemantic("v1.9.0")
MinimumControlPlaneVersion = version.MustParseSemantic("v1.10.0")
// MinimumKubeletVersion specifies the minimum version of kubelet which kubeadm supports
MinimumKubeletVersion = version.MustParseSemantic("v1.9.0")
MinimumKubeletVersion = version.MustParseSemantic("v1.10.0")
// SupportedEtcdVersion lists officially supported etcd versions with corresponding kubernetes releases
SupportedEtcdVersion = map[uint8]string{
9: "3.1.11",
10: "3.2.14",
11: "3.2.14",
10: "3.1.12",
11: "3.2.18",
12: "3.2.18",
}
)
@ -301,7 +338,7 @@ func EtcdSupportedVersion(versionString string) (*version.Version, error) {
}
return etcdVersion, nil
}
return nil, fmt.Errorf("Unsupported or unknown kubernetes version")
return nil, fmt.Errorf("Unsupported or unknown kubernetes version(%v)", kubernetesVersion)
}
// GetStaticPodDirectory returns the location on the disk where the Static Pod should be present
@ -319,6 +356,16 @@ func GetAdminKubeConfigPath() string {
return filepath.Join(KubernetesDir, AdminKubeConfigFileName)
}
// GetBootstrapKubeletKubeConfigPath returns the location on the disk where bootstrap kubelet kubeconfig is located by default
func GetBootstrapKubeletKubeConfigPath() string {
return filepath.Join(KubernetesDir, KubeletBootstrapKubeConfigFileName)
}
// GetKubeletKubeConfigPath returns the location on the disk where kubelet kubeconfig is located by default
func GetKubeletKubeConfigPath() string {
return filepath.Join(KubernetesDir, KubeletKubeConfigFileName)
}
// AddSelfHostedPrefix adds the self-hosted- prefix to the component name
func AddSelfHostedPrefix(componentName string) string {
return fmt.Sprintf("%s%s", SelfHostingPrefix, componentName)
@ -326,18 +373,36 @@ func AddSelfHostedPrefix(componentName string) string {
// CreateTempDirForKubeadm is a function that creates a temporary directory under /etc/kubernetes/tmp (not using /tmp as that would potentially be dangerous)
func CreateTempDirForKubeadm(dirName string) (string, error) {
tempDir := path.Join(KubernetesDir, TempDirForKubeadm)
// creates target folder if not already exists
if err := os.MkdirAll(TempDirForKubeadm, 0700); err != nil {
return "", fmt.Errorf("failed to create directory %q: %v", TempDirForKubeadm, err)
if err := os.MkdirAll(tempDir, 0700); err != nil {
return "", fmt.Errorf("failed to create directory %q: %v", tempDir, err)
}
tempDir, err := ioutil.TempDir(TempDirForKubeadm, dirName)
tempDir, err := ioutil.TempDir(tempDir, dirName)
if err != nil {
return "", fmt.Errorf("couldn't create a temporary directory: %v", err)
}
return tempDir, nil
}
// CreateTimestampDirForKubeadm is a function that creates a temporary directory under /etc/kubernetes/tmp formatted with the current date
func CreateTimestampDirForKubeadm(dirName string) (string, error) {
tempDir := path.Join(KubernetesDir, TempDirForKubeadm)
// creates target folder if not already exists
if err := os.MkdirAll(tempDir, 0700); err != nil {
return "", fmt.Errorf("failed to create directory %q: %v", tempDir, err)
}
timestampDirName := fmt.Sprintf("%s-%s", dirName, time.Now().Format("2006-01-02-15-04-05"))
timestampDir := path.Join(tempDir, timestampDirName)
if err := os.Mkdir(timestampDir, 0700); err != nil {
return "", fmt.Errorf("could not create timestamp directory: %v", err)
}
return timestampDir, nil
}
// GetDNSIP returns a dnsIP, which is 10th IP in svcSubnet CIDR range
func GetDNSIP(svcSubnet string) (net.IP, error) {
// Get the service subnet CIDR
@ -359,3 +424,13 @@ func GetDNSIP(svcSubnet string) (net.IP, error) {
func GetStaticPodAuditPolicyFile() string {
return filepath.Join(KubernetesDir, AuditPolicyDir, AuditPolicyFile)
}
// GetDNSVersion is a handy function that returns the DNS version by DNS type
func GetDNSVersion(dnsType string) string {
switch dnsType {
case CoreDNS:
return CoreDNSVersion
default:
return KubeDNSVersion
}
}

View File

@ -18,9 +18,10 @@ package constants
import (
"fmt"
"k8s.io/kubernetes/pkg/util/version"
"strings"
"testing"
"k8s.io/kubernetes/pkg/util/version"
)
func TestGetStaticPodDirectory(t *testing.T) {
@ -49,6 +50,32 @@ func TestGetAdminKubeConfigPath(t *testing.T) {
}
}
func TestGetBootstrapKubeletKubeConfigPath(t *testing.T) {
expected := "/etc/kubernetes/bootstrap-kubelet.conf"
actual := GetBootstrapKubeletKubeConfigPath()
if actual != expected {
t.Errorf(
"failed GetBootstrapKubeletKubeConfigPath:\n\texpected: %s\n\t actual: %s",
expected,
actual,
)
}
}
func TestGetKubeletKubeConfigPath(t *testing.T) {
expected := "/etc/kubernetes/kubelet.conf"
actual := GetKubeletKubeConfigPath()
if actual != expected {
t.Errorf(
"failed GetKubeletKubeConfigPath:\n\texpected: %s\n\t actual: %s",
expected,
actual,
)
}
}
func TestGetStaticPodFilepath(t *testing.T) {
var tests = []struct {
componentName, manifestsDir, expected string
@ -123,26 +150,26 @@ func TestEtcdSupportedVersion(t *testing.T) {
{
kubernetesVersion: "1.99.0",
expectedVersion: nil,
expectedError: fmt.Errorf("Unsupported or unknown kubernetes version"),
},
{
kubernetesVersion: "1.9.0",
expectedVersion: version.MustParseSemantic("3.1.11"),
expectedError: nil,
},
{
kubernetesVersion: "1.9.2",
expectedVersion: version.MustParseSemantic("3.1.11"),
expectedError: nil,
expectedError: fmt.Errorf("Unsupported or unknown kubernetes version(1.99.0)"),
},
{
kubernetesVersion: "1.10.0",
expectedVersion: version.MustParseSemantic("3.2.14"),
expectedVersion: version.MustParseSemantic("3.1.12"),
expectedError: nil,
},
{
kubernetesVersion: "1.10.1",
expectedVersion: version.MustParseSemantic("3.2.14"),
kubernetesVersion: "1.10.2",
expectedVersion: version.MustParseSemantic("3.1.12"),
expectedError: nil,
},
{
kubernetesVersion: "1.11.0",
expectedVersion: version.MustParseSemantic("3.2.18"),
expectedError: nil,
},
{
kubernetesVersion: "1.12.1",
expectedVersion: version.MustParseSemantic("3.2.18"),
expectedError: nil,
},
}
@ -171,3 +198,29 @@ func TestEtcdSupportedVersion(t *testing.T) {
}
}
}
func TestGetKubeDNSVersion(t *testing.T) {
var tests = []struct {
dns string
expected string
}{
{
dns: KubeDNS,
expected: KubeDNSVersion,
},
{
dns: CoreDNS,
expected: CoreDNSVersion,
},
}
for _, rt := range tests {
actualDNSVersion := GetDNSVersion(rt.dns)
if actualDNSVersion != rt.expected {
t.Errorf(
"failed GetDNSVersion:\n\texpected: %s\n\t actual: %s",
rt.expected,
actualDNSVersion,
)
}
}
}

View File

@ -43,7 +43,7 @@ func For(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) {
return kubeconfigutil.CreateWithToken(
clusterinfo.Server,
"kubernetes",
cfg.ClusterName,
TokenUser,
clusterinfo.CertificateAuthorityData,
cfg.TLSBootstrapToken,
@ -55,17 +55,17 @@ func GetValidatedClusterInfoObject(cfg *kubeadmapi.NodeConfiguration) (*clientcm
switch {
case len(cfg.DiscoveryFile) != 0:
if isHTTPSURL(cfg.DiscoveryFile) {
return https.RetrieveValidatedClusterInfo(cfg.DiscoveryFile)
return https.RetrieveValidatedClusterInfo(cfg.DiscoveryFile, cfg.ClusterName)
}
return file.RetrieveValidatedClusterInfo(cfg.DiscoveryFile)
return file.RetrieveValidatedClusterInfo(cfg.DiscoveryFile, cfg.ClusterName)
case len(cfg.DiscoveryToken) != 0:
return token.RetrieveValidatedClusterInfo(cfg.DiscoveryToken, cfg.DiscoveryTokenAPIServers, cfg.DiscoveryTokenCACertHashes)
return token.RetrieveValidatedClusterInfo(cfg)
default:
return nil, fmt.Errorf("couldn't find a valid discovery configuration")
}
}
// isHTTPSURL checks whether the string is parsable as an URL and whether the Scheme is https
// isHTTPSURL checks whether the string is parsable as a URL and whether the Scheme is https
func isHTTPSURL(s string) bool {
u, err := url.Parse(s)
return err == nil && u.Scheme == "https"

View File

@ -33,18 +33,18 @@ import (
// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk
// securely to the API Server using the provided CA cert and
// optionally refreshes the cluster-info information from the cluster-info ConfigMap
func RetrieveValidatedClusterInfo(filepath string) (*clientcmdapi.Cluster, error) {
func RetrieveValidatedClusterInfo(filepath, clustername string) (*clientcmdapi.Cluster, error) {
clusterinfo, err := clientcmd.LoadFromFile(filepath)
if err != nil {
return nil, err
}
return ValidateClusterInfo(clusterinfo)
return ValidateClusterInfo(clusterinfo, clustername)
}
// ValidateClusterInfo connects to the API Server and makes sure it can talk
// securely to the API Server using the provided CA cert and
// optionally refreshes the cluster-info information from the cluster-info ConfigMap
func ValidateClusterInfo(clusterinfo *clientcmdapi.Config) (*clientcmdapi.Cluster, error) {
func ValidateClusterInfo(clusterinfo *clientcmdapi.Config, clustername string) (*clientcmdapi.Cluster, error) {
err := validateClusterInfoKubeConfig(clusterinfo)
if err != nil {
return nil, err
@ -57,7 +57,7 @@ func ValidateClusterInfo(clusterinfo *clientcmdapi.Config) (*clientcmdapi.Cluste
// We do this in order to not pick up other possible misconfigurations in the clusterinfo file
configFromClusterInfo := kubeconfigutil.CreateBasic(
defaultCluster.Server,
"kubernetes",
clustername,
"", // no user provided
defaultCluster.CertificateAuthorityData,
)

View File

@ -29,7 +29,7 @@ import (
// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk
// securely to the API Server using the provided CA cert and
// optionally refreshes the cluster-info information from the cluster-info ConfigMap
func RetrieveValidatedClusterInfo(httpsURL string) (*clientcmdapi.Cluster, error) {
func RetrieveValidatedClusterInfo(httpsURL, clustername string) (*clientcmdapi.Cluster, error) {
client := &http.Client{Transport: netutil.SetOldTransportDefaults(&http.Transport{})}
response, err := client.Get(httpsURL)
if err != nil {
@ -46,5 +46,5 @@ func RetrieveValidatedClusterInfo(httpsURL string) (*clientcmdapi.Cluster, error
if err != nil {
return nil, err
}
return file.ValidateClusterInfo(clusterinfo)
return file.ValidateClusterInfo(clusterinfo, clustername)
}

View File

@ -11,10 +11,10 @@ go_library(
srcs = ["token.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/discovery/token",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util/pubkeypin:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/controller/bootstrap:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -22,6 +22,7 @@ import (
"encoding/pem"
"fmt"
"sync"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -29,10 +30,10 @@ import (
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
"k8s.io/kubernetes/pkg/controller/bootstrap"
)
@ -40,26 +41,26 @@ import (
const BootstrapUser = "token-bootstrap-client"
// RetrieveValidatedClusterInfo connects to the API Server and tries to fetch the cluster-info ConfigMap
// It then makes sure it can trust the API Server by looking at the JWS-signed tokens and (if rootCAPubKeys is not empty)
// It then makes sure it can trust the API Server by looking at the JWS-signed tokens and (if cfg.DiscoveryTokenCACertHashes is not empty)
// validating the cluster CA against a set of pinned public keys
func RetrieveValidatedClusterInfo(discoveryToken string, tokenAPIServers, rootCAPubKeys []string) (*clientcmdapi.Cluster, error) {
tokenID, tokenSecret, err := tokenutil.ParseToken(discoveryToken)
func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Cluster, error) {
token, err := kubeadmapi.NewBootstrapTokenString(cfg.DiscoveryToken)
if err != nil {
return nil, err
}
// Load the cfg.DiscoveryTokenCACertHashes into a pubkeypin.Set
pubKeyPins := pubkeypin.NewSet()
err = pubKeyPins.Allow(rootCAPubKeys...)
err = pubKeyPins.Allow(cfg.DiscoveryTokenCACertHashes...)
if err != nil {
return nil, err
}
// The function below runs for every endpoint, and all endpoints races with each other.
// The endpoint that wins the race and completes the task first gets its kubeconfig returned below
baseKubeConfig := runForEndpointsAndReturnFirst(tokenAPIServers, func(endpoint string) (*clientcmdapi.Config, error) {
baseKubeConfig, err := runForEndpointsAndReturnFirst(cfg.DiscoveryTokenAPIServers, cfg.DiscoveryTimeout.Duration, func(endpoint string) (*clientcmdapi.Config, error) {
insecureBootstrapConfig := buildInsecureBootstrapKubeConfig(endpoint)
insecureBootstrapConfig := buildInsecureBootstrapKubeConfig(endpoint, cfg.ClusterName)
clusterName := insecureBootstrapConfig.Contexts[insecureBootstrapConfig.CurrentContext].Cluster
insecureClient, err := kubeconfigutil.ToClientSet(insecureBootstrapConfig)
@ -86,11 +87,11 @@ func RetrieveValidatedClusterInfo(discoveryToken string, tokenAPIServers, rootCA
if !ok || len(insecureKubeconfigString) == 0 {
return nil, fmt.Errorf("there is no %s key in the %s ConfigMap. This API Server isn't set up for token bootstrapping, can't connect", bootstrapapi.KubeConfigKey, bootstrapapi.ConfigMapClusterInfo)
}
detachedJWSToken, ok := insecureClusterInfo.Data[bootstrapapi.JWSSignatureKeyPrefix+tokenID]
detachedJWSToken, ok := insecureClusterInfo.Data[bootstrapapi.JWSSignatureKeyPrefix+token.ID]
if !ok || len(detachedJWSToken) == 0 {
return nil, fmt.Errorf("token id %q is invalid for this cluster or it has expired. Use \"kubeadm token create\" on the master node to creating a new valid token", tokenID)
return nil, fmt.Errorf("token id %q is invalid for this cluster or it has expired. Use \"kubeadm token create\" on the master node to creating a new valid token", token.ID)
}
if !bootstrap.DetachedTokenIsValid(detachedJWSToken, insecureKubeconfigString, tokenID, tokenSecret) {
if !bootstrap.DetachedTokenIsValid(detachedJWSToken, insecureKubeconfigString, token.ID, token.Secret) {
return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object, can't trust this API Server")
}
insecureKubeconfigBytes := []byte(insecureKubeconfigString)
@ -126,7 +127,7 @@ func RetrieveValidatedClusterInfo(discoveryToken string, tokenAPIServers, rootCA
}
// Now that we know the proported cluster CA, connect back a second time validating with that CA
secureBootstrapConfig := buildSecureBootstrapKubeConfig(endpoint, clusterCABytes)
secureBootstrapConfig := buildSecureBootstrapKubeConfig(endpoint, clusterCABytes, clusterName)
secureClient, err := kubeconfigutil.ToClientSet(secureBootstrapConfig)
if err != nil {
return nil, err
@ -158,28 +159,30 @@ func RetrieveValidatedClusterInfo(discoveryToken string, tokenAPIServers, rootCA
fmt.Printf("[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server %q\n", endpoint)
return secureKubeconfig, nil
})
if err != nil {
return nil, err
}
return kubeconfigutil.GetClusterFromKubeConfig(baseKubeConfig), nil
}
// buildInsecureBootstrapKubeConfig makes a KubeConfig object that connects insecurely to the API Server for bootstrapping purposes
func buildInsecureBootstrapKubeConfig(endpoint string) *clientcmdapi.Config {
func buildInsecureBootstrapKubeConfig(endpoint, clustername string) *clientcmdapi.Config {
masterEndpoint := fmt.Sprintf("https://%s", endpoint)
clusterName := "kubernetes"
bootstrapConfig := kubeconfigutil.CreateBasic(masterEndpoint, clusterName, BootstrapUser, []byte{})
bootstrapConfig.Clusters[clusterName].InsecureSkipTLSVerify = true
bootstrapConfig := kubeconfigutil.CreateBasic(masterEndpoint, clustername, BootstrapUser, []byte{})
bootstrapConfig.Clusters[clustername].InsecureSkipTLSVerify = true
return bootstrapConfig
}
// buildSecureBootstrapKubeConfig makes a KubeConfig object that connects securely to the API Server for bootstrapping purposes (validating with the specified CA)
func buildSecureBootstrapKubeConfig(endpoint string, caCert []byte) *clientcmdapi.Config {
func buildSecureBootstrapKubeConfig(endpoint string, caCert []byte, clustername string) *clientcmdapi.Config {
masterEndpoint := fmt.Sprintf("https://%s", endpoint)
bootstrapConfig := kubeconfigutil.CreateBasic(masterEndpoint, "kubernetes", BootstrapUser, caCert)
bootstrapConfig := kubeconfigutil.CreateBasic(masterEndpoint, clustername, BootstrapUser, caCert)
return bootstrapConfig
}
// runForEndpointsAndReturnFirst loops the endpoints slice and let's the endpoints race for connecting to the master
func runForEndpointsAndReturnFirst(endpoints []string, fetchKubeConfigFunc func(string) (*clientcmdapi.Config, error)) *clientcmdapi.Config {
func runForEndpointsAndReturnFirst(endpoints []string, discoveryTimeout time.Duration, fetchKubeConfigFunc func(string) (*clientcmdapi.Config, error)) (*clientcmdapi.Config, error) {
stopChan := make(chan struct{})
var resultingKubeConfig *clientcmdapi.Config
var once sync.Once
@ -205,8 +208,17 @@ func runForEndpointsAndReturnFirst(endpoints []string, fetchKubeConfigFunc func(
}, constants.DiscoveryRetryInterval, stopChan)
}(endpoint)
}
wg.Wait()
return resultingKubeConfig
select {
case <-time.After(discoveryTimeout):
close(stopChan)
err := fmt.Errorf("abort connecting to API servers after timeout of %v", discoveryTimeout)
fmt.Printf("[discovery] %v\n", err)
wg.Wait()
return nil, err
case <-stopChan:
wg.Wait()
return resultingKubeConfig, nil
}
}
// parsePEMCert decodes a PEM-formatted certificate and returns it as an x509.Certificate

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