get.porter.sh/porter@v1.3.0/pkg/exec/lint.go (about) 1 package exec 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "get.porter.sh/porter/pkg/encoding" 9 "get.porter.sh/porter/pkg/exec/builder" 10 "get.porter.sh/porter/pkg/linter" 11 "get.porter.sh/porter/pkg/yaml" 12 ) 13 14 // BuildInput represents stdin sent by porter to the build and lint commands 15 type BuildInput struct { 16 // exec mixin doesn't have any buildtime config, so we don't have that field 17 18 // Actions is all the exec actions defined in the manifest 19 Actions Actions `yaml:"actions"` 20 } 21 22 const ( 23 // CodeEmbeddedBash is the linter code for when a bash -c command is found. 24 CodeEmbeddedBash linter.Code = "exec-100" 25 26 // CodeBashCArgMissingQuotes is the linter code for when a bash -c flag argument is missing the required wrapping quotes. 27 CodeBashCArgMissingQuotes linter.Code = "exec-101" 28 ) 29 30 func (m *Mixin) Lint(ctx context.Context) (linter.Results, error) { 31 var input BuildInput 32 33 err := builder.LoadAction(ctx, m.Config, "", func(contents []byte) (interface{}, error) { 34 err := yaml.Unmarshal(contents, &input) 35 return &input, err 36 }) 37 if err != nil { 38 return nil, err 39 } 40 41 // Right now the only exec invocation we are looking for is 42 // bash -c "some command" 43 // We are looking for: 44 // * using that command at all (WARN) 45 // * missing wrapping quotes around the command (ERROR) 46 results := make(linter.Results, 0) 47 48 for _, action := range input.Actions { 49 for stepNumber, step := range action.Steps { 50 if step.Command != "bash" { 51 continue 52 } 53 54 var embeddedBashFlag *builder.Flag 55 for _, flag := range step.Flags { 56 if flag.Name == "c" { 57 embeddedBashFlag = &flag 58 break 59 } 60 } 61 62 if embeddedBashFlag == nil { 63 continue 64 } 65 66 // Found embedded bash 🚨 67 // Check for wrapping quotes, if missing -> hard error, otherwise just warn 68 result := linter.Result{ 69 Level: linter.LevelWarning, 70 Code: CodeEmbeddedBash, 71 Location: linter.Location{ 72 Action: action.Name, 73 Mixin: "exec", 74 StepNumber: stepNumber + 1, // We index from 1 for natural counting, 1st, 2nd, etc. 75 StepDescription: step.Description, 76 }, 77 Title: "Best Practice: Avoid Embedded Bash", 78 Message: "", 79 URL: "https://porter.sh/best-practices/exec-mixin/#use-scripts", 80 } 81 results = append(results, result) 82 83 for _, bashCmd := range embeddedBashFlag.Values { 84 if (!strings.HasPrefix(bashCmd, `"`) || !strings.HasSuffix(bashCmd, `"`)) && 85 (!strings.HasPrefix(bashCmd, `'`) || !strings.HasSuffix(bashCmd, `'`)) { 86 result := linter.Result{ 87 Level: linter.LevelError, 88 Code: CodeBashCArgMissingQuotes, 89 Location: linter.Location{ 90 Action: action.Name, 91 Mixin: "exec", 92 StepNumber: stepNumber + 1, 93 StepDescription: step.Description, 94 }, 95 Title: "bash -c argument missing wrapping quotes", 96 Message: `The bash -c flag argument must be wrapped in quotes, for example 97 exec: 98 description: Say Hello 99 command: bash 100 flags: 101 c: '"echo Hello World"' 102 `, 103 URL: "https://porter.sh/best-practices/exec-mixin/#quoting-escaping-bash-and-yaml", 104 } 105 results = append(results, result) 106 break 107 } 108 } 109 } 110 } 111 112 return results, nil 113 } 114 115 func (m *Mixin) PrintLintResults(ctx context.Context) error { 116 results, err := m.Lint(ctx) 117 if err != nil { 118 return err 119 } 120 121 b, err := encoding.MarshalJson(results) 122 if err != nil { 123 return fmt.Errorf("could not marshal lint results %#v: %w", results, err) 124 } 125 126 // Print the results as json to stdout for Porter to read 127 resultsJson := string(b) 128 fmt.Fprintln(m.Config.Out, resultsJson) 129 130 return nil 131 }