github.com/github/skeema@v1.2.6/cmd_lint.go (about) 1 package main 2 3 import ( 4 "fmt" 5 6 log "github.com/sirupsen/logrus" 7 "github.com/skeema/mybase" 8 "github.com/skeema/skeema/fs" 9 "github.com/skeema/skeema/linter" 10 "github.com/skeema/skeema/workspace" 11 "github.com/skeema/tengo" 12 ) 13 14 func init() { 15 summary := "Verify table files and reformat them in a standardized way" 16 desc := `Reformats the filesystem representation of tables to match the format of SHOW 17 CREATE TABLE. Verifies that all table files contain valid SQL in their CREATE 18 TABLE statements. 19 20 This command relies on accessing database instances to test the SQL DDL. All DDL 21 will be run against a temporary schema, with no impact on the real schema. 22 23 You may optionally pass an environment name as a CLI option. This will affect 24 which section of .skeema config files is used for obtaining a database instance 25 to test the SQL DDL against. For example, running ` + "`" + `skeema lint staging` + "`" + ` will 26 apply config directives from the [staging] section of config files, as well as 27 any sectionless directives at the top of the file. If no environment name is 28 supplied, the default is "production". 29 30 An exit code of 0 will be returned if no errors or warnings were emitted and all 31 files were already formatted properly; 1 if any warnings were emitted and/or 32 some files were reformatted; or 2+ if any errors were emitted for any reason.` 33 34 cmd := mybase.NewCommand("lint", summary, desc, LintHandler) 35 linter.AddCommandOptions(cmd) 36 cmd.AddArg("environment", "production", false) 37 CommandSuite.AddSubCommand(cmd) 38 } 39 40 // LintHandler is the handler method for `skeema lint` 41 func LintHandler(cfg *mybase.Config) error { 42 dir, err := fs.ParseDir(".", cfg) 43 if err != nil { 44 return err 45 } 46 47 result := lintWalker(dir, 5) 48 switch { 49 case len(result.Exceptions) > 0: 50 exitCode := CodeFatalError 51 for _, err := range result.Exceptions { 52 if _, ok := err.(linter.ConfigError); ok { 53 exitCode = CodeBadConfig 54 } 55 } 56 return NewExitValue(exitCode, "Skipped %d operations due to fatal errors", len(result.Exceptions)) 57 case len(result.Errors) > 0: 58 return NewExitValue(CodeFatalError, "Found %d errors", len(result.Errors)) 59 case len(result.Warnings) > 0: 60 return NewExitValue(CodeDifferencesFound, "Found %d warnings", len(result.Warnings)) 61 case len(result.FormatNotices) > 0: 62 return NewExitValue(CodeDifferencesFound, "") 63 } 64 return nil 65 } 66 67 func lintWalker(dir *fs.Dir, maxDepth int) (result *linter.Result) { 68 log.Infof("Linting %s", dir) 69 70 // Connect to first defined instance, unless configured to use local Docker 71 var inst *tengo.Instance 72 if wsType, _ := dir.Config.GetEnum("workspace", "temp-schema", "docker"); wsType != "docker" || !dir.Config.Changed("flavor") { 73 var err error 74 if inst, err = dir.FirstInstance(); err != nil { 75 result = linter.BadConfigResult(dir, err) 76 } 77 } 78 opts, err := workspace.OptionsForDir(dir, inst) 79 if err != nil { 80 result = linter.BadConfigResult(dir, err) 81 } 82 83 if result == nil { 84 result = linter.LintDir(dir, opts) 85 } 86 for _, err := range result.Exceptions { 87 log.Error(fmt.Errorf("Skipping schema in %s due to error: %s", dir.RelPath(), err)) 88 } 89 for _, annotation := range result.Errors { 90 log.Error(annotation.MessageWithLocation()) 91 } 92 for _, annotation := range result.Warnings { 93 log.Warning(annotation.MessageWithLocation()) 94 } 95 for _, annotation := range result.FormatNotices { 96 length, err := annotation.Statement.FromFile.Rewrite() 97 if err != nil { 98 writeErr := fmt.Errorf("Unable to write to %s: %s", annotation.Statement.File, err) 99 log.Error(writeErr.Error()) 100 result.Exceptions = append(result.Exceptions, writeErr) 101 } else { 102 log.Infof("Wrote %s (%d bytes) -- updated file to normalize format", annotation.Statement.File, length) 103 } 104 } 105 for _, dl := range result.DebugLogs { 106 log.Debug(dl) 107 } 108 109 var subdirErr error 110 if subdirs, badCount, err := dir.Subdirs(); err != nil { 111 subdirErr = fmt.Errorf("Cannot list subdirs of %s: %s", dir, err) 112 } else if len(subdirs) > 0 && maxDepth <= 0 { 113 subdirErr = fmt.Errorf("Not walking subdirs of %s: max depth reached", dir) 114 } else { 115 if badCount > 0 { 116 subdirErr = fmt.Errorf("Ignoring %d subdirs of %s with configuration errors", badCount, dir) 117 } 118 for _, sub := range subdirs { 119 result.Merge(lintWalker(sub, maxDepth-1)) 120 } 121 } 122 if subdirErr != nil { 123 log.Error(subdirErr) 124 result.Exceptions = append(result.Exceptions, subdirErr) 125 } 126 return result 127 }