diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bfceca3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,21 @@ +# name of the action +name: build + +# trigger on pull_request or push events +on: + pull_request: + push: + +# pipeline to execute +jobs: + build: + runs-on: ubuntu-latest + container: + image: golang:1.17 + steps: + - name: clone + uses: actions/checkout@v2 + + - name: build + run: | + make build diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..e7f488d --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,67 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '32 7 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml new file mode 100644 index 0000000..59c71a8 --- /dev/null +++ b/.github/workflows/prerelease.yml @@ -0,0 +1,42 @@ +# name of the action +name: prerelease + +# trigger on push events with `v*` in tag +on: + push: + tags: + - "v*" + +# pipeline to execute +jobs: + prerelease: + runs-on: ubuntu-latest + container: + image: golang:1.17 + steps: + - name: clone + uses: actions/checkout@v2 + with: + # ensures we fetch tag history for the repository + fetch-depth: 0 + + - name: setup + run: | + # setup git tag in Actions environment + echo "GITHUB_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + + - name: build + env: + GOOS: linux + CGO_ENABLED: "0" + run: | + make build-static-ci + + - name: publish + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: target/vela-ansible + cache: true + tag_names: true + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..fee090a --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,35 @@ +# name of the action +name: publish + +# trigger on push events with branch main +on: + push: + branches: [ main ] + +# pipeline to execute +jobs: + publish: + runs-on: ubuntu-latest + container: + image: golang:1.17 + steps: + - name: clone + uses: actions/checkout@v2 + with: + # ensures we fetch tag history for the repository + fetch-depth: 0 + + - name: build + env: + GOOS: linux + CGO_ENABLED: '0' + run: | + make build-static-ci + + - name: publish + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: target/vela-ansible + cache: true + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml new file mode 100644 index 0000000..94d21fc --- /dev/null +++ b/.github/workflows/reviewdog.yml @@ -0,0 +1,41 @@ +# name of the action +name: reviewdog + +# trigger on pull_request events +on: + pull_request: + +# pipeline to execute +jobs: + diff-review: + runs-on: ubuntu-latest + container: + image: golang:1.17 + steps: + - name: clone + uses: actions/checkout@v2 + + - name: golangci-lint + uses: reviewdog/action-golangci-lint@v2 + with: + github_token: ${{ secrets.github_token }} + golangci_lint_flags: "--config=.golangci.yml" + fail_on_error: true + filter_mode: diff_context + reporter: github-pr-review + + full-review: + runs-on: ubuntu-latest + container: + image: golang:1.17 + steps: + - name: clone + uses: actions/checkout@v2 + + - name: golangci-lint + uses: reviewdog/action-golangci-lint@v2 + with: + github_token: ${{ secrets.github_token }} + golangci_lint_flags: "--config=.golangci.yml" + fail_on_error: false + filter_mode: nofilter diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b2f98b5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +# name of the action +name: test + +# trigger on pull_request or push events +on: + pull_request: + push: + +# pipeline to execute +jobs: + test: + runs-on: ubuntu-latest + container: + image: golang:1.17 + steps: + - name: clone + uses: actions/checkout@v2 + + - name: test + run: | + go test -race -covermode=atomic -coverprofile=coverage.out ./... + + - name: coverage + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: coverage.out diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..7ace2e2 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,28 @@ +# name of the action +name: validate + +# trigger on pull_request or push events +on: + pull_request: + push: + +# pipeline to execute +jobs: + validate: + runs-on: ubuntu-latest + container: + image: golang:1.17 + steps: + - name: clone + uses: actions/checkout@v2 + + - name: validate + run: | + # Check that go mod tidy produces a zero diff; clean up any changes afterwards. + go mod tidy && git diff --exit-code; code=$?; git checkout -- .; (exit $code) + # Check that go vet ./... produces a zero diff; clean up any changes afterwards. + go vet ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) + # Check that go fmt ./... produces a zero diff; clean up any changes afterwards. + go fmt ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) + # Check that go fix ./... produces a zero diff; clean up any changes afterwards. + go fix ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) diff --git a/.golangci.yml b/.golangci.yml index 665b1f0..97b6d15 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -28,27 +28,16 @@ linters-settings: # https://github.com/ultraware/funlen funlen: - lines: 100 - statements: 50 + # accounting for comments + lines: 160 + statements: 70 - # https://github.com/golang/lint - golint: - min-confidence: 0 - - # https://github.com/tommy-muehle/go-mnd - gomnd: - settings: - mnd: - # don't include the "operation" and "assign" - checks: argument,case,condition,return - - # https://github.com/walle/lll - lll: - line-length: 100 - - # https://github.com/mdempsky/maligned - maligned: - suggest-new: true + # https://github.com/denis-tingaikin/go-header + goheader: + template: |- + Copyright (c) {{ YEAR }} Target Brands, Inc. All rights reserved. + + Use of this source code is governed by the LICENSE file in this repository. # https://github.com/client9/misspell misspell: @@ -56,10 +45,10 @@ linters-settings: # https://github.com/golangci/golangci-lint/blob/master/pkg/golinters/nolintlint nolintlint: - allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space) - allow-unused: false # report any unused nolint directives - require-explanation: false # don't require an explanation for nolint directives - require-specific: false # don't require nolint directives to be specific about which linter is being skipped + allow-leading-space: true # allow non-"machine-readable" format (ie. with leading space) + allow-unused: false # allow nolint directives that don't address a linting issue + require-explanation: true # require an explanation for nolint directives + require-specific: true # require nolint directives to be specific about which linter is being skipped # This section provides the configuration for which linters # golangci will execute. Several of them were disabled by @@ -70,58 +59,91 @@ linters: # enable a specific set of linters to run enable: - - bodyclose - - deadcode # enabled by default - - dupl - - errcheck # enabled by default - - funlen - - goconst - - gocyclo - - godot - - gofmt - - goimports - - golint - - gomnd - - goprintffuncname - - gosec - - gosimple # enabled by default - - govet # enabled by default - - ineffassign # enabled by default - - lll - - maligned - - misspell - - nakedret - - nolintlint - - staticcheck # enabled by default - - structcheck # enabled by default - - stylecheck - - typecheck # enabled by default - - unconvert - - unparam - - unused # enabled by default - - varcheck # enabled by default - - whitespace - + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - contextcheck # check the function whether use a non-inherited context + - deadcode # finds unused code + - dupl # code clone detection + - errcheck # checks for unchecked errors + - errorlint # find misuses of errors + - exportloopref # check for exported loop vars + - funlen # detects long functions + - goconst # finds repeated strings that could be replaced by a constant + - gocyclo # computes and checks the cyclomatic complexity of functions + - godot # checks if comments end in a period + - gofmt # checks whether code was gofmt-ed + - goheader # checks is file header matches to pattern + - goimports # fixes imports and formats code in same style as gofmt + - gomoddirectives # manage the use of 'replace', 'retract', and 'excludes' directives in go.mod + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects code for security problems + - gosimple # linter that specializes in simplifying a code + - govet # reports suspicious constructs, ex. Printf calls whose arguments don't align with the format string + - ineffassign # detects when assignments to existing variables aren't used + - makezero # finds slice declarations with non-zero initial length + - misspell # finds commonly misspelled English words in comments + - nakedret # finds naked returns in functions greater than a specified function length + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - noctx # noctx finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - revive # linter for go + - staticcheck # applies static analysis checks, go vet on steroids + - structcheck # finds unused struct fields + - stylecheck # replacement for golint + - tenv # analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 + - typecheck # parses and type-checks go code, like the front-end of a go compiler + - unconvert # remove unnecessary type conversions + - unparam # reports unused function parameters + - unused # checks for unused constants, variables, functions and types + - varcheck # finds unused global variables and constants + - whitespace # detects leading and trailing whitespace + - wsl # forces code to use empty lines + # static list of linters we know golangci can run but we've # chosen to leave disabled for now - # - asciicheck - # - depguard - # - dogsled - # - exhaustive - # - gochecknoinits - # - gochecknoglobals - # - gocognit - # - gocritic - # - godox - # - goerr113 - # - interfacer - # - nestif - # - noctx - # - prealloc - # - rowserrcheck - # - scopelint - # - testpackage - # - wsl + # - asciicheck - non-critical + # - cyclop - unused complexity metric + # - depguard - unused + # - dogsled - blanks allowed + # - durationcheck - unused + # - errname - unused + # - exhaustive - unused + # - exhaustivestruct - style preference + # - forbidigo - unused + # - forcetypeassert - unused + # - gci - use goimports + # - gochecknoinits - unused + # - gochecknoglobals - global variables allowed + # - gocognit - unused complexity metric + # - gocritic - style preference + # - godox - to be used in the future + # - goerr113 - to be used in the future + # - golint - archived, replaced with revive + # - gofumpt - use gofmt + # - gomnd - get too many false-positives + # - gomodguard - unused + # - ifshort - use both styles + # - ireturn - allow interfaces to be returned + # - importas - want flexibility with naming + # - lll - not too concerned about line length + # - interfacer - archived + # - nestif - non-critical + # - nilnil - style preference + # - nlreturn - style preference + # - maligned - archived, replaced with govet 'fieldalignment' + # - paralleltest - false-positives + # - prealloc - don't use + # - predeclared - unused + # - promlinter - style preference + # - rowserrcheck - unused + # - scopelint - deprecated - replaced with exportloopref + # - sqlclosecheck - unused + # - tagliatelle - use a mix of variable naming + # - testpackage - don't use this style of testing + # - thelper - false-positives + # - varnamelen - unused + # - wastedassign - duplicate functionality + # - wrapcheck - style preference # This section provides the configuration for how golangci # will report the issues it finds. @@ -131,7 +153,7 @@ issues: # prevent linters from running on *_test.go files - path: _test\.go linters: + - dupl - funlen - goconst - gocyclo - - gomnd diff --git a/action/lint/executor.go b/action/lint/executor.go index 5acedb8..b47115d 100644 --- a/action/lint/executor.go +++ b/action/lint/executor.go @@ -25,6 +25,7 @@ func Exec(l *Linter) error { if err := execCmd(cmd); err != nil { return err } + logrus.Info("ansible-lint: complete") return nil diff --git a/action/lint/executor_test.go b/action/lint/executor_test.go index 91779de..00ed17b 100644 --- a/action/lint/executor_test.go +++ b/action/lint/executor_test.go @@ -28,6 +28,7 @@ func TestExec(t *testing.T) { if err == nil { t.Errorf("Flags should have returned err") } + continue } diff --git a/action/lint/flag.go b/action/lint/flag.go index b54e912..1041d39 100644 --- a/action/lint/flag.go +++ b/action/lint/flag.go @@ -13,7 +13,6 @@ import ( "github.com/sirupsen/logrus" ) -// nolint:funlen // false positive // Command creates the command line with flags to run ansible-lint. func setFlags(l *Linter) *exec.Cmd { logrus.Trace("entered plugin.lint.setFlags") @@ -116,7 +115,7 @@ func setFlags(l *Linter) *exec.Cmd { } logrus.Info("ansible-lint: command created") - + // ansible-lint cli return exec.Command(lint, flags...) } diff --git a/action/lint/validate.go b/action/lint/validate.go index c55bb35..9f87ee9 100644 --- a/action/lint/validate.go +++ b/action/lint/validate.go @@ -37,11 +37,13 @@ func Validate(l *Linter) error { // check for valid playbook logrus.Info("validating ansible-lint") + if len(l.LintPlaybook) != 0 { file, err := os.Stat(l.LintPlaybook) if err != nil { return ErrorInvalidLintPlaybook } + if file.Size() == 0 { return ErrorEmptyLintPlaybook } diff --git a/action/lint/validate_test.go b/action/lint/validate_test.go index bf34ce7..eb62f4f 100644 --- a/action/lint/validate_test.go +++ b/action/lint/validate_test.go @@ -5,6 +5,7 @@ package lint import ( + "errors" "testing" ) @@ -75,7 +76,7 @@ func TestValidateError(t *testing.T) { t.Errorf("should have returned err") } - if err != test.wantErr { + if !errors.Is(err, test.wantErr) { t.Errorf("Should have returned error: %v, instead got error: %v", test.wantErr, err) } } diff --git a/action/playbook/executor.go b/action/playbook/executor.go index a444a06..4aa960b 100644 --- a/action/playbook/executor.go +++ b/action/playbook/executor.go @@ -19,12 +19,13 @@ func Exec(p *Playbook) error { // sets ansible-playbook flags cmd := setFlags(p) - + logrus.Info("ansible-playbook: running") // execute ansible-playbook cli if err := execCmd(cmd); err != nil { return err } + logrus.Info("ansible-playbook: complete") return nil diff --git a/action/playbook/executor_test.go b/action/playbook/executor_test.go index 9a4cabb..00cc2a9 100644 --- a/action/playbook/executor_test.go +++ b/action/playbook/executor_test.go @@ -40,6 +40,7 @@ func TestExec(t *testing.T) { if err == nil { t.Errorf("setFlags should have returned err") } + continue } diff --git a/action/playbook/flag.go b/action/playbook/flag.go index 627ebe2..1a6cd35 100644 --- a/action/playbook/flag.go +++ b/action/playbook/flag.go @@ -13,7 +13,7 @@ import ( "github.com/sirupsen/logrus" ) -// nolint // false positive +// nolint: funlen, gocyclo // ignore statements for flags // setFlags creates the command line with flags to run ansible-playbook. func setFlags(p *Playbook) *exec.Cmd { logrus.Trace("entered plugin.playbook.setFlags") diff --git a/action/playbook/validate.go b/action/playbook/validate.go index d2270e5..2c211c3 100644 --- a/action/playbook/validate.go +++ b/action/playbook/validate.go @@ -47,11 +47,13 @@ func Validate(p *Playbook) error { // validate playbook logrus.Info("validating ansible-playbook") + if len(p.Playbook) != 0 { file, err := os.Stat(p.Playbook) if err != nil { return ErrorInvalidPlaybook } + if file.Size() == 0 { return ErrorEmptyPlaybook } @@ -67,6 +69,7 @@ func Validate(p *Playbook) error { if err != nil { return ErrorInvalidInventory } + if file.Size() == 0 { return ErrorEmptyInventory } diff --git a/action/playbook/validate_test.go b/action/playbook/validate_test.go index ee3899a..6c04268 100644 --- a/action/playbook/validate_test.go +++ b/action/playbook/validate_test.go @@ -5,6 +5,7 @@ package playbook import ( + "errors" "testing" ) @@ -140,7 +141,7 @@ func TestValidateError(t *testing.T) { t.Errorf("should have returned err") } - if err != test.wantErr { + if !errors.Is(err, test.wantErr) { t.Errorf("Should have returned error: %v, instead got error: %v", test.wantErr, err) } } diff --git a/cmd/vela-ansible/plugin.go b/cmd/vela-ansible/plugin.go index 3e07d84..955732d 100644 --- a/cmd/vela-ansible/plugin.go +++ b/cmd/vela-ansible/plugin.go @@ -68,11 +68,13 @@ func (p *Plugin) Exec() error { switch strings.ToLower(p.action) { case AnsibleLint: logrus.Info("ansible-lint") + if err := lint.Exec(p.Lint); err != nil { return err } case AnsiblePlaybook: logrus.Info("ansible-playbook") + if err := playbook.Exec(p.Playbook); err != nil { return err } diff --git a/cmd/vela-ansible/plugin_test.go b/cmd/vela-ansible/plugin_test.go index 11af58a..b4b76b5 100644 --- a/cmd/vela-ansible/plugin_test.go +++ b/cmd/vela-ansible/plugin_test.go @@ -5,65 +5,12 @@ package main import ( "errors" - "fmt" - "os" - "strings" "testing" "github.com/go-vela/vela-ansible/action/lint" "github.com/go-vela/vela-ansible/action/playbook" ) -const ( - testMainEnvVar = "env-var" - testMainSuccessOutput = "success-output" - testMainFailOutput = "fail-output" -) - -// TestMain is used so that we can mock calls to binaries that need -// to return specific output, or errors, exit codes, etc. -func TestMain(m *testing.M) { - switch os.Getenv("GO_MAIN_TEST_CASE") { - case "": - os.Exit(m.Run()) - case testMainEnvVar: - if strings.Contains(strings.Join(os.Args, ""), "$") { - os.Exit(1) - } - os.Exit(0) - case testMainSuccessOutput: - if len(os.Args) != 4 { - fmt.Printf("invalid os.Args: %s", strings.Join(os.Args, " ")) - os.Exit(2) - } - fmt.Println(os.Args[2]) - fmt.Fprint(os.Stderr, os.Args[3]) - os.Exit(0) - case testMainFailOutput: - if len(os.Args) != 4 { - fmt.Printf("invalid os.Args: %s", strings.Join(os.Args, " ")) - os.Exit(3) - } - fmt.Println(os.Args[2]) - fmt.Fprint(os.Stderr, os.Args[3]) - os.Exit(4) - } -} - -type mockExecConfig struct { - validationError string - binaryPath string - arguments []string - environment map[string]string -} - -func (m *mockExecConfig) Validate() error { - if m.validationError != "" { - return errors.New(m.validationError) - } - - return nil -} func TestPluginValidateSuccess(t *testing.T) { tests := []struct { plugin *Plugin @@ -237,7 +184,7 @@ func TestPluginValidateError(t *testing.T) { t.Errorf("should have returned err") } - if err != test.wantErr { + if !errors.Is(err, test.wantErr) { t.Errorf("Should have returned error: %v, instead got error: %v", test.wantErr, err) } } @@ -269,7 +216,7 @@ func TestPluginExecError(t *testing.T) { t.Errorf("should have returned err") } - if err != test.wantErr { + if !errors.Is(err, test.wantErr) { t.Errorf("Should have returned error: %v, instead got error: %v", test.wantErr, err) } } diff --git a/version/version.go b/version/version.go index 52d281e..e3b4302 100644 --- a/version/version.go +++ b/version/version.go @@ -44,7 +44,7 @@ func New() *version.Version { v, err := semver.NewVersion(Tag) if err != nil { - fmt.Println(fmt.Errorf("unable to parse semantic version for %s: %v", Tag, err)) + fmt.Println(fmt.Errorf("unable to parse semantic version for %s: %w", Tag, err)) } return &version.Version{