github.com/splunk/dan1-qbec@v0.7.3/internal/commands/validate.go (about) 1 /* 2 Copyright 2019 Splunk Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package commands 18 19 import ( 20 "fmt" 21 "io" 22 "strings" 23 "sync" 24 25 "github.com/spf13/cobra" 26 "github.com/splunk/qbec/internal/model" 27 "github.com/splunk/qbec/internal/remote/k8smeta" 28 ) 29 30 const ( 31 escGreen = "\x1b[32m" 32 escRed = "\x1b[31m" 33 escDim = "\x1b[2m" 34 escReset = "\x1b[0m" 35 36 unicodeCheck = "\u2714" 37 unicodeX = "\u2718" 38 unicodeQuestion = "\u003f" 39 ) 40 41 type validatorStats struct { 42 l sync.Mutex 43 ValidCount int `json:"valid,omitempty"` 44 Unknown []string `json:"unknown,omitempty"` 45 Invalid []string `json:"invalid,omitempty"` 46 Errors []string `json:"errors,omitempty"` 47 } 48 49 func (v *validatorStats) valid(s string) { 50 v.l.Lock() 51 defer v.l.Unlock() 52 v.ValidCount++ 53 } 54 55 func (v *validatorStats) invalid(s string) { 56 v.l.Lock() 57 defer v.l.Unlock() 58 v.Invalid = append(v.Invalid, s) 59 } 60 61 func (v *validatorStats) unknown(s string) { 62 v.l.Lock() 63 defer v.l.Unlock() 64 v.Unknown = append(v.Unknown, s) 65 } 66 67 func (v *validatorStats) errors(s string) { 68 v.l.Lock() 69 defer v.l.Unlock() 70 v.Errors = append(v.Errors, s) 71 } 72 73 type validator struct { 74 w io.Writer 75 client Client 76 stats validatorStats 77 red, green, dim, reset string 78 silent bool 79 } 80 81 func (v *validator) validate(obj model.K8sLocalObject) error { 82 name := v.client.DisplayName(obj) 83 schema, err := v.client.ValidatorFor(obj.GroupVersionKind()) 84 if err != nil { 85 if err == k8smeta.ErrSchemaNotFound { 86 if !v.silent { 87 fmt.Fprintf(v.w, "%s%s %s: no schema found, cannot validate%s\n", v.dim, unicodeQuestion, name, v.reset) 88 } 89 v.stats.unknown(name) 90 return nil 91 } 92 fmt.Fprintf(v.w, "%s%s %s: schema fetch error %v%s\n", v.red, unicodeX, name, err, v.reset) 93 v.stats.errors(name) 94 return err 95 } 96 errs := schema.Validate(obj.ToUnstructured()) 97 if len(errs) == 0 { 98 if !v.silent { 99 fmt.Fprintf(v.w, "%s%s %s is valid%s\n", v.green, unicodeCheck, name, v.reset) 100 } 101 v.stats.valid(name) 102 return nil 103 } 104 var lines []string 105 for _, e := range errs { 106 lines = append(lines, e.Error()) 107 } 108 fmt.Fprintf(v.w, "%s%s %s is invalid\n\t- %s%s\n", v.red, unicodeX, name, strings.Join(lines, "\n\t- "), v.reset) 109 v.stats.invalid(name) 110 return nil 111 } 112 113 func validateObjects(objs []model.K8sLocalObject, client Client, parallel int, colors bool, out io.Writer, silent bool) error { 114 v := &validator{ 115 w: &lockWriter{Writer: out}, 116 client: client, 117 silent: silent, 118 } 119 if colors { 120 v.green = escGreen 121 v.red = escRed 122 v.dim = escDim 123 v.reset = escReset 124 } 125 126 vErr := runInParallel(objs, v.validate, parallel) 127 printStats(v.w, &v.stats) 128 129 switch { 130 case vErr != nil: 131 return vErr 132 case len(v.stats.Invalid) > 0: 133 return fmt.Errorf("%d invalid objects found", len(v.stats.Invalid)) 134 default: 135 return nil 136 } 137 } 138 139 type validateCommandConfig struct { 140 *Config 141 parallel int 142 silent bool 143 filterFunc func() (filterParams, error) 144 } 145 146 func doValidate(args []string, config validateCommandConfig) error { 147 if len(args) != 1 { 148 return newUsageError("exactly one environment required") 149 } 150 env := args[0] 151 if env == model.Baseline { 152 return newUsageError("cannot validate baseline environment, use a real environment") 153 } 154 fp, err := config.filterFunc() 155 if err != nil { 156 return err 157 } 158 client, err := config.Client(env) 159 if err != nil { 160 return err 161 } 162 objects, err := filteredObjects(config.Config, env, client.ObjectKey, fp) 163 if err != nil { 164 return err 165 } 166 return validateObjects(objects, client, config.parallel, config.Colorize(), config.Stdout(), config.silent) 167 168 } 169 170 func newValidateCommand(cp ConfigProvider) *cobra.Command { 171 cmd := &cobra.Command{ 172 Use: "validate <environment>", 173 Short: "validate one or more components against the spec of a kubernetes cluster", 174 Example: validateExamples(), 175 } 176 177 config := validateCommandConfig{ 178 filterFunc: addFilterParams(cmd, true), 179 } 180 181 cmd.Flags().IntVar(&config.parallel, "parallel", 5, "number of parallel routines to run") 182 cmd.Flags().BoolVar(&config.silent, "silent", false, "do not print success messages for every object") 183 cmd.RunE = func(c *cobra.Command, args []string) error { 184 config.Config = cp() 185 return wrapError(doValidate(args, config)) 186 } 187 return cmd 188 }