diff --git a/README.md b/README.md index 3765391..333902e 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,36 @@ fail if an issue arises. ## Usage +### Locally + +Create a `Taskfile.yml` with the following content: + +```yml +--- +version: 3 + +vars: + REMOTE_URL: https://raw.githubusercontent.com + REMOTE_URL_REF: 100-remote-taskfile + REMOTE_URL_REPO: schubergphilis/mcvs-golang-action + +includes: + remote: >- + {{.REMOTE_URL}}/{{.REMOTE_URL_REPO}}/{{.REMOTE_URL_REF}}/Taskfile.yml +``` + +and run: + +```zsh +TASK_X_REMOTE_TASKFILES=1 \ +task remote:test +``` + +### GitHub + Create a `.github/workflows/golang.yml` file with the following content: -```yaml +```yml --- name: Golang "on": push @@ -60,16 +87,17 @@ and a [.golangci.yml](https://golangci-lint.run/usage/configuration/). -| Option | Default | Required | Description | -| :--------------------------------- | :----------------------------------- | -------- | :--------------------------------------------------------------------------------------------------------------- | -| code_coverage_expected | 80 | | | -| gci | true | | Check for 'incorrect import order'. If failed then instructions are shown to resolve the issue | -| golang-unit-tests-exclusions | ' ' | | | -| golangci-lint-version | v1.55.2 | | | -| golang-number-of-tests-in-parallel | 4 | | | -| token | ' ' | x | GitHub token that is required to push an image to the registry of the project and to pull cached Trivy DB images | -| trivy-action-db | ghcr.io/aquasecurity/trivy-db:2 | | Replace this with a cached image to prevent bump into pull rate limiting issues | -| trivy-action-java-db | ghcr.io/aquasecurity/trivy-java-db:1 | | Replace this with a cached image to prevent bump into pull rate limiting issues | +| Option | Default | Required | Description | +| :--------------------------------- | :----------------------------------- | -------- | :--------------------------------------------------------------------------------------------- | +| code-coverage-expected | 80 | | | +| gci | true | | Check for 'incorrect import order'. If failed then instructions are shown to resolve the issue | +| golang-unit-tests-exclusions | ' ' | | | +| golangci-lint-version | v1.55.2 | | | +| golang-number-of-tests-in-parallel | 4 | | | +| task-version | | | | +| token | ' ' | | GitHub token that is required to pull cached Trivy DB images | +| trivy-action-db | ghcr.io/aquasecurity/trivy-db:2 | | Replace this with a cached image to prevent bump into pull rate limiting issues | +| trivy-action-java-db | ghcr.io/aquasecurity/trivy-java-db:1 | | Replace this with a cached image to prevent bump into pull rate limiting issues | @@ -83,9 +111,9 @@ Additionally, include the following header in the file: //go:build integration ``` -After adding this header, issue the command `go test ./... --tags=integration` +After adding this header, issue the command `task remote:test-integration --yes` as demonstrated in this example. This action will run both unit and integration -tests. If the `--tags` step is omitted, only unit tests will be executed. +tests. If `task remote:test --yes` is executed, only unit tests will be run. ### Component diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..168ae34 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,181 @@ +# TASK_X_REMOTE_TASKFILES=1 task remote:lint +# cmd: steps that will be run sequentially +# deps: tasks that will be run in parallel +--- +version: "3" + +vars: + COVERPROFILE: profile.cov + GCI_VERSION: 0.13.5 + GOLANGCI_LINT_VERSION: 1.61.0 + GOLANG_PARALLEL_TESTS: + sh: | + if [ "$(uname -s)" = "Darwin" ]; then + sysctl -n hw.ncpu + else + nproc + fi + MOCKERY_BIN: "{{.GOPATH}}/bin/mockery" + MOCKERY_MAJOR_VERSION: v2 + MOCKERY_VERSION: "{{.MOCKERY_MAJOR_VERSION}}.46.0" + YQ_MAJOR_VERSION: v4 + YQ_VERSION: "{{.YQ_MAJOR_VERSION}}.44.3" + +tasks: + build-golang-download-modules: + silent: true + cmds: + - | + go mod tidy + desc: download go modules + coverage: + silent: true + cmds: + - task: test + vars: + TEST_EXTRA_ARGS: >- + -coverpkg=$(go list --tags={{.TEST_TAGS}} ./... | grep -v '{{.CODE_COVERAGE_FILE_EXCLUSIONS}}' | tr '\n' ',') + -coverprofile={{.COVERPROFILE}} + TEST_TAGS: integration + - | + code_coverage_output=$(go tool cover -func {{.COVERPROFILE}}) + code_coverage_actual=$(echo "${code_coverage_output}" |\ + grep total: |\ + awk '{print $3}' |\ + sed 's/%//') + + echo "Code coverage overview:" + echo "{{.CODE_COVERAGE_EXPECTED}}" + if (( $(echo "{{.CODE_COVERAGE_EXPECTED}} > ${code_coverage_actual}" | bc -l) )); then + echo "The actual code coverage: '${code_coverage_actual}' is too low. Expected: '{{.CODE_COVERAGE_EXPECTED}}'." + exit 1 + elif (( $(echo "${code_coverage_actual} > {{.CODE_COVERAGE_EXPECTED}}" | bc -l) )); then + echo "The actual code coverage: '${code_coverage_actual}' exceeds the expected coverage. Please adjust the threshold to align with the expected: '{{.CODE_COVERAGE_EXPECTED}}'." + exit 1 + fi + coverage-visual: + silent: true + cmds: + - task: test + vars: + TEST_EXTRA_ARGS: >- + -coverpkg=$(go list --tags={{.TEST_TAGS}} ./... | grep -v '{{.CODE_COVERAGE_FILE_EXCLUSIONS}}' | tr '\n' ',') + -coverprofile={{.COVERPROFILE}} + TEST_TAGS: integration + - | + go tool cover \ + -func={{.COVERPROFILE}} > \ + functioncoverage.out + go tool cover \ + -html={{.COVERPROFILE}} \ + -o coverage.html + open ./coverage.html + gci-install: + silent: true + cmds: + - | + if ! ~/go/bin/gci --version | grep -q "gci version {{.GCI_VERSION}}"; then + go install github.com/daixiang0/gci@v{{.GCI_VERSION}} + fi + gci: + silent: true + cmds: + - task: gci-install + - | + if ~/go/bin/gci list --skip-generated . | grep "\.go$"; then + echo "One or more golang files detected with: 'incorrect import order':" + echo " * Observe: '~/go/bin/gci diff --skip-generated .'" + echo " * Resolve: '~/go/bin/gci write --skip-generated .'" + exit 1 + fi + gci-write: + silent: true + cmds: + - task: gci-install + - ~/go/bin/gci write --skip-generated -s standard -s default . + golangci-lint-install: + silent: true + cmds: + - | + if ! golangci-lint --version | grep -q "has version {{.GOLANGCI_LINT_VERSION}}"; then + curl \ + -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh |\ + sh -s -- -b $(go env GOPATH)/bin v{{.GOLANGCI_LINT_VERSION}} + fi + golangci-lint-run: + silent: true + cmds: + - | + echo "GOLANG_PARALLEL_TESTS: {{.GOLANG_PARALLEL_TESTS}}" + golangci-lint run \ + --build-tags component,e2e,integration \ + --concurrency {{.GOLANG_PARALLEL_TESTS}} \ + --timeout 2m30s \ + --verbose + golangci-lint: + silent: true + cmds: + - task: golangci-lint-install + - task: golangci-lint-run + golangci-lint-run-without-cache: + silent: true + cmds: + - task: golangci-lint-install + - golangci-lint cache clean + - task: golangci-lint-run + lint: + silent: true + deps: + - task: gci + - task: golangci-lint + mock-generate: + silent: true + cmds: + - | + if ! {{.MOCKERY_BIN}} --version | grep "{{.MOCKERY_VERSION}}"; then + go install github.com/vektra/mockery/{{.MOCKERY_MAJOR_VERSION}}@{{.MOCKERY_VERSION}} + fi + echo "{{.MOCK_GENERATE_DIR}} {{.MOCK_GENERATE_INTERFACE_NAME}}" + {{.MOCKERY_BIN}} \ + --dir {{.MOCK_GENERATE_DIR}} \ + --name {{.MOCK_GENERATE_INTERFACE_NAME}} \ + --output {{.MOCK_GENERATE_DIR}}/mocks + test: + silent: true + cmds: + - | + echo "GOLANG_PARALLEL_TESTS: {{.GOLANG_PARALLEL_TESTS}}" + go test \ + -p {{.GOLANG_PARALLEL_TESTS}} \ + -race \ + -short \ + --tags={{.TEST_TAGS}} \ + -v \ + ./... \ + {{.TEST_EXTRA_ARGS}} + test-component: + silent: true + cmds: + - task: test + vars: + TEST_TAGS: component + test-e2e: + silent: true + cmds: + - task: test + vars: + TEST_TAGS: e2e + test-integration: + silent: true + cmds: + - task: test + vars: + TEST_TAGS: integration + yq: + silent: true + cmds: + - | + if ! yq --version | grep -q "version {{.YQ_VERSION}}"; then + go install \ + github.com/mikefarah/yq/{{.YQ_MAJOR_VERSION}}@{{.YQ_VERSION}} + fi diff --git a/action.yml b/action.yml index e4a6226..c45536c 100644 --- a/action.yml +++ b/action.yml @@ -3,7 +3,7 @@ name: mcvs-golang-action description: | The Mission Critical Vulnerability Scanner (MCVS) Golang action. inputs: - code_coverage_expected: + code-coverage-expected: default: "80" description: | The minimum code coverage. @@ -22,6 +22,10 @@ inputs: golang-number-of-tests-in-parallel: description: | Number of test in parallel. + task-version: + default: v3.39.2 + description: | + The Task version that has to be installed and used. testing-type: description: | The testing type, e.g. integration, unit or some other. @@ -37,27 +41,27 @@ inputs: description: | A token is required to allow the mcvs-golang-action to pull the cached trivy DBs to prevent bump into pull rate limits. - required: true runs: using: "composite" steps: # - # YAML linting. - # - - run: | - pip install --user yamllint==1.35.1 - yamllint -d '{extends: default, rules: {comments: {min-spaces-from-content: 1}}}' . - if: inputs.testing-type == 'lint' - shell: bash - # - # Install the golang version that has been defined in the go.mod file. + # Install task and the golang version that has been defined in the go.mod + # file. # # yamllint disable rule:line-length - uses: actions/setup-go@v5.1.0 - if: inputs.testing-type == 'component' || inputs.testing-type == 'coverage' || inputs.testing-type == 'integration' || inputs.testing-type == 'unit' + if: inputs.testing-type == 'component' || inputs.testing-type == 'coverage' || inputs.testing-type == 'integration' || inputs.testing-type == 'lint' || inputs.testing-type == 'unit' with: go-version-file: "go.mod" cache: false + - name: install task + if: inputs.testing-type == 'component' || inputs.testing-type == 'coverage' || inputs.testing-type == 'integration' || inputs.testing-type == 'lint' || inputs.testing-type == 'unit' + shell: bash + run: | + if ! ~/go/bin/task --version | grep "Task version: ${{ inputs.task-version }}"; then + major_version=$(echo "${{ inputs.task-version }}" | sed -E 's/^v([0-9]+).*/\1/') + go install github.com/go-task/task/v${major_version}/cmd/task@${{ inputs.task-version }} + fi # yamllint enable rule:line-length # # Verify downloaded dependencies. @@ -80,14 +84,10 @@ runs: - name: gci if: inputs.gci == 'true' && inputs.testing-type == 'lint' shell: bash + env: + TASK_X_REMOTE_TASKFILES: 1 run: | - go install github.com/daixiang0/gci@v0.13.4 - if ~/go/bin/gci list --skip-generated . | grep "\.go$"; then - echo "One or more golang files detected with: 'incorrect import order':" - echo " * Observe: '~/go/bin/gci diff --skip-generated .'" - echo " * Resolve: '~/go/bin/gci write --skip-generated .'" - exit 1 - fi + task remote:gci --yes # # Code security scanning. # @@ -145,28 +145,22 @@ runs: # Run golangci-lint. # - name: golangci-lint - uses: golangci/golangci-lint-action@v6.1.1 if: inputs.testing-type == 'lint' - with: - args: |- - --enable-all \ - --out-format=colored-line-number \ - --timeout 2m30s \ - -v - version: ${{ inputs.golangci-lint-version }} + shell: bash + env: + TASK_X_REMOTE_TASKFILES: 1 + run: | + task remote:golangci-lint --yes # # Unit tests. # - name: unit tests if: inputs.testing-type == 'unit' shell: bash + env: + TASK_X_REMOTE_TASKFILES: 1 run: | - ${GITHUB_ACTION_PATH}/src/go-test.sh \ - "" \ - "" \ - "${{ inputs.golang-number-of-tests-in-parallel }}" \ - "${{ inputs.golang-unit-tests-exclusions }}" \ - "" + task remote:test --yes # # Integration tests. # @@ -176,12 +170,7 @@ runs: env: GITHUB_TOKEN: ${{ inputs.token }} run: | - ${GITHUB_ACTION_PATH}/src/go-test.sh \ - "" \ - "" \ - "${{ inputs.golang-number-of-tests-in-parallel }}" \ - "${{ inputs.golang-unit-tests-exclusions }}" \ - "integration" + task remote:test-integration --yes # # Coverage. # @@ -189,31 +178,11 @@ runs: if: inputs.testing-type == 'coverage' shell: bash env: + CODE_COVERAGE_EXPECTED: ${{ inputs.code-coverage-expected }} + CODE_COVERAGE_FILE_EXCLUSIONS: ${{ inputs.golang-unit-tests-exclusions }} GITHUB_TOKEN: ${{ inputs.token }} run: | - ${GITHUB_ACTION_PATH}/src/go-test.sh \ - "./..." \ - "profile.cov" \ - "${{ inputs.golang-number-of-tests-in-parallel }}" \ - "${{ inputs.golang-unit-tests-exclusions }}" \ - "integration" - - code_coverage_output=$(go tool cover -func profile.cov) - code_coverage_actual=$(echo "${code_coverage_output}" |\ - grep total: |\ - awk '{print $3}' |\ - sed 's/%//') - - echo "Code coverage overview:" - echo "${code_coverage_output}" - - if (( $(echo "${{ inputs.code_coverage_expected }} > ${code_coverage_actual}" | bc -l) )); then - echo "The actual code coverage: '${code_coverage_actual}' is too low. Expected: '${{ inputs.code_coverage_expected }}'." - exit 1 - elif (( $(echo "${code_coverage_actual} > ${{ inputs.code_coverage_expected }}" | bc -l) )); then - echo "The actual code coverage: '${code_coverage_actual}' exceeds the expected coverage. Please adjust the threshold to align with the expected: '${{ inputs.code_coverage_expected }}'." - exit 1 - fi + task remote:coverage --yes # # Component tests. # @@ -223,9 +192,4 @@ runs: env: GITHUB_TOKEN: ${{ inputs.token }} run: | - ${GITHUB_ACTION_PATH}/src/go-test.sh \ - "" \ - "" \ - "${{ inputs.golang-number-of-tests-in-parallel }}" \ - "${{ inputs.golang-unit-tests-exclusions }}" \ - "component" + task remote:test-component --yes diff --git a/src/go-test.sh b/src/go-test.sh deleted file mode 100755 index 4c52b18..0000000 --- a/src/go-test.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -COVERPKG=$1 -COVERPROFILE=$2 -GOLANG_PARALLEL_TESTS=$3 -GOLANG_TEST_EXCLUSIONS=$4 -TAGS=$5 - -variables() { - if [ -z "${GOLANG_PARALLEL_TESTS}" ]; then - if [ "$(uname -s)" = "Darwin" ]; then - GOLANG_PARALLEL_TESTS=$(sysctl -n hw.ncpu) - else - GOLANG_PARALLEL_TESTS=$(nproc) - fi - fi - - if [ -n "${COVERPROFILE}" ]; then - COVERPROFILE="-coverprofile=${COVERPROFILE}" - fi - - if [ -n "${TAGS}" ]; then - TAGS="--tags=${TAGS}" - fi - - GO_LIST_TEST_EXCLUSIONS=$(go list $TAGS ./... | grep -v ${GOLANG_TEST_EXCLUSIONS}) - - if [ -n "${COVERPKG}" ]; then - COVERPKG="-coverpkg=$(echo "${GO_LIST_TEST_EXCLUSIONS}" | tr '\n' ',')" - fi - - echo "COVERPKG: ${COVERPKG}" - echo "COVERPROFILE: ${COVERPROFILE}" - echo "GO_LIST_TEST_EXCLUSIONS: ${GO_LIST_TEST_EXCLUSIONS}" - echo "GOLANG_PARALLEL_TESTS: ${GOLANG_PARALLEL_TESTS}" - echo "GOLANG_TEST_EXCLUSIONS: ${GOLANG_TEST_EXCLUSIONS}" - echo "TAGS: ${TAGS}" -} - -run_go_tests() { - go test \ - -p "${GOLANG_PARALLEL_TESTS}" \ - -race \ - -short \ - -v \ - $COVERPKG \ - $COVERPROFILE \ - $TAGS \ - $(echo "${GO_LIST_TEST_EXCLUSIONS}") -} - -main() { - variables - run_go_tests -} - -main