Browse code

added oadm commands to validate node and master config

Steve Kuznetsov authored on 2015/08/12 02:33:04
Showing 8 changed files
... ...
@@ -147,6 +147,18 @@ openshift start \
147 147
   --etcd-dir="${ETCD_DATA_DIR}" \
148 148
   --images="${USE_IMAGES}"
149 149
 
150
+# validate config that was generated
151
+[ "$(openshift ex validate master-config ${MASTER_CONFIG_DIR}/master-config.yaml 2>&1 | grep SUCCESS)" ]
152
+[ "$(openshift ex validate node-config ${NODE_CONFIG_DIR}/node-config.yaml 2>&1 | grep SUCCESS)" ]
153
+# breaking the config fails the validation check
154
+cp ${MASTER_CONFIG_DIR}/master-config.yaml ${TEMP_DIR}/master-config-broken.yaml
155
+sed -i '5,10d' ${TEMP_DIR}/master-config-broken.yaml
156
+[ "$(openshift ex validate master-config ${TEMP_DIR}/master-config-broken.yaml 2>&1 | grep error)" ]
157
+
158
+cp ${NODE_CONFIG_DIR}/node-config.yaml ${TEMP_DIR}/node-config-broken.yaml
159
+sed -i '5,10d' ${TEMP_DIR}/node-config-broken.yaml
160
+[ "$(openshift ex validate node-config ${TEMP_DIR}/node-config-broken.yaml 2>&1 | grep ERROR)" ]
161
+echo "validation: ok"
150 162
 
151 163
 # Don't try this at home.  We don't have flags for setting etcd ports in the config, but we want deconflicted ones.  Use sed to replace defaults in a completely unsafe way
152 164
 sed -i "s/:4001$/:${ETCD_PORT}/g" ${SERVER_CONFIG_DIR}/master/master-config.yaml
153 165
new file mode 100644
... ...
@@ -0,0 +1,16 @@
0
+package admin
1
+
2
+const (
3
+	BashCompletionFunc = `
4
+__custom_func() {
5
+    case ${last_command} in
6
+        oadm_validate_master-config | oadm_validate_node-config)
7
+            _filedir
8
+            return
9
+            ;;
10
+        *)
11
+            ;;
12
+    esac
13
+}
14
+`
15
+)
0 16
new file mode 100644
... ...
@@ -0,0 +1,176 @@
0
+package validate
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"io"
6
+	"os"
7
+	"text/tabwriter"
8
+
9
+	"github.com/spf13/cobra"
10
+
11
+	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
12
+	"k8s.io/kubernetes/pkg/util/fielderrors"
13
+
14
+	configapilatest "github.com/openshift/origin/pkg/cmd/server/api/latest"
15
+	"github.com/openshift/origin/pkg/cmd/server/api/validation"
16
+)
17
+
18
+const (
19
+	ValidateMasterConfigRecommendedName = "master-config"
20
+
21
+	validateMasterConfigLong = `
22
+Validate the configuration file for a master server.
23
+
24
+This command validates that a configuration file intended to be used for a master server is valid.
25
+`
26
+
27
+	validateMasterConfigExample = ` // Validate master server configuration file
28
+  $ %s openshift.local.config/master/master-config.yaml`
29
+)
30
+
31
+type ValidateMasterConfigOptions struct {
32
+	// MasterConfigFile is the location of the config file to be validated
33
+	MasterConfigFile string
34
+
35
+	// Out is the writer to write output to
36
+	Out io.Writer
37
+}
38
+
39
+// NewCommandValidateMasterConfig provides a CLI handler for the `validate all-in-one` command
40
+func NewCommandValidateMasterConfig(name, fullName string, out io.Writer) *cobra.Command {
41
+	options := &ValidateMasterConfigOptions{
42
+		Out: out,
43
+	}
44
+
45
+	cmd := &cobra.Command{
46
+		Use:     fmt.Sprintf("%s SOURCE", name),
47
+		Short:   "Validate the configuration file for a master server",
48
+		Long:    validateMasterConfigLong,
49
+		Example: fmt.Sprintf(validateMasterConfigExample, fullName),
50
+		Run: func(c *cobra.Command, args []string) {
51
+			if err := options.Complete(args); err != nil {
52
+				cmdutil.CheckErr(cmdutil.UsageError(c, err.Error()))
53
+			}
54
+
55
+			ok, err := options.Run()
56
+			cmdutil.CheckErr(err)
57
+			if !ok {
58
+				fmt.Fprintf(options.Out, "FAILURE: Validation failed for file: %s\n", options.MasterConfigFile)
59
+				os.Exit(1)
60
+			}
61
+
62
+			fmt.Fprintf(options.Out, "SUCCESS: Validation succeded for file: %s\n", options.MasterConfigFile)
63
+		},
64
+	}
65
+
66
+	return cmd
67
+}
68
+
69
+func (o *ValidateMasterConfigOptions) Complete(args []string) error {
70
+	if len(args) != 1 {
71
+		return errors.New("exactly one source file is required")
72
+	}
73
+	o.MasterConfigFile = args[0]
74
+	return nil
75
+}
76
+
77
+// Run runs the master config validation and returns the result of the validation as a boolean as well as any errors
78
+// that occured trying to validate the file
79
+func (o *ValidateMasterConfigOptions) Run() (bool, error) {
80
+	masterConfig, err := configapilatest.ReadAndResolveMasterConfig(o.MasterConfigFile)
81
+	if err != nil {
82
+		return true, err
83
+	}
84
+
85
+	results := validation.ValidateMasterConfig(masterConfig)
86
+	writer := tabwriter.NewWriter(o.Out, minColumnWidth, tabWidth, padding, padchar, flags)
87
+	err = prettyPrintValidationResults(results, writer)
88
+	if err != nil {
89
+		return len(results.Errors) == 0, fmt.Errorf("could not print results: %v", err)
90
+	}
91
+	writer.Flush()
92
+	return len(results.Errors) == 0, nil
93
+}
94
+
95
+const (
96
+	minColumnWidth          = 4
97
+	tabWidth                = 4
98
+	padding                 = 2
99
+	padchar                 = byte(' ')
100
+	flags                   = 0
101
+	validationErrorHeadings = "ERROR\tFIELD\tVALUE\tDETAILS\n"
102
+)
103
+
104
+// prettyPrintValidationResults prints the contents of the ValidationResults into the buffer of a tabwriter.Writer.
105
+// The writer must be Flush()ed after calling this to write the buffered data.
106
+func prettyPrintValidationResults(results validation.ValidationResults, writer *tabwriter.Writer) error {
107
+	if len(results.Errors) > 0 {
108
+		fmt.Fprintf(writer, "VALIDATION ERRORS:\t\t\t\n")
109
+		err := prettyPrintValidationErrorList(results.Errors, writer)
110
+		if err != nil {
111
+			return err
112
+		}
113
+	}
114
+	if len(results.Warnings) > 0 {
115
+		fmt.Fprintf(writer, "VALIDATION WARNINGS:\t\t\t\n")
116
+		err := prettyPrintValidationErrorList(results.Errors, writer)
117
+		if err != nil {
118
+			return err
119
+		}
120
+	}
121
+	return nil
122
+}
123
+
124
+// prettyPrintValidationErrorList prints the contents of the ValidationErrorList into the buffer of a tabwriter.Writer.
125
+// The writer must be Flush()ed after calling this to write the buffered data.
126
+func prettyPrintValidationErrorList(validationErrors fielderrors.ValidationErrorList, writer *tabwriter.Writer) error {
127
+	if len(validationErrors) > 0 {
128
+		fmt.Fprintf(writer, validationErrorHeadings)
129
+		for _, err := range validationErrors {
130
+			switch validationError := err.(type) {
131
+			case (*fielderrors.ValidationError):
132
+				err := prettyPrintValidationError(validationError, writer)
133
+				if err != nil {
134
+					return err
135
+				}
136
+			default:
137
+				// This is not a validation error but we can grab the error message for details nonetheless
138
+				err := prettyPrintGenericError(validationError, writer)
139
+				if err != nil {
140
+					return err
141
+				}
142
+			}
143
+		}
144
+	}
145
+	return nil
146
+}
147
+
148
+// prettyPrintValidationError prints the contents of the ValidationError into the buffer of a tabwriter.Writer.
149
+// The writer must be Flush()ed after calling this to write the buffered data.
150
+func prettyPrintValidationError(validationError *fielderrors.ValidationError, writer *tabwriter.Writer) error {
151
+	_, printError := fmt.Fprintf(writer, "%s\t%s\t%s\t%s\n",
152
+		toString(validationError.Type),
153
+		validationError.Field,
154
+		toString(validationError.BadValue),
155
+		validationError.Detail)
156
+
157
+	return printError
158
+}
159
+
160
+const missingValue = "<none>"
161
+
162
+func toString(v interface{}) string {
163
+	value := fmt.Sprintf("%v", v)
164
+	if len(value) == 0 {
165
+		value = missingValue
166
+	}
167
+	return value
168
+}
169
+
170
+// prettyPrintGenericError prints the contents of the generic error into the buffer of a tabwriter.Writer.
171
+// The writer must be Flush()ed after calling this to write the buffered data.
172
+func prettyPrintGenericError(err error, writer *tabwriter.Writer) error {
173
+	_, printError := fmt.Fprintf(writer, "\t\t\t%s\n", err.Error())
174
+	return printError
175
+}
0 176
new file mode 100644
... ...
@@ -0,0 +1,93 @@
0
+package validate
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"io"
6
+	"os"
7
+	"text/tabwriter"
8
+
9
+	"github.com/spf13/cobra"
10
+
11
+	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
12
+
13
+	configapilatest "github.com/openshift/origin/pkg/cmd/server/api/latest"
14
+	"github.com/openshift/origin/pkg/cmd/server/api/validation"
15
+)
16
+
17
+const (
18
+	ValidateNodeConfigRecommendedName = "node-config"
19
+
20
+	validateNodeConfigLong = `
21
+Validate the configuration file for a node.
22
+
23
+This command validates that a configuration file intended to be used for a node is valid.
24
+`
25
+
26
+	valiateNodeConfigExample = ` // Validate node configuration file
27
+  $ %s openshift.local.config/master/node-config.yaml`
28
+)
29
+
30
+type ValidateNodeConfigOptions struct {
31
+	// NodeConfigFile is the location of the config file to be validated
32
+	NodeConfigFile string
33
+
34
+	// Out is the writer to write output to
35
+	Out io.Writer
36
+}
37
+
38
+// NewCommandValidateMasterConfig provides a CLI handler for the `validate all-in-one` command
39
+func NewCommandValidateNodeConfig(name, fullName string, out io.Writer) *cobra.Command {
40
+	options := &ValidateNodeConfigOptions{
41
+		Out: out,
42
+	}
43
+
44
+	cmd := &cobra.Command{
45
+		Use:     fmt.Sprintf("%s SOURCE", name),
46
+		Short:   "Validate the configuration file for a node",
47
+		Long:    validateNodeConfigLong,
48
+		Example: fmt.Sprintf(valiateNodeConfigExample, fullName),
49
+		Run: func(c *cobra.Command, args []string) {
50
+			if err := options.Complete(args); err != nil {
51
+				cmdutil.CheckErr(cmdutil.UsageError(c, err.Error()))
52
+			}
53
+
54
+			ok, err := options.Run()
55
+			cmdutil.CheckErr(err)
56
+			if !ok {
57
+				fmt.Fprintf(options.Out, "FAILURE: Validation failed for file: %s\n", options.NodeConfigFile)
58
+				os.Exit(1)
59
+			}
60
+
61
+			fmt.Fprintf(options.Out, "SUCCESS: Validation succeded for file: %s\n", options.NodeConfigFile)
62
+		},
63
+	}
64
+
65
+	return cmd
66
+}
67
+
68
+func (o *ValidateNodeConfigOptions) Complete(args []string) error {
69
+	if len(args) != 1 {
70
+		return errors.New("exactly one source file is required")
71
+	}
72
+	o.NodeConfigFile = args[0]
73
+	return nil
74
+}
75
+
76
+// Run runs the node config validation and returns the result of the validation as a boolean as well as any errors
77
+// that occured trying to validate the file
78
+func (o *ValidateNodeConfigOptions) Run() (ok bool, err error) {
79
+	nodeConfig, err := configapilatest.ReadAndResolveNodeConfig(o.NodeConfigFile)
80
+	if err != nil {
81
+		return true, err
82
+	}
83
+
84
+	results := validation.ValidateNodeConfig(nodeConfig)
85
+	writer := tabwriter.NewWriter(o.Out, minColumnWidth, tabWidth, padding, padchar, flags)
86
+	err = prettyPrintValidationErrorList(results, writer)
87
+	if err != nil {
88
+		return len(results) == 0, fmt.Errorf("could not print results: %v", err)
89
+	}
90
+	writer.Flush()
91
+	return len(results) == 0, nil
92
+}
0 93
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package validate
1
+
2
+import (
3
+	"io"
4
+
5
+	"github.com/spf13/cobra"
6
+
7
+	cmdutil "github.com/openshift/origin/pkg/cmd/util"
8
+)
9
+
10
+const ValidateRecommendedName = "validate"
11
+
12
+const validateLong = `Validate configuration file integrity
13
+
14
+The commands here allow administrators to validate the integrity of configuration files.
15
+`
16
+
17
+func NewCommandValidate(name, fullName string, out io.Writer) *cobra.Command {
18
+	// Parent command to which all subcommands are added.
19
+	cmds := &cobra.Command{
20
+		Use:   name,
21
+		Short: "Validate configuration file integrity",
22
+		Long:  validateLong,
23
+		Run:   cmdutil.DefaultSubCommandRun(out),
24
+	}
25
+
26
+	cmds.AddCommand(NewCommandValidateMasterConfig(ValidateMasterConfigRecommendedName,
27
+		fullName+" "+ValidateMasterConfigRecommendedName, out))
28
+	cmds.AddCommand(NewCommandValidateNodeConfig(ValidateNodeConfigRecommendedName,
29
+		fullName+" "+ValidateNodeConfigRecommendedName, out))
30
+	return cmds
31
+}
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"github.com/spf13/cobra"
10 10
 
