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  }