From 3dc507b9e1bc99ea3327251ea9e1a715eb5b665d Mon Sep 17 00:00:00 2001 From: Chris McGehee Date: Mon, 8 Nov 2021 10:57:03 -0800 Subject: [PATCH] Using library to parse github workflows --- .golangci.yml | 3 + checks/fileparser/errors.go | 21 +++ checks/fileparser/github_workflow.go | 154 +++++++----------- checks/fileparser/github_workflow_test.go | 12 +- checks/permissions.go | 98 ++++------- checks/pinned_dependencies.go | 63 ++++--- checks/pinned_dependencies_test.go | 2 +- ...ithub-workflow-multiple-unpinned-uses.yaml | 2 - go.mod | 12 +- go.sum | 16 +- 10 files changed, 181 insertions(+), 202 deletions(-) create mode 100644 checks/fileparser/errors.go diff --git a/.golangci.yml b/.golangci.yml index d3f34274..aec9c7e4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -150,3 +150,6 @@ linters-settings: - ptrToRefParam - typeUnparen - unnecessaryBlock + wrapcheck: + ignorePackageGlobs: + - github.com/ossf/scorecard/v3/checks/fileparser diff --git a/checks/fileparser/errors.go b/checks/fileparser/errors.go new file mode 100644 index 00000000..4e4a5abd --- /dev/null +++ b/checks/fileparser/errors.go @@ -0,0 +1,21 @@ +// Copyright 2021 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fileparser + +import ( + "errors" +) + +var errInvalidGitHubWorkflow = errors.New("invalid GitHub workflow") diff --git a/checks/fileparser/github_workflow.go b/checks/fileparser/github_workflow.go index 75d40d6a..e38723ee 100644 --- a/checks/fileparser/github_workflow.go +++ b/checks/fileparser/github_workflow.go @@ -20,7 +20,7 @@ import ( "regexp" "strings" - "gopkg.in/yaml.v3" + "github.com/rhysd/actionlint" sce "github.com/ossf/scorecard/v3/errors" ) @@ -31,110 +31,51 @@ const defaultShellNonWindows = "bash" // defaultShellWindows is the default shell used for GitHub workflow actions for Windows. const defaultShellWindows = "pwsh" -// Structure for workflow config. -// We only declare the fields we need. -// Github workflows format: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions -type GitHubActionWorkflowConfig struct { - Jobs map[string]GitHubActionWorkflowJob - Name string `yaml:"name"` -} - -// A Github Action Workflow Job. -// We only declare the fields we need. -// Github workflows format: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions -// nolint: govet -type GitHubActionWorkflowJob struct { - Name string `yaml:"name"` - Steps []GitHubActionWorkflowStep `yaml:"steps"` - Defaults struct { - Run struct { - Shell string `yaml:"shell"` - } `yaml:"run"` - } `yaml:"defaults"` - RunsOn stringOrSlice `yaml:"runs-on"` - Strategy struct { - // In most cases, the 'matrix' field will have a key of 'os' which is an array of strings, but there are - // some repos that have something like: 'matrix: ${{ fromJson(needs.matrix.outputs.latest) }}'. - Matrix interface{} `yaml:"matrix"` - } `yaml:"strategy"` -} - -// A Github Action Workflow Step. -// We only declare the fields we need. -// Github workflows format: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions -type GitHubActionWorkflowStep struct { - Name string `yaml:"name"` - ID string `yaml:"id"` - Shell string `yaml:"shell"` - Run string `yaml:"run"` - If string `yaml:"if"` - Uses stringWithLine `yaml:"uses"` -} - -// stringOrSlice is for fields that can be a single string or a slice of strings. If the field is a single string, -// this value will be a slice with a single string item. -type stringOrSlice []string - -func (s *stringOrSlice) UnmarshalYAML(value *yaml.Node) error { - var stringSlice []string - err := value.Decode(&stringSlice) - if err == nil { - *s = stringSlice +// FormatActionlintError combines the errors into a single one. +func FormatActionlintError(errs []*actionlint.Error) error { + if len(errs) == 0 { return nil } - var single string - err = value.Decode(&single) - if err != nil { - return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("error decoding stringOrSlice Value: %v", err)) + builder := strings.Builder{} + builder.WriteString(errInvalidGitHubWorkflow.Error() + ":") + for _, err := range errs { + builder.WriteString("\n" + err.Error()) } - *s = []string{single} - return nil -} - -// stringWithLine is for when you want to keep track of the line number that the string came from. -type stringWithLine struct { - Value string - Line int -} - -func (ws *stringWithLine) UnmarshalYAML(value *yaml.Node) error { - err := value.Decode(&ws.Value) - if err != nil { - return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("error decoding stringWithLine Value: %v", err)) - } - ws.Line = value.Line - - return nil + return sce.WithMessage(sce.ErrScorecardInternal, builder.String()) } // GetOSesForJob returns the OSes this job runs on. -func GetOSesForJob(job *GitHubActionWorkflowJob) ([]string, error) { +func GetOSesForJob(job *actionlint.Job) ([]string, error) { // The 'runs-on' field either lists the OS'es directly, or it can have an expression '${{ matrix.os }}' which // is where the OS'es are actually listed. - getFromMatrix := len(job.RunsOn) == 1 && strings.Contains(job.RunsOn[0], "matrix.os") - if !getFromMatrix { - return job.RunsOn, nil - } jobOSes := make([]string, 0) - // nolint: nestif - if m, ok := job.Strategy.Matrix.(map[string]interface{}); ok { - if osVal, ok := m["os"]; ok { - if oses, ok := osVal.([]interface{}); ok { - for _, os := range oses { - if strVal, ok := os.(string); ok { - jobOSes = append(jobOSes, strVal) - } - } - return jobOSes, nil - } + getFromMatrix := len(job.RunsOn.Labels) == 1 && strings.Contains(job.RunsOn.Labels[0].Value, "matrix.os") + if !getFromMatrix { + // We can get the OSes straight from 'runs-on'. + for _, os := range job.RunsOn.Labels { + jobOSes = append(jobOSes, os.Value) + } + return jobOSes, nil + } + + for rowKey, rowValue := range job.Strategy.Matrix.Rows { + if rowKey != "os" { + continue + } + for _, os := range rowValue.Values { + jobOSes = append(jobOSes, strings.Trim(os.String(), "'\"")) } } - return jobOSes, sce.WithMessage(sce.ErrScorecardInternal, - fmt.Sprintf("unable to determine OS for job: %v", job.Name)) + + if len(jobOSes) == 0 { + return jobOSes, sce.WithMessage(sce.ErrScorecardInternal, + fmt.Sprintf("unable to determine OS for job: %v", job.Name.Value)) + } + return jobOSes, nil } // JobAlwaysRunsOnWindows returns true if the only OS that this job runs on is Windows. -func JobAlwaysRunsOnWindows(job *GitHubActionWorkflowJob) (bool, error) { +func JobAlwaysRunsOnWindows(job *actionlint.Job) (bool, error) { jobOSes, err := GetOSesForJob(job) if err != nil { return false, err @@ -148,13 +89,27 @@ func JobAlwaysRunsOnWindows(job *GitHubActionWorkflowJob) (bool, error) { } // GetShellForStep returns the shell that is used to run the given step. -func GetShellForStep(step *GitHubActionWorkflowStep, job *GitHubActionWorkflowJob) (string, error) { +func GetShellForStep(step *actionlint.Step, job *actionlint.Job) (string, error) { // https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell. - if step.Shell != "" { - return step.Shell, nil + execRun, ok := step.Exec.(*actionlint.ExecRun) + if !ok { + jobName := "" + if job.Name != nil { + jobName = job.Name.Value + } + stepName := "" + if step.Name != nil { + stepName = step.Name.Value + } + return "", sce.WithMessage(sce.ErrScorecardInternal, + fmt.Sprintf("unable to parse step '%v' for job '%v'", jobName, stepName)) } - if job.Defaults.Run.Shell != "" { - return job.Defaults.Run.Shell, nil + if execRun != nil && execRun.Shell != nil && execRun.Shell.Value != "" { + return execRun.Shell.Value, nil + } + if job.Defaults != nil && job.Defaults.Run != nil && job.Defaults.Run.Shell != nil && + job.Defaults.Run.Shell.Value != "" { + return job.Defaults.Run.Shell.Value, nil } isStepWindows, err := IsStepWindows(step) @@ -177,7 +132,10 @@ func GetShellForStep(step *GitHubActionWorkflowStep, job *GitHubActionWorkflowJo } // IsStepWindows returns true if the step will be run on Windows. -func IsStepWindows(step *GitHubActionWorkflowStep) (bool, error) { +func IsStepWindows(step *actionlint.Step) (bool, error) { + if step.If == nil { + return false, nil + } windowsRegexes := []string{ // Looking for "if: runner.os == 'Windows'" (and variants) `(?i)runner\.os\s*==\s*['"]windows['"]`, @@ -188,7 +146,7 @@ func IsStepWindows(step *GitHubActionWorkflowStep) (bool, error) { } for _, windowsRegex := range windowsRegexes { - matches, err := regexp.MatchString(windowsRegex, step.If) + matches, err := regexp.MatchString(windowsRegex, step.If.Value) if err != nil { return false, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("error matching Windows regex: %v", err)) } diff --git a/checks/fileparser/github_workflow_test.go b/checks/fileparser/github_workflow_test.go index eca0706a..7f8bbeac 100644 --- a/checks/fileparser/github_workflow_test.go +++ b/checks/fileparser/github_workflow_test.go @@ -18,7 +18,7 @@ import ( "io/ioutil" "testing" - "gopkg.in/yaml.v3" + "github.com/rhysd/actionlint" "gotest.tools/assert/cmp" ) @@ -113,17 +113,17 @@ func TestGitHubWorkflowShell(t *testing.T) { if err != nil { t.Errorf("cannot read file: %v", err) } - var workflow GitHubActionWorkflowConfig - err = yaml.Unmarshal(content, &workflow) - if err != nil { - t.Errorf("cannot unmarshal file: %v", err) + + workflow, errs := actionlint.Parse(content) + if len(errs) > 0 && workflow == nil { + t.Errorf("cannot unmarshal file: %v", errs[0]) } actualShells := make([]string, 0) for _, job := range workflow.Jobs { job := job for _, step := range job.Steps { step := step - shell, err := GetShellForStep(&step, &job) + shell, err := GetShellForStep(step, job) if err != nil { t.Errorf("error getting shell: %v", err) } diff --git a/checks/permissions.go b/checks/permissions.go index 979c4c21..deacb358 100644 --- a/checks/permissions.go +++ b/checks/permissions.go @@ -18,7 +18,7 @@ import ( "fmt" "strings" - "gopkg.in/yaml.v2" + "github.com/rhysd/actionlint" "github.com/ossf/scorecard/v3/checker" "github.com/ossf/scorecard/v3/checks/fileparser" @@ -53,25 +53,24 @@ func TokenPermissions(c *checker.CheckRequest) checker.CheckResult { return createResultForLeastPrivilegeTokens(data, err) } -func validatePermission(key string, value interface{}, path string, +func validatePermission(permissionKey string, permissionValue *actionlint.PermissionScope, path string, dl checker.DetailLogger, pPermissions map[string]bool, ignoredPermissions map[string]bool) error { - val, ok := value.(string) - if !ok { + if permissionValue.Value == nil { return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error()) } - + val := permissionValue.Value.Value if strings.EqualFold(val, "write") { - if isPermissionOfInterest(key, ignoredPermissions) { + if isPermissionOfInterest(permissionKey, ignoredPermissions) { dl.Warn3(&checker.LogMessage{ Path: path, Type: checker.FileTypeSource, // TODO: set line. Offset: 1, - Text: fmt.Sprintf("'%v' permission set to '%v'", key, val), + Text: fmt.Sprintf("'%v' permission set to '%v'", permissionKey, val), // TODO: set Snippet. }) - recordPermissionWrite(key, pPermissions) + recordPermissionWrite(permissionKey, pPermissions) } else { // Only log for debugging, otherwise // it may confuse users. @@ -80,7 +79,7 @@ func validatePermission(key string, value interface{}, path string, Type: checker.FileTypeSource, // TODO: set line. Offset: 1, - Text: fmt.Sprintf("'%v' permission set to '%v'", key, val), + Text: fmt.Sprintf("'%v' permission set to '%v'", permissionKey, val), // TODO: set Snippet. }) } @@ -92,22 +91,16 @@ func validatePermission(key string, value interface{}, path string, Type: checker.FileTypeSource, // TODO: set line correctly. Offset: 1, - Text: fmt.Sprintf("'%v' permission set to '%v'", key, val), + Text: fmt.Sprintf("'%v' permission set to '%v'", permissionKey, val), // TODO: set Snippet. }) return nil } -func validateMapPermissions(values map[interface{}]interface{}, path string, +func validateMapPermissions(scopes map[string]*actionlint.PermissionScope, path string, dl checker.DetailLogger, pPermissions map[string]bool, ignoredPermissions map[string]bool) error { - // Iterate over the permission, verify keys and values are strings. - for k, v := range values { - key, ok := k.(string) - if !ok { - return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error()) - } - + for key, v := range scopes { if err := validatePermission(key, v, path, dl, pPermissions, ignoredPermissions); err != nil { return err } @@ -125,14 +118,12 @@ func recordAllPermissionsWrite(pPermissions map[string]bool) { pPermissions["all"] = true } -func validatePermissions(permissions interface{}, path string, +func validatePermissions(permissions *actionlint.Permissions, path string, dl checker.DetailLogger, pPermissions map[string]bool, ignoredPermissions map[string]bool) error { - // Check the type of our values. - switch val := permissions.(type) { - // Empty string is nil type. - // It defaults to 'none' - case nil: + allIsSet := permissions != nil && permissions.All != nil && permissions.All.Value != "" + scopeIsSet := permissions != nil && len(permissions.Scopes) > 0 + if permissions == nil || (!allIsSet && !scopeIsSet) { dl.Info3(&checker.LogMessage{ Path: path, Type: checker.FileTypeSource, @@ -141,8 +132,9 @@ func validatePermissions(permissions interface{}, path string, Text: "permissions set to 'none'", // TODO: set Snippet. }) - // String type. - case string: + } + if allIsSet { + val := permissions.All.Value if !strings.EqualFold(val, "read-all") && val != "" { dl.Warn3(&checker.LogMessage{ Path: path, @@ -164,25 +156,17 @@ func validatePermissions(permissions interface{}, path string, Text: fmt.Sprintf("permissions set to '%v'", val), // TODO: set Snippet. }) - - // Map type. - case map[interface{}]interface{}: - if err := validateMapPermissions(val, path, dl, pPermissions, ignoredPermissions); err != nil { - return err - } - - // Invalid type. - default: - return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error()) + } else /* scopeIsSet == true */ if err := validateMapPermissions(permissions.Scopes, path, dl, pPermissions, + ignoredPermissions); err != nil { + return err } return nil } -func validateTopLevelPermissions(config map[interface{}]interface{}, path string, +func validateTopLevelPermissions(workflow *actionlint.Workflow, path string, dl checker.DetailLogger, pdata *permissionCbData) error { // Check if permissions are set explicitly. - permissions, ok := config["permissions"] - if !ok { + if workflow.Permissions == nil { dl.Warn3(&checker.LogMessage{ Path: path, Type: checker.FileTypeSource, @@ -193,35 +177,18 @@ func validateTopLevelPermissions(config map[interface{}]interface{}, path string return nil } - return validatePermissions(permissions, path, dl, + return validatePermissions(workflow.Permissions, path, dl, pdata.topLevelWritePermissions, map[string]bool{}) } -func validateRunLevelPermissions(config map[interface{}]interface{}, path string, +func validateRunLevelPermissions(workflow *actionlint.Workflow, path string, dl checker.DetailLogger, pdata *permissionCbData, ignoredPermissions map[string]bool) error { - var jobs interface{} - - jobs, ok := config["jobs"] - if !ok { - return nil - } - - mjobs, ok := jobs.(map[interface{}]interface{}) - if !ok { - return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error()) - } - - for _, value := range mjobs { - job, ok := value.(map[interface{}]interface{}) - if !ok { - return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error()) - } + for _, job := range workflow.Jobs { // Run-level permissions may be left undefined. // For most workflows, no write permissions are needed, // so only top-level read-only permissions need to be declared. - permissions, ok := job["permissions"] - if !ok { + if job.Permissions == nil { dl.Debug3(&checker.LogMessage{ Path: path, Type: checker.FileTypeSource, @@ -230,8 +197,7 @@ func validateRunLevelPermissions(config map[interface{}]interface{}, path string }) continue } - err := validatePermissions(permissions, path, dl, - pdata.runLevelWritePermissions, ignoredPermissions) + err := validatePermissions(job.Permissions, path, dl, pdata.runLevelWritePermissions, ignoredPermissions) if err != nil { return err } @@ -375,11 +341,9 @@ func validateGitHubActionTokenPermissions(path string, content []byte, return true, nil } - var workflow map[interface{}]interface{} - err := yaml.Unmarshal(content, &workflow) - if err != nil { - return false, - sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("yaml.Unmarshal: %v", err)) + workflow, errs := actionlint.Parse(content) + if len(errs) > 0 && workflow == nil { + return false, fileparser.FormatActionlintError(errs) } // 1. Top-level permission definitions. diff --git a/checks/pinned_dependencies.go b/checks/pinned_dependencies.go index 1c0f265d..ef433249 100644 --- a/checks/pinned_dependencies.go +++ b/checks/pinned_dependencies.go @@ -20,7 +20,7 @@ import ( "strings" "github.com/moby/buildkit/frontend/dockerfile/parser" - "gopkg.in/yaml.v3" + "github.com/rhysd/actionlint" "github.com/ossf/scorecard/v3/checker" "github.com/ossf/scorecard/v3/checks/fileparser" @@ -451,11 +451,11 @@ func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []by return true, nil } - var workflow fileparser.GitHubActionWorkflowConfig - err := yaml.Unmarshal(content, &workflow) - if err != nil { - return false, sce.WithMessage(sce.ErrScorecardInternal, - fmt.Sprintf("%v: %v", errInternalInvalidYamlFile, err)) + workflow, errs := actionlint.Parse(content) + if len(errs) > 0 && workflow == nil { + // actionlint is a linter, so it will return errors when the yaml file does not meet its linting standards. + // Often we don't care about these errors. + return false, fileparser.FormatActionlintError(errs) } githubVarRegex := regexp.MustCompile(`{{[^{}]*}}`) @@ -465,12 +465,12 @@ func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []by job := job for _, step := range job.Steps { step := step - if step.Run == "" { + if step.Exec.Kind() != actionlint.ExecKindRun { continue } // https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun. - shell, err := fileparser.GetShellForStep(&step, &job) + shell, err := fileparser.GetShellForStep(step, job) if err != nil { return false, err } @@ -479,7 +479,7 @@ func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []by continue } - run := step.Run + run := step.Exec.(*actionlint.ExecRun).Run.Value // We replace the `${{ github.variable }}` to avoid shell parsing failures. script := githubVarRegex.ReplaceAll([]byte(run), []byte("GITHUB_REDACTED_VAR")) scriptContent = fmt.Sprintf("%v\n%v", scriptContent, string(script)) @@ -487,6 +487,7 @@ func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []by } if scriptContent != "" { + var err error validated, err = validateShellFile(pathfn, []byte(scriptContent), dl) if err != nil { return false, err @@ -534,30 +535,42 @@ func validateGitHubActionWorkflow(pathfn string, content []byte, return true, nil } - var workflow fileparser.GitHubActionWorkflowConfig - err := yaml.Unmarshal(content, &workflow) - if err != nil { - return false, sce.WithMessage(sce.ErrScorecardInternal, - fmt.Sprintf("%v: %v", errInternalInvalidYamlFile, err)) + workflow, errs := actionlint.Parse(content) + if len(errs) > 0 && workflow == nil { + // actionlint is a linter, so it will return errors when the yaml file does not meet its linting standards. + // Often we don't care about these errors. + return false, fileparser.FormatActionlintError(errs) } hashRegex := regexp.MustCompile(`^.*@[a-f\d]{40,}`) for jobName, job := range workflow.Jobs { - if len(job.Name) > 0 { - jobName = job.Name + if job.Name != nil && len(job.Name.Value) > 0 { + jobName = job.Name.Value } for _, step := range job.Steps { - if len(step.Uses.Value) > 0 { - // Ensure a hash at least as large as SHA1 is used (40 hex characters). - // Example: action-name@hash - match := hashRegex.Match([]byte(step.Uses.Value)) - if !match { - dl.Warn3(&checker.LogMessage{ - Path: pathfn, Type: checker.FileTypeSource, Offset: step.Uses.Line, Snippet: step.Uses.Value, - Text: fmt.Sprintf("dependency not pinned by hash (job '%v')", jobName), - }) + if step.Exec.Kind() != actionlint.ExecKindAction { + continue + } + execAction, ok := step.Exec.(*actionlint.ExecAction) + if !ok { + stepName := "" + if step.Name != nil { + stepName = step.Name.Value } + return false, sce.WithMessage(sce.ErrScorecardInternal, + fmt.Sprintf("unable to parse step '%v' for job '%v'", jobName, stepName)) + } + // Ensure a hash at least as large as SHA1 is used (40 hex characters). + // Example: action-name@hash + match := hashRegex.Match([]byte(execAction.Uses.Value)) + if !match { + dl.Warn3(&checker.LogMessage{ + Path: pathfn, Type: checker.FileTypeSource, Offset: execAction.Uses.Pos.Line, Snippet: execAction.Uses.Value, + Text: fmt.Sprintf("dependency not pinned by hash (job '%v')", jobName), + }) + } + githubOwned := fileparser.IsGitHubOwnedAction(execAction.Uses.Value) addWorkflowPinnedResult(pdata, match, githubOwned) } diff --git a/checks/pinned_dependencies_test.go b/checks/pinned_dependencies_test.go index 1d463628..b1c3e0a0 100644 --- a/checks/pinned_dependencies_test.go +++ b/checks/pinned_dependencies_test.go @@ -933,7 +933,7 @@ func TestGitHubWorkflowUsesLineNumber(t *testing.T) { }, { dependency: "docker/build-push-action@1.2.3", - lineNumber: 26, + lineNumber: 24, }, }, }, diff --git a/checks/testdata/github-workflow-multiple-unpinned-uses.yaml b/checks/testdata/github-workflow-multiple-unpinned-uses.yaml index a844c579..66c27933 100644 --- a/checks/testdata/github-workflow-multiple-unpinned-uses.yaml +++ b/checks/testdata/github-workflow-multiple-unpinned-uses.yaml @@ -20,7 +20,5 @@ jobs: steps: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 - run: echo "unpinned dependency" - name: some name - run: echo "unpinned dependency" uses: docker/build-push-action@1.2.3 diff --git a/go.mod b/go.mod index 7140fecd..ecf3aae9 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,10 @@ require ( mvdan.cc/sh/v3 v3.4.0 ) -require gotest.tools v2.2.0+incompatible +require ( + github.com/rhysd/actionlint v1.6.7 + gotest.tools v2.2.0+incompatible +) require ( cloud.google.com/go v0.94.1 // indirect @@ -52,6 +55,7 @@ require ( github.com/docker/docker v20.10.7+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.3 // indirect github.com/emirpasic/gods v1.12.0 // indirect + github.com/fatih/color v1.12.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect @@ -69,12 +73,16 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/klauspost/compress v1.13.5 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.13 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/robfig/cron v1.2.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/go.sum b/go.sum index 2a561ea4..60cfe54c 100644 --- a/go.sum +++ b/go.sum @@ -483,6 +483,7 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -504,6 +505,8 @@ github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= @@ -931,6 +934,8 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= @@ -941,9 +946,12 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -1127,6 +1135,12 @@ github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1: github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rhysd/actionlint v1.6.7 h1:cOojXJ/N3OAoOaTo8zEEA6uWovUC+0yP3dLYEZnRWg4= +github.com/rhysd/actionlint v1.6.7/go.mod h1:vFbgcUNEK84kr9Lb3vEfqjTov/Q3X5py+ppKihWgH+w= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=