Merge pull request #20 from justinsb/upup_export_kubecfg

upup: add command to generate kubecfg
This commit is contained in:
Justin Santa Barbara 2016-05-22 21:00:34 +01:00
commit 7dc7e848a7
9 changed files with 469 additions and 38 deletions

21
upup/cmd/upup/kubecfg.go Normal file
View File

@ -0,0 +1,21 @@
package main
import (
"fmt"
"github.com/spf13/cobra"
)
// kubecfgCmd represents the kubecfg command
var kubecfgCmd = &cobra.Command{
Use: "kubecfg",
Short: "Manage kubecfg files",
Long: `Manage kubecfg files`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Usage: generate")
},
}
func init() {
RootCmd.AddCommand(kubecfgCmd)
}

View File

@ -0,0 +1,158 @@
package main
import (
"fmt"
"github.com/spf13/cobra"
"io"
"io/ioutil"
"k8s.io/kube-deploy/upup/pkg/fi"
"k8s.io/kube-deploy/upup/pkg/kubecfg"
"os"
"path"
)
type KubecfgGenerateCommand struct {
StateDir string
ClusterName string
CloudProvider string
Project string
Master string
tmpdir string
caStore fi.CAStore
}
var kubecfgGenerateCommand KubecfgGenerateCommand
func init() {
cmd := &cobra.Command{
Use: "generate",
Short: "Generate a kubecfg file for a cluster",
Long: `Creates a kubecfg file for a cluster, based on the state`,
Run: func(cmd *cobra.Command, args []string) {
err := kubecfgGenerateCommand.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
},
}
kubecfgCmd.AddCommand(cmd)
// TODO: We need to store this in the persistent state dir
cmd.Flags().StringVarP(&kubecfgGenerateCommand.ClusterName, "name", "", kubecfgGenerateCommand.ClusterName, "Name for cluster")
cmd.Flags().StringVarP(&kubecfgGenerateCommand.CloudProvider, "cloud", "", kubecfgGenerateCommand.CloudProvider, "Cloud provider to use - gce, aws")
cmd.Flags().StringVarP(&kubecfgGenerateCommand.Project, "project", "", kubecfgGenerateCommand.Project, "Project to use (must be set on GCE)")
cmd.Flags().StringVarP(&kubecfgGenerateCommand.Master, "master", "", kubecfgGenerateCommand.Master, "IP adddress or host of API server")
cmd.Flags().StringVarP(&kubecfgGenerateCommand.StateDir, "state", "", "", "State directory")
}
func (c *KubecfgGenerateCommand) Run() error {
if c.StateDir == "" {
return fmt.Errorf("state must be specified")
}
if c.Master == "" {
return fmt.Errorf("master must be specified")
}
if c.ClusterName == "" {
return fmt.Errorf("name must be specified")
}
if c.CloudProvider == "" {
return fmt.Errorf("cloud must be specified")
}
var err error
c.tmpdir, err = ioutil.TempDir("", "k8s")
if err != nil {
return fmt.Errorf("error creating temporary directory: %v", err)
}
defer os.RemoveAll(c.tmpdir)
b := &kubecfg.KubeconfigBuilder{}
b.Init()
switch c.CloudProvider {
case "aws":
b.Context = "aws_" + c.ClusterName
case "gce":
if c.Project == "" {
return fmt.Errorf("--project must be specified (for GCE)")
}
b.Context = c.Project + "_" + c.ClusterName
default:
return fmt.Errorf("Unknown cloud provider %q", c.CloudProvider)
}
c.caStore, err = fi.NewFilesystemCAStore(path.Join(c.StateDir, "pki"))
if err != nil {
return fmt.Errorf("error building CA store: %v", err)
}
if b.CACert, err = c.copyCertificate(fi.CertificateId_CA); err != nil {
return err
}
if b.KubecfgCert, err = c.copyCertificate("kubecfg"); err != nil {
return err
}
if b.KubecfgKey, err = c.copyPrivateKey("kubecfg"); err != nil {
return err
}
b.KubeMasterIP = c.Master
err = b.CreateKubeconfig()
if err != nil {
return err
}
return nil
}
func (c *KubecfgGenerateCommand) copyCertificate(id string) (string, error) {
p := path.Join(c.tmpdir, id+".crt")
cert, err := c.caStore.Cert(id)
if err != nil {
return "", fmt.Errorf("error fetching certificate %q: %v", id, err)
}
_, err = writeFile(p, cert)
if err != nil {
return "", fmt.Errorf("error writing certificate %q: %v", id, err)
}
return p, nil
}
func (c *KubecfgGenerateCommand) copyPrivateKey(id string) (string, error) {
p := path.Join(c.tmpdir, id+".key")
cert, err := c.caStore.PrivateKey(id)
if err != nil {
return "", fmt.Errorf("error fetching private key %q: %v", id, err)
}
_, err = writeFile(p, cert)
if err != nil {
return "", fmt.Errorf("error writing private key %q: %v", id, err)
}
return p, nil
}
func writeFile(dst string, src io.WriterTo) (int64, error) {
f, err := os.Create(dst)
if err != nil {
return 0, fmt.Errorf("error creating file %q: %v", dst, err)
}
defer fi.SafeClose(f)
return src.WriteTo(f)
}

5
upup/cmd/upup/main.go Normal file
View File

@ -0,0 +1,5 @@
package main
func main() {
Execute()
}

48
upup/cmd/upup/root.go Normal file
View File

@ -0,0 +1,48 @@
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
// This represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "upup",
Short: "upup manages kubernetes clusters",
Long: `upup manages kubernetes clusters.
It allows you to create, destroy, upgrade and maintain them.`,
}
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
}
func init() {
cobra.OnInitialize(initConfig)
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.upup.yaml)")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" { // enable ability to specify config file via flag
viper.SetConfigFile(cfgFile)
}
viper.SetConfigName(".upup") // name of config file (without extension)
viper.AddConfigPath("$HOME") // adding home directory as first search path
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}

63
upup/glide.lock generated
View File

@ -1,8 +1,8 @@
hash: 9864d4fe0b94042cab08d2e80910638776d3d1e655b5a3cb9006731ae0306761
updated: 2016-05-13T09:50:25.860201305-04:00
hash: a9f93fedfb45e32b891cdf1f53c475acff2dd80fc02577b7c1d4da4e099bc450
updated: 2016-05-17T12:58:07.23069873-04:00
imports:
- name: github.com/aws/aws-sdk-go
version: d85fa529a99a833067e11c0a838b9db7a5d5ea71
version: bf2f8fe7f45e68017086d069498638893feddf64
subpackages:
- aws
- aws/awserr
@ -29,27 +29,60 @@ imports:
- private/protocol/rest
- aws/credentials/ec2rolecreds
- aws/ec2metadata
- name: github.com/BurntSushi/toml
version: f0aeabca5a127c4078abb8c8d64298b147264b55
- name: github.com/cloudfoundry-incubator/candiedyaml
version: 99c3df83b51532e3615f851d8c2dbb638f5313bf
- name: github.com/fsnotify/fsnotify
version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8
- name: github.com/ghodss/yaml
version: e8e0db9016175449df0e9c4b6e6995a9433a395c
- name: github.com/go-ini/ini
version: 12f418cc7edc5a618a51407b7ac1f1f512139df3
version: 2e44421e256d82ebbf3d4d4fcabe8930b905eff3
- name: github.com/golang/glog
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
- name: github.com/golang/protobuf
version: 7cc19b78d562895b13596ddce7aafb59dd789318
version: b982704f8bb716bb608144408cff30e15fbde841
subpackages:
- proto
- name: github.com/hashicorp/hcl
version: 9a905a34e6280ce905da1a32344b25e81011197a
subpackages:
- hcl/ast
- hcl/parser
- hcl/token
- json/parser
- hcl/scanner
- hcl/strconv
- json/scanner
- json/token
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/jmespath/go-jmespath
version: 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74
version: 3433f3ea46d9f8019119e7dd41274e112a2359a9
- name: github.com/magiconair/properties
version: c265cfa48dda6474e208715ca93e987829f572f8
- name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905
- name: github.com/spf13/cast
version: 27b586b42e29bec072fe7379259cc719e1289da6
- name: github.com/spf13/cobra
version: 0f866a6211e33cde2091d9290c08f6afd6c9ebbc
- name: github.com/spf13/jwalterweatherman
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
- name: github.com/spf13/pflag
version: cb88ea77998c3f024757528e3305022ab50b43be
- name: github.com/spf13/viper
version: d8a428b8a30606e1d0b355d91edf282609ade1a6
- name: golang.org/x/crypto
version: 47ff8dfbc528fea3003fc0ce2d88ffbbfbc46a43
version: c84e1f8e3a7e322d497cd16c0e8a13c7e127baf3
subpackages:
- ssh
- curve25519
- ed25519
- ed25519/internal/edwards25519
- name: golang.org/x/net
version: 7e42c0e1329bb108f7376a7618a2871ab90f1c4d
version: ef00b378c73f107bf44d5c9b69875255ce89b79a
subpackages:
- context
- context/ctxhttp
@ -60,8 +93,12 @@ imports:
- internal
- jws
- jwt
- name: golang.org/x/sys
version: 833a04a10549a95dc34458c195cbad61bbb6cb4d
subpackages:
- unix
- name: google.golang.org/api
version: f9a4669e07732c84854dce1f5c451c22427228fb
version: b34a26664e9b96e9d4aab8a6e8175ea07af5b8b6
subpackages:
- compute/v1
- googleapi
@ -81,14 +118,16 @@ imports:
- internal/log
- internal/remote_api
- name: google.golang.org/cloud
version: 200292f09e3aaa34878d801ab71fe823b1f7d36a
version: eb47ba841d53d93506cfbfbc03927daf9cc48f88
subpackages:
- compute/metadata
- internal
- name: google.golang.org/grpc
version: 9604a2bb7dd81d87c2873a9580258465f3c311c8
version: e802f420af1fde2c7e456cec047c51fbeff2d8fc
- name: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4
- name: k8s.io/kubernetes
version: bb3f5b1768f3bf6c81914c5bb1d7e846561fdc31
version: a24f03c3c99bd305ace7745b7a5749790be060e3
subpackages:
- pkg/util/exec
- pkg/util/mount

View File

@ -22,3 +22,4 @@ import:
subpackages:
- ssh
- package: github.com/cloudfoundry-incubator/candiedyaml
- package: github.com/spf13/cobra

View File

@ -17,6 +17,8 @@ import (
"time"
)
const CertificateId_CA = "ca"
type Certificate struct {
Subject pkix.Name
IsCA bool
@ -45,7 +47,7 @@ func (c *Certificate) UnmarshalJSON(b []byte) error {
func (c *Certificate) MarshalJSON() ([]byte, error) {
var data bytes.Buffer
err := c.WriteCertificate(&data)
_, err := c.WriteTo(&data)
if err != nil {
return nil, fmt.Errorf("error writing SSL certificate: %v", err)
}
@ -70,7 +72,7 @@ func (c *Certificate) AsString() (string, error) {
}
var data bytes.Buffer
err := c.WriteCertificate(&data)
_, err := c.WriteTo(&data)
if err != nil {
return "", fmt.Errorf("error writing SSL certificate: %v", err)
}
@ -88,7 +90,7 @@ func (c *PrivateKey) AsString() (string, error) {
}
var data bytes.Buffer
err := WritePrivateKey(c.Key, &data)
_, err := c.WriteTo(&data)
if err != nil {
return "", fmt.Errorf("error writing SSL private key: %v", err)
}
@ -114,13 +116,33 @@ func (k *PrivateKey) UnmarshalJSON(b []byte) (err error) {
func (k *PrivateKey) MarshalJSON() ([]byte, error) {
var data bytes.Buffer
err := WritePrivateKey(k.Key, &data)
_, err := k.WriteTo(&data)
if err != nil {
return nil, fmt.Errorf("error writing SSL private key: %v", err)
}
return json.Marshal(data.String())
}
var _ io.WriterTo = &PrivateKey{}
func (k *PrivateKey) WriteTo(w io.Writer) (int64, error) {
var data bytes.Buffer
var err error
switch pk := k.Key.(type) {
case *rsa.PrivateKey:
err = pem.Encode(w, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)})
default:
return 0, fmt.Errorf("unknown private key type: %T", k.Key)
}
if err != nil {
return 0, fmt.Errorf("error writing SSL private key: %v", err)
}
return data.WriteTo(w)
}
func LoadPEMCertificate(pemData []byte) (*Certificate, error) {
cert, err := parsePEMCertificate(pemData)
if err != nil {
@ -199,8 +221,15 @@ func SignNewCertificate(privateKey *PrivateKey, template *x509.Certificate, sign
return c, nil
}
func (c *Certificate) WriteCertificate(w io.Writer) error {
return pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: c.Certificate.Raw})
var _ io.WriterTo = &Certificate{}
func (c *Certificate) WriteTo(w io.Writer) (int64, error) {
var b bytes.Buffer
err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: c.Certificate.Raw})
if err != nil {
return 0, err
}
return b.WriteTo(w)
}
func parsePEMCertificate(pemData []byte) (*x509.Certificate, error) {
@ -221,15 +250,6 @@ func parsePEMCertificate(pemData []byte) (*x509.Certificate, error) {
}
}
func WritePrivateKey(privateKey crypto.PrivateKey, w io.Writer) error {
rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
if ok {
return pem.Encode(w, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivateKey)})
}
return fmt.Errorf("unknown private key type: %T", privateKey)
}
func parsePEMPrivateKey(pemData []byte) (crypto.PrivateKey, error) {
for {
block, rest := pem.Decode(pemData)

View File

@ -2,7 +2,6 @@ package fi
import (
"bytes"
"crypto"
crypto_rand "crypto/rand"
"crypto/rsa"
"crypto/x509"
@ -72,12 +71,14 @@ func (c *FilesystemCAStore) generateCACertificate() error {
IsCA: true,
}
caPrivateKey, err := rsa.GenerateKey(crypto_rand.Reader, 2048)
caRsaKey, err := rsa.GenerateKey(crypto_rand.Reader, 2048)
if err != nil {
return fmt.Errorf("error generating RSA private key: %v", err)
}
caCertificate, err := SignNewCertificate(&PrivateKey{Key: caPrivateKey}, template, nil, nil)
caPrivateKey := &PrivateKey{Key: caRsaKey}
caCertificate, err := SignNewCertificate(caPrivateKey, template, nil, nil)
if err != nil {
return err
}
@ -100,7 +101,7 @@ func (c *FilesystemCAStore) generateCACertificate() error {
return err
}
c.caPrivateKey = &PrivateKey{Key: caPrivateKey}
c.caPrivateKey = caPrivateKey
c.caCertificate = caCertificate
return nil
}
@ -176,7 +177,7 @@ func (c *FilesystemCAStore) Cert(id string) (*Certificate, error) {
func (c *FilesystemCAStore) FindCert(id string) (*Certificate, error) {
var cert *Certificate
if id == "ca" {
if id == CertificateId_CA {
cert = c.caCertificate
} else {
var err error
@ -228,7 +229,7 @@ func (c *FilesystemCAStore) loadPrivateKey(p string) (*PrivateKey, error) {
func (c *FilesystemCAStore) FindPrivateKey(id string) (*PrivateKey, error) {
var key *PrivateKey
if id == "ca" {
if id == CertificateId_CA {
key = c.caPrivateKey
} else {
var err error
@ -253,22 +254,23 @@ func (c *FilesystemCAStore) PrivateKey(id string) (*PrivateKey, error) {
func (c *FilesystemCAStore) CreatePrivateKey(id string) (*PrivateKey, error) {
p := c.buildPrivateKeyPath(id)
privateKey, err := rsa.GenerateKey(crypto_rand.Reader, 2048)
rsaKey, err := rsa.GenerateKey(crypto_rand.Reader, 2048)
if err != nil {
return nil, fmt.Errorf("error generating RSA private key: %v", err)
}
privateKey := &PrivateKey{Key: rsaKey}
err = c.storePrivateKey(privateKey, p)
if err != nil {
return nil, err
}
return &PrivateKey{Key: privateKey}, nil
return privateKey, nil
}
func (c *FilesystemCAStore) storePrivateKey(privateKey crypto.PrivateKey, p string) error {
func (c *FilesystemCAStore) storePrivateKey(privateKey *PrivateKey, p string) error {
var data bytes.Buffer
err := WritePrivateKey(privateKey, &data)
_, err := privateKey.WriteTo(&data)
if err != nil {
return err
}
@ -277,8 +279,9 @@ func (c *FilesystemCAStore) storePrivateKey(privateKey crypto.PrivateKey, p stri
}
func (c *FilesystemCAStore) storeCertificate(cert *Certificate, p string) error {
// TODO: replace storePrivateKey & storeCertificate with writeFile(io.WriterTo)?
var data bytes.Buffer
err := cert.WriteCertificate(&data)
_, err := cert.WriteTo(&data)
if err != nil {
return err
}

View File

@ -0,0 +1,136 @@
package kubecfg
import (
"fmt"
"github.com/golang/glog"
"os"
"os/exec"
"path"
"strings"
)
// KubeconfigBuilder builds a kubecfg file
// This logic previously lives in the bash scripts (create-kubeconfig in cluster/common.sh)
type KubeconfigBuilder struct {
KubectlPath string
KubeconfigPath string
KubeMasterIP string
Context string
KubeBearerToken string
KubeUser string
KubePassword string
CACert string
KubecfgCert string
KubecfgKey string
}
func (c *KubeconfigBuilder) Init() {
c.KubectlPath = "kubectl" // default to in-path
kubeconfig := os.Getenv("KUBECONFIG")
if kubeconfig == "" {
homedir := os.Getenv("HOME")
kubeconfig = path.Join(homedir, ".kube", "config")
}
c.KubeconfigPath = kubeconfig
}
func (c *KubeconfigBuilder) CreateKubeconfig() error {
if _, err := os.Stat(c.KubeconfigPath); os.IsNotExist(err) {
err := os.MkdirAll(path.Dir(c.KubeconfigPath), 0700)
if err != nil {
return fmt.Errorf("error creating directories for %q: %v", c.KubeconfigPath, err)
}
f, err := os.OpenFile(c.KubeconfigPath, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return fmt.Errorf("error creating config file %q: %v", c.KubeconfigPath, err)
}
f.Close()
}
var clusterArgs []string
clusterArgs = append(clusterArgs, "--server=https://"+c.KubeMasterIP)
if c.CACert == "" {
clusterArgs = append(clusterArgs, "--insecure-skip-tls-verify=true")
} else {
clusterArgs = append(clusterArgs, "--certificate-authority="+c.CACert)
clusterArgs = append(clusterArgs, "--embed-certs=true")
}
var userArgs []string
if c.KubeBearerToken != "" {
userArgs = append(userArgs, "--token="+c.KubeBearerToken)
} else if c.KubeUser != "" && c.KubePassword != "" {
userArgs = append(userArgs, "--username="+c.KubeUser)
userArgs = append(userArgs, "--password="+c.KubePassword)
}
if c.KubecfgCert != "" && c.KubecfgKey != "" {
userArgs = append(userArgs, "--client-certificate="+c.KubecfgCert)
userArgs = append(userArgs, "--client-key="+c.KubecfgKey)
userArgs = append(userArgs, "--embed-certs=true")
}
setClusterArgs := []string{"config", "set-cluster", c.Context}
setClusterArgs = append(setClusterArgs, clusterArgs...)
err := c.execKubectl(setClusterArgs...)
if err != nil {
return err
}
if len(userArgs) != 0 {
setCredentialsArgs := []string{"config", "set-credentials", c.Context}
setCredentialsArgs = append(setCredentialsArgs, userArgs...)
err := c.execKubectl(setCredentialsArgs...)
if err != nil {
return err
}
}
err = c.execKubectl("config", "set-context", c.Context, "--cluster="+c.Context, "--user="+c.Context)
if err != nil {
return err
}
err = c.execKubectl("config", "use-context", c.Context, "--cluster="+c.Context, "--user="+c.Context)
if err != nil {
return err
}
// If we have a bearer token, also create a credential entry with basic auth
// so that it is easy to discover the basic auth password for your cluster
// to use in a web browser.
if c.KubeBearerToken != "" && c.KubeUser != "" && c.KubePassword != "" {
err := c.execKubectl("config", "set-credentials", c.Context+"-basic-auth", "--username="+c.KubeUser, "--password="+c.KubePassword)
if err != nil {
return err
}
}
fmt.Printf("Wrote config for %s to %q\n", c.Context, c.KubeconfigPath)
return nil
}
func (c *KubeconfigBuilder) execKubectl(args ...string) error {
cmd := exec.Command(c.KubectlPath, args...)
env := os.Environ()
env = append(env, fmt.Sprintf("KUBECONFIG=%s", c.KubeconfigPath))
cmd.Env = env
glog.V(2).Infof("Running command: %s %s", cmd.Path, strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
glog.Info("error running kubectl:")
glog.Info(string(output))
return fmt.Errorf("error running kubectl")
}
glog.V(2).Info(string(output))
return nil
}