mirror of https://github.com/kubernetes/kops.git
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
bc26eecf49
|
@ -7,8 +7,8 @@ import (
|
|||
// createCmd represents the create command
|
||||
var createCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "create clusters",
|
||||
Long: `Create clusters`,
|
||||
Short: "create resources",
|
||||
Long: `Create resources`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type CreateSecretCommand struct {
|
||||
cobraCommand *cobra.Command
|
||||
}
|
||||
|
||||
var createSecretCmd = CreateSecretCommand{
|
||||
cobraCommand: &cobra.Command{
|
||||
Use: "secret",
|
||||
Short: "Create secrets",
|
||||
Long: `Create secrets.`,
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd := createSecretCmd.cobraCommand
|
||||
|
||||
createCmd.AddCommand(cmd)
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type CreateSecretPublickeyCommand struct {
|
||||
cobraCommand *cobra.Command
|
||||
|
||||
Pubkey string
|
||||
}
|
||||
|
||||
var createSecretPublickeyCommand = CreateSecretPublickeyCommand{
|
||||
cobraCommand: &cobra.Command{
|
||||
Use: "sshpublickey",
|
||||
Short: "Create SSH publickey",
|
||||
Long: `Create SSH publickey.`,
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd := createSecretPublickeyCommand.cobraCommand
|
||||
|
||||
cmd.Run = func(cmd *cobra.Command, args []string) {
|
||||
err := createSecretPublickeyCommand.Run(args)
|
||||
if err != nil {
|
||||
exitWithError(err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&createSecretPublickeyCommand.Pubkey, "pubkey", "i", "", "Path to SSH public key")
|
||||
|
||||
createSecretCmd.cobraCommand.AddCommand(cmd)
|
||||
}
|
||||
|
||||
func (cmd *CreateSecretPublickeyCommand) Run(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("syntax: NAME -i <PublicKeyPath>")
|
||||
}
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("syntax: NAME -i <PublicKeyPath>")
|
||||
}
|
||||
name := args[0]
|
||||
|
||||
if cmd.Pubkey == "" {
|
||||
return fmt.Errorf("pubkey path is required (use -i)")
|
||||
}
|
||||
|
||||
caStore, err := rootCommand.KeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(cmd.Pubkey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading SSH public key %v: %v", cmd.Pubkey, err)
|
||||
}
|
||||
|
||||
err = caStore.AddSSHPublicKey(name, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding SSH public key: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package main
|
||||
|
||||
//
|
||||
//import (
|
||||
// "fmt"
|
||||
//
|
||||
// "crypto/x509"
|
||||
// "github.com/golang/glog"
|
||||
// "github.com/spf13/cobra"
|
||||
// "k8s.io/kops/upup/pkg/fi"
|
||||
// "net"
|
||||
// "strings"
|
||||
//)
|
||||
//
|
||||
//type CreateSecretsCommand struct {
|
||||
// Id string
|
||||
// Type string
|
||||
//
|
||||
// Usage string
|
||||
// Subject string
|
||||
// AlternateNames []string
|
||||
//}
|
||||
//
|
||||
//var createSecretsCommand CreateSecretsCommand
|
||||
//
|
||||
//func init() {
|
||||
// cmd := &cobra.Command{
|
||||
// Use: "secret",
|
||||
// Short: "Create secrets",
|
||||
// Long: `Create secrets.`,
|
||||
// Run: func(cmd *cobra.Command, args []string) {
|
||||
// err := createSecretsCommand.Run()
|
||||
// if err != nil {
|
||||
// exitWithError(err)
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// createCmd.AddCommand(cmd)
|
||||
//
|
||||
// cmd.Flags().StringVarP(&createSecretsCommand.Type, "type", "", "", "Type of secret to create")
|
||||
// cmd.Flags().StringVarP(&createSecretsCommand.Id, "id", "", "", "Id of secret to create")
|
||||
// cmd.Flags().StringVarP(&createSecretsCommand.Usage, "usage", "", "", "Usage of secret (for SSL certificate)")
|
||||
// cmd.Flags().StringVarP(&createSecretsCommand.Subject, "subject", "", "", "Subject (for SSL certificate)")
|
||||
// cmd.Flags().StringSliceVarP(&createSecretsCommand.AlternateNames, "san", "", nil, "Alternate name (for SSL certificate)")
|
||||
//}
|
||||
//
|
||||
//func (cmd *CreateSecretsCommand) Run() error {
|
||||
// if cmd.Id == "" {
|
||||
// return fmt.Errorf("id is required")
|
||||
// }
|
||||
//
|
||||
// if cmd.Type == "" {
|
||||
// return fmt.Errorf("type is required")
|
||||
// }
|
||||
//
|
||||
// // TODO: Prompt before replacing?
|
||||
// // TODO: Keep history?
|
||||
//
|
||||
// if strings.ToLower(cmd.Type) == strings.ToLower(fi.SecretTypeSecret) {
|
||||
// return fmt.Errorf("creating secrets of type %q not (currently) supported", cmd.Type)
|
||||
// //{
|
||||
// // secretStore, err := rootCommand.SecretStore()
|
||||
// // if err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// // secret, err := fi.CreateSecret()
|
||||
// // if err != nil {
|
||||
// // return fmt.Errorf("error creating secret: %v", err)
|
||||
// // }
|
||||
// // _, created, err := secretStore.GetOrCreateSecret(cmd.Id, secret)
|
||||
// // if err != nil {
|
||||
// // return fmt.Errorf("error creating secret: %v", err)
|
||||
// // }
|
||||
// // if !created {
|
||||
// // return fmt.Errorf("secret already exists")
|
||||
// // }
|
||||
// // return nil
|
||||
// //}
|
||||
// }
|
||||
//
|
||||
// if strings.ToLower(cmd.Type) == strings.ToLower(fi.SecretTypeKeypair) {
|
||||
// return fmt.Errorf("creating secrets of type %q not (currently) supported", cmd.Type)
|
||||
// //
|
||||
// //// TODO: Create a rotate command which keeps the same values?
|
||||
// //// Or just do it here a "replace" action - existing=fail, replace or rotate
|
||||
// //// TODO: Create a CreateKeypair class, move to fi (this is duplicated code)
|
||||
// //{
|
||||
// // if cmd.Subject == "" {
|
||||
// // return fmt.Errorf("subject is required")
|
||||
// // }
|
||||
// //
|
||||
// // subject, err := parsePkixName(cmd.Subject)
|
||||
// // if err != nil {
|
||||
// // return fmt.Errorf("Error parsing subject: %v", err)
|
||||
// // }
|
||||
// // template := &x509.Certificate{
|
||||
// // Subject: *subject,
|
||||
// // BasicConstraintsValid: true,
|
||||
// // IsCA: false,
|
||||
// // }
|
||||
// //
|
||||
// // if len(template.Subject.ToRDNSequence()) == 0 {
|
||||
// // return fmt.Errorf("Subject name was empty")
|
||||
// // }
|
||||
// //
|
||||
// // switch cmd.Usage {
|
||||
// // case "client":
|
||||
// // template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
||||
// // template.KeyUsage = x509.KeyUsageDigitalSignature
|
||||
// // break
|
||||
// //
|
||||
// // case "server":
|
||||
// // template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||
// // template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
|
||||
// // break
|
||||
// //
|
||||
// // default:
|
||||
// // return fmt.Errorf("unknown usage: %q", cmd.Usage)
|
||||
// // }
|
||||
// //
|
||||
// // for _, san := range cmd.AlternateNames {
|
||||
// // san = strings.TrimSpace(san)
|
||||
// // if san == "" {
|
||||
// // continue
|
||||
// // }
|
||||
// // if ip := net.ParseIP(san); ip != nil {
|
||||
// // template.IPAddresses = append(template.IPAddresses, ip)
|
||||
// // } else {
|
||||
// // template.DNSNames = append(template.DNSNames, san)
|
||||
// // }
|
||||
// // }
|
||||
// //
|
||||
// // caStore, err := rootCommand.KeyStore()
|
||||
// // if err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// //
|
||||
// // // TODO: Allow resigning of the existing private key?
|
||||
// //
|
||||
// // _, _, err = caStore.CreateKeypair(cmd.Id, template)
|
||||
// // if err != nil {
|
||||
// // return fmt.Errorf("error creating keypair %v", err)
|
||||
// // }
|
||||
// // return nil
|
||||
// }
|
||||
//
|
||||
// if strings.ToLower(cmd.Type) == strings.ToLower(fi.SecretTypeSSHPublicKey) {
|
||||
// return fmt.Errorf("creating secrets of type %q not (currently) supported", cmd.Type)
|
||||
// }
|
||||
//
|
||||
// return fmt.Errorf("secret type not known: %q", cmd.Type)
|
||||
//}
|
|
@ -0,0 +1,79 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
)
|
||||
|
||||
type DeleteSecretCmd struct {
|
||||
}
|
||||
|
||||
var deleteSecretCmd DeleteSecretCmd
|
||||
|
||||
func init() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "secret",
|
||||
Short: "Delete secret",
|
||||
Long: `Delete a secret.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := deleteSecretCmd.Run(args)
|
||||
if err != nil {
|
||||
exitWithError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
deleteCmd.AddCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *DeleteSecretCmd) Run(args []string) error {
|
||||
if len(args) != 2 && len(args) != 3 {
|
||||
return fmt.Errorf("Syntax: <type> <name> [<id>]")
|
||||
}
|
||||
|
||||
secretType := args[0]
|
||||
secretName := args[1]
|
||||
|
||||
secretID := ""
|
||||
if len(args) == 3 {
|
||||
secretID = args[2]
|
||||
}
|
||||
|
||||
secrets, err := listSecrets(secretType, []string{secretName})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if secretID != "" {
|
||||
var matches []*fi.KeystoreItem
|
||||
for _, s := range secrets {
|
||||
if s.Id == secretID {
|
||||
matches = append(matches, s)
|
||||
}
|
||||
}
|
||||
secrets = matches
|
||||
}
|
||||
|
||||
if len(secrets) == 0 {
|
||||
return fmt.Errorf("secret %q not found")
|
||||
}
|
||||
|
||||
if len(secrets) != 1 {
|
||||
// TODO: it would be friendly to print the matching keys
|
||||
return fmt.Errorf("found multiple matching secrets; specify the id of the key")
|
||||
}
|
||||
|
||||
keyStore, err := rootCommand.KeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = keyStore.DeleteSecret(secrets[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting secret: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// DescribeCmd represents the describe command
|
||||
type DescribeCmd struct {
|
||||
cobraCommand *cobra.Command
|
||||
}
|
||||
|
||||
var describeCmd = DescribeCmd{
|
||||
cobraCommand: &cobra.Command{
|
||||
Use: "describe",
|
||||
Short: "describe objects",
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd := describeCmd.cobraCommand
|
||||
|
||||
rootCommand.AddCommand(cmd)
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type DescribeSecretsCommand struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
var describeSecretsCommand DescribeSecretsCommand
|
||||
|
||||
func init() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "secrets",
|
||||
Aliases: []string{"secret"},
|
||||
Short: "Describe secrets",
|
||||
Long: `Describe secrets.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := describeSecretsCommand.Run(args)
|
||||
if err != nil {
|
||||
exitWithError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
describeCmd.cobraCommand.AddCommand(cmd)
|
||||
|
||||
cmd.Flags().StringVarP(&describeSecretsCommand.Type, "type", "", "", "Filter by secret type")
|
||||
}
|
||||
|
||||
func (c *DescribeSecretsCommand) Run(args []string) error {
|
||||
items, err := listSecrets(c.Type, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "No secrets found\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
w := new(tabwriter.Writer)
|
||||
var b bytes.Buffer
|
||||
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape)
|
||||
|
||||
keyStore, err := rootCommand.KeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, i := range items {
|
||||
fmt.Fprintf(w, "Name:\t%s\n", i.Name)
|
||||
fmt.Fprintf(w, "Type:\t%s\n", i.Type)
|
||||
fmt.Fprintf(w, "Id:\t%s\n", i.Id)
|
||||
|
||||
switch i.Type {
|
||||
case fi.SecretTypeKeypair:
|
||||
err = describeKeypair(keyStore, i, &b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case fi.SecretTypeSSHPublicKey:
|
||||
err = describeSSHPublicKey(i, &b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case fi.SecretTypeSecret:
|
||||
err = describeSecret(i, &b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
_, err = w.Write(b.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to output: %v", err)
|
||||
}
|
||||
|
||||
b.Reset()
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func describeKeypair(keyStore fi.CAStore, item *fi.KeystoreItem, w *bytes.Buffer) error {
|
||||
name := item.Name
|
||||
|
||||
cert, err := keyStore.FindCert(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving cert %q: %v", name, err)
|
||||
}
|
||||
|
||||
key, err := keyStore.FindPrivateKey(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving private key %q: %v", name, err)
|
||||
}
|
||||
|
||||
var alternateNames []string
|
||||
if cert != nil {
|
||||
alternateNames = append(alternateNames, cert.Certificate.DNSNames...)
|
||||
alternateNames = append(alternateNames, cert.Certificate.EmailAddresses...)
|
||||
for _, ip := range cert.Certificate.IPAddresses {
|
||||
alternateNames = append(alternateNames, ip.String())
|
||||
}
|
||||
sort.Strings(alternateNames)
|
||||
}
|
||||
|
||||
if cert != nil {
|
||||
fmt.Fprintf(w, "Subject:\t%s\n", pkixNameToString(&cert.Certificate.Subject))
|
||||
fmt.Fprintf(w, "Issuer:\t%s\n", pkixNameToString(&cert.Certificate.Issuer))
|
||||
fmt.Fprintf(w, "AlternateNames:\t%s\n", strings.Join(alternateNames, ", "))
|
||||
fmt.Fprintf(w, "CA:\t%v\n", cert.IsCA)
|
||||
fmt.Fprintf(w, "NotAfter:\t%s\n", cert.Certificate.NotAfter)
|
||||
fmt.Fprintf(w, "NotBefore:\t%s\n", cert.Certificate.NotBefore)
|
||||
|
||||
// PublicKeyAlgorithm doesn't have a String() function. Also, is this important information?
|
||||
//fmt.Fprintf(w, "PublicKeyAlgorithm:\t%v\n", c.Certificate.PublicKeyAlgorithm)
|
||||
//fmt.Fprintf(w, "SignatureAlgorithm:\t%v\n", c.Certificate.SignatureAlgorithm)
|
||||
}
|
||||
|
||||
if key != nil {
|
||||
if rsaPrivateKey, ok := key.Key.(*rsa.PrivateKey); ok {
|
||||
fmt.Fprintf(w, "PrivateKeyType:\t%v\n", "rsa")
|
||||
fmt.Fprintf(w, "KeyLength:\t%v\n", rsaPrivateKey.N.BitLen())
|
||||
} else {
|
||||
fmt.Fprintf(w, "PrivateKeyType:\tunknown (%T)\n", key.Key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func describeSecret(item *fi.KeystoreItem, w *bytes.Buffer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func describeSSHPublicKey(item *fi.KeystoreItem, w *bytes.Buffer) error {
|
||||
return nil
|
||||
}
|
|
@ -129,7 +129,7 @@ func (t *Table) Render(items interface{}, out io.Writer, columnNames ...string)
|
|||
w := new(tabwriter.Writer)
|
||||
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w.Init(out, 0, 8, 0, '\t', tabwriter.StripEscape)
|
||||
w.Init(out, 0, 8, 1, '\t', tabwriter.StripEscape)
|
||||
|
||||
writeHeader := true
|
||||
if writeHeader {
|
||||
|
|
|
@ -4,14 +4,31 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// getCmd represents the get command
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
SuggestFor: []string{"list"},
|
||||
Short: "list or get obejcts",
|
||||
Long: `list or get obejcts`,
|
||||
// GetCmd represents the get command
|
||||
type GetCmd struct {
|
||||
output string
|
||||
|
||||
cobraCommand *cobra.Command
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCommand.AddCommand(getCmd)
|
||||
var getCmd = GetCmd{
|
||||
cobraCommand: &cobra.Command{
|
||||
Use: "get",
|
||||
SuggestFor: []string{"list"},
|
||||
Short: "list or get objects",
|
||||
Long: `list or get objects`,
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
OutputYaml = "yaml"
|
||||
OutputTable = "table"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := getCmd.cobraCommand
|
||||
|
||||
rootCommand.AddCommand(cmd)
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&getCmd.output, "output", "o", OutputTable, "output format. One of: table, yaml")
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"os"
|
||||
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/upup/pkg/api"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
)
|
||||
|
||||
type GetClustersCmd struct {
|
||||
FullSpec bool
|
||||
}
|
||||
|
||||
var getClustersCmd GetClustersCmd
|
||||
|
@ -21,28 +23,34 @@ func init() {
|
|||
Short: "get clusters",
|
||||
Long: `List or get clusters.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := getClustersCmd.Run()
|
||||
err := getClustersCmd.Run(args)
|
||||
if err != nil {
|
||||
glog.Exitf("%v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getCmd.AddCommand(cmd)
|
||||
getCmd.cobraCommand.AddCommand(cmd)
|
||||
|
||||
cmd.Flags().BoolVar(&getClustersCmd.FullSpec, "full", false, "Show fully populated configuration")
|
||||
}
|
||||
|
||||
func (c *GetClustersCmd) Run() error {
|
||||
func (c *GetClustersCmd) Run(args []string) error {
|
||||
clusterRegistry, err := rootCommand.ClusterRegistry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clusterNames, err := clusterRegistry.List()
|
||||
if err != nil {
|
||||
return err
|
||||
var clusters []*api.Cluster
|
||||
|
||||
clusterNames := args
|
||||
if len(args) == 0 {
|
||||
clusterNames, err = clusterRegistry.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var clusters []*api.Cluster
|
||||
for _, clusterName := range clusterNames {
|
||||
cluster, err := clusterRegistry.Find(clusterName)
|
||||
if err != nil {
|
||||
|
@ -50,28 +58,59 @@ func (c *GetClustersCmd) Run() error {
|
|||
}
|
||||
|
||||
if cluster == nil {
|
||||
glog.Warningf("cluster was listed, but then not found %q", clusterName)
|
||||
return fmt.Errorf("cluster not found %q", clusterName)
|
||||
}
|
||||
|
||||
clusters = append(clusters, cluster)
|
||||
}
|
||||
|
||||
if len(clusters) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "No clusters found\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
t := &Table{}
|
||||
t.AddColumn("NAME", func(c *api.Cluster) string {
|
||||
return c.Name
|
||||
})
|
||||
t.AddColumn("CLOUD", func(c *api.Cluster) string {
|
||||
return c.Spec.CloudProvider
|
||||
})
|
||||
t.AddColumn("ZONES", func(c *api.Cluster) string {
|
||||
var zoneNames []string
|
||||
for _, z := range c.Spec.Zones {
|
||||
zoneNames = append(zoneNames, z.Name)
|
||||
output := getCmd.output
|
||||
if output == OutputTable {
|
||||
t := &Table{}
|
||||
t.AddColumn("NAME", func(c *api.Cluster) string {
|
||||
return c.Name
|
||||
})
|
||||
t.AddColumn("CLOUD", func(c *api.Cluster) string {
|
||||
return c.Spec.CloudProvider
|
||||
})
|
||||
t.AddColumn("ZONES", func(c *api.Cluster) string {
|
||||
var zoneNames []string
|
||||
for _, z := range c.Spec.Zones {
|
||||
zoneNames = append(zoneNames, z.Name)
|
||||
}
|
||||
return strings.Join(zoneNames, ",")
|
||||
})
|
||||
return t.Render(clusters, os.Stdout, "NAME", "CLOUD", "ZONES")
|
||||
} else if output == OutputYaml {
|
||||
if c.FullSpec {
|
||||
var fullSpecs []*api.Cluster
|
||||
for _, cluster := range clusters {
|
||||
spec, err := clusterRegistry.ReadCompletedConfig(cluster.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading full cluster spec for %q: %v", cluster.Name, err)
|
||||
}
|
||||
fullSpecs = append(fullSpecs, spec)
|
||||
}
|
||||
clusters = fullSpecs
|
||||
}
|
||||
return strings.Join(zoneNames, ",")
|
||||
})
|
||||
return t.Render(clusters, os.Stdout, "NAME", "CLOUD", "ZONES")
|
||||
|
||||
for _, cluster := range clusters {
|
||||
y, err := api.ToYaml(cluster)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling yaml for %q: %v", cluster.Name, err)
|
||||
}
|
||||
_, err = os.Stdout.Write(y)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("Unknown output format: %q", output)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"os"
|
||||
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/upup/pkg/api"
|
||||
|
@ -22,17 +23,17 @@ func init() {
|
|||
Short: "get instancegroups",
|
||||
Long: `List or get InstanceGroups.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := getInstanceGroupsCmd.Run()
|
||||
err := getInstanceGroupsCmd.Run(args)
|
||||
if err != nil {
|
||||
glog.Exitf("%v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getCmd.AddCommand(cmd)
|
||||
getCmd.cobraCommand.AddCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *GetInstanceGroupsCmd) Run() error {
|
||||
func (c *GetInstanceGroupsCmd) Run(args []string) error {
|
||||
registry, err := rootCommand.InstanceGroupRegistry()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -43,30 +44,64 @@ func (c *GetInstanceGroupsCmd) Run() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if len(args) != 0 {
|
||||
m := make(map[string]*api.InstanceGroup)
|
||||
for _, ig := range instancegroups {
|
||||
m[ig.Name] = ig
|
||||
}
|
||||
instancegroups = make([]*api.InstanceGroup, 0, len(args))
|
||||
for _, arg := range args {
|
||||
ig := m[arg]
|
||||
if ig == nil {
|
||||
return fmt.Errorf("instancegroup not found %q", arg)
|
||||
}
|
||||
|
||||
instancegroups = append(instancegroups, ig)
|
||||
}
|
||||
}
|
||||
|
||||
if len(instancegroups) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "No InstanceGroup objects found\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
t := &Table{}
|
||||
t.AddColumn("NAME", func(c *api.InstanceGroup) string {
|
||||
return c.Name
|
||||
})
|
||||
t.AddColumn("ROLE", func(c *api.InstanceGroup) string {
|
||||
return string(c.Spec.Role)
|
||||
})
|
||||
t.AddColumn("MACHINETYPE", func(c *api.InstanceGroup) string {
|
||||
return c.Spec.MachineType
|
||||
})
|
||||
t.AddColumn("ZONES", func(c *api.InstanceGroup) string {
|
||||
return strings.Join(c.Spec.Zones, ",")
|
||||
})
|
||||
t.AddColumn("MIN", func(c *api.InstanceGroup) string {
|
||||
return intPointerToString(c.Spec.MinSize)
|
||||
})
|
||||
t.AddColumn("MAX", func(c *api.InstanceGroup) string {
|
||||
return intPointerToString(c.Spec.MinSize)
|
||||
})
|
||||
return t.Render(instancegroups, os.Stdout, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "ZONES")
|
||||
output := getCmd.output
|
||||
if output == OutputTable {
|
||||
t := &Table{}
|
||||
t.AddColumn("NAME", func(c *api.InstanceGroup) string {
|
||||
return c.Name
|
||||
})
|
||||
t.AddColumn("ROLE", func(c *api.InstanceGroup) string {
|
||||
return string(c.Spec.Role)
|
||||
})
|
||||
t.AddColumn("MACHINETYPE", func(c *api.InstanceGroup) string {
|
||||
return c.Spec.MachineType
|
||||
})
|
||||
t.AddColumn("ZONES", func(c *api.InstanceGroup) string {
|
||||
return strings.Join(c.Spec.Zones, ",")
|
||||
})
|
||||
t.AddColumn("MIN", func(c *api.InstanceGroup) string {
|
||||
return intPointerToString(c.Spec.MinSize)
|
||||
})
|
||||
t.AddColumn("MAX", func(c *api.InstanceGroup) string {
|
||||
return intPointerToString(c.Spec.MinSize)
|
||||
})
|
||||
return t.Render(instancegroups, os.Stdout, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "ZONES")
|
||||
} else if output == OutputYaml {
|
||||
for _, ig := range instancegroups {
|
||||
y, err := api.ToYaml(ig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling yaml for %q: %v", ig.Name, err)
|
||||
}
|
||||
_, err = os.Stdout.Write(y)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("Unknown output format: %q", output)
|
||||
}
|
||||
}
|
||||
|
||||
func intPointerToString(v *int) string {
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type GetSecretsCommand struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
var getSecretsCommand GetSecretsCommand
|
||||
|
||||
func init() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "secrets",
|
||||
Aliases: []string{"secret"},
|
||||
Short: "get secrets",
|
||||
Long: `List or get secrets.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := getSecretsCommand.Run(args)
|
||||
if err != nil {
|
||||
exitWithError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getCmd.cobraCommand.AddCommand(cmd)
|
||||
|
||||
cmd.Flags().StringVarP(&getSecretsCommand.Type, "type", "", "", "Filter by secret type")
|
||||
}
|
||||
|
||||
func listSecrets(secretType string, names []string) ([]*fi.KeystoreItem, error) {
|
||||
var items []*fi.KeystoreItem
|
||||
|
||||
findType := strings.ToLower(secretType)
|
||||
switch findType {
|
||||
case "":
|
||||
// OK
|
||||
case "sshpublickey", "keypair", "secret":
|
||||
// OK
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown secret type %q", secretType)
|
||||
}
|
||||
|
||||
{
|
||||
caStore, err := rootCommand.KeyStore()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := caStore.List()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing CA store items %v", err)
|
||||
}
|
||||
|
||||
for _, i := range l {
|
||||
if findType != "" && findType != strings.ToLower(i.Type) {
|
||||
continue
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
}
|
||||
|
||||
if findType == "" || findType == strings.ToLower(fi.SecretTypeSecret) {
|
||||
secretStore, err := rootCommand.SecretStore()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l, err := secretStore.ListSecrets()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing secrets %v", err)
|
||||
}
|
||||
|
||||
for _, id := range l {
|
||||
i := &fi.KeystoreItem{
|
||||
Name: id,
|
||||
Type: fi.SecretTypeSecret,
|
||||
}
|
||||
if findType != "" && findType != strings.ToLower(i.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
items = append(items, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(names) != 0 {
|
||||
var matches []*fi.KeystoreItem
|
||||
for _, arg := range names {
|
||||
var found []*fi.KeystoreItem
|
||||
for _, i := range items {
|
||||
// There may be multiple secrets with the same name (of different type)
|
||||
if i.Name == arg {
|
||||
found = append(found, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(found) == 0 {
|
||||
return nil, fmt.Errorf("Secret not found: %q", arg)
|
||||
}
|
||||
|
||||
matches = append(matches, found...)
|
||||
}
|
||||
items = matches
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (c *GetSecretsCommand) Run(args []string) error {
|
||||
items, err := listSecrets(c.Type, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "No secrets found\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
output := getCmd.output
|
||||
if output == OutputTable {
|
||||
t := &Table{}
|
||||
t.AddColumn("NAME", func(i *fi.KeystoreItem) string {
|
||||
return i.Name
|
||||
})
|
||||
t.AddColumn("ID", func(i *fi.KeystoreItem) string {
|
||||
return i.Id
|
||||
})
|
||||
t.AddColumn("TYPE", func(i *fi.KeystoreItem) string {
|
||||
return i.Type
|
||||
})
|
||||
return t.Render(items, os.Stdout, "TYPE", "NAME", "ID")
|
||||
} else if output == OutputYaml {
|
||||
return fmt.Errorf("yaml output format is not (currently) supported for secrets")
|
||||
} else if output == "plaintext" {
|
||||
secretStore, err := rootCommand.SecretStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, i := range items {
|
||||
var data string
|
||||
switch i.Type {
|
||||
case fi.SecretTypeSecret:
|
||||
secret, err := secretStore.FindSecret(i.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting secret %q: %v", i.Name, err)
|
||||
}
|
||||
if secret == nil {
|
||||
return fmt.Errorf("cannot find secret %q", i.Name)
|
||||
}
|
||||
data = string(secret.Data)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("secret type %v cannot (currently) be exported as plaintext", i.Type)
|
||||
}
|
||||
|
||||
_, err := fmt.Fprintf(os.Stdout, "%s\n", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing output: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("Unknown output format: %q", output)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
Execute()
|
||||
}
|
||||
|
||||
// exitWithError will terminate execution with an error result
|
||||
// It prints the error to stderr and exits with a non-zero exit code
|
||||
func exitWithError(err error) {
|
||||
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
@ -2,141 +2,18 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"crypto/x509"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CreateSecretsCommand struct {
|
||||
Id string
|
||||
Type string
|
||||
|
||||
Usage string
|
||||
Subject string
|
||||
AlternateNames []string
|
||||
}
|
||||
|
||||
var createSecretsCommand CreateSecretsCommand
|
||||
|
||||
func init() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create secrets",
|
||||
Long: `Create secrets.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := createSecretsCommand.Run()
|
||||
if err != nil {
|
||||
glog.Exitf("%v", err)
|
||||
}
|
||||
exitWithError(fmt.Errorf("The 'secrets create' command has been replaced by 'create secrets'"))
|
||||
},
|
||||
}
|
||||
|
||||
secretsCmd.AddCommand(cmd)
|
||||
|
||||
cmd.Flags().StringVarP(&createSecretsCommand.Type, "type", "", "", "Type of secret to create")
|
||||
cmd.Flags().StringVarP(&createSecretsCommand.Id, "id", "", "", "Id of secret to create")
|
||||
cmd.Flags().StringVarP(&createSecretsCommand.Usage, "usage", "", "", "Usage of secret (for SSL certificate)")
|
||||
cmd.Flags().StringVarP(&createSecretsCommand.Subject, "subject", "", "", "Subject (for SSL certificate)")
|
||||
cmd.Flags().StringSliceVarP(&createSecretsCommand.AlternateNames, "san", "", nil, "Alternate name (for SSL certificate)")
|
||||
}
|
||||
|
||||
func (cmd *CreateSecretsCommand) Run() error {
|
||||
if cmd.Id == "" {
|
||||
return fmt.Errorf("id is required")
|
||||
}
|
||||
|
||||
if cmd.Type == "" {
|
||||
return fmt.Errorf("type is required")
|
||||
}
|
||||
|
||||
// TODO: Prompt before replacing?
|
||||
// TODO: Keep history?
|
||||
|
||||
switch cmd.Type {
|
||||
case "secret":
|
||||
{
|
||||
secretStore, err := rootCommand.SecretStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, created, err := secretStore.GetOrCreateSecret(cmd.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating secrets %v", err)
|
||||
}
|
||||
if !created {
|
||||
return fmt.Errorf("secret already exists")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
case "keypair":
|
||||
// TODO: Create a rotate command which keeps the same values?
|
||||
// Or just do it here a "replace" action - existing=fail, replace or rotate
|
||||
// TODO: Create a CreateKeypair class, move to fi (this is duplicated code)
|
||||
{
|
||||
if cmd.Subject == "" {
|
||||
return fmt.Errorf("subject is required")
|
||||
}
|
||||
|
||||
subject, err := parsePkixName(cmd.Subject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing subject: %v", err)
|
||||
}
|
||||
template := &x509.Certificate{
|
||||
Subject: *subject,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
}
|
||||
|
||||
if len(template.Subject.ToRDNSequence()) == 0 {
|
||||
return fmt.Errorf("Subject name was empty")
|
||||
}
|
||||
|
||||
switch cmd.Usage {
|
||||
case "client":
|
||||
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
||||
template.KeyUsage = x509.KeyUsageDigitalSignature
|
||||
break
|
||||
|
||||
case "server":
|
||||
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||
template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
|
||||
break
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown usage: %q", cmd.Usage)
|
||||
}
|
||||
|
||||
for _, san := range cmd.AlternateNames {
|
||||
san = strings.TrimSpace(san)
|
||||
if san == "" {
|
||||
continue
|
||||
}
|
||||
if ip := net.ParseIP(san); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, san)
|
||||
}
|
||||
}
|
||||
|
||||
caStore, err := rootCommand.KeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Allow resigning of the existing private key?
|
||||
|
||||
_, _, err = caStore.CreateKeypair(cmd.Id, template)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating keypair %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("secret type not known: %q", cmd.Type)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,178 +3,18 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kops/upup/pkg/fi"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type DescribeSecretsCommand struct {
|
||||
}
|
||||
|
||||
var describeSecretsCommand DescribeSecretsCommand
|
||||
|
||||
func init() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "describe",
|
||||
Short: "Describe secrets",
|
||||
Long: `Describe secrets.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := describeSecretsCommand.Run()
|
||||
if err != nil {
|
||||
glog.Exitf("%v", err)
|
||||
}
|
||||
exitWithError(fmt.Errorf("The 'secrets describe' command has been replaced by 'describe secrets'"))
|
||||
},
|
||||
}
|
||||
|
||||
secretsCmd.AddCommand(cmd)
|
||||
}
|
||||
|
||||
func (c *DescribeSecretsCommand) Run() error {
|
||||
|
||||
w := new(tabwriter.Writer)
|
||||
var b bytes.Buffer
|
||||
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape)
|
||||
|
||||
{
|
||||
caStore, err := rootCommand.KeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ids, err := caStore.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing CA store items %v", err)
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
cert, err := caStore.FindCert(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving cert %q: %v", id, err)
|
||||
}
|
||||
|
||||
key, err := caStore.FindPrivateKey(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving private key %q: %v", id, err)
|
||||
}
|
||||
|
||||
if key == nil && cert == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err = describeKeypair(id, cert, key, &b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
|
||||
_, err = w.Write(b.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to output: %v", err)
|
||||
}
|
||||
|
||||
b.Reset()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
secretStore, err := rootCommand.SecretStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ids, err := secretStore.ListSecrets()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing secrets %v", err)
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
secret, err := secretStore.FindSecret(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving secret %q: %v", id, err)
|
||||
}
|
||||
|
||||
if secret == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err = describeSecret(id, secret, &b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
|
||||
_, err = w.Write(b.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to output: %v", err)
|
||||
}
|
||||
|
||||
b.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func describeKeypair(id string, c *fi.Certificate, k *fi.PrivateKey, w *bytes.Buffer) error {
|
||||
var alternateNames []string
|
||||
if c != nil {
|
||||
alternateNames = append(alternateNames, c.Certificate.DNSNames...)
|
||||
alternateNames = append(alternateNames, c.Certificate.EmailAddresses...)
|
||||
for _, ip := range c.Certificate.IPAddresses {
|
||||
alternateNames = append(alternateNames, ip.String())
|
||||
}
|
||||
sort.Strings(alternateNames)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "Id:\t%s\n", id)
|
||||
if c != nil && k != nil {
|
||||
fmt.Fprintf(w, "Type:\t%s\n", "keypair")
|
||||
} else if c != nil && k == nil {
|
||||
fmt.Fprintf(w, "Type:\t%s\n", "certificate")
|
||||
} else if k != nil && c == nil {
|
||||
// Unexpected!
|
||||
fmt.Fprintf(w, "Type:\t%s\n", "privatekey")
|
||||
} else {
|
||||
return fmt.Errorf("expected either certificate or key to be set")
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
fmt.Fprintf(w, "Subject:\t%s\n", pkixNameToString(&c.Certificate.Subject))
|
||||
fmt.Fprintf(w, "Issuer:\t%s\n", pkixNameToString(&c.Certificate.Issuer))
|
||||
fmt.Fprintf(w, "AlternateNames:\t%s\n", strings.Join(alternateNames, ", "))
|
||||
fmt.Fprintf(w, "CA:\t%v\n", c.IsCA)
|
||||
fmt.Fprintf(w, "NotAfter:\t%s\n", c.Certificate.NotAfter)
|
||||
fmt.Fprintf(w, "NotBefore:\t%s\n", c.Certificate.NotBefore)
|
||||
|
||||
// PublicKeyAlgorithm doesn't have a String() function. Also, is this important information?
|
||||
//fmt.Fprintf(w, "PublicKeyAlgorithm:\t%v\n", c.Certificate.PublicKeyAlgorithm)
|
||||
//fmt.Fprintf(w, "SignatureAlgorithm:\t%v\n", c.Certificate.SignatureAlgorithm)
|
||||
}
|
||||
|
||||
if k != nil {
|
||||
if rsaPrivateKey, ok := k.Key.(*rsa.PrivateKey); ok {
|
||||
fmt.Fprintf(w, "PrivateKeyType:\t%v\n", "rsa")
|
||||
fmt.Fprintf(w, "KeyLength:\t%v\n", rsaPrivateKey.N.BitLen())
|
||||
} else {
|
||||
fmt.Fprintf(w, "PrivateKeyType:\tunknown (%T)\n", k.Key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func describeSecret(id string, s *fi.Secret, w *bytes.Buffer) error {
|
||||
fmt.Fprintf(w, "Id:\t%s\n", id)
|
||||
fmt.Fprintf(w, "Type:\t%s\n", "secret")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,108 +3,18 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
type ExposeSecretsCommand struct {
|
||||
ID string
|
||||
Type string
|
||||
}
|
||||
|
||||
var exposeSecretsCommand ExposeSecretsCommand
|
||||
|
||||
func init() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "expose",
|
||||
Short: "Expose secrets",
|
||||
Long: `Expose secrets.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := exposeSecretsCommand.Run()
|
||||
if err != nil {
|
||||
glog.Exitf("%v", err)
|
||||
}
|
||||
exitWithError(fmt.Errorf("The 'secrets export' command has been replaced by 'get secrets -oplaintext'"))
|
||||
},
|
||||
}
|
||||
|
||||
secretsCmd.AddCommand(cmd)
|
||||
|
||||
cmd.Flags().StringVarP(&exposeSecretsCommand.Type, "type", "", "", "Type of secret to create")
|
||||
cmd.Flags().StringVarP(&exposeSecretsCommand.ID, "id", "", "", "Id of secret to create")
|
||||
}
|
||||
|
||||
func (cmd *ExposeSecretsCommand) Run() error {
|
||||
id := cmd.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("id is required")
|
||||
}
|
||||
|
||||
if cmd.Type == "" {
|
||||
return fmt.Errorf("type is required")
|
||||
}
|
||||
|
||||
var value string
|
||||
switch cmd.Type {
|
||||
case "secret":
|
||||
{
|
||||
secretStore, err := rootCommand.SecretStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secret, err := secretStore.FindSecret(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding secret %q: %v", id, err)
|
||||
}
|
||||
if secret == nil {
|
||||
return fmt.Errorf("secret not found: %q", id)
|
||||
}
|
||||
value = string(secret.Data)
|
||||
}
|
||||
|
||||
case "certificate", "privatekey":
|
||||
{
|
||||
caStore, err := rootCommand.KeyStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error building CA store: %v", err)
|
||||
}
|
||||
|
||||
if cmd.Type == "privatekey" {
|
||||
k, err := caStore.FindPrivateKey(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding privatekey: %v", err)
|
||||
}
|
||||
if k == nil {
|
||||
return fmt.Errorf("privatekey not found: %q", id)
|
||||
}
|
||||
value, err = k.AsString()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encoding privatekey: %v", err)
|
||||
}
|
||||
} else {
|
||||
c, err := caStore.FindCert(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding certificate: %v", err)
|
||||
}
|
||||
if c == nil {
|
||||
return fmt.Errorf("certificate not found: %q", id)
|
||||
}
|
||||
value, err = c.AsString()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encoding certiifcate: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("secret type not known: %q", cmd.Type)
|
||||
}
|
||||
|
||||
_, err := fmt.Fprint(os.Stdout, value+"\n")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to output: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
|
|
@ -2,101 +2,18 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"bytes"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type GetSecretsCommand struct {
|
||||
}
|
||||
|
||||
var getSecretsCommand GetSecretsCommand
|
||||
|
||||
func init() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get secrets",
|
||||
Long: `Get secrets.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := getSecretsCommand.Run()
|
||||
if err != nil {
|
||||
glog.Exitf("%v", err)
|
||||
}
|
||||
exitWithError(fmt.Errorf("The 'secrets get' command has been replaced by 'get secret'"))
|
||||
},
|
||||
}
|
||||
|
||||
secretsCmd.AddCommand(cmd)
|
||||
}
|
||||
|
||||
type SecretInfo struct {
|
||||
Id string
|
||||
Type string
|
||||
}
|
||||
|
||||
func (c *GetSecretsCommand) Run() error {
|
||||
var infos []*SecretInfo
|
||||
{
|
||||
caStore, err := rootCommand.KeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ids, err := caStore.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing CA store items %v", err)
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
info := &SecretInfo{
|
||||
Id: id,
|
||||
Type: "keypair",
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
secretStore, err := rootCommand.SecretStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ids, err := secretStore.ListSecrets()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing secrets %v", err)
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
info := &SecretInfo{
|
||||
Id: id,
|
||||
Type: "secret",
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
w := new(tabwriter.Writer)
|
||||
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape)
|
||||
for _, info := range infos {
|
||||
b.WriteByte(tabwriter.Escape)
|
||||
b.WriteString(info.Type)
|
||||
b.WriteByte(tabwriter.Escape)
|
||||
b.WriteByte('\t')
|
||||
b.WriteByte(tabwriter.Escape)
|
||||
b.WriteString(info.Id)
|
||||
b.WriteByte(tabwriter.Escape)
|
||||
b.WriteByte('\n')
|
||||
|
||||
_, err := w.Write(b.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to output: %v", err)
|
||||
}
|
||||
b.Reset()
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ And then navigate to `https://<clustername>/ui`
|
|||
The login credentials are:
|
||||
|
||||
* Username: `admin`
|
||||
* Password: get by running `kops secrets expose --id kube --type secret`
|
||||
* Password: get by running `kops get secrets kube --type secret -oplaintext`
|
||||
|
||||
|
||||
### Monitoring - Standalone
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
## Managing secrets
|
||||
|
||||
### get secrets
|
||||
|
||||
### get secret <name> -oplaintext
|
||||
|
||||
-oplaintext exposes the raw secret value.
|
||||
|
||||
### describe secret
|
||||
|
||||
`kops describe secret`
|
||||
|
||||
### create secret
|
||||
|
||||
`kops create secret publickey admin -i ~/.ssh/id_rsa.pub`
|
||||
|
||||
### delete secret
|
||||
|
||||
Syntax: `kops delete secret <type> <name>`
|
||||
or `kops delete secret <type> <name> <id>`
|
||||
|
||||
The ID form can be used when there are multiple matching keys.
|
||||
|
||||
example:
|
||||
`kops delete secret sshpublickey admin`
|
||||
|
|
@ -28,6 +28,19 @@ type Certificate struct {
|
|||
PublicKey crypto.PublicKey
|
||||
}
|
||||
|
||||
const (
|
||||
SecretTypeSSHPublicKey = "SSHPublicKey"
|
||||
SecretTypeKeypair = "Keypair"
|
||||
SecretTypeSecret = "Secret"
|
||||
)
|
||||
|
||||
type KeystoreItem struct {
|
||||
Type string
|
||||
Name string
|
||||
Id string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (c *Certificate) UnmarshalJSON(b []byte) error {
|
||||
s := ""
|
||||
if err := json.Unmarshal(b, &s); err == nil {
|
||||
|
@ -67,26 +80,36 @@ func (c *Certificate) MarshalJSON() ([]byte, error) {
|
|||
|
||||
type CAStore interface {
|
||||
// Cert returns the primary specified certificate
|
||||
Cert(id string) (*Certificate, error)
|
||||
Cert(name string) (*Certificate, error)
|
||||
// CertificatePool returns all active certificates with the specified id
|
||||
CertificatePool(id string) (*CertificatePool, error)
|
||||
PrivateKey(id string) (*PrivateKey, error)
|
||||
CertificatePool(name string) (*CertificatePool, error)
|
||||
PrivateKey(name string) (*PrivateKey, error)
|
||||
|
||||
FindCert(id string) (*Certificate, error)
|
||||
FindPrivateKey(id string) (*PrivateKey, error)
|
||||
FindCert(name string) (*Certificate, error)
|
||||
FindPrivateKey(name string) (*PrivateKey, error)
|
||||
|
||||
//IssueCert(id string, privateKey *PrivateKey, template *x509.Certificate) (*Certificate, error)
|
||||
//CreatePrivateKey(id string) (*PrivateKey, error)
|
||||
|
||||
CreateKeypair(id string, template *x509.Certificate) (*Certificate, *PrivateKey, error)
|
||||
CreateKeypair(name string, template *x509.Certificate) (*Certificate, *PrivateKey, error)
|
||||
|
||||
List() ([]string, error)
|
||||
// List will list all the items, but will not fetch the data
|
||||
List() ([]*KeystoreItem, error)
|
||||
|
||||
// VFSPath returns the path where the CAStore is stored
|
||||
VFSPath() vfs.Path
|
||||
|
||||
// AddCert adds an alternative certificate to the pool (primarily useful for CAs)
|
||||
AddCert(id string, cert *Certificate) error
|
||||
AddCert(name string, cert *Certificate) error
|
||||
|
||||
// AddSSHPublicKey adds an SSH public key
|
||||
AddSSHPublicKey(name string, data []byte) error
|
||||
|
||||
// FindSSHPublicKeys retrieves the SSH public keys with the specific name
|
||||
FindSSHPublicKeys(name string) ([]*KeystoreItem, error)
|
||||
|
||||
// DeleteSecret will delete the specified item
|
||||
DeleteSecret(item *KeystoreItem) error
|
||||
}
|
||||
|
||||
func (c *Certificate) AsString() (string, error) {
|
||||
|
|
|
@ -290,7 +290,7 @@ func (c *populateClusterSpec) run() error {
|
|||
if cluster.Spec.DNSZone == "" {
|
||||
dnsZone, err := cloud.FindDNSHostedZone(cluster.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error determining default DNS zone; please specify --zone-name: %v", err)
|
||||
return fmt.Errorf("Error determining default DNS zone; please specify --dns-zone: %v", err)
|
||||
}
|
||||
glog.Infof("Defaulting DNS zone to: %s", dnsZone)
|
||||
cluster.Spec.DNSZone = dnsZone
|
||||
|
|
|
@ -61,7 +61,12 @@ func (_ *Secret) Render(c *fi.Context, a, e, changes *Secret) error {
|
|||
|
||||
secrets := c.SecretStore
|
||||
|
||||
_, _, err := secrets.GetOrCreateSecret(name)
|
||||
secret, err := fi.CreateSecret()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating secret %q: %v", name, err)
|
||||
}
|
||||
|
||||
_, _, err = secrets.GetOrCreateSecret(name, secret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating secret %q: %v", name, err)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ type SecretStore interface {
|
|||
// Find a secret, if exists. Returns nil,nil if not found
|
||||
FindSecret(id string) (*Secret, error)
|
||||
// Create or replace a secret
|
||||
GetOrCreateSecret(id string) (secret *Secret, created bool, err error)
|
||||
GetOrCreateSecret(id string, secret *Secret) (current *Secret, created bool, err error)
|
||||
// Lists the ids of all known secrets
|
||||
ListSecrets() ([]string, error)
|
||||
|
||||
|
|
|
@ -2,12 +2,14 @@ package fi
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
crypto_rand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"k8s.io/kops/upup/pkg/fi/vfs"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -316,20 +318,66 @@ func (c *VFSCAStore) FindCertificatePool(id string) (*CertificatePool, error) {
|
|||
return pool, nil
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) List() ([]string, error) {
|
||||
var ids []string
|
||||
func (c *VFSCAStore) List() ([]*KeystoreItem, error) {
|
||||
var items []*KeystoreItem
|
||||
|
||||
issuedDir := c.basedir.Join("issued")
|
||||
files, err := issuedDir.ReadDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading directory %q: %v", issuedDir, err)
|
||||
{
|
||||
baseDir := c.basedir.Join("issued")
|
||||
files, err := baseDir.ReadTree()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading directory %q: %v", baseDir, err)
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
relativePath, err := vfs.RelativePath(baseDir, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokens := strings.Split(relativePath, "/")
|
||||
if len(tokens) != 2 {
|
||||
glog.V(2).Infof("ignoring unexpected file in keystore: %q", f)
|
||||
continue
|
||||
}
|
||||
|
||||
item := &KeystoreItem{
|
||||
Name: tokens[0],
|
||||
Id: strings.TrimSuffix(tokens[1], ".crt"),
|
||||
Type: SecretTypeKeypair,
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
name := f.Base()
|
||||
ids = append(ids, name)
|
||||
{
|
||||
baseDir := c.basedir.Join("ssh", "public")
|
||||
files, err := baseDir.ReadTree()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading directory %q: %v", baseDir, err)
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
relativePath, err := vfs.RelativePath(baseDir, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokens := strings.Split(relativePath, "/")
|
||||
if len(tokens) != 2 {
|
||||
glog.V(2).Infof("ignoring unexpected file in keystore: %q", f)
|
||||
continue
|
||||
}
|
||||
|
||||
item := &KeystoreItem{
|
||||
Name: tokens[0],
|
||||
Id: insertFingerprintColons(tokens[1]),
|
||||
Type: SecretTypeSSHPublicKey,
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) IssueCert(id string, serial *big.Int, privateKey *PrivateKey, template *x509.Certificate) (*Certificate, error) {
|
||||
|
@ -572,3 +620,143 @@ func buildSerial(timestamp int64) *big.Int {
|
|||
|
||||
return serial
|
||||
}
|
||||
|
||||
func formatFingerprint(data []byte) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
for i, b := range data {
|
||||
s := fmt.Sprintf("%0.2x", b)
|
||||
if i != 0 {
|
||||
buf.WriteString(":")
|
||||
}
|
||||
buf.WriteString(s)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func insertFingerprintColons(id string) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
for {
|
||||
if id == "" {
|
||||
break
|
||||
}
|
||||
if buf.Len() != 0 {
|
||||
buf.WriteString(":")
|
||||
}
|
||||
if len(id) < 2 {
|
||||
buf.WriteString(id)
|
||||
} else {
|
||||
buf.WriteString(id[0:2])
|
||||
id = id[2:]
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// AddSSHPublicKey stores an SSH public key
|
||||
func (c *VFSCAStore) AddSSHPublicKey(name string, pubkey []byte) error {
|
||||
var id string
|
||||
{
|
||||
sshPublicKey, _, _, _, err := ssh.ParseAuthorizedKey(pubkey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing public key: %v", err)
|
||||
}
|
||||
|
||||
// compute fingerprint to serve as id
|
||||
h := md5.New()
|
||||
_, err = h.Write(sshPublicKey.Marshal())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id = formatFingerprint(h.Sum(nil))
|
||||
}
|
||||
|
||||
p := c.buildSSHPublicKeyPath(name, id)
|
||||
return c.storeData(pubkey, p)
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) buildSSHPublicKeyPath(name string, id string) vfs.Path {
|
||||
// id is fingerprint with colons, but we store without colons
|
||||
id = strings.Replace(id, ":", "", -1)
|
||||
return c.basedir.Join("ssh", "public", name, id)
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) storeData(data []byte, p vfs.Path) error {
|
||||
return p.WriteFile(data)
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) FindSSHPublicKeys(name string) ([]*KeystoreItem, error) {
|
||||
p := c.basedir.Join("ssh", "public", name)
|
||||
|
||||
items, err := c.loadPath(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range items {
|
||||
// Fill in the missing fields
|
||||
item.Type = SecretTypeSSHPublicKey
|
||||
item.Name = name
|
||||
|
||||
item.Id = insertFingerprintColons(item.Id)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) loadPath(p vfs.Path) ([]*KeystoreItem, error) {
|
||||
files, err := p.ReadDir()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var keystoreItems []*KeystoreItem
|
||||
|
||||
for _, f := range files {
|
||||
data, err := f.ReadFile()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
glog.V(2).Infof("Ignoring not-found issue reading %q", f)
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("error loading keystore item %q: %v", f, err)
|
||||
}
|
||||
name := f.Base()
|
||||
keystoreItem := &KeystoreItem{
|
||||
Id: name,
|
||||
Data: data,
|
||||
}
|
||||
keystoreItems = append(keystoreItems, keystoreItem)
|
||||
}
|
||||
|
||||
return keystoreItems, nil
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) loadData(p vfs.Path) (*PrivateKey, error) {
|
||||
data, err := p.ReadFile()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
k, err := ParsePEMPrivateKey(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing private key from %q: %v", p, err)
|
||||
}
|
||||
return k, err
|
||||
}
|
||||
|
||||
func (c *VFSCAStore) DeleteSecret(item *KeystoreItem) error {
|
||||
switch item.Type {
|
||||
case SecretTypeSSHPublicKey:
|
||||
p := c.buildSSHPublicKeyPath(item.Name, item.Id)
|
||||
return p.Remove()
|
||||
|
||||
default:
|
||||
// Primarily because we need to make sure users can recreate them!
|
||||
return fmt.Errorf("deletion of keystore items of type %v not (yet) supported", item.Type)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ func (c *VFSSecretStore) Secret(id string) (*Secret, error) {
|
|||
return s, nil
|
||||
}
|
||||
|
||||
func (c *VFSSecretStore) GetOrCreateSecret(id string) (*Secret, bool, error) {
|
||||
func (c *VFSSecretStore) GetOrCreateSecret(id string, secret *Secret) (*Secret, bool, error) {
|
||||
p := c.buildSecretPath(id)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
|
@ -75,12 +75,7 @@ func (c *VFSSecretStore) GetOrCreateSecret(id string) (*Secret, bool, error) {
|
|||
return s, false, nil
|
||||
}
|
||||
|
||||
s, err = CreateSecret()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
err = c.createSecret(s, p)
|
||||
err = c.createSecret(secret, p)
|
||||
if err != nil {
|
||||
if os.IsExist(err) && i == 0 {
|
||||
glog.Infof("Got already-exists error when writing secret; likely due to concurrent creation. Will retry")
|
||||
|
|
Loading…
Reference in New Issue