2018-07-23 10:06:37 -07:00
|
|
|
/*
|
|
|
|
|
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 cluster
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2018-08-27 10:21:43 -07:00
|
|
|
"os"
|
2018-08-28 11:45:21 -07:00
|
|
|
"regexp"
|
2019-02-21 05:15:54 +02:00
|
|
|
"runtime"
|
2019-01-14 19:45:20 -08:00
|
|
|
"strings"
|
2018-08-23 18:27:52 -07:00
|
|
|
"time"
|
|
|
|
|
|
2019-01-21 18:54:39 +08:00
|
|
|
"github.com/pkg/errors"
|
2018-09-02 00:47:40 -07:00
|
|
|
log "github.com/sirupsen/logrus"
|
2018-08-06 17:05:46 -07:00
|
|
|
|
2018-09-12 15:44:17 -07:00
|
|
|
"sigs.k8s.io/kind/pkg/cluster/config"
|
2019-01-15 17:57:08 -08:00
|
|
|
"sigs.k8s.io/kind/pkg/cluster/config/encoding"
|
2019-01-15 00:53:40 -08:00
|
|
|
"sigs.k8s.io/kind/pkg/cluster/internal/create"
|
|
|
|
|
"sigs.k8s.io/kind/pkg/cluster/internal/meta"
|
2018-12-10 16:13:56 +01:00
|
|
|
"sigs.k8s.io/kind/pkg/cluster/logs"
|
2018-11-09 17:33:53 -08:00
|
|
|
"sigs.k8s.io/kind/pkg/cluster/nodes"
|
2018-10-19 18:19:57 -07:00
|
|
|
logutil "sigs.k8s.io/kind/pkg/log"
|
2018-07-23 10:06:37 -07:00
|
|
|
)
|
|
|
|
|
|
2018-08-28 11:45:21 -07:00
|
|
|
// Context is used to create / manipulate kubernetes-in-docker clusters
|
2019-01-10 20:40:13 -08:00
|
|
|
// See: NewContext()
|
2018-07-23 10:06:37 -07:00
|
|
|
type Context struct {
|
2019-01-15 00:53:40 -08:00
|
|
|
*meta.ClusterMeta
|
2019-01-08 13:00:44 -08:00
|
|
|
}
|
|
|
|
|
|
2018-08-28 11:45:21 -07:00
|
|
|
// similar to valid docker container names, but since we will prefix
|
|
|
|
|
// and suffix this name, we can relax it a little
|
|
|
|
|
// see NewContext() for usage
|
|
|
|
|
// https://godoc.org/github.com/docker/docker/daemon/names#pkg-constants
|
|
|
|
|
var validNameRE = regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`)
|
|
|
|
|
|
2018-11-27 16:54:23 -08:00
|
|
|
// DefaultName is the default Context name
|
|
|
|
|
// TODO(bentheelder): consider removing automatic prefixing in favor
|
|
|
|
|
// of letting the user specify the full name..
|
2019-02-03 03:50:11 +02:00
|
|
|
const DefaultName = "kind"
|
2018-11-27 16:54:23 -08:00
|
|
|
|
2018-08-28 11:45:21 -07:00
|
|
|
// NewContext returns a new cluster management context
|
2019-02-03 03:50:11 +02:00
|
|
|
// if name is "" the default name will be used
|
2018-11-27 16:54:23 -08:00
|
|
|
func NewContext(name string) *Context {
|
2018-08-28 11:45:21 -07:00
|
|
|
if name == "" {
|
2018-11-27 16:54:23 -08:00
|
|
|
name = DefaultName
|
2018-08-28 11:45:21 -07:00
|
|
|
}
|
2018-11-27 16:54:23 -08:00
|
|
|
return &Context{
|
2019-01-15 00:53:40 -08:00
|
|
|
ClusterMeta: meta.NewClusterMeta(name),
|
2018-07-23 10:06:37 -07:00
|
|
|
}
|
2018-11-14 16:26:53 -08:00
|
|
|
}
|
|
|
|
|
|
2018-11-27 16:54:23 -08:00
|
|
|
// Validate will be called before creating new resources using the context
|
|
|
|
|
// It will not be called before deleting or listing resources, so as to allow
|
|
|
|
|
// contexts based around previously valid values to be used in newer versions
|
|
|
|
|
// You can call this early yourself to check validation before creation calls,
|
|
|
|
|
// though it will be called internally.
|
|
|
|
|
func (c *Context) Validate() error {
|
|
|
|
|
// validate the name
|
2019-01-15 00:53:40 -08:00
|
|
|
if !validNameRE.MatchString(c.Name()) {
|
2019-01-21 18:54:39 +08:00
|
|
|
return errors.Errorf(
|
2018-11-27 16:54:23 -08:00
|
|
|
"'%s' is not a valid cluster name, cluster names must match `%s`",
|
2019-01-15 00:53:40 -08:00
|
|
|
c.Name(), validNameRE.String(),
|
2018-11-27 16:54:23 -08:00
|
|
|
)
|
2018-11-14 16:26:53 -08:00
|
|
|
}
|
2018-11-27 16:54:23 -08:00
|
|
|
return nil
|
2018-08-28 11:45:21 -07:00
|
|
|
}
|
|
|
|
|
|
2019-01-10 20:40:13 -08:00
|
|
|
// ControlPlaneMeta tracks various outputs that are relevant to the control plane created with Kind.
|
|
|
|
|
// Here we can define things like ports and listen or bind addresses as needed.
|
|
|
|
|
type ControlPlaneMeta struct {
|
|
|
|
|
// APIServerPort is the port that the container is forwarding to the
|
|
|
|
|
// Kubernetes API server running in the container
|
|
|
|
|
APIServerPort int
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-22 03:42:27 +02:00
|
|
|
// GetControlPlaneMeta attempts to retrieve / compute metadata about
|
2019-01-10 20:40:13 -08:00
|
|
|
// the control plane for the context's cluster
|
|
|
|
|
// NOTE: due to refactoring this is currently non-functional (!)
|
|
|
|
|
// TODO(bentheelder): fix this
|
|
|
|
|
func (c *Context) GetControlPlaneMeta() (*ControlPlaneMeta, error) {
|
2019-01-21 18:54:39 +08:00
|
|
|
return nil, errors.New("needs-reimplementation")
|
2019-01-10 20:40:13 -08:00
|
|
|
}
|
|
|
|
|
|
2018-08-28 11:45:21 -07:00
|
|
|
// ClusterName returns the Kubernetes cluster name based on the context name
|
|
|
|
|
func (c *Context) ClusterName() string {
|
2019-02-03 03:50:11 +02:00
|
|
|
return c.Name()
|
2019-01-10 20:40:13 -08:00
|
|
|
}
|
|
|
|
|
|
2019-02-19 21:20:24 +01:00
|
|
|
//CreateOption is a Context configuration option supplied to create
|
|
|
|
|
type CreateOption func(*create.Context)
|
|
|
|
|
|
|
|
|
|
// Retain configures create to retain nodes after failing for debugging pourposes
|
|
|
|
|
func Retain(retain bool) CreateOption {
|
|
|
|
|
return func(c *create.Context) {
|
|
|
|
|
c.Retain = retain
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WaitForReady configures create to use interval as maximum wait time for the control plane node to be ready
|
|
|
|
|
func WaitForReady(interval time.Duration) CreateOption {
|
|
|
|
|
return func(c *create.Context) {
|
|
|
|
|
// NB. WaitForReady option does not apply directly to create, but it should be forwarded to the exec command
|
|
|
|
|
c.ExecOptions = append(c.ExecOptions, create.WaitForReady(interval))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-23 10:06:37 -07:00
|
|
|
// Create provisions and starts a kubernetes-in-docker cluster
|
2019-02-19 21:20:24 +01:00
|
|
|
func (c *Context) Create(cfg *config.Config, options ...CreateOption) error {
|
2019-01-15 17:57:08 -08:00
|
|
|
// default config fields (important for usage as a library, where the config
|
|
|
|
|
// may be constructed in memory rather than from disk)
|
|
|
|
|
encoding.Scheme.Default(cfg)
|
|
|
|
|
|
|
|
|
|
// then validate
|
2018-11-27 16:54:23 -08:00
|
|
|
if err := cfg.Validate(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-10 11:59:54 -08:00
|
|
|
// derive info necessary for creation
|
2019-01-15 00:53:40 -08:00
|
|
|
derived, err := create.Derive(cfg)
|
2019-01-10 11:59:54 -08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2019-01-15 17:57:08 -08:00
|
|
|
|
2019-01-10 11:59:54 -08:00
|
|
|
// validate node configuration
|
|
|
|
|
if err := derived.Validate(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-10 16:13:56 +01:00
|
|
|
// init the create context and logging
|
2019-01-15 00:53:40 -08:00
|
|
|
cc := &create.Context{
|
|
|
|
|
Config: cfg,
|
|
|
|
|
DerivedConfig: derived,
|
|
|
|
|
ClusterMeta: c.ClusterMeta,
|
2018-11-27 16:54:23 -08:00
|
|
|
}
|
|
|
|
|
|
2019-01-15 00:53:40 -08:00
|
|
|
cc.Status = logutil.NewStatus(os.Stdout)
|
|
|
|
|
cc.Status.MaybeWrapLogrus(log.StandardLogger())
|
2018-10-19 18:19:57 -07:00
|
|
|
|
2019-01-15 00:53:40 -08:00
|
|
|
defer cc.Status.End(false)
|
2018-12-06 14:08:11 +01:00
|
|
|
|
2019-02-19 21:20:24 +01:00
|
|
|
// apply create options
|
|
|
|
|
for _, option := range options {
|
|
|
|
|
option(cc)
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-10 16:13:56 +01:00
|
|
|
// attempt to explicitly pull the required node images if they doesn't exist locally
|
2018-09-26 20:09:34 -07:00
|
|
|
// we don't care if this errors, we'll still try to run which also pulls
|
2018-12-10 16:13:56 +01:00
|
|
|
cc.EnsureNodeImages()
|
2018-09-26 20:09:34 -07:00
|
|
|
|
2018-12-10 16:13:56 +01:00
|
|
|
// Create node containers implementing defined config Nodes
|
2019-01-15 00:53:40 -08:00
|
|
|
nodeList, err := cc.ProvisionNodes()
|
2018-12-10 16:13:56 +01:00
|
|
|
if err != nil {
|
|
|
|
|
// In case of errors nodes are deleted (except if retain is explicitly set)
|
|
|
|
|
log.Error(err)
|
2019-01-15 00:53:40 -08:00
|
|
|
if !cc.Retain {
|
|
|
|
|
c.Delete()
|
2018-12-10 16:13:56 +01:00
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
2019-01-15 00:53:40 -08:00
|
|
|
cc.Status.End(true)
|
2018-08-27 10:21:43 -07:00
|
|
|
|
2018-12-10 16:13:56 +01:00
|
|
|
// After creating node containers the Kubernetes provisioning is executed
|
|
|
|
|
// By default `kind` executes all the actions required to get a fully working
|
|
|
|
|
// Kubernetes cluster; please note that the list of actions automatically
|
|
|
|
|
// adapt to the topology defined in config
|
|
|
|
|
// TODO(fabrizio pandini): make the list of executed actions configurable from CLI
|
2019-02-19 21:20:24 +01:00
|
|
|
err = cc.Exec(nodeList, []string{"haproxy", "config", "init", "join"}, cc.ExecOptions...)
|
2018-08-27 10:21:43 -07:00
|
|
|
if err != nil {
|
2018-12-10 16:13:56 +01:00
|
|
|
// In case of errors nodes are deleted (except if retain is explicitly set)
|
|
|
|
|
log.Error(err)
|
2019-01-15 00:53:40 -08:00
|
|
|
if !cc.Retain {
|
|
|
|
|
c.Delete()
|
2018-12-10 16:13:56 +01:00
|
|
|
}
|
2018-08-27 10:21:43 -07:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-21 05:15:54 +02:00
|
|
|
// TODO: consider shell detection.
|
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
|
fmt.Printf(
|
|
|
|
|
"Cluster creation complete. To setup KUBECONFIG:\n\n"+
|
|
|
|
|
|
|
|
|
|
"For the default cmd.exe console call:\n"+
|
2019-02-23 05:10:58 +02:00
|
|
|
"kind get kubeconfig-path > kindpath\n"+
|
|
|
|
|
"set /p KUBECONFIG=<kindpath && del kindpath\n\n"+
|
2019-02-21 05:15:54 +02:00
|
|
|
|
|
|
|
|
"for PowerShell call:\n"+
|
2019-02-23 05:10:58 +02:00
|
|
|
"$env:KUBECONFIG=\"$(kind get kubeconfig-path --name=%[1]q)\"\n\n"+
|
2019-02-21 05:15:54 +02:00
|
|
|
|
|
|
|
|
"For bash on Windows:\n"+
|
2019-02-23 05:10:58 +02:00
|
|
|
"export KUBECONFIG=\"$(kind get kubeconfig-path --name=%[1]q)\"\n\n"+
|
2019-02-21 05:15:54 +02:00
|
|
|
|
|
|
|
|
"You can now use the cluster:\n"+
|
2019-02-23 05:10:58 +02:00
|
|
|
"kubectl cluster-info\n",
|
2019-02-21 05:15:54 +02:00
|
|
|
cc.Name(),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
fmt.Printf(
|
|
|
|
|
"Cluster creation complete. You can now use the cluster with:\n\n"+
|
|
|
|
|
|
2019-02-23 05:10:58 +02:00
|
|
|
"export KUBECONFIG=\"$(kind get kubeconfig-path --name=%q)\"\n"+
|
|
|
|
|
"kubectl cluster-info\n",
|
2019-02-21 05:15:54 +02:00
|
|
|
cc.Name(),
|
|
|
|
|
)
|
|
|
|
|
}
|
2018-08-27 10:21:43 -07:00
|
|
|
return nil
|
2018-07-23 10:06:37 -07:00
|
|
|
}
|
|
|
|
|
|
2018-12-10 16:13:56 +01:00
|
|
|
// Delete tears down a kubernetes-in-docker cluster
|
|
|
|
|
func (c *Context) Delete() error {
|
|
|
|
|
n, err := c.ListNodes()
|
2018-10-25 16:29:29 -07:00
|
|
|
if err != nil {
|
2019-01-21 18:54:39 +08:00
|
|
|
return errors.Wrap(err, "error listing nodes")
|
2018-10-25 16:29:29 -07:00
|
|
|
}
|
2018-12-10 16:13:56 +01:00
|
|
|
|
|
|
|
|
// try to remove the kind kube config file generated by "kind create cluster"
|
|
|
|
|
err = os.Remove(c.KubeConfigPath())
|
2019-01-10 20:41:50 -08:00
|
|
|
if err != nil && !os.IsNotExist(err) {
|
2018-12-10 16:13:56 +01:00
|
|
|
log.Warningf("Tried to remove %s but received error: %s\n", c.KubeConfigPath(), err)
|
2018-08-27 10:21:43 -07:00
|
|
|
}
|
2018-07-23 10:06:37 -07:00
|
|
|
|
2018-12-10 16:13:56 +01:00
|
|
|
// check if $KUBECONFIG is set and let the user know to unset if so
|
2019-01-11 22:23:59 +08:00
|
|
|
if strings.Contains(os.Getenv("KUBECONFIG"), c.KubeConfigPath()) {
|
2018-12-10 16:13:56 +01:00
|
|
|
fmt.Printf("$KUBECONFIG is still set to use %s even though that file has been deleted, remember to unset it\n", c.KubeConfigPath())
|
2018-10-25 16:29:29 -07:00
|
|
|
}
|
2018-12-10 16:13:56 +01:00
|
|
|
|
|
|
|
|
return nodes.Delete(n...)
|
2018-10-25 16:29:29 -07:00
|
|
|
}
|
|
|
|
|
|
2018-07-23 10:06:37 -07:00
|
|
|
// ListNodes returns the list of container IDs for the "nodes" in the cluster
|
2018-11-20 12:01:24 -08:00
|
|
|
func (c *Context) ListNodes() ([]nodes.Node, error) {
|
2018-11-09 17:33:53 -08:00
|
|
|
return nodes.List("label=" + c.ClusterLabel())
|
2018-07-23 10:06:37 -07:00
|
|
|
}
|
2018-11-20 12:01:24 -08:00
|
|
|
|
|
|
|
|
// CollectLogs will populate dir with cluster logs and other debug files
|
|
|
|
|
func (c *Context) CollectLogs(dir string) error {
|
|
|
|
|
nodes, err := c.ListNodes()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return logs.Collect(nodes, dir)
|
|
|
|
|
}
|