11 11
 	"github.com/openshift/origin/pkg/cmd/admin"
12
+	"github.com/openshift/origin/pkg/cmd/admin/validate"
12 13
 	"github.com/openshift/origin/pkg/cmd/cli"
13 14
 	"github.com/openshift/origin/pkg/cmd/cli/cmd"
14 15
 	"github.com/openshift/origin/pkg/cmd/experimental/buildchain"
... ...
@@ -152,10 +153,12 @@ func newExperimentalCommand(name, fullName string) *cobra.Command {
152 152
 			c.SetOutput(out)
153 153
 			c.Help()
154 154
 		},
155
+		BashCompletionFunction: admin.BashCompletionFunc,
155 156
 	}
156 157
 
157 158
 	f := clientcmd.New(experimental.PersistentFlags())
158 159
 
160
+	experimental.AddCommand(validate.NewCommandValidate(validate.ValidateRecommendedName, fullName+" "+validate.ValidateRecommendedName, out))
159 161
 	experimental.AddCommand(tokens.NewCmdTokens(tokens.TokenRecommendedCommandName, fullName+" "+tokens.TokenRecommendedCommandName, f, out))
160 162
 	experimental.AddCommand(exipfailover.NewCmdIPFailoverConfig(f, fullName, "ipfailover", out))
161 163
 	experimental.AddCommand(buildchain.NewCmdBuildChain(name, fullName+" "+buildchain.BuildChainRecommendedCommandName, f, out))
... ...
@@ -86,7 +86,7 @@ func ReadYAMLFile(filename string, obj runtime.Object) error {
86 86
 		return err
87 87
 	}
88 88
 	err = Codec.DecodeInto(data, obj)
89
-	return captureSurroundingJSONForError(fmt.Sprintf("could not load config file %q due to a error: ", filename), data, err)
89
+	return captureSurroundingJSONForError(fmt.Sprintf("could not load config file %q due to an error: ", filename), data, err)
90 90
 }
91 91
 
92 92
 // TODO: we ultimately want a better decoder for JSON that allows us exact line numbers and better
... ...
@@ -4099,6 +4099,53 @@ _openshift_kube()
4099 4099
     must_have_one_noun=()
4100 4100
 }
4101 4101
 
4102
+_openshift_ex_validate_master-config()
4103
+{
4104
+    last_command="openshift_ex_validate_master-config"
4105
+    commands=()
4106
+
4107
+    flags=()
4108
+    two_word_flags=()
4109
+    flags_with_completion=()
4110
+    flags_completion=()
4111
+
4112
+
4113
+    must_have_one_flag=()
4114
+    must_have_one_noun=()
4115
+}
4116
+
4117
+_openshift_ex_validate_node-config()
4118
+{
4119
+    last_command="openshift_ex_validate_node-config"
4120
+    commands=()
4121
+
4122
+    flags=()
4123
+    two_word_flags=()
4124
+    flags_with_completion=()
4125
+    flags_completion=()
4126
+
4127
+
4128
+    must_have_one_flag=()
4129
+    must_have_one_noun=()
4130
+}
4131
+
4132
+_openshift_ex_validate()
4133
+{
4134
+    last_command="openshift_ex_validate"
4135
+    commands=()
4136
+    commands+=("master-config")
4137
+    commands+=("node-config")
4138
+
4139
+    flags=()
4140
+    two_word_flags=()
4141
+    flags_with_completion=()
4142
+    flags_completion=()
4143
+
4144
+
4145
+    must_have_one_flag=()
4146
+    must_have_one_noun=()
4147
+}
4148
+
4102 4149
 _openshift_ex_tokens_validate-token()
4103 4150
 {
4104 4151
     last_command="openshift_ex_tokens_validate-token"
... ...
@@ -4249,6 +4296,7 @@ _openshift_ex()
4249 4249
 {
4250 4250
     last_command="openshift_ex"
4251 4251
     commands=()
4252
+    commands+=("validate")
4252 4253
     commands+=("tokens")
4253 4254
     commands+=("ipfailover")
4254 4255
     commands+=("build-chain")