From c01455cf91d8f7cea80e0ac04578cd5e4e0500c0 Mon Sep 17 00:00:00 2001 From: Ole Markus With Date: Thu, 27 Aug 2020 06:53:08 +0200 Subject: [PATCH] Keep the good part from last attempt --- cmd/kops/BUILD.bazel | 4 + cmd/kops/get.go | 1 + cmd/kops/get_instances.go | 247 +++++++++++++++++++++++++++++++++ docs/cli/kops_get.md | 1 + docs/cli/kops_get_instances.md | 53 +++++++ 5 files changed, 306 insertions(+) create mode 100644 cmd/kops/get_instances.go create mode 100644 docs/cli/kops_get_instances.md diff --git a/cmd/kops/BUILD.bazel b/cmd/kops/BUILD.bazel index 77403c4989..64be5cc24d 100644 --- a/cmd/kops/BUILD.bazel +++ b/cmd/kops/BUILD.bazel @@ -32,6 +32,7 @@ go_library( "get.go", "get_cluster.go", "get_instancegroups.go", + "get_instances.go", "get_secrets.go", "import.go", "import_cluster.go", @@ -81,6 +82,8 @@ go_library( "//pkg/pki:go_default_library", "//pkg/pretty:go_default_library", "//pkg/resources:go_default_library", + "//pkg/resources/aws:go_default_library", + "//pkg/resources/openstack:go_default_library", "//pkg/resources/ops:go_default_library", "//pkg/sshcredentials:go_default_library", "//pkg/try:go_default_library", @@ -89,6 +92,7 @@ go_library( "//upup/pkg/fi:go_default_library", "//upup/pkg/fi/cloudup:go_default_library", "//upup/pkg/fi/cloudup/awsup:go_default_library", + "//upup/pkg/fi/cloudup/openstack:go_default_library", "//upup/pkg/fi/utils:go_default_library", "//upup/pkg/kutil:go_default_library", "//util/pkg/tables:go_default_library", diff --git a/cmd/kops/get.go b/cmd/kops/get.go index 25efe696b1..1d69cb2dd8 100644 --- a/cmd/kops/get.go +++ b/cmd/kops/get.go @@ -109,6 +109,7 @@ func NewCmdGet(f *util.Factory, out io.Writer) *cobra.Command { cmd.AddCommand(NewCmdGetCluster(f, out, options)) cmd.AddCommand(NewCmdGetInstanceGroups(f, out, options)) cmd.AddCommand(NewCmdGetSecrets(f, out, options)) + cmd.AddCommand(NewCmdGetInstances(f, out, options)) return cmd } diff --git a/cmd/kops/get_instances.go b/cmd/kops/get_instances.go new file mode 100644 index 0000000000..f48a3976af --- /dev/null +++ b/cmd/kops/get_instances.go @@ -0,0 +1,247 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "io" + "os" + "strings" + + "k8s.io/kops/pkg/cloudinstances" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" + + "k8s.io/kops/upup/pkg/fi" + + "k8s.io/kops/pkg/client/simple" + + "k8s.io/kops/pkg/resources/openstack" + + "k8s.io/klog/v2" + + "k8s.io/client-go/kubernetes" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" + + "k8s.io/kops/pkg/resources" + "k8s.io/kops/util/pkg/tables" + + "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/pkg/resources/aws" + + "github.com/spf13/cobra" + "k8s.io/kops/cmd/kops/util" + "k8s.io/kops/upup/pkg/fi/cloudup" + + osCloudup "k8s.io/kops/upup/pkg/fi/cloudup/openstack" +) + +func NewCmdGetInstances(f *util.Factory, out io.Writer, options *GetOptions) *cobra.Command { + getInstancesShort := i18n.T(`Display cluster instances.`) + + getInstancesLong := templates.LongDesc(i18n.T(` + Display cluster instances.`)) + + getInstancesExample := templates.Examples(i18n.T(` + # Display all instances. + kops get instances + `)) + + cmd := &cobra.Command{ + Use: "instances", + Short: getInstancesShort, + Long: getInstancesLong, + Example: getInstancesExample, + Run: func(cmd *cobra.Command, args []string) { + ctx := context.TODO() + + if err := rootCommand.ProcessArgs(args); err != nil { + exitWithError(err) + } + + err := RunGetInstances(ctx, f, out, options) + if err != nil { + exitWithError(err) + } + }, + } + + return cmd +} + +func RunGetInstances(ctx context.Context, f *util.Factory, out io.Writer, options *GetOptions) error { + + clientset, err := f.Clientset() + if err != nil { + return err + } + + clusterName := rootCommand.ClusterName() + options.clusterName = clusterName + if clusterName == "" { + return fmt.Errorf("--name is required") + } + + cluster, err := clientset.GetCluster(ctx, options.clusterName) + if err != nil { + return err + } + + if cluster == nil { + return fmt.Errorf("cluster not found %q", options.clusterName) + } + + cloud, err := cloudup.BuildCloud(cluster) + if err != nil { + return err + } + + k8sClient, err := createK8sClient(cluster) + if err != nil { + return err + } + + var status map[string]string + nodeList, err := k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil { + klog.V(2).Infof("error listing nodes: %v", err) + } else { + status, _ = getNodeStatus(ctx, cloud, clientset, cluster, nodeList.Items) + } + + var instances []*resources.Instance + + switch cloud.ProviderID() { + case kops.CloudProviderAWS: + rs, _ := aws.ListInstances(cloud, options.clusterName) + for _, r := range rs { + instances = append(instances, aws.GetInstanceFromResource(r)) + } + case kops.CloudProviderOpenstack: + rs, _ := openstack.ListResources(cloud.(osCloudup.OpenstackCloud), options.clusterName) + for _, r := range rs { + if r.Type == "Instance" { + instances = append(instances, openstack.GetInstanceFromResource(r)) + } + } + default: + return fmt.Errorf("cloud provider not supported") + } + + switch options.output { + case OutputTable: + return instanceOutputTable(instances, status, out) + default: + return fmt.Errorf("Unsupported output format: %q", options.output) + } +} + +func instanceOutputTable(instances []*resources.Instance, status map[string]string, out io.Writer) error { + t := &tables.Table{} + t.AddColumn("ID", func(i *resources.Instance) string { + return i.ID + }) + t.AddColumn("NAME", func(i *resources.Instance) string { + return i.Name + }) + t.AddColumn("STATUS", func(i *resources.Instance) string { + s := status[i.ID] + if s == "" { + return "NotJoined" + } else { + return s + } + }) + t.AddColumn("ROLES", func(i *resources.Instance) string { + return strings.Join(i.Roles, ", ") + }) + t.AddColumn("INTERNAL-IP", func(i *resources.Instance) string { + return i.PrivateAddress + }) + t.AddColumn("INSTANCE-GROUP", func(i *resources.Instance) string { + return i.InstanceGroup + }) + t.AddColumn("MACHINE-TYPE", func(i *resources.Instance) string { + return i.MachineType + }) + return t.Render(instances, os.Stdout, "ID", "NAME", "STATUS", "ROLES", "INTERNAL-IP", "INSTANCE-GROUP", "MACHINE-TYPE") +} + +func getNodeStatus(ctx context.Context, cloud fi.Cloud, clientset simple.Clientset, cluster *kops.Cluster, nodes []v1.Node) (map[string]string, error) { + status := make(map[string]string) + igList, err := clientset.InstanceGroupsFor(cluster).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + + var instanceGroups []*kops.InstanceGroup + for i := range igList.Items { + instanceGroups = append(instanceGroups, &igList.Items[i]) + } + igs := cloudinstances.GetNodeMap(nodes, cluster) + + cloudGroups, err := cloud.GetCloudGroups(cluster, instanceGroups, false, nodes) + if err != nil { + return nil, err + } + + for _, cg := range cloudGroups { + for _, instance := range cg.Ready { + if instance.Detached { + status[instance.ID] = "Detached" + } else { + if igs[instance.ID] != nil { + status[instance.ID] = "Ready" + } else { + status[instance.ID] = "NotJoined" + } + } + } + } + + for _, cg := range cloudGroups { + for _, node := range cg.NeedUpdate { + if node.Detached { + status[node.ID] = "Detached" + } else { + status[node.ID] = "NeedsUpdate" + } + } + } + return status, nil +} + +func createK8sClient(cluster *kops.Cluster) (*kubernetes.Clientset, error) { + contextName := cluster.ObjectMeta.Name + clientGetter := genericclioptions.NewConfigFlags(true) + clientGetter.Context = &contextName + + config, err := clientGetter.ToRESTConfig() + if err != nil { + return nil, fmt.Errorf("cannot load kubecfg settings for %q: %v", contextName, err) + } + k8sClient, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("cannot build kubernetes api client for %q: %v", contextName, err) + } + return k8sClient, nil + +} diff --git a/docs/cli/kops_get.md b/docs/cli/kops_get.md index d7735b365e..64d4dfc726 100644 --- a/docs/cli/kops_get.md +++ b/docs/cli/kops_get.md @@ -71,5 +71,6 @@ kops get [flags] * [kops](kops.md) - kops is Kubernetes ops. * [kops get clusters](kops_get_clusters.md) - Get one or many clusters. * [kops get instancegroups](kops_get_instancegroups.md) - Get one or many instancegroups +* [kops get instances](kops_get_instances.md) - Display cluster instances. * [kops get secrets](kops_get_secrets.md) - Get one or many secrets. diff --git a/docs/cli/kops_get_instances.md b/docs/cli/kops_get_instances.md new file mode 100644 index 0000000000..29bcae906d --- /dev/null +++ b/docs/cli/kops_get_instances.md @@ -0,0 +1,53 @@ + + + +## kops get instances + +Display cluster instances. + +### Synopsis + +Display cluster instances. + +``` +kops get instances [flags] +``` + +### Examples + +``` + # Display all instances. + kops get instances +``` + +### Options + +``` + -h, --help help for instances +``` + +### Options inherited from parent commands + +``` + --add_dir_header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files + --config string yaml config file (default is $HOME/.kops.yaml) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory + --log_file string If non-empty, use this log file + --log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --name string Name of cluster. Overrides KOPS_CLUSTER_NAME environment variable + -o, --output string output format. One of: table, yaml, json (default "table") + --skip_headers If true, avoid header prefixes in the log messages + --skip_log_headers If true, avoid headers when opening log files + --state string Location of state storage (kops 'config' file). Overrides KOPS_STATE_STORE environment variable + --stderrthreshold severity logs at or above this threshold go to stderr (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [kops get](kops_get.md) - Get one or many resources. +