kubeadm: join ux changes

This commit is contained in:
Derek McQuay 2017-02-22 12:53:01 -08:00
parent 81d01a84e0
commit 1d37c6be49
No known key found for this signature in database
GPG Key ID: 92A7BC0C86B0B91A
12 changed files with 244 additions and 197 deletions

View File

@ -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"
}, },
} }
} }

View File

@ -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

View File

@ -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) {
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
}
}
} }

View File

@ -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

View File

@ -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(
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(
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",
],
)

View File

@ -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{
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,70 +57,107 @@ 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
}
func ValidateDiscovery(c *kubeadm.Discovery, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
var count int
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 {
allErrs = append(allErrs, ValidateHTTPSDiscovery(c.HTTPS, fldPath)...)
count++
}
if count != 1 {
allErrs = append(allErrs, field.Invalid(fldPath, nil, "exactly one discovery strategy can be provided"))
}
return allErrs
}
func ValidateFileDiscovery(c *kubeadm.FileDiscovery, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
return allErrs
}
func ValidateHTTPSDiscovery(c *kubeadm.HTTPSDiscovery, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
return allErrs
}
func ValidateTokenDiscovery(c *kubeadm.TokenDiscovery, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(c.ID) == 0 || len(c.Secret) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, nil, "token must be specific as <ID>:<Secret>"))
}
if len(c.Addresses) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, nil, "at least one address is required"))
} }
return allErrs return allErrs
} }
func ValidateAuthorizationMode(authzMode string, fldPath *field.Path) field.ErrorList { func ValidateAuthorizationMode(authzMode string, fldPath *field.Path) field.ErrorList {
if !authzmodes.IsValidAuthorizationMode(authzMode) { if !authzmodes.IsValidAuthorizationMode(authzMode) {
return field.ErrorList{field.Invalid(fldPath, nil, "invalid authorization mode")} return field.ErrorList{field.Invalid(fldPath, authzMode, "invalid authorization mode")}
} }
return field.ErrorList{} return field.ErrorList{}
} }
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 len(c.DiscoveryFile) != 0 {
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
}
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"))
}
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
}
func ValidateJoinDiscoveryTokenAPIServer(c *kubeadm.NodeConfiguration, fldPath *field.Path) 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
}
func ValidateDiscoveryFile(discoveryFile string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
u, err := url.Parse(discoveryFile)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, discoveryFile, "not a valid HTTPS URL or a file on disk"))
return allErrs
}
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
}
func ValidateToken(t string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
id, secret, err := tokenutil.ParseToken(t)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, nil, err.Error()))
}
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 {
_, svcSubnet, err := net.ParseCIDR(subnet) _, svcSubnet, err := net.ParseCIDR(subnet)
if err != nil { if err != nil {
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{}

View File

@ -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",
@ -125,21 +124,6 @@ func TestValidateMasterConfiguration(t *testing.T) {
expected bool expected bool
}{ }{
{&kubeadm.MasterConfiguration{}, false}, {&kubeadm.MasterConfiguration{}, false},
{&kubeadm.MasterConfiguration{
Discovery: kubeadm.Discovery{
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{ {&kubeadm.MasterConfiguration{
Discovery: kubeadm.Discovery{ Discovery: kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"}, HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"},
@ -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}, }, false},
{&kubeadm.NodeConfiguration{
Discovery: kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"},
},
CACertPath: "/some/path", // no .crt suffix
}, 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)

View File

@ -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 {
// 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
} }

View File

@ -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 (
) )
// 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
} }
// 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
} }

View File

@ -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",

View File

@ -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()
}
}

View File

@ -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
@ -647,6 +649,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
@ -666,6 +669,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