Skip to content

Commit

Permalink
introduce the --use flag when creating a cluster
Browse files Browse the repository at this point in the history
  • Loading branch information
frederiko committed May 10, 2019
1 parent 69f8a8c commit 3fb00a8
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 2 deletions.
3 changes: 3 additions & 0 deletions cmd/kind/create/cluster/createcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type flagpole struct {
Config string
ImageName string
Retain bool
Use bool
Wait time.Duration
}

Expand All @@ -55,6 +56,7 @@ func NewCommand() *cobra.Command {
cmd.Flags().StringVar(&flags.Config, "config", "", "path to a kind config file")
cmd.Flags().StringVar(&flags.ImageName, "image", "", "node docker image to use for booting the cluster")
cmd.Flags().BoolVar(&flags.Retain, "retain", false, "retain nodes for debugging when cluster creation fails")
cmd.Flags().BoolVar(&flags.Use, "use", false, "set KUBECONFIG to the configuration of this cluster, once it's created")
cmd.Flags().DurationVar(&flags.Wait, "wait", time.Duration(0), "Wait for control plane node to be ready (default 0s)")
return cmd
}
Expand Down Expand Up @@ -106,6 +108,7 @@ func runE(flags *flagpole, cmd *cobra.Command, args []string) error {
if err = ctx.Create(cfg,
create.Retain(flags.Retain),
create.WaitForReady(flags.Wait),
create.Use(flags.Use),
); err != nil {
return errors.Wrap(err, "failed to create cluster")
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/cluster/create/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ func WaitForReady(interval time.Duration) ClusterOption {
}
}

// Use configures the cluster created to be active cluster after its creation
func Use(use bool) ClusterOption {
return func(o *internalcreate.Options) *internalcreate.Options {
o.Use = use
return o
}
}

// SetupKubernetes configures create command to setup kubernetes after creating nodes containers
// TODO: Refactor this. It is a temporary solution for a phased breakdown of different
// operations, specifically create. see https://github.com/kubernetes-sigs/kind/issues/324
Expand Down
54 changes: 52 additions & 2 deletions pkg/cluster/internal/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,23 @@ import (
"sigs.k8s.io/kind/pkg/cluster/internal/delete"
logutil "sigs.k8s.io/kind/pkg/log"

pkgutil "k8s.io/minikube/pkg/util"
configaction "sigs.k8s.io/kind/pkg/cluster/internal/create/actions/config"
"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/installcni"
"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/installstorage"
"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/kubeadminit"
"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/kubeadmjoin"
"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/loadbalancer"
"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/waitforready"
"sigs.k8s.io/kind/pkg/util"
)

// Options holds cluster creation options
// NOTE: this is only exported for usage by ./../create
type Options struct {
Retain bool
WaitForReady time.Duration
Use bool
//TODO: Refactor this. It is a temporary solution for a phased breakdown of different
// operations, specifically create. see https://github.com/kubernetes-sigs/kind/issues/324
SetupKubernetes bool // if kind should setup kubernetes after creating nodes
Expand Down Expand Up @@ -111,8 +114,51 @@ func Cluster(ctx *context.Context, cfg *config.Cluster, opts *Options) error {
return nil
}

// print how to set KUBECONFIG to point to the cluster etc.
printUsage(ctx.Name())
if opts.Use {
if err := setupKubeconfig(ctx, cfg); err != nil {
return err
}
printFootnote()
} else {
// print how to set KUBECONFIG to point to the cluster etc.
printUsage(ctx.Name())
}

return nil
}

func setupKubeconfig(ctx *context.Context, cfg *config.Cluster) error {

pki := util.NewKubeConfig()
certData, err := pki.CertData(ctx.KubeConfigPath())
if err != nil {
return err
}

pkiPaths, err := pki.CertDataPath(certData)
if err != nil {
return err
}

var server string
for _, v := range certData.Clusters {
server = v.Server
}

kcs := &pkgutil.KubeConfigSetup{
ClusterName: ctx.Name(),
ClusterServerAddress: server,
ClientCertificate: pkiPaths.ClientCertPath,
ClientKey: pkiPaths.ClientKeyPath,
CertificateAuthority: pkiPaths.CertificateAuthorityPath,
KeepContext: false,
EmbedCerts: true,
}

kcs.SetKubeConfigFile(util.DefaultKubeconfigFile)
if err := pkgutil.SetupKubeConfig(kcs); err != nil {
return err
}

return nil
}
Expand Down Expand Up @@ -154,3 +200,7 @@ func printSetupInstruction(name string) {
name,
)
}

func printFootnote() {
fmt.Print("You can now use the cluster:\nkubectl cluster-info\n")
}
39 changes: 39 additions & 0 deletions pkg/cluster/nodes/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,45 @@ func (n *Node) Role() (role string, err error) {
return role, nil
}

// matches kubeconfig server entry like:
// server: https://172.17.0.2:6443
// which we rewrite to:
// server: https://localhost:$PORT
var serverAddressRE = regexp.MustCompile(`^(\s+server:) https://.*:\d+$`)

// WriteKubeConfig writes a fixed KUBECONFIG to dest
// this should only be called on a control plane node
// While copyng to the host machine the control plane address
// is replaced with local host and the control plane port with
// a randomly generated port reserved during node creation.
func (n *Node) WriteKubeConfig(dest string, hostPort int32) error {
cmd := n.Command("cat", "/etc/kubernetes/admin.conf")
lines, err := exec.CombinedOutputLines(cmd)
if err != nil {
return errors.Wrap(err, "failed to get kubeconfig from node")
}

// fix the config file, swapping out the server for the forwarded localhost:port
var buff bytes.Buffer
for _, line := range lines {
match := serverAddressRE.FindStringSubmatch(line)
if len(match) > 1 {
line = fmt.Sprintf("%s https://localhost:%d", match[1], hostPort)
}
buff.WriteString(line)
buff.WriteString("\n")
}

// create the directory to contain the KUBECONFIG file.
// 0755 is taken from client-go's config handling logic: https://github.com/kubernetes/client-go/blob/5d107d4ebc00ee0ea606ad7e39fd6ce4b0d9bf9e/tools/clientcmd/loader.go#L412
err = os.MkdirAll(filepath.Dir(dest), 0755)
if err != nil {
return errors.Wrap(err, "failed to create kubeconfig output directory")
}

return ioutil.WriteFile(dest, buff.Bytes(), 0600)
}

// WriteFile writes content to dest on the node
func (n *Node) WriteFile(dest, content string) error {
// create destination directory
Expand Down
164 changes: 164 additions & 0 deletions pkg/util/kubeconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package util

import (
"encoding/base64"
"io/ioutil"
"os"
"path"
"path/filepath"

"gopkg.in/yaml.v2"
"k8s.io/client-go/util/homedir"
)

// DefaultKubeconfigFile is the absolute default location of the Kubernetes Config File
var DefaultKubeconfigFile = filepath.Join(homedir.HomeDir(), ".kube", "config")

// CertificateAuthorityPath stores the absolute path of Certificate Authority Cert and
// user's Certificate and Key
type CertificateAuthorityPath struct {
CertificateAuthorityPath, ClientKeyPath, ClientCertPath string
}

// KubeConfig stores the Cluster and User information read from
// Kubernetes configuration file
type KubeConfig struct {
Clusters []Clusters `yaml:"clusters"`
Users []Users `yaml:"users"`
}

// Clusters stores an item of a slice of clusters stored in a
// Kubernetes configuration file
type Clusters struct {
Name string `yaml:"name"`
Cluster struct {
CertAuthData string `yaml:"certificate-authority-data"`
Server string `yaml:"server"`
}
}

// Users is an item of a slice of users stored in a
// Kubernetes configuration file
type Users struct {
Name string
User struct {
ClientCertData string `yaml:"client-certificate-data"`
ClientKeyData string `yaml:"client-key-data"`
}
}

// CertificateAuthorityData is used to return both clusters and users information
type CertificateAuthorityData struct {
Clusters map[string]Cluster
Users map[string]User
}

// Cluster is an entry containing one entry with cluster information
// CertAuthorityData holds a base64 representation of the CA Certificate
type Cluster struct {
CertAuthorityData string
Server string
}

// User is an entry containing the user of the cluster. It contains the base64 representation
// of client certificate and key
type User struct {
ClientCertificateData string
ClientKeyData string
}

// NewKubeConfig provides is used to store the CA information read from
// the kubeconfig file read created
func NewKubeConfig() *KubeConfig {
return &KubeConfig{}
}

// CertDataPath reads the base64 data, stores PKI files into $HOME/.kube/<cluster_name>
// and returns the absolute path of where these files are
func (k *KubeConfig) CertDataPath(cad *CertificateAuthorityData) (*CertificateAuthorityPath, error) {
cdp := CertificateAuthorityPath{}

pkiDir := filepath.Join(os.Getenv("HOME"), ".kube", k.Clusters[0].Name)

if err := os.MkdirAll(pkiDir, 0750); err != nil {
return nil, err
}

for _, value := range k.Clusters {

decoded, err := base64.StdEncoding.DecodeString(value.Cluster.CertAuthData)
if err != nil {
return nil, err
}

if err = ioutil.WriteFile(path.Join(pkiDir, "ca.crt"), decoded, 0640); err != nil {
return nil, err
}

cdp.CertificateAuthorityPath = path.Join(pkiDir, "ca.crt")

}

for _, value := range k.Users {

cdp.ClientCertPath = path.Join(pkiDir, "client-cert.crt")
decoded, err := base64.StdEncoding.DecodeString(value.User.ClientCertData)
if err != nil {
return nil, err
}

if err = ioutil.WriteFile(cdp.ClientCertPath, decoded, 0640); err != nil {
return nil, err
}

cdp.ClientKeyPath = path.Join(pkiDir, "client.key")
decoded, err = base64.StdEncoding.DecodeString(value.User.ClientKeyData)
if err != nil {
return nil, err
}

if err = ioutil.WriteFile(cdp.ClientKeyPath, decoded, 0400); err != nil {
return nil, err
}

}

return &cdp, nil
}

// CertData reads the base 64 certificate info from kubeconfig and return a
// a CertificateAuthorityData
func (k *KubeConfig) CertData(kubeconfigPath string) (*CertificateAuthorityData, error) {

kconfigContent, err := ioutil.ReadFile(kubeconfigPath)

if err != nil {
return nil, err
}

err = yaml.Unmarshal(kconfigContent, &k)
if err != nil {
return nil, err
}

caData := CertificateAuthorityData{}

for _, c := range k.Clusters {
caData.Clusters = map[string]Cluster{
c.Name: Cluster{
CertAuthorityData: c.Cluster.CertAuthData,
Server: c.Cluster.Server,
},
}
}

for _, u := range k.Users {
caData.Users = map[string]User{
u.Name: User{
ClientCertificateData: u.User.ClientCertData,
ClientKeyData: u.User.ClientKeyData,
},
}
}
return &caData, nil
}

0 comments on commit 3fb00a8

Please sign in to comment.