mirror of https://github.com/kubernetes/kops.git
Merge pull request #4107 from chrislovecnm/validate-ouput
kops validate cluster can output YAML or JSON
This commit is contained in:
commit
b8a701b2da
|
@ -17,12 +17,14 @@ 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"
|
||||
|
@ -45,11 +47,16 @@ func init() {
|
|||
}
|
||||
|
||||
type ValidateClusterOptions struct {
|
||||
// No options yet
|
||||
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",
|
||||
|
@ -64,6 +71,8 @@ func NewCmdValidateCluster(f *util.Factory, out io.Writer) *cobra.Command {
|
|||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&options.output, "output", "o", options.output, "Ouput format. One of json|yaml|table.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -88,7 +97,9 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
|
|||
return fmt.Errorf("cannot get InstanceGroups for %q: %v", cluster.ObjectMeta.Name, err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "Validating cluster %v\n\n", cluster.ObjectMeta.Name)
|
||||
if options.output == OutputTable {
|
||||
fmt.Fprintf(out, "Validating cluster %v\n\n", cluster.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
var instanceGroups []api.InstanceGroup
|
||||
for _, ig := range list.Items {
|
||||
|
@ -116,30 +127,60 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
|
|||
|
||||
// Do not use if we are running gossip
|
||||
if !dns.IsGossipHostname(cluster.ObjectMeta.Name) {
|
||||
// TODO we may want to return validation.ValidationCluster instead of building it later on
|
||||
hasPlaceHolderIPAddress, err := validation.HasPlaceHolderIP(contextName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasPlaceHolderIPAddress {
|
||||
fmt.Println(
|
||||
"Validation Failed\n\n" +
|
||||
"The dns-controller Kubernetes deployment has not updated the Kubernetes cluster's API DNS entry to the correct IP address." +
|
||||
" The API DNS IP address is the placeholder address that kops creates: 203.0.113.123." +
|
||||
" Please wait about 5-10 minutes for a master to start, dns-controller to launch, and DNS to propagate." +
|
||||
" The protokube container and dns-controller deployment logs may contain more diagnostic information." +
|
||||
" Etcd and the API DNS entries must be updated for a kops Kubernetes cluster to start.")
|
||||
return fmt.Errorf("\nCannot reach cluster's API server: unable to Validate Cluster: %s", cluster.ObjectMeta.Name)
|
||||
message := "Validation Failed\n\n" +
|
||||
"The dns-controller Kubernetes deployment has not updated the Kubernetes cluster's API DNS entry to the correct IP address." +
|
||||
" The API DNS IP address is the placeholder address that kops creates: 203.0.113.123." +
|
||||
" Please wait about 5-10 minutes for a master to start, dns-controller to launch, and DNS to propagate." +
|
||||
" The protokube container and dns-controller deployment logs may contain more diagnostic information." +
|
||||
" Etcd and the API DNS entries must be updated for a kops Kubernetes cluster to start."
|
||||
validationCluster := &validation.ValidationCluster{
|
||||
ClusterName: cluster.ObjectMeta.Name,
|
||||
ErrorMessage: message,
|
||||
Status: validation.ClusterValidationFailed,
|
||||
}
|
||||
validationFailed := fmt.Errorf("\nCannot reach cluster's API server: unable to Validate Cluster: %s", cluster.ObjectMeta.Name)
|
||||
switch options.output {
|
||||
case OutputTable:
|
||||
fmt.Println(message)
|
||||
return validationFailed
|
||||
case OutputYaml:
|
||||
return validateClusterOutputYAML(validationCluster, validationFailed, out)
|
||||
case OutputJSON:
|
||||
return validateClusterOutputJSON(validationCluster, validationFailed, out)
|
||||
default:
|
||||
return fmt.Errorf("Unknown output format: %q", options.output)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
validationCluster, validationFailed := validation.ValidateCluster(cluster.ObjectMeta.Name, list, k8sClient)
|
||||
|
||||
if validationCluster == nil || validationCluster.NodeList == nil || validationCluster.NodeList.Items == nil {
|
||||
// validationFailed error is already formatted
|
||||
return validationFailed
|
||||
}
|
||||
|
||||
switch options.output {
|
||||
case OutputTable:
|
||||
return validateClusterOutputTable(validationCluster, validationFailed, instanceGroups, out)
|
||||
case OutputYaml:
|
||||
return validateClusterOutputYAML(validationCluster, validationFailed, out)
|
||||
case OutputJSON:
|
||||
return validateClusterOutputJSON(validationCluster, validationFailed, out)
|
||||
default:
|
||||
return fmt.Errorf("Unknown output format: %q", options.output)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func validateClusterOutputTable(validationCluster *validation.ValidationCluster, validationFailed error, instanceGroups []api.InstanceGroup, out io.Writer) error {
|
||||
t := &tables.Table{}
|
||||
t.AddColumn("NAME", func(c api.InstanceGroup) string {
|
||||
return c.ObjectMeta.Name
|
||||
|
@ -161,10 +202,10 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
|
|||
})
|
||||
|
||||
fmt.Fprintln(out, "INSTANCE GROUPS")
|
||||
err = t.Render(instanceGroups, out, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "SUBNETS")
|
||||
err := t.Render(instanceGroups, out, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "SUBNETS")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render nodes for %q: %v", cluster.ObjectMeta.Name, err)
|
||||
return fmt.Errorf("cannot render nodes for %q: %v", validationCluster.ClusterName, err)
|
||||
}
|
||||
|
||||
nodeTable := &tables.Table{}
|
||||
|
@ -191,7 +232,7 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
|
|||
err = nodeTable.Render(validationCluster.NodeList.Items, out, "NAME", "ROLE", "READY")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render nodes for %q: %v", cluster.ObjectMeta.Name, err)
|
||||
return fmt.Errorf("cannot render nodes for %q: %v", validationCluster.ClusterName, err)
|
||||
}
|
||||
|
||||
if len(validationCluster.ComponentFailures) != 0 {
|
||||
|
@ -204,7 +245,7 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
|
|||
err = componentFailuresTable.Render(validationCluster.ComponentFailures, out, "NAME")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render components for %q: %v", cluster.ObjectMeta.Name, err)
|
||||
return fmt.Errorf("cannot render components for %q: %v", validationCluster.ClusterName, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,12 +259,12 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
|
|||
err = podFailuresTable.Render(validationCluster.PodFailures, out, "NAME")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render pods for %q: %v", cluster.ObjectMeta.Name, err)
|
||||
return fmt.Errorf("cannot render pods for %q: %v", validationCluster.ClusterName, err)
|
||||
}
|
||||
}
|
||||
|
||||
if validationFailed == nil {
|
||||
fmt.Fprintf(out, "\nYour cluster %s is ready\n", cluster.ObjectMeta.Name)
|
||||
fmt.Fprintf(out, "\nYour cluster %s is ready\n", validationCluster.ClusterName)
|
||||
return nil
|
||||
} else {
|
||||
// do we need to print which instance group is not ready?
|
||||
|
@ -234,3 +275,26 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
|
|||
return validationFailed
|
||||
}
|
||||
}
|
||||
|
||||
func validateClusterOutputYAML(validationCluster *validation.ValidationCluster, validationFailed error, out io.Writer) error {
|
||||
y, err := yaml.Marshal(validationCluster)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshall YAML: %v\n", err)
|
||||
}
|
||||
return validateOutput(y, validationFailed, out)
|
||||
}
|
||||
|
||||
func validateClusterOutputJSON(validationCluster *validation.ValidationCluster, validationFailed error, out io.Writer) error {
|
||||
j, err := json.Marshal(validationCluster)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshall JSON: %v\n", err)
|
||||
}
|
||||
return validateOutput(j, validationFailed, out)
|
||||
}
|
||||
|
||||
func validateOutput(b []byte, validationFailed error, out io.Writer) error {
|
||||
if _, err := out.Write(b); err != nil {
|
||||
return fmt.Errorf("unable to print data: %v\n", err)
|
||||
}
|
||||
return validationFailed
|
||||
}
|
||||
|
|
|
@ -28,6 +28,12 @@ kops validate cluster
|
|||
kops validate cluster
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-o, --output string Ouput format. One of json|yaml|table. (default "table")
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
|
|
|
@ -31,7 +31,7 @@ import (
|
|||
"k8s.io/kops/pkg/apis/kops/util"
|
||||
)
|
||||
|
||||
// A cluster to validate
|
||||
// ValidationCluster a cluster to validate.
|
||||
type ValidationCluster struct {
|
||||
MastersReady bool `json:"mastersReady,omitempty"`
|
||||
MastersReadyArray []*ValidationNode `json:"mastersReadyArray,omitempty"`
|
||||
|
@ -45,11 +45,15 @@ type ValidationCluster struct {
|
|||
|
||||
NodeList *v1.NodeList `json:"nodeList,omitempty"`
|
||||
|
||||
ComponentFailures []string `json:"componentFailures,omitempty"`
|
||||
PodFailures []string `json:"podFailures,omitempty"`
|
||||
ComponentFailures []string `json:"componentFailures,omitempty"`
|
||||
PodFailures []string `json:"podFailures,omitempty"`
|
||||
ErrorMessage string `json:"errorMessage,omitempty"`
|
||||
Status string `json:"status"`
|
||||
ClusterName string `json:"clusterName"`
|
||||
InstanceGroups []*kops.InstanceGroup `json:"instanceGroups,omitempty"`
|
||||
}
|
||||
|
||||
// A K8s node to be validated
|
||||
// ValidationNode is A K8s node to be validated.
|
||||
type ValidationNode struct {
|
||||
Zone string `json:"zone,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
|
@ -57,7 +61,12 @@ type ValidationNode struct {
|
|||
Status v1.ConditionStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// HasPlaceHolderIP checks if the API DNS has been updated
|
||||
const (
|
||||
ClusterValidationFailed = "FAILED"
|
||||
ClusterValidationPassed = "PASSED"
|
||||
)
|
||||
|
||||
// HasPlaceHolderIP checks if the API DNS has been updated.
|
||||
func HasPlaceHolderIP(clusterName string) (bool, error) {
|
||||
|
||||
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
|
@ -86,7 +95,6 @@ func HasPlaceHolderIP(clusterName string) (bool, error) {
|
|||
// ValidateCluster validate a k8s cluster with a provided instance group list
|
||||
func ValidateCluster(clusterName string, instanceGroupList *kops.InstanceGroupList, clusterKubernetesClient kubernetes.Interface) (*ValidationCluster, error) {
|
||||
var instanceGroups []*kops.InstanceGroup
|
||||
validationCluster := &ValidationCluster{}
|
||||
|
||||
for i := range instanceGroupList.Items {
|
||||
ig := &instanceGroupList.Items[i]
|
||||
|
@ -94,7 +102,7 @@ func ValidateCluster(clusterName string, instanceGroupList *kops.InstanceGroupLi
|
|||
}
|
||||
|
||||
if len(instanceGroups) == 0 {
|
||||
return validationCluster, fmt.Errorf("no InstanceGroup objects found")
|
||||
return nil, fmt.Errorf("no InstanceGroup objects found")
|
||||
}
|
||||
|
||||
timeout, err := time.ParseDuration("10s")
|
||||
|
@ -107,6 +115,12 @@ func ValidateCluster(clusterName string, instanceGroupList *kops.InstanceGroupLi
|
|||
return nil, fmt.Errorf("error building node adapter for %q: %v", clusterName, err)
|
||||
}
|
||||
|
||||
validationCluster := &ValidationCluster{
|
||||
ClusterName: clusterName,
|
||||
ErrorMessage: ClusterValidationPassed,
|
||||
InstanceGroups: instanceGroups,
|
||||
}
|
||||
|
||||
validationCluster.NodeList, err = nodeAA.GetAllNodes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get nodes for %q: %v", clusterName, err)
|
||||
|
@ -161,7 +175,7 @@ func validateTheNodes(clusterName string, validationCluster *ValidationCluster)
|
|||
nodes := validationCluster.NodeList
|
||||
|
||||
if nodes == nil || len(nodes.Items) == 0 {
|
||||
return validationCluster, fmt.Errorf("No nodes found in validationCluster")
|
||||
return nil, fmt.Errorf("No nodes found in validationCluster")
|
||||
}
|
||||
// Needed for when NodesCount and MastersCounts are predefined, i.e tests
|
||||
presetNodeCount := validationCluster.NodesCount == 0
|
||||
|
@ -217,19 +231,27 @@ func validateTheNodes(clusterName string, validationCluster *ValidationCluster)
|
|||
}
|
||||
|
||||
if !validationCluster.MastersReady {
|
||||
return validationCluster, fmt.Errorf("your masters are NOT ready %s", clusterName)
|
||||
validationCluster.Status = ClusterValidationFailed
|
||||
validationCluster.ErrorMessage = fmt.Sprintf("your masters are NOT ready %s", clusterName)
|
||||
return validationCluster, fmt.Errorf(validationCluster.ErrorMessage)
|
||||
}
|
||||
|
||||
if !validationCluster.NodesReady {
|
||||
return validationCluster, fmt.Errorf("your nodes are NOT ready %s", clusterName)
|
||||
validationCluster.Status = ClusterValidationFailed
|
||||
validationCluster.ErrorMessage = fmt.Sprintf("your nodes are NOT ready %s", clusterName)
|
||||
return validationCluster, fmt.Errorf(validationCluster.ErrorMessage)
|
||||
}
|
||||
|
||||
if len(validationCluster.ComponentFailures) != 0 {
|
||||
return validationCluster, fmt.Errorf("your components are NOT healthy %s", clusterName)
|
||||
validationCluster.Status = ClusterValidationFailed
|
||||
validationCluster.ErrorMessage = fmt.Sprintf("your components are NOT healthy %s", clusterName)
|
||||
return validationCluster, fmt.Errorf(validationCluster.ErrorMessage)
|
||||
}
|
||||
|
||||
if len(validationCluster.PodFailures) != 0 {
|
||||
return validationCluster, fmt.Errorf("your kube-system pods are NOT healthy %s", clusterName)
|
||||
validationCluster.Status = ClusterValidationFailed
|
||||
validationCluster.ErrorMessage = fmt.Sprintf("your kube-system pods are NOT healthy %s", clusterName)
|
||||
return validationCluster, fmt.Errorf(validationCluster.ErrorMessage)
|
||||
}
|
||||
|
||||
return validationCluster, nil
|
||||
|
|
Loading…
Reference in New Issue