Commit 089947d9 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #41921 from apprenda/kubeadm_join_ux_update_2

Automatic merge from submit-queue (batch tested with PRs 41921, 41695, 42139, 42090, 41949) kubeadm: join ux changes **What this PR does / why we need it**: Update `kubeadm join` UX according to https://github.com/kubernetes/community/pull/381 **Which issue this PR fixes**: fixes # https://github.com/kubernetes/kubeadm/issues/176 **Special notes for your reviewer**: /cc @luxas @jbeda **Release note**: ```release-note NONE ```
parents fed7cea0 1d37c6be
...@@ -37,7 +37,12 @@ func KubeadmFuzzerFuncs(t apitesting.TestingCommon) []interface{} { ...@@ -37,7 +37,12 @@ func KubeadmFuzzerFuncs(t apitesting.TestingCommon) []interface{} {
func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) { func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) {
c.FuzzNoCustom(obj) c.FuzzNoCustom(obj)
obj.CACertPath = "foo" obj.CACertPath = "foo"
obj.Discovery.Token = &kubeadm.TokenDiscovery{} obj.CACertPath = "foo"
obj.DiscoveryFile = "foo"
obj.DiscoveryToken = "foo"
obj.DiscoveryTokenAPIServers = []string{"foo"}
obj.TLSBootstrapToken = "foo"
obj.Token = "foo"
}, },
} }
} }
...@@ -92,8 +92,13 @@ type Etcd struct { ...@@ -92,8 +92,13 @@ type Etcd struct {
type NodeConfiguration struct { type NodeConfiguration struct {
metav1.TypeMeta metav1.TypeMeta
Discovery Discovery CACertPath string
CACertPath string DiscoveryFile string
DiscoveryToken string
// Currently we only pay attention to one api server but hope to support >1 in the future
DiscoveryTokenAPIServers []string
TLSBootstrapToken string
Token string
} }
// ClusterInfo TODO add description // ClusterInfo TODO add description
......
...@@ -16,7 +16,11 @@ limitations under the License. ...@@ -16,7 +16,11 @@ limitations under the License.
package v1alpha1 package v1alpha1
import "k8s.io/apimachinery/pkg/runtime" import (
"net/url"
"k8s.io/apimachinery/pkg/runtime"
)
const ( const (
DefaultServiceDNSDomain = "cluster.local" DefaultServiceDNSDomain = "cluster.local"
...@@ -68,4 +72,17 @@ func SetDefaults_NodeConfiguration(obj *NodeConfiguration) { ...@@ -68,4 +72,17 @@ func SetDefaults_NodeConfiguration(obj *NodeConfiguration) {
if obj.CACertPath == "" { if obj.CACertPath == "" {
obj.CACertPath = DefaultCACertPath 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
}
}
} }
...@@ -82,8 +82,12 @@ type Etcd struct { ...@@ -82,8 +82,12 @@ type Etcd struct {
type NodeConfiguration struct { type NodeConfiguration struct {
metav1.TypeMeta `json:",inline"` metav1.TypeMeta `json:",inline"`
Discovery Discovery `json:"discovery"` CACertPath string `json:"caCertPath"`
CACertPath string `json:"caCertPath"` DiscoveryFile string `json:"discoveryFile"`
DiscoveryToken string `json:"discoveryToken"`
DiscoveryTokenAPIServers []string `json:"discoveryTokenAPIServers"`
TLSBootstrapToken string `json:"tlsBootstrapToken"`
Token string `json:"token"`
} }
// ClusterInfo TODO add description // ClusterInfo TODO add description
......
...@@ -8,6 +8,17 @@ load( ...@@ -8,6 +8,17 @@ load(
"go_test", "go_test",
) )
go_test(
name = "go_default_test",
srcs = ["validation_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
],
)
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = ["validation.go"], srcs = ["validation.go"],
...@@ -15,6 +26,7 @@ go_library( ...@@ -15,6 +26,7 @@ go_library(
deps = [ deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/kubeapiserver/authorizer/modes:go_default_library", "//pkg/kubeapiserver/authorizer/modes:go_default_library",
"//pkg/registry/core/service/ipallocator:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field", "//vendor:k8s.io/apimachinery/pkg/util/validation/field",
...@@ -33,14 +45,3 @@ filegroup( ...@@ -33,14 +45,3 @@ filegroup(
srcs = [":package-srcs"], srcs = [":package-srcs"],
tags = ["automanaged"], tags = ["automanaged"],
) )
go_test(
name = "go_default_test",
srcs = ["validation_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
],
)
...@@ -17,13 +17,17 @@ limitations under the License. ...@@ -17,13 +17,17 @@ limitations under the License.
package validation package validation
import ( import (
"fmt"
"net" "net"
"net/url"
"os"
"path" "path"
"strings" "strings"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/constants"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
) )
...@@ -45,7 +49,6 @@ var cloudproviders = []string{ ...@@ -45,7 +49,6 @@ var cloudproviders = []string{
func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList { func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateDiscovery(&c.Discovery, field.NewPath("discovery"))...)
allErrs = append(allErrs, ValidateServiceSubnet(c.Networking.ServiceSubnet, field.NewPath("service subnet"))...) allErrs = append(allErrs, ValidateServiceSubnet(c.Networking.ServiceSubnet, field.NewPath("service subnet"))...)
allErrs = append(allErrs, ValidateCloudProvider(c.CloudProvider, field.NewPath("cloudprovider"))...) allErrs = append(allErrs, ValidateCloudProvider(c.CloudProvider, field.NewPath("cloudprovider"))...)
allErrs = append(allErrs, ValidateAuthorizationMode(c.AuthorizationMode, field.NewPath("authorization-mode"))...) allErrs = append(allErrs, ValidateAuthorizationMode(c.AuthorizationMode, field.NewPath("authorization-mode"))...)
...@@ -54,61 +57,98 @@ func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList ...@@ -54,61 +57,98 @@ func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList
func ValidateNodeConfiguration(c *kubeadm.NodeConfiguration) field.ErrorList { func ValidateNodeConfiguration(c *kubeadm.NodeConfiguration) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateDiscovery(&c.Discovery, field.NewPath("discovery"))...) allErrs = append(allErrs, ValidateDiscovery(c, field.NewPath("discovery"))...)
if !path.IsAbs(c.CACertPath) || !strings.HasSuffix(c.CACertPath, ".crt") { if !path.IsAbs(c.CACertPath) || !strings.HasSuffix(c.CACertPath, ".crt") {
allErrs = append(allErrs, field.Invalid(field.NewPath("ca-cert-path"), nil, "the ca certificate path must be an absolute path")) allErrs = append(allErrs, field.Invalid(field.NewPath("ca-cert-path"), c.CACertPath, "the ca certificate path must be an absolute path"))
} }
return allErrs return allErrs
} }
func ValidateDiscovery(c *kubeadm.Discovery, fldPath *field.Path) field.ErrorList { func ValidateAuthorizationMode(authzMode string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} if !authzmodes.IsValidAuthorizationMode(authzMode) {
var count int return field.ErrorList{field.Invalid(fldPath, authzMode, "invalid authorization mode")}
if c.Token != nil {
allErrs = append(allErrs, ValidateTokenDiscovery(c.Token, fldPath)...)
count++
}
if c.File != nil {
allErrs = append(allErrs, ValidateFileDiscovery(c.File, fldPath)...)
count++
} }
if c.HTTPS != nil { return field.ErrorList{}
allErrs = append(allErrs, ValidateHTTPSDiscovery(c.HTTPS, fldPath)...) }
count++
func ValidateDiscovery(c *kubeadm.NodeConfiguration, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(c.DiscoveryToken) != 0 {
allErrs = append(allErrs, ValidateToken(c.DiscoveryToken, fldPath)...)
} }
if count != 1 { if len(c.DiscoveryFile) != 0 {
allErrs = append(allErrs, field.Invalid(fldPath, nil, "exactly one discovery strategy can be provided")) allErrs = append(allErrs, ValidateDiscoveryFile(c.DiscoveryFile, fldPath)...)
} }
allErrs = append(allErrs, ValidateArgSelection(c, fldPath)...)
allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, fldPath)...)
allErrs = append(allErrs, ValidateJoinDiscoveryTokenAPIServer(c, fldPath)...)
return allErrs return allErrs
} }
func ValidateFileDiscovery(c *kubeadm.FileDiscovery, fldPath *field.Path) field.ErrorList { func ValidateArgSelection(cfg *kubeadm.NodeConfiguration, fldPath *field.Path) field.ErrorList {
allErrs := 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"))
}
if len(cfg.DiscoveryToken) == 0 && len(cfg.DiscoveryFile) == 0 {
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"))
}
// TODO remove once we support multiple api servers
if len(cfg.DiscoveryTokenAPIServers) > 1 {
fmt.Println("[validation] WARNING: kubeadm doesn't fully support multiple API Servers yet")
}
return allErrs return allErrs
} }
func ValidateHTTPSDiscovery(c *kubeadm.HTTPSDiscovery, fldPath *field.Path) field.ErrorList { func ValidateJoinDiscoveryTokenAPIServer(c *kubeadm.NodeConfiguration, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
for _, m := range c.DiscoveryTokenAPIServers {
_, _, err := net.SplitHostPort(m)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, m, err.Error()))
}
}
return allErrs return allErrs
} }
func ValidateTokenDiscovery(c *kubeadm.TokenDiscovery, fldPath *field.Path) field.ErrorList { func ValidateDiscoveryFile(discoveryFile string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if len(c.ID) == 0 || len(c.Secret) == 0 { u, err := url.Parse(discoveryFile)
allErrs = append(allErrs, field.Invalid(fldPath, nil, "token must be specific as <ID>:<Secret>")) if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, discoveryFile, "not a valid HTTPS URL or a file on disk"))
return allErrs
} }
if len(c.Addresses) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, nil, "at least one address is required")) if u.Scheme == "" {
// URIs with no scheme should be treated as files
if _, err := os.Stat(discoveryFile); os.IsNotExist(err) {
allErrs = append(allErrs, field.Invalid(fldPath, discoveryFile, "not a valid HTTPS URL or a file on disk"))
}
return allErrs
}
if u.Scheme != "https" {
allErrs = append(allErrs, field.Invalid(fldPath, discoveryFile, "if an URL is used, the scheme must be https"))
} }
return allErrs return allErrs
} }
func ValidateAuthorizationMode(authzMode string, fldPath *field.Path) field.ErrorList { func ValidateToken(t string, fldPath *field.Path) field.ErrorList {
if !authzmodes.IsValidAuthorizationMode(authzMode) { allErrs := field.ErrorList{}
return field.ErrorList{field.Invalid(fldPath, nil, "invalid authorization mode")}
id, secret, err := tokenutil.ParseToken(t)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, nil, err.Error()))
} }
return field.ErrorList{}
if len(id) == 0 || len(secret) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, nil, "token must be of form '[a-z0-9]{6}.[a-z0-9]{16}'"))
}
return allErrs
} }
func ValidateServiceSubnet(subnet string, fldPath *field.Path) field.ErrorList { func ValidateServiceSubnet(subnet string, fldPath *field.Path) field.ErrorList {
...@@ -117,7 +157,7 @@ func ValidateServiceSubnet(subnet string, fldPath *field.Path) field.ErrorList { ...@@ -117,7 +157,7 @@ func ValidateServiceSubnet(subnet string, fldPath *field.Path) field.ErrorList {
return field.ErrorList{field.Invalid(fldPath, nil, "couldn't parse the service subnet")} return field.ErrorList{field.Invalid(fldPath, nil, "couldn't parse the service subnet")}
} }
numAddresses := ipallocator.RangeSize(svcSubnet) numAddresses := ipallocator.RangeSize(svcSubnet)
if numAddresses < kubeadmconstants.MinimumAddressesInServiceSubnet { if numAddresses < constants.MinimumAddressesInServiceSubnet {
return field.ErrorList{field.Invalid(fldPath, nil, "service subnet is too small")} return field.ErrorList{field.Invalid(fldPath, nil, "service subnet is too small")}
} }
return field.ErrorList{} return field.ErrorList{}
......
...@@ -25,17 +25,16 @@ import ( ...@@ -25,17 +25,16 @@ import (
func TestValidateTokenDiscovery(t *testing.T) { func TestValidateTokenDiscovery(t *testing.T) {
var tests = []struct { var tests = []struct {
c *kubeadm.TokenDiscovery c *kubeadm.NodeConfiguration
f *field.Path f *field.Path
expected bool expected bool
}{ }{
{&kubeadm.TokenDiscovery{ID: "772ef5", Secret: "6b6baab1d4a0a171", Addresses: []string{"192.168.122.100:9898"}}, nil, true}, {&kubeadm.NodeConfiguration{Token: "772ef5.6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"192.168.122.100:9898"}}, nil, true},
{&kubeadm.TokenDiscovery{ID: "", Secret: "6b6baab1d4a0a171", Addresses: []string{"192.168.122.100:9898"}}, nil, false}, {&kubeadm.NodeConfiguration{Token: ".6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"192.168.122.100:9898"}}, nil, false},
{&kubeadm.TokenDiscovery{ID: "772ef5", Secret: "", Addresses: []string{"192.168.122.100:9898"}}, nil, false}, {&kubeadm.NodeConfiguration{Token: "772ef5.", DiscoveryTokenAPIServers: []string{"192.168.122.100:9898"}}, nil, false},
{&kubeadm.TokenDiscovery{ID: "772ef5", Secret: "6b6baab1d4a0a171", Addresses: []string{}}, nil, false},
} }
for _, rt := range tests { for _, rt := range tests {
err := ValidateTokenDiscovery(rt.c, rt.f).ToAggregate() err := ValidateToken(rt.c.Token, rt.f).ToAggregate()
if (err == nil) != rt.expected { if (err == nil) != rt.expected {
t.Errorf( t.Errorf(
"failed ValidateTokenDiscovery:\n\texpected: %t\n\t actual: %t", "failed ValidateTokenDiscovery:\n\texpected: %t\n\t actual: %t",
...@@ -128,21 +127,6 @@ func TestValidateMasterConfiguration(t *testing.T) { ...@@ -128,21 +127,6 @@ func TestValidateMasterConfiguration(t *testing.T) {
{&kubeadm.MasterConfiguration{ {&kubeadm.MasterConfiguration{
Discovery: kubeadm.Discovery{ Discovery: kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"}, HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"},
File: &kubeadm.FileDiscovery{Path: "foo"},
Token: &kubeadm.TokenDiscovery{
ID: "abcdef",
Secret: "1234567890123456",
Addresses: []string{"foobar"},
},
},
AuthorizationMode: "RBAC",
Networking: kubeadm.Networking{
ServiceSubnet: "10.96.0.1/12",
},
}, false},
{&kubeadm.MasterConfiguration{
Discovery: kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"},
}, },
AuthorizationMode: "RBAC", AuthorizationMode: "RBAC",
Networking: kubeadm.Networking{ Networking: kubeadm.Networking{
...@@ -191,45 +175,10 @@ func TestValidateNodeConfiguration(t *testing.T) { ...@@ -191,45 +175,10 @@ func TestValidateNodeConfiguration(t *testing.T) {
}{ }{
{&kubeadm.NodeConfiguration{}, false}, {&kubeadm.NodeConfiguration{}, false},
{&kubeadm.NodeConfiguration{ {&kubeadm.NodeConfiguration{
Discovery: kubeadm.Discovery{ DiscoveryFile: "foo",
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"}, DiscoveryToken: "abcdef.1234567890123456@foobar",
File: &kubeadm.FileDiscovery{Path: "foo"}, CACertPath: "/some/cert.crt",
Token: &kubeadm.TokenDiscovery{
ID: "abcdef",
Secret: "1234567890123456",
Addresses: []string{"foobar"},
},
},
CACertPath: "/some/cert.crt",
}, false},
{&kubeadm.NodeConfiguration{
Discovery: kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"},
},
CACertPath: "/some/path", // no .crt suffix
}, false}, }, false},
{&kubeadm.NodeConfiguration{
Discovery: kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"},
},
CACertPath: "/some/cert.crt",
}, true},
{&kubeadm.NodeConfiguration{
Discovery: kubeadm.Discovery{
File: &kubeadm.FileDiscovery{Path: "foo"},
},
CACertPath: "/some/other/cert.crt",
}, true},
{&kubeadm.NodeConfiguration{
Discovery: kubeadm.Discovery{
Token: &kubeadm.TokenDiscovery{
ID: "abcdef",
Secret: "1234567890123456",
Addresses: []string{"foobar"},
},
},
CACertPath: "/a/third/cert.crt",
}, true},
} }
for _, rt := range tests { for _, rt := range tests {
actual := ValidateNodeConfiguration(rt.s) actual := ValidateNodeConfiguration(rt.s)
......
...@@ -52,37 +52,76 @@ var ( ...@@ -52,37 +52,76 @@ var (
// NewCmdJoin returns "kubeadm join" command. // NewCmdJoin returns "kubeadm join" command.
func NewCmdJoin(out io.Writer) *cobra.Command { func NewCmdJoin(out io.Writer) *cobra.Command {
versioned := &kubeadmapiext.NodeConfiguration{} cfg := &kubeadmapiext.NodeConfiguration{}
api.Scheme.Default(versioned) api.Scheme.Default(cfg)
cfg := kubeadmapi.NodeConfiguration{}
api.Scheme.Convert(versioned, &cfg, nil)
var skipPreFlight bool var skipPreFlight bool
var cfgPath string var cfgPath string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "join <master address>", Use: "join <flags> [DiscoveryTokenAPIServers]",
Short: "Run this on any machine you wish to join an existing cluster", Short: "Run this on any machine you wish to join an existing cluster",
Long: dedent.Dedent(`
When joining a kubeadm initialized cluster, we need to establish
bidirectional trust. This is split into discovery (having the Node
trust the Kubernetes Master) and TLS bootstrap (having the Kubernetes
Master trust the Node).
There are 2 main schemes for discovery. The first is to use a shared
token along with the IP address of the API server. The second is to
provide a file (a subset of the standard kubeconfig file). This file
can be a local file or downloaded via an HTTPS URL. The forms are
kubeadm join --discovery-token abcdef.1234567890abcdef 1.2.3.4:6443,
kubeadm join --discovery-file path/to/file.conf or kubeadm join
--discovery-file https://url/file.conf. Only one form can be used. If
the discovery information is loaded from a URL, HTTPS must be used and
the host installed CA bundle is used to verify the connection.
The TLS bootstrap mechanism is also driven via a shared token. This is
used to temporarily authenticate with the Kubernetes Master to submit a
certificate signing request (CSR) for a locally created key pair. By
default kubeadm will set up the Kubernetes Master to automatically
approve these signing requests. This token is passed in with the
--tls-bootstrap-token abcdef.1234567890abcdef flag.
Often times the same token is use for both parts. In this case, the
--token flag can be used instead of specifying the each token
individually.
`),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
j, err := NewJoin(cfgPath, args, &cfg, skipPreFlight) cfg.DiscoveryTokenAPIServers = args
api.Scheme.Default(cfg)
internalcfg := &kubeadmapi.NodeConfiguration{}
api.Scheme.Convert(cfg, internalcfg, nil)
j, err := NewJoin(cfgPath, args, internalcfg, skipPreFlight)
kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(j.Validate()) kubeadmutil.CheckErr(j.Validate())
kubeadmutil.CheckErr(j.Run(out)) kubeadmutil.CheckErr(j.Run(out))
}, },
} }
cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file") cmd.PersistentFlags().StringVar(
&cfgPath, "config", cfgPath,
"Path to kubeadm config file")
cmd.PersistentFlags().StringVar(
&cfg.DiscoveryFile, "discovery-file", "",
"A file or url from which to load cluster information")
cmd.PersistentFlags().StringVar(
&cfg.DiscoveryToken, "discovery-token", "",
"A token used to validate cluster information fetched from the master")
cmd.PersistentFlags().StringVar(
&cfg.TLSBootstrapToken, "tls-bootstrap-token", "",
"A token used for TLS bootstrapping")
cmd.PersistentFlags().StringVar(
&cfg.Token, "token", "",
"Use this token for both discovery-token and tls-bootstrap-token")
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
&skipPreFlight, "skip-preflight-checks", false, &skipPreFlight, "skip-preflight-checks", false,
"skip preflight checks normally run before modifying the system", "skip preflight checks normally run before modifying the system",
) )
cmd.PersistentFlags().Var(
discovery.NewDiscoveryValue(&cfg.Discovery), "discovery",
"The discovery method kubeadm will use for connecting nodes to the master",
)
return cmd return cmd
} }
...@@ -131,7 +170,7 @@ func (j *Join) Validate() error { ...@@ -131,7 +170,7 @@ func (j *Join) Validate() error {
// Run executes worker node provisioning and tries to join an existing cluster. // Run executes worker node provisioning and tries to join an existing cluster.
func (j *Join) Run(out io.Writer) error { func (j *Join) Run(out io.Writer) error {
cfg, err := discovery.For(j.cfg.Discovery) cfg, err := discovery.For(j.cfg)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api" clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
...@@ -29,27 +30,34 @@ import ( ...@@ -29,27 +30,34 @@ import (
) )
// For identifies and executes the desired discovery mechanism. // For identifies and executes the desired discovery mechanism.
func For(d kubeadmapi.Discovery) (*clientcmdapi.Config, error) { func For(d *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) {
switch { switch {
case d.File != nil: case len(d.DiscoveryFile) != 0:
return runFileDiscovery(d.File) if isHTTPSURL(d.DiscoveryFile) {
case d.HTTPS != nil: return runHTTPSDiscovery(d.DiscoveryFile)
return runHTTPSDiscovery(d.HTTPS) }
case d.Token != nil: return runFileDiscovery(d.DiscoveryFile)
return runTokenDiscovery(d.Token) case len(d.DiscoveryToken) != 0:
return runTokenDiscovery(d.DiscoveryToken, d.DiscoveryTokenAPIServers)
default: default:
return nil, fmt.Errorf("couldn't find a valid discovery configuration.") return nil, fmt.Errorf("couldn't find a valid discovery configuration.")
} }
} }
// isHTTPSURL checks whether the string is parsable as an URL
func isHTTPSURL(s string) bool {
u, err := url.Parse(s)
return err == nil && u.Scheme == "https"
}
// runFileDiscovery executes file-based discovery. // runFileDiscovery executes file-based discovery.
func runFileDiscovery(fd *kubeadmapi.FileDiscovery) (*clientcmdapi.Config, error) { func runFileDiscovery(fd string) (*clientcmdapi.Config, error) {
return clientcmd.LoadFromFile(fd.Path) return clientcmd.LoadFromFile(fd)
} }
// runHTTPSDiscovery executes HTTPS-based discovery. // runHTTPSDiscovery executes HTTPS-based discovery.
func runHTTPSDiscovery(hd *kubeadmapi.HTTPSDiscovery) (*clientcmdapi.Config, error) { func runHTTPSDiscovery(hd string) (*clientcmdapi.Config, error) {
response, err := http.Get(hd.URL) response, err := http.Get(hd)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -64,17 +72,23 @@ func runHTTPSDiscovery(hd *kubeadmapi.HTTPSDiscovery) (*clientcmdapi.Config, err ...@@ -64,17 +72,23 @@ func runHTTPSDiscovery(hd *kubeadmapi.HTTPSDiscovery) (*clientcmdapi.Config, err
} }
// runTokenDiscovery executes token-based discovery. // runTokenDiscovery executes token-based discovery.
func runTokenDiscovery(td *kubeadmapi.TokenDiscovery) (*clientcmdapi.Config, error) { func runTokenDiscovery(td string, m []string) (*clientcmdapi.Config, error) {
if valid, err := tokenutil.ValidateToken(td); valid == false { id, secret, err := tokenutil.ParseToken(td)
if err != nil {
return nil, err
}
t := &kubeadmapi.TokenDiscovery{ID: id, Secret: secret, Addresses: m}
if valid, err := tokenutil.ValidateToken(t); valid == false {
return nil, err return nil, err
} }
clusterInfo, err := kubenode.RetrieveTrustedClusterInfo(td) clusterInfo, err := kubenode.RetrieveTrustedClusterInfo(t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cfg, err := kubenode.EstablishMasterConnection(td, clusterInfo) cfg, err := kubenode.EstablishMasterConnection(t, clusterInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -24,40 +24,37 @@ import ( ...@@ -24,40 +24,37 @@ import (
func TestFor(t *testing.T) { func TestFor(t *testing.T) {
tests := []struct { tests := []struct {
d kubeadm.Discovery d kubeadm.NodeConfiguration
expect bool expect bool
}{ }{
{d: kubeadm.Discovery{}, expect: false}, {d: kubeadm.NodeConfiguration{}, expect: false},
{ {
d: kubeadm.Discovery{ d: kubeadm.NodeConfiguration{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "notnil"}, DiscoveryFile: "notnil",
}, },
expect: false, expect: false,
}, },
{ {
d: kubeadm.Discovery{ d: kubeadm.NodeConfiguration{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "http://localhost"}, DiscoveryFile: "https://localhost",
}, },
expect: false, expect: false,
}, },
{ {
d: kubeadm.Discovery{ d: kubeadm.NodeConfiguration{
File: &kubeadm.FileDiscovery{Path: "notnil"}, DiscoveryFile: "notnil",
}, },
expect: false, expect: false,
}, },
{ {
d: kubeadm.Discovery{ d: kubeadm.NodeConfiguration{
Token: &kubeadm.TokenDiscovery{ DiscoveryToken: "foo.bar@foobar",
ID: "foo", },
Secret: "bar", expect: false,
Addresses: []string{"foobar"},
},
}, expect: false,
}, },
} }
for _, rt := range tests { for _, rt := range tests {
_, actual := For(rt.d) _, actual := For(&rt.d)
if (actual == nil) != rt.expect { if (actual == nil) != rt.expect {
t.Errorf( t.Errorf(
"failed For:\n\texpected: %t\n\t actual: %t", "failed For:\n\texpected: %t\n\t actual: %t",
......
...@@ -46,32 +46,3 @@ func TestCmdJoinConfig(t *testing.T) { ...@@ -46,32 +46,3 @@ func TestCmdJoinConfig(t *testing.T) {
kubeadmReset() kubeadmReset()
} }
} }
func TestCmdJoinDiscovery(t *testing.T) {
if *kubeadmCmdSkip {
t.Log("kubeadm cmd tests being skipped")
t.Skip()
}
var initTest = []struct {
args string
expected bool
}{
{"--discovery=foobar", false},
{"--dicovery=magic", false},
}
for _, rt := range initTest {
_, _, actual := RunCmd(*kubeadmPath, "join", rt.args, "--skip-preflight-checks")
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdJoinDiscovery running 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",
rt.args,
actual,
rt.expected,
(actual == nil),
)
}
kubeadmReset()
}
}
...@@ -154,7 +154,9 @@ disable-attach-detach-reconcile-sync ...@@ -154,7 +154,9 @@ disable-attach-detach-reconcile-sync
disable-filter disable-filter
disable-kubenet disable-kubenet
disable-log-dump disable-log-dump
discovery-file
discovery-port discovery-port
discovery-token
dns-bind-address dns-bind-address
dns-port dns-port
dns-provider dns-provider
...@@ -646,6 +648,7 @@ tcp-services ...@@ -646,6 +648,7 @@ tcp-services
terminated-pod-gc-threshold terminated-pod-gc-threshold
test-flags test-flags
test-timeout test-timeout
tls-bootstrap-token
tls-ca-file tls-ca-file
tls-cert-file tls-cert-file
tls-private-key-file tls-private-key-file
...@@ -665,6 +668,8 @@ upgrade-image ...@@ -665,6 +668,8 @@ upgrade-image
upgrade-target upgrade-target
use-kubernetes-cluster-service use-kubernetes-cluster-service
use-kubernetes-version use-kubernetes-version
use-service-account-credentials
use-taint-based-evictions
user-whitelist user-whitelist
use-service-account-credentials use-service-account-credentials
use-service-account-credentials use-service-account-credentials
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment