kops/cmd/kops/validate_cluster.go

241 lines
6.6 KiB
Go

/*
Copyright 2016 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 (
"encoding/json"
"fmt"
"io"
"os"
"runtime"
"strings"
"github.com/ghodss/yaml"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kops/cmd/kops/util"
api "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/validation"
"k8s.io/kops/util/pkg/tables"
)
func init() {
if runtime.GOOS == "darwin" {
// In order for net.LookupHost(apiAddr.Host) to lookup our placeholder address on darwin, we have to
os.Setenv("GODEBUG", "netdns=go")
}
}
type ValidateClusterOptions struct {
output string
}
func (o *ValidateClusterOptions) InitDefaults() {
o.output = OutputTable
}
func NewCmdValidateCluster(f *util.Factory, out io.Writer) *cobra.Command {
options := &ValidateClusterOptions{}
options.InitDefaults()
cmd := &cobra.Command{
Use: "cluster",
Short: validateShort,
Long: validateLong,
Example: validateExample,
Run: func(cmd *cobra.Command, args []string) {
result, err := RunValidateCluster(f, cmd, args, os.Stdout, options)
if err != nil {
exitWithError(err)
}
// We want the validate command to exit non-zero if validation found a problem,
// even if we didn't really hit an error during validation.
if len(result.Failures) != 0 {
os.Exit(2)
}
},
}
cmd.Flags().StringVarP(&options.output, "output", "o", options.output, "Output format. One of json|yaml|table.")
return cmd
}
func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out io.Writer, options *ValidateClusterOptions) (*validation.ValidationCluster, error) {
err := rootCommand.ProcessArgs(args)
if err != nil {
return nil, err
}
cluster, err := rootCommand.Cluster()
if err != nil {
return nil, err
}
clientSet, err := f.Clientset()
if err != nil {
return nil, err
}
list, err := clientSet.InstanceGroupsFor(cluster).List(metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("cannot get InstanceGroups for %q: %v", cluster.ObjectMeta.Name, err)
}
if options.output == OutputTable {
fmt.Fprintf(out, "Validating cluster %v\n\n", cluster.ObjectMeta.Name)
}
var instanceGroups []api.InstanceGroup
for _, ig := range list.Items {
instanceGroups = append(instanceGroups, ig)
glog.V(2).Infof("instance group: %#v\n\n", ig.Spec)
}
if len(instanceGroups) == 0 {
return nil, fmt.Errorf("no InstanceGroup objects found")
}
// TODO: Refactor into util.Factory
contextName := cluster.ObjectMeta.Name
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
clientcmd.NewDefaultClientConfigLoadingRules(),
&clientcmd.ConfigOverrides{CurrentContext: contextName}).ClientConfig()
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)
}
result, err := validation.ValidateCluster(cluster, list, k8sClient)
if err != nil {
return nil, fmt.Errorf("unexpected error during validation: %v", err)
}
switch options.output {
case OutputTable:
if err := validateClusterOutputTable(result, cluster, instanceGroups, out); err != nil {
return nil, err
}
case OutputYaml:
y, err := yaml.Marshal(result)
if err != nil {
return nil, fmt.Errorf("unable to marshal YAML: %v", err)
}
if _, err := out.Write(y); err != nil {
return nil, fmt.Errorf("error writing to output: %v", err)
}
case OutputJSON:
j, err := json.Marshal(result)
if err != nil {
return nil, fmt.Errorf("unable to marshal JSON: %v", err)
}
if _, err := out.Write(j); err != nil {
return nil, fmt.Errorf("error writing to output: %v", err)
}
default:
return nil, fmt.Errorf("Unknown output format: %q", options.output)
}
return result, nil
}
func validateClusterOutputTable(result *validation.ValidationCluster, cluster *api.Cluster, instanceGroups []api.InstanceGroup, out io.Writer) error {
t := &tables.Table{}
t.AddColumn("NAME", func(c api.InstanceGroup) string {
return c.ObjectMeta.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("SUBNETS", func(c api.InstanceGroup) string {
return strings.Join(c.Spec.Subnets, ",")
})
t.AddColumn("MIN", func(c api.InstanceGroup) string {
return int32PointerToString(c.Spec.MinSize)
})
t.AddColumn("MAX", func(c api.InstanceGroup) string {
return int32PointerToString(c.Spec.MaxSize)
})
fmt.Fprintln(out, "INSTANCE GROUPS")
err := t.Render(instanceGroups, out, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "SUBNETS")
if err != nil {
return fmt.Errorf("cannot render nodes for %q: %v", cluster.Name, err)
}
{
nodeTable := &tables.Table{}
nodeTable.AddColumn("NAME", func(n *validation.ValidationNode) string {
return n.Name
})
nodeTable.AddColumn("READY", func(n *validation.ValidationNode) v1.ConditionStatus {
return n.Status
})
nodeTable.AddColumn("ROLE", func(n *validation.ValidationNode) string {
return n.Role
})
fmt.Fprintln(out, "\nNODE STATUS")
if err := nodeTable.Render(result.Nodes, out, "NAME", "ROLE", "READY"); err != nil {
return fmt.Errorf("cannot render nodes for %q: %v", cluster.Name, err)
}
}
if len(result.Failures) != 0 {
failuresTable := &tables.Table{}
failuresTable.AddColumn("KIND", func(e *validation.ValidationError) string {
return e.Kind
})
failuresTable.AddColumn("NAME", func(e *validation.ValidationError) string {
return e.Name
})
failuresTable.AddColumn("MESSAGE", func(e *validation.ValidationError) string {
return e.Message
})
fmt.Fprintln(out, "\nVALIDATION ERRORS")
if err := failuresTable.Render(result.Failures, out, "KIND", "NAME", "MESSAGE"); err != nil {
return fmt.Errorf("error rendering failures table: %v", err)
}
}
if len(result.Failures) == 0 {
fmt.Fprintf(out, "\nYour cluster %s is ready\n", cluster.Name)
} else {
fmt.Fprint(out, "\nValidation Failed\n")
}
return nil
}