diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8c61f13..7e33a0b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,7 +3,7 @@ updates: - package-ecosystem: gomod directory: / schedule: - interval: weekly + interval: monthly groups: minor: update-types: @@ -12,4 +12,4 @@ updates: - package-ecosystem: github-actions directory: / schedule: - interval: weekly \ No newline at end of file + interval: monthly \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ab170cf..7c9792c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,9 +3,11 @@ name: Run CI on: workflow_dispatch: push: - pull_request: branches: - "main" + tags: + - 'v*' + pull_request: permissions: contents: read @@ -20,8 +22,6 @@ jobs: name: gitctl uses: ./.github/workflows/reusable-go-ci.yaml with: + name: gitctl module: . - run_build_image: true - run_tests: true - image_ref: "ghcr.io/bjoernkarma/gitctl" ko_build_path: "main.go" diff --git a/.github/workflows/reusable-go-ci.yaml b/.github/workflows/reusable-go-ci.yaml index 66830d2..1436c50 100644 --- a/.github/workflows/reusable-go-ci.yaml +++ b/.github/workflows/reusable-go-ci.yaml @@ -7,16 +7,25 @@ on: description: "The Go module to process" required: true type: string + name: + description: "Name of the module for display purposes" + required: true + type: string run_tests: description: "Set to true to run unit tests and code coverage" required: false type: boolean - default: true + default: ${{ github.event_name == 'pull_request' || github.ref_name == 'main' }} + allow_tests_failure: + description: "Set to true to allow tests to fail without failing the job" + required: false + type: boolean + default: false run_build_image: description: "Set to true to build the container image using Ko" required: false type: boolean - default: true + default: ${{ github.event_name == 'pull_request' || github.ref_name == 'main' }} ko_build_path: description: "Path to the main package for ko build (e.g., cmd/main.go or ./cmd/server)" required: false @@ -26,17 +35,17 @@ on: description: "Set to true to run govulncheck" required: false type: boolean - default: ${{ github.event_name == 'pull_request' }} + default: ${{ github.event_name == 'pull_request' || github.ref_name == 'main' }} run_code_analysis: description: "Set to true to run CodeQL analysis" required: false type: boolean - default: ${{ github.event_name == 'pull_request' }} + default: ${{ github.event_name == 'pull_request' || github.ref_name == 'main' }} run_lint: description: "Set to true to run golangci-lint" required: false type: boolean - default: ${{ github.event_name == 'pull_request' }} + default: ${{ github.event_name == 'pull_request' || github.ref_name == 'main' }} github_repository: description: "GitHub repository (owner/repo), e.g., github.repository. Required if run_build_image is true." required: false @@ -52,16 +61,11 @@ on: required: false type: string default: "ghcr.io" - image_ref: - description: "Container image reference for Ko. If not provided, defaults to container_registry/github_repository/module." - required: false - type: string - default: ${{ inputs.container_registry }}/${{ inputs.github_repository }}/${{ inputs.module }} image_tags: - description: "Comma-separated list of tags to apply to the built image. If empty, GITHUB_REF_SLUG will be used." + description: "Comma-separated list of tags to apply to the built image. If empty, GITHUB_HEAD_REF_SLUG or latest will be used." required: false type: string - default: "" + default: ${{ github.event_name == 'schedule' && 'nightly' || '' }} outputs: image_digest: @@ -70,8 +74,8 @@ on: jobs: static_checks: - name: "Static Checks for ${{ inputs.module }}" - if: ${{ inputs.run_lint }} + name: "Static Checks for ${{ inputs.name }}" + if: ${{ inputs.run_lint || inputs.run_check_generated_files }} runs-on: ubuntu-latest steps: - name: Checkout Code @@ -104,7 +108,7 @@ jobs: args: --timeout 5m --issues-exit-code=0 --config .golangci.yml tests: - name: "Tests & Coverage for ${{ inputs.module }}" + name: "Tests & Coverage for ${{ inputs.name }}" if: ${{ inputs.run_tests }} runs-on: ubuntu-latest outputs: @@ -133,6 +137,8 @@ jobs: - name: Set up gotestfmt uses: gotesttools/gotestfmt-action@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} - name: Build shell: bash @@ -142,6 +148,7 @@ jobs: - name: Run Tests shell: bash working-directory: ${{ inputs.module }} + continue-on-error: ${{ inputs.allow_tests_failure }} run: | set -euo pipefail go test -coverprofile cover.out -json -v ./... 2>&1 | tee gotest.log | gotestfmt @@ -162,7 +169,7 @@ jobs: if: ${{ always() }} # Upload even if previous steps fail uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0 with: - name: ${{ inputs.module }}-gotest.log + name: ${{ inputs.name }}-gotest.log path: ${{ inputs.module }}/gotest.log if-no-files-found: error @@ -171,7 +178,7 @@ jobs: if: ${{ always() }} # Upload even if previous steps fail uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0 with: - name: ${{ inputs.module }}-test-report + name: ${{ inputs.name }}-test-report path: | ${{ inputs.module }}/cover.out ${{ inputs.module }}/coverage.html @@ -183,7 +190,7 @@ jobs: uses: mikepenz/action-junit-report@v6 with: report_paths: "${{ inputs.module }}/junit.xml" - check_name: "Test Report (${{ inputs.module }})" + check_name: "Test Report (${{ inputs.name }})" comment: true include_passed: true @@ -204,14 +211,14 @@ jobs: if: ${{ always() && (github.event_name == 'pull_request') }} uses: marocchino/sticky-pull-request-comment@v2 with: - header: module-coverage-${{ inputs.module }} + header: module-coverage-${{ inputs.name }} message: | - **Coverage for ${{ inputs.module }}** + **Coverage for ${{ inputs.name }}** ${{ steps.prepare_coverage_comment_step.outputs.markdown }} - Download the latest HTML coverage report for ${{ inputs.module }} [here](${{ github.server_url }}/${{ inputs.github_repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_test_reports_artifact_step.outputs.artifact-id }}). + Download the latest HTML coverage report for ${{ inputs.name }} [here](${{ github.server_url }}/${{ inputs.github_repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.upload_test_reports_artifact_step.outputs.artifact-id }}). source_scan: - name: "Source Vulnerability Scan for ${{ inputs.module }}" + name: "Source Vulnerability Scan for ${{ inputs.name }}" if: ${{ inputs.run_vulnerability_check }} runs-on: ubuntu-latest steps: @@ -244,7 +251,7 @@ jobs: work-dir: ${{ inputs.module }} codeql: - name: "CodeQL Analysis for ${{ inputs.module }}" + name: "CodeQL Analysis for ${{ inputs.name }}" if: ${{ inputs.run_code_analysis }} runs-on: ubuntu-latest steps: @@ -279,24 +286,24 @@ jobs: shell: bash working-directory: ${{ inputs.module }} run: | - echo "Attempting to build ${{ inputs.module }} for CodeQL analysis..." - if [ -f Makefile ] && grep -q -E "^build[:[:space:]]" Makefile; then - echo "Found Makefile with build target. Running make build..." - make build - elif [ -f go.mod ]; then + echo "Attempting to build ${{ inputs.name }} for CodeQL analysis..." + if [ -f go.mod ]; then echo "Found go.mod. Running go build ./..." go build ./... + elif [ -f Makefile ] && grep -q -E "^build[:[:space:]]" Makefile; then + echo "Found Makefile with build target. Running make build..." + make build else - echo "WARNING: No Makefile with a 'build' target or go.mod found in ${{ inputs.module }}. CodeQL may not analyze effectively." + echo "WARNING: No Makefile with a 'build' target or go.mod found in ${{ inputs.name }}. CodeQL may not analyze effectively." fi - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # pinv4.30.8 with: - category: "/language:go" + category: "/language:go/${{ inputs.name }}" build: - name: "Build Image for ${{ inputs.module }}" + name: "Build Image for ${{ inputs.name }}" if: ${{ inputs.run_build_image && (needs.static_checks.result != 'failure') && (needs.tests.result != 'failure') }} needs: [tests] runs-on: ubuntu-latest @@ -335,7 +342,6 @@ jobs: shell: bash working-directory: ${{ inputs.module }} env: - KO_DOCKER_REPO: ${{ inputs.image_ref }} KO_CONFIG_PATH: ${{ github.workspace }}/.ko.yaml run: | if [ -z "${{ inputs.github_repository }}" ] || [ -z "${{ inputs.github_ref }}" ]; then @@ -343,30 +349,45 @@ jobs: exit 1 fi + # Convert repository name to lowercase for registry + REPO_LOWER=$(echo "${{ inputs.github_repository }}" | tr '[:upper:]' '[:lower:]') + export KO_DOCKER_REPO="${{ inputs.container_registry }}/${REPO_LOWER}" + effective_tags="" if [ -n "${{ inputs.image_tags }}" ]; then effective_tags="${{ inputs.image_tags }}" else - effective_tags="${GITHUB_REF_SLUG}" + effective_tags="${GITHUB_HEAD_REF_SLUG:-latest}" fi echo "Building with tags: $effective_tags" + echo "Using KO_DOCKER_REPO: ${KO_DOCKER_REPO}" + export VERSION=$(git describe --tags --always --dirty || echo 'develop') + export BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + output=$(ko build ${{ inputs.ko_build_path }} --bare --tags "$effective_tags") echo "Ko output: $output" digest=$(echo "$output" | grep -o 'sha256:[a-f0-9]\{64\}') echo "digest=$digest" >> $GITHUB_OUTPUT + echo "image_ref=${KO_DOCKER_REPO}@${digest}" >> $GITHUB_OUTPUT image_scan: - name: "Image Vulnerability Scan for ${{ inputs.module }}" + name: "Image Vulnerability Scan for ${{ inputs.name }}" if: ${{ inputs.run_build_image && needs.build.result == 'success' }} needs: [build] runs-on: ubuntu-latest steps: - # No checkout or Go setup needed if only running Trivy on a remote image + - name: Convert repository name to lowercase + id: repo_lower + shell: bash + run: | + REPO_LOWER=$(echo "${{ inputs.github_repository }}" | tr '[:upper:]' '[:lower:]') + echo "repo=${REPO_LOWER}" >> $GITHUB_OUTPUT + - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.33.1 with: - image-ref: ${{ inputs.image_ref }}@${{ needs.build.outputs.image_digest }} + image-ref: ${{ inputs.container_registry }}/${{ steps.repo_lower.outputs.repo }}@${{ needs.build.outputs.image_digest }} exit-code: "1" vuln-type: "os,library" severity: "CRITICAL,HIGH"