github.com/Tyktechnologies/tyk@v2.9.5+incompatible/cli/linter/linter.go (about)

     1  package linter
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net"
     7  	"os"
     8  
     9  	schema "github.com/xeipuuv/gojsonschema"
    10  
    11  	"github.com/TykTechnologies/tyk/config"
    12  )
    13  
    14  // Run will lint the configuration file. It will return the path to the
    15  // config file that was checked, a list of warnings and an error, if any
    16  // happened.
    17  func Run(schm string, paths []string) (string, []string, error) {
    18  	addFormats(&schema.FormatCheckers)
    19  	var conf config.Config
    20  	if err := config.Load(paths, &conf); err != nil {
    21  		return "", nil, err
    22  	}
    23  	schemaLoader := schema.NewBytesLoader([]byte(schm))
    24  
    25  	var orig map[string]interface{}
    26  	f, err := os.Open(conf.OriginalPath)
    27  	if err != nil {
    28  		return "", nil, err
    29  	}
    30  	defer f.Close()
    31  	if err := json.NewDecoder(f).Decode(&orig); err != nil {
    32  		return "", nil, err
    33  	}
    34  	if v, ok := orig["Monitor"]; ok {
    35  		// As the old confs wrongly capitalized this key. Would
    36  		// be fixed by WriteConf below, but we want the JSON
    37  		// schema to not flag this error.
    38  		orig["monitor"] = v
    39  		delete(orig, "Monitor")
    40  	}
    41  
    42  	fileLoader := schema.NewGoLoader(orig)
    43  	result, err := schema.Validate(schemaLoader, fileLoader)
    44  	if err != nil {
    45  		return "", nil, err
    46  	}
    47  
    48  	// ensure it's well formatted and the keys are all lowercase
    49  	if err := config.WriteConf(conf.OriginalPath, &conf); err != nil {
    50  		return "", nil, err
    51  	}
    52  
    53  	return conf.OriginalPath, resultWarns(result), nil
    54  }
    55  
    56  type stringFormat func(string) bool
    57  
    58  func (f stringFormat) IsFormat(v interface{}) bool {
    59  	s := v.(string)
    60  	if s == "" {
    61  		return true // empty string is ok
    62  	}
    63  	return f(s)
    64  }
    65  
    66  func addFormats(chain *schema.FormatCheckerChain) {
    67  	chain.Add("path", stringFormat(func(path string) bool {
    68  		_, err := os.Stat(path)
    69  		return err == nil // must be accessible
    70  	}))
    71  	chain.Add("host-no-port", stringFormat(func(host string) bool {
    72  		_, port, err := net.SplitHostPort(host)
    73  		if a, ok := err.(*net.AddrError); ok && a.Err == "missing port in address" {
    74  			// port being missing is ok
    75  			err = nil
    76  		}
    77  		return err == nil && port == "" // valid host with no port
    78  	}))
    79  }
    80  
    81  func resultWarns(result *schema.Result) []string {
    82  	warns := result.Errors()
    83  	strs := make([]string, len(warns))
    84  	for i, warn := range warns {
    85  		ferr, ok := warn.(*schema.DoesNotMatchFormatError)
    86  		if !ok {
    87  			strs[i] = warn.String()
    88  			continue
    89  		}
    90  		// We need this since formats can only return bools, not
    91  		// custom errors/messages.
    92  		var desc string
    93  		switch format := ferr.Details()["format"].(string); format {
    94  		case "path":
    95  			desc = "Path does not exist or is not accessible"
    96  		case "host-no-port":
    97  			desc = "Address should be a host without port"
    98  		default:
    99  			panic(fmt.Sprintf("unexpected format type: %q", format))
   100  		}
   101  		ferr.SetDescription(desc)
   102  		strs[i] = ferr.String()
   103  	}
   104  	return strs
   105  }