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  }