1 package main
2
3 import (
4 "bytes"
5 "crypto/tls"
6 "errors"
7 "fmt"
8 "io"
9 "io/ioutil"
10 "net/http"
11 "os"
12 "path/filepath"
13 "regexp"
14 "runtime"
15 "strings"
16
17 "github.com/fatih/color"
18 multierror "github.com/hashicorp/go-multierror"
19 "github.com/spf13/cobra"
20 "github.com/spf13/viper"
21
22 "github.com/instrumenta/kubeval/kubeval"
23 "github.com/instrumenta/kubeval/log"
24 )
25
26 var (
27 version = "dev"
28 commit = "none"
29 date = "unknown"
30 directories = []string{}
31 ignoredPathPatterns = []string{}
32
33
34
35 forceColor bool
36
37 config = kubeval.NewDefaultConfig()
38 )
39
40
41 var RootCmd = &cobra.Command{
42 Short: "Validate a Kubernetes YAML file against the relevant schema",
43 Long: `Validate a Kubernetes YAML file against the relevant schema`,
44 Version: fmt.Sprintf("Version: %s\nCommit: %s\nDate: %s\n", version, commit, date),
45 Run: func(cmd *cobra.Command, args []string) {
46 if config.IgnoreMissingSchemas && !config.Quiet {
47 log.Warn("Set to ignore missing schemas")
48 }
49
50
51
52
53
54 if config.InsecureSkipTLSVerify {
55 http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
56 InsecureSkipVerify: true,
57 }
58 }
59
60 success := true
61 windowsStdinIssue := false
62 outputManager := kubeval.GetOutputManager(config.OutputFormat)
63
64 stat, err := os.Stdin.Stat()
65 if err != nil {
66
67
68
69 if runtime.GOOS != "windows" {
70 log.Error(err)
71 os.Exit(1)
72 } else {
73 windowsStdinIssue = true
74 }
75 }
76
77 if forceColor {
78 color.NoColor = false
79 }
80
81
82 notty := (stat.Mode() & os.ModeCharDevice) == 0
83 noFileOrDirArgs := (len(args) < 1 || args[0] == "-") && len(directories) < 1
84 if noFileOrDirArgs && !windowsStdinIssue && notty {
85 buffer := new(bytes.Buffer)
86 _, err := io.Copy(buffer, os.Stdin)
87 if err != nil {
88 log.Error(err)
89 os.Exit(1)
90 }
91 schemaCache := kubeval.NewSchemaCache()
92 config.FileName = viper.GetString("filename")
93 results, err := kubeval.ValidateWithCache(buffer.Bytes(), schemaCache, config)
94 if err != nil {
95 log.Error(err)
96 os.Exit(1)
97 }
98 success = !hasErrors(results)
99
100 for _, r := range results {
101 err = outputManager.Put(r)
102 if err != nil {
103 log.Error(err)
104 os.Exit(1)
105 }
106 }
107 } else {
108 if len(args) < 1 && len(directories) < 1 {
109 log.Error(errors.New("You must pass at least one file as an argument, or at least one directory to the directories flag"))
110 os.Exit(1)
111 }
112 schemaCache := kubeval.NewSchemaCache()
113 files, err := aggregateFiles(args)
114 if err != nil {
115 log.Error(err)
116 success = false
117 }
118
119 var aggResults []kubeval.ValidationResult
120 for _, fileName := range files {
121 filePath, _ := filepath.Abs(fileName)
122 fileContents, err := ioutil.ReadFile(filePath)
123 if err != nil {
124 log.Error(fmt.Errorf("Could not open file %v", fileName))
125 earlyExit()
126 success = false
127 continue
128 }
129 config.FileName = fileName
130 results, err := kubeval.ValidateWithCache(fileContents, schemaCache, config)
131 if err != nil {
132 log.Error(err)
133 earlyExit()
134 success = false
135 continue
136 }
137
138 for _, r := range results {
139 err := outputManager.Put(r)
140 if err != nil {
141 log.Error(err)
142 os.Exit(1)
143 }
144 }
145
146 aggResults = append(aggResults, results...)
147 }
148
149
150 success = success && !hasErrors(aggResults)
151 }
152
153
154 err = outputManager.Flush()
155 if err != nil {
156 log.Error(err)
157 os.Exit(1)
158 }
159
160 if !success {
161 os.Exit(1)
162 }
163 },
164 }
165
166
167
168 func hasErrors(res []kubeval.ValidationResult) bool {
169 for _, r := range res {
170 if len(r.Errors) > 0 {
171 return true
172 }
173 }
174 return false
175 }
176
177
178 func isIgnored(path string) (bool, error) {
179 for _, p := range ignoredPathPatterns {
180 m, err := regexp.MatchString(p, path)
181 if err != nil {
182 return false, err
183 }
184 if m {
185 return true, nil
186 }
187 }
188 return false, nil
189 }
190
191 func aggregateFiles(args []string) ([]string, error) {
192 files := make([]string, len(args))
193 copy(files, args)
194
195 var allErrors *multierror.Error
196 for _, directory := range directories {
197 err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
198 if err != nil {
199 return err
200 }
201 ignored, err := isIgnored(path)
202 if err != nil {
203 return err
204 }
205 if !info.IsDir() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) && !ignored {
206 files = append(files, path)
207 }
208 return nil
209 })
210 if err != nil {
211 allErrors = multierror.Append(allErrors, err)
212 }
213 }
214
215 return files, allErrors.ErrorOrNil()
216 }
217
218 func earlyExit() {
219 if config.ExitOnError {
220 os.Exit(1)
221 }
222 }
223
224
225
226 func Execute() {
227 if err := RootCmd.Execute(); err != nil {
228 log.Error(err)
229 os.Exit(-1)
230 }
231 }
232
233 func init() {
234 rootCmdName := filepath.Base(os.Args[0])
235 if strings.HasPrefix(rootCmdName, "kubectl-") {
236 rootCmdName = strings.Replace(rootCmdName, "-", " ", 1)
237 }
238 RootCmd.Use = fmt.Sprintf("%s <file> [file...]", rootCmdName)
239 kubeval.AddKubevalFlags(RootCmd, config)
240 RootCmd.Flags().BoolVarP(&forceColor, "force-color", "", false, "Force colored output even if stdout is not a TTY")
241 RootCmd.SetVersionTemplate(`{{.Version}}`)
242 RootCmd.Flags().StringSliceVarP(&directories, "directories", "d", []string{}, "A comma-separated list of directories to recursively search for YAML documents")
243 RootCmd.Flags().StringSliceVarP(&ignoredPathPatterns, "ignored-path-patterns", "i", []string{}, "A comma-separated list of regular expressions specifying paths to ignore")
244 RootCmd.Flags().StringSliceVarP(&ignoredPathPatterns, "ignored-filename-patterns", "", []string{}, "An alias for ignored-path-patterns")
245
246 viper.SetEnvPrefix("KUBEVAL")
247 viper.AutomaticEnv()
248 viper.BindPFlag("schema_location", RootCmd.Flags().Lookup("schema-location"))
249 viper.BindPFlag("filename", RootCmd.Flags().Lookup("filename"))
250 }
251
252 func main() {
253 Execute()
254 }
255
View as plain text