Internal improvements to linter and util packages
* linter.Result now includes a map field, storing pointers to all schemas that were introspected during linting. The schemas are keyed by dir path. * util.ShellOut now includes a Dir field, allowing callers to specify the initial working directory of the process. * util.NewShellOut() has been removed, as it was unused and would not support adding additional fields to the ShellOut struct. Callers should create &ShellOut{} values directly with the desired fields.
This commit is contained in:
Родитель
5ed5daf409
Коммит
705fb734c9
|
@ -40,6 +40,7 @@ type Result struct {
|
|||
FormatNotices []*Annotation
|
||||
DebugLogs []string
|
||||
Exceptions []error
|
||||
Schemas map[string]*tengo.Schema // Keyed by dir path and optionally schema name
|
||||
}
|
||||
|
||||
// Merge combines other into r's value in-place.
|
||||
|
@ -52,6 +53,12 @@ func (r *Result) Merge(other *Result) {
|
|||
r.FormatNotices = append(r.FormatNotices, other.FormatNotices...)
|
||||
r.DebugLogs = append(r.DebugLogs, other.DebugLogs...)
|
||||
r.Exceptions = append(r.Exceptions, other.Exceptions...)
|
||||
if r.Schemas == nil {
|
||||
r.Schemas = make(map[string]*tengo.Schema)
|
||||
}
|
||||
for key, value := range other.Schemas {
|
||||
r.Schemas[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// BadConfigResult returns a *Result containing a single ConfigError in the
|
||||
|
@ -91,7 +98,14 @@ func LintDir(dir *fs.Dir, wsOpts workspace.Options) *Result {
|
|||
return result
|
||||
}
|
||||
}
|
||||
_, res := ExecLogicalSchema(logicalSchema, wsOpts, opts)
|
||||
schema, res := ExecLogicalSchema(logicalSchema, wsOpts, opts)
|
||||
if schema != nil {
|
||||
schemaKey := dir.Path
|
||||
if logicalSchema.Name != "" {
|
||||
schemaKey = fmt.Sprintf("%s:%s", schemaKey, logicalSchema.Name)
|
||||
}
|
||||
res.Schemas = map[string]*tengo.Schema{schemaKey: schema}
|
||||
}
|
||||
result.Merge(res)
|
||||
}
|
||||
|
||||
|
@ -112,7 +126,8 @@ func LintDir(dir *fs.Dir, wsOpts workspace.Options) *Result {
|
|||
|
||||
// ExecLogicalSchema is a wrapper around workspace.ExecLogicalSchema. After the
|
||||
// tengo.Schema is obtained and introspected, it is also linted. Any errors
|
||||
// are captured as part of the *Result.
|
||||
// are captured as part of the *Result. However, the schema itself is not yet
|
||||
// placed into the *Result; this is the caller's responsibility.
|
||||
func ExecLogicalSchema(logicalSchema *fs.LogicalSchema, wsOpts workspace.Options, opts Options) (*tengo.Schema, *Result) {
|
||||
result := &Result{}
|
||||
|
||||
|
|
|
@ -118,6 +118,17 @@ func TestLintDir(t *testing.T) {
|
|||
if len(result.DebugLogs) != 1 {
|
||||
t.Errorf("Expected 1 debug log, instead found %d", len(result.DebugLogs))
|
||||
}
|
||||
|
||||
// One *tengo.Schema expected in map, with key corresponding to dir
|
||||
if len(result.Schemas) != 1 {
|
||||
t.Errorf("Expected 1 schema in Schemas map, instead found %d", len(result.Schemas))
|
||||
} else {
|
||||
for key := range result.Schemas {
|
||||
if key != dir.Path {
|
||||
t.Errorf("Expected schema to have key %s, instead found %s", dir.Path, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLintDirIgnoreSchema(t *testing.T) {
|
||||
|
|
|
@ -9,17 +9,11 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// varPlaceholder is a regexp for detecting placeholders in format "{VARNAME}"
|
||||
var varPlaceholder = regexp.MustCompile(`{([^}]*)}`)
|
||||
|
||||
// noQuotesNeeded is a regexp for detecting which variable values do not require
|
||||
// escaping and quote-wrapping
|
||||
var noQuotesNeeded = regexp.MustCompile(`^[\w/@%=:.,+-]*$`)
|
||||
|
||||
// ShellOut represents a command-line for an external command, executed via sh -c
|
||||
type ShellOut struct {
|
||||
Command string
|
||||
PrintableCommand string // Same as Command, but used in String() if non-empty; useful for hiding passwords in output
|
||||
PrintableCommand string // Used in String() if non-empty; useful for hiding passwords in output
|
||||
Dir string // Initial working dir for the command if non-empty
|
||||
}
|
||||
|
||||
func (s *ShellOut) String() string {
|
||||
|
@ -37,6 +31,7 @@ func (s *ShellOut) Run() error {
|
|||
return errors.New("Attempted to shell out to an empty command string")
|
||||
}
|
||||
cmd := exec.Command("/bin/sh", "-c", s.Command)
|
||||
cmd.Dir = s.Dir
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
@ -51,6 +46,7 @@ func (s *ShellOut) RunCapture() (string, error) {
|
|||
return "", errors.New("Attempted to shell out to an empty command string")
|
||||
}
|
||||
cmd := exec.Command("/bin/sh", "-c", s.Command)
|
||||
cmd.Dir = s.Dir
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.Output()
|
||||
|
@ -91,14 +87,9 @@ func (s *ShellOut) RunCaptureSplit() ([]string, error) {
|
|||
return result, err
|
||||
}
|
||||
|
||||
// NewShellOut takes a shell command-line string and returns a ShellOut, without
|
||||
// performing any variable interpolation.
|
||||
func NewShellOut(command, printableCommand string) *ShellOut {
|
||||
return &ShellOut{
|
||||
Command: command,
|
||||
PrintableCommand: printableCommand,
|
||||
}
|
||||
}
|
||||
// varPlaceholder is a regexp for detecting placeholders of format "{VARNAME}"
|
||||
// in NewInterpolatedShellOut()
|
||||
var varPlaceholder = regexp.MustCompile(`{([^}]*)}`)
|
||||
|
||||
// NewInterpolatedShellOut takes a shell command-line containing variables of
|
||||
// format {VARNAME}, and performs substitution on them based on the supplied
|
||||
|
@ -115,33 +106,37 @@ func NewShellOut(command, printableCommand string) *ShellOut {
|
|||
// string contains "{PASSWORDX}" and variables has a key "PASSWORD", it will be
|
||||
// replaced in a manner that obfuscates the actual password in PrintableCommand.
|
||||
func NewInterpolatedShellOut(command string, variables map[string]string) (*ShellOut, error) {
|
||||
var err error
|
||||
var forDisplay bool
|
||||
var forDisplay bool // affects behavior of replacer closure
|
||||
var err error // may be mutated by replacer closure
|
||||
replacer := func(input string) string {
|
||||
input = strings.ToUpper(input[1 : len(input)-1])
|
||||
value, ok := variables[input]
|
||||
if !ok && input[len(input)-1] == 'X' {
|
||||
value, ok = variables[input[:len(input)-1]]
|
||||
varName := strings.ToUpper(input[1 : len(input)-1])
|
||||
value, ok := variables[varName]
|
||||
if !ok && varName[len(varName)-1] == 'X' {
|
||||
value, ok = variables[varName[:len(varName)-1]]
|
||||
if ok && forDisplay {
|
||||
return "XXXXX"
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
return escapeVarValue(value)
|
||||
if !ok {
|
||||
err = fmt.Errorf("Unknown variable %s", input)
|
||||
return input
|
||||
}
|
||||
err = fmt.Errorf("Unknown variable {%s}", input)
|
||||
return fmt.Sprintf("{%s}", input)
|
||||
return escapeVarValue(value)
|
||||
}
|
||||
|
||||
result := varPlaceholder.ReplaceAllStringFunc(command, replacer)
|
||||
s := &ShellOut{}
|
||||
s.Command = varPlaceholder.ReplaceAllStringFunc(command, replacer)
|
||||
if strings.Contains(strings.ToUpper(command), "X}") {
|
||||
forDisplay = true
|
||||
resultForDisplay := varPlaceholder.ReplaceAllStringFunc(command, replacer)
|
||||
return NewShellOut(result, resultForDisplay), err
|
||||
s.PrintableCommand = varPlaceholder.ReplaceAllStringFunc(command, replacer)
|
||||
}
|
||||
return NewShellOut(result, ""), err
|
||||
return s, err
|
||||
}
|
||||
|
||||
// noQuotesNeeded is a regexp for detecting which variable values do not require
|
||||
// escaping and quote-wrapping in escapeVarValue()
|
||||
var noQuotesNeeded = regexp.MustCompile(`^[\w/@%=:.,+-]*$`)
|
||||
|
||||
// escapeVarValue takes a string, and wraps it in single-quotes so that it will
|
||||
// be interpretted as a single arg in a shell-out command line. If the value
|
||||
// already contained any single-quotes, they will be escaped in a way that will
|
||||
|
|
|
@ -6,24 +6,26 @@ import (
|
|||
)
|
||||
|
||||
func TestShellOutRun(t *testing.T) {
|
||||
assertResult := func(command string, expectSuccess bool) {
|
||||
assertResult := func(command, dirPath string, expectSuccess bool) {
|
||||
t.Helper()
|
||||
s := NewShellOut(command, "")
|
||||
s := &ShellOut{Command: command, Dir: dirPath}
|
||||
if err := s.Run(); expectSuccess && err != nil {
|
||||
t.Errorf("Expected command `%s` to return no error, but it returned error %s", command, err)
|
||||
} else if !expectSuccess && err == nil {
|
||||
t.Errorf("Expected command `%s` to return an error, but it did not", command)
|
||||
}
|
||||
}
|
||||
assertResult("", false)
|
||||
assertResult("false", false)
|
||||
assertResult("/does/not/exist", false)
|
||||
assertResult("true", true)
|
||||
assertResult("", "", false)
|
||||
assertResult("false", "", false)
|
||||
assertResult("/does/not/exist", "", false)
|
||||
assertResult("true", "", true)
|
||||
assertResult("true", "..", true)
|
||||
assertResult("true", "/invalid/dir", false)
|
||||
}
|
||||
|
||||
func TestRunCaptureSplit(t *testing.T) {
|
||||
assertResult := func(command string, expectedTokens ...string) {
|
||||
s := NewShellOut(command, "")
|
||||
s := &ShellOut{Command: command}
|
||||
result, err := s.RunCaptureSplit()
|
||||
if err != nil {
|
||||
t.Logf("Unexpected error return from %#v: %s", s, err)
|
||||
|
@ -42,11 +44,11 @@ func TestRunCaptureSplit(t *testing.T) {
|
|||
assertResult(`/usr/bin/printf 'intentionally "no support" for quotes'`, "intentionally", `"no`, `support"`, "for", "quotes")
|
||||
|
||||
// Test error responses
|
||||
s := NewShellOut("", "")
|
||||
s := &ShellOut{}
|
||||
if _, err := s.RunCaptureSplit(); err == nil {
|
||||
t.Error("Expected empty shellout to error, but it did not")
|
||||
}
|
||||
s = NewShellOut("false", "")
|
||||
s = &ShellOut{Command: "false"}
|
||||
if _, err := s.RunCaptureSplit(); err == nil {
|
||||
t.Error("Expected non-zero exit code from shellout to error, but it did not")
|
||||
}
|
||||
|
@ -95,7 +97,7 @@ func TestNewInterpolatedShellOut(t *testing.T) {
|
|||
t.Errorf("Unexpected result from NewInterpolatedShellOut when an invalid variable was present: %+v", s)
|
||||
}
|
||||
}
|
||||
assertShellOutError("/bin/echo {HOST} {iNvAlId} {SCHEMA}", "/bin/echo ahost {INVALID} aschema")
|
||||
assertShellOutError("/bin/echo {HOST} {iNvAlId} {SCHEMA}", "/bin/echo ahost {iNvAlId} aschema")
|
||||
assertShellOutError("/bin/echo {HOST} {INVALIDX} {SCHEMA}", "/bin/echo ahost {INVALIDX} aschema")
|
||||
assertShellOutError("/bin/echo {HOST} {X} {SCHEMA}", "/bin/echo ahost {X} aschema")
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче