diff --git a/upup/cmd/upup/get.go b/upup/cmd/upup/get.go new file mode 100644 index 0000000000..b728252fd7 --- /dev/null +++ b/upup/cmd/upup/get.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +// getCmd represents the get command +var getCmd = &cobra.Command{ + Use: "get", + Short: "list or get obejcts", + Long: `list or get obejcts`, +} + +func init() { + rootCommand.AddCommand(getCmd) +} diff --git a/upup/cmd/upup/get_cluster.go b/upup/cmd/upup/get_cluster.go new file mode 100644 index 0000000000..a02bc3cfe6 --- /dev/null +++ b/upup/cmd/upup/get_cluster.go @@ -0,0 +1,139 @@ +package main + +import ( + "fmt" + + "bytes" + "github.com/golang/glog" + "github.com/spf13/cobra" + "k8s.io/kube-deploy/upup/pkg/api" + "k8s.io/kube-deploy/upup/pkg/fi" + "os" + "reflect" + "text/tabwriter" +) + +type GetClustersCmd struct { +} + +var getClustersCmd GetClustersCmd + +func init() { + cmd := &cobra.Command{ + Use: "cluster", + Aliases: []string{"clusters"}, + Short: "get clusters", + Long: `List or get clusters.`, + Run: func(cmd *cobra.Command, args []string) { + err := getClustersCmd.Run() + if err != nil { + glog.Exitf("%v", err) + } + }, + } + + getCmd.AddCommand(cmd) +} + +func (c *GetClustersCmd) Run() error { + clusterNames, err := rootCommand.ListClusters() + if err != nil { + return err + } + + columns := []string{} + fields := []func(*api.Cluster) string{} + + columns = append(columns, "NAME") + fields = append(fields, func(c *api.Cluster) string { + return c.Name + }) + + var clusters []*api.Cluster + + for _, clusterName := range clusterNames { + stateStore, err := rootCommand.StateStoreForCluster(clusterName) + if err != nil { + return err + } + + // TODO: Faster if we don't read groups... + // We probably can just have a comand which directly reads all cluster config files + cluster, _, err := api.ReadConfig(stateStore) + clusters = append(clusters, cluster) + } + if len(clusters) == 0 { + return nil + } + return WriteTable(clusters, columns, fields) +} + +func WriteTable(items interface{}, columns []string, fields interface{}) error { + itemsValue := reflect.ValueOf(items) + if itemsValue.Kind() != reflect.Slice { + glog.Fatal("unexpected kind for items in WriteTable: ", itemsValue.Kind()) + } + fieldsValue := reflect.ValueOf(fields) + if fieldsValue.Kind() != reflect.Slice { + glog.Fatal("unexpected kind for fields in WriteTable: ", fieldsValue.Kind()) + } + + length := itemsValue.Len() + + 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) + + writeHeader := true + if writeHeader { + for i, c := range columns { + if i != 0 { + b.WriteByte('\t') + } + b.WriteByte(tabwriter.Escape) + b.WriteString(c) + 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() + } + + for i := 0; i < length; i++ { + item := itemsValue.Index(i) + + for j := range columns { + if j != 0 { + b.WriteByte('\t') + } + + fieldFunc := fieldsValue.Index(j) + var args []reflect.Value + args = append(args, item) + fvs := fieldFunc.Call(args) + fv := fvs[0] + + s := fi.ValueAsString(fv) + + b.WriteByte(tabwriter.Escape) + b.WriteString(s) + 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 +} diff --git a/upup/cmd/upup/root.go b/upup/cmd/upup/root.go index 683195a0a8..9811fa4a9a 100644 --- a/upup/cmd/upup/root.go +++ b/upup/cmd/upup/root.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/viper" "k8s.io/kube-deploy/upup/pkg/fi" "k8s.io/kube-deploy/upup/pkg/fi/vfs" + "strings" ) type RootCmd struct { @@ -75,14 +76,27 @@ func (c *RootCmd) AddCommand(cmd *cobra.Command) { } func (c *RootCmd) StateStore() (fi.StateStore, error) { + if c.clusterName == "" { + return nil, fmt.Errorf("--name is required") + } + if c.stateStore != nil { return c.stateStore, nil } + stateStore, err := c.StateStoreForCluster(c.clusterName) + if err != nil { + return nil, err + } + c.stateStore = stateStore + return stateStore, nil +} + +func (c *RootCmd) StateStoreForCluster(clusterName string) (fi.StateStore, error) { if c.stateLocation == "" { return nil, fmt.Errorf("--state is required") } - if c.clusterName == "" { - return nil, fmt.Errorf("--name is required") + if clusterName == "" { + return nil, fmt.Errorf("clusterName is required") } statePath, err := vfs.Context.BuildVfsPath(c.stateLocation) @@ -91,13 +105,43 @@ func (c *RootCmd) StateStore() (fi.StateStore, error) { } isDryrun := false - stateStore, err := fi.NewVFSStateStore(statePath, c.clusterName, isDryrun) + stateStore, err := fi.NewVFSStateStore(statePath, clusterName, isDryrun) if err != nil { return nil, fmt.Errorf("error building state store: %v", err) } - c.stateStore = stateStore return stateStore, nil } + +func (c *RootCmd) ListClusters() ([]string, error) { + if c.stateLocation == "" { + return nil, fmt.Errorf("--state is required") + } + + statePath, err := vfs.Context.BuildVfsPath(c.stateLocation) + if err != nil { + return nil, fmt.Errorf("error building state store path: %v", err) + } + + paths, err := statePath.ReadTree() + if err != nil { + return nil, fmt.Errorf("error reading state store: %v", err) + } + + var keys []string + for _, p := range paths { + relativePath, err := vfs.RelativePath(statePath, p) + if err != nil { + return nil, err + } + if !strings.HasSuffix(relativePath, "/config") { + continue + } + key := strings.TrimSuffix(relativePath, "/config") + keys = append(keys, key) + } + return keys, nil +} + func (c *RootCmd) Secrets() (fi.SecretStore, error) { s, err := c.StateStore() if err != nil { diff --git a/upup/pkg/api/registry.go b/upup/pkg/api/registry.go index d2f27d9269..14f0ebec13 100644 --- a/upup/pkg/api/registry.go +++ b/upup/pkg/api/registry.go @@ -3,7 +3,9 @@ package api import ( "fmt" "k8s.io/kube-deploy/upup/pkg/fi" + "k8s.io/kube-deploy/upup/pkg/fi/vfs" "k8s.io/kubernetes/pkg/api/unversioned" + "strings" "time" ) @@ -65,3 +67,40 @@ func ReadConfig(stateStore fi.StateStore) (*Cluster, []*InstanceGroup, error) { return cluster, instanceGroups, nil } + +func DeleteConfig(stateStore fi.StateStore) error { + paths, err := stateStore.VFSPath().ReadTree() + if err != nil { + return fmt.Errorf("error listing files in state store: %v", err) + } + + for _, path := range paths { + relativePath, err := vfs.RelativePath(stateStore.VFSPath(), path) + if err != nil { + return err + } + if relativePath == "config" || relativePath == "cluster.spec" { + continue + } + if strings.HasPrefix(relativePath, "pki/") { + continue + } + if strings.HasPrefix(relativePath, "secrets/") { + continue + } + if strings.HasPrefix(relativePath, "instancegroup/") { + continue + } + + return fmt.Errorf("refusing to delete: unknown file found: %s", path) + } + + for _, path := range paths { + err = path.Remove() + if err != nil { + return fmt.Errorf("error deleting cluster file %s: %v", path, err) + } + } + + return nil +}