diff --git a/.github/actions/security-container-scan/action.yml b/.github/actions/security-container-scan/action.yml index ce00d4a..cbd7e62 100644 --- a/.github/actions/security-container-scan/action.yml +++ b/.github/actions/security-container-scan/action.yml @@ -70,10 +70,11 @@ runs: - name: Precheck local image exists id: precheck shell: bash + env: + INPUT_IMAGE: ${{ inputs.image }} run: | set +e - IMAGE="${{ inputs.image }}" - docker image inspect "${IMAGE}" >/dev/null 2>&1 + docker image inspect "${INPUT_IMAGE}" >/dev/null 2>&1 rc=$? if [ $rc -eq 0 ]; then echo "image_exists=true" >> "$GITHUB_OUTPUT" @@ -96,13 +97,14 @@ runs: id: grype if: ${{ steps.precheck.outputs.image_exists == 'true' }} shell: bash + env: + IMAGE: ${{ inputs.image }} + REPORT_JSON: ${{ inputs.report-json }} + REPORT_SARIF: ${{ inputs.report-sarif }} + FAIL_ON: ${{ inputs.fail-on }} + GRYPE_IMAGE: ${{ inputs.grype-image }} run: | set +e - IMAGE="${{ inputs.image }}" - REPORT_JSON="${{ inputs.report-json }}" - REPORT_SARIF="${{ inputs.report-sarif }}" - FAIL_ON="${{ inputs.fail-on }}" - GRYPE_IMAGE="${{ inputs.grype-image }}" echo "Pulling Grype scanner image..." docker pull "${GRYPE_IMAGE}" >/dev/null 2>&1 @@ -147,36 +149,45 @@ runs: - name: Write scan summary if: ${{ inputs.write-summary == 'true' }} shell: bash + env: + INPUT_IMAGE: ${{ inputs.image }} + INPUT_ARTIFACT_NAME: ${{ inputs.artifact-name }} + INPUT_REPORT_JSON: ${{ inputs.report-json }} run: | set -euo pipefail echo "### ๐Ÿ” Container Scan (SBOM + Grype)" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" - echo "- Image: \`${{ inputs.image }}\`" >> "$GITHUB_STEP_SUMMARY" - echo "- Reports artifact: \`${{ inputs.artifact-name }}\` (sarif + json)" >> "$GITHUB_STEP_SUMMARY" + echo "- Image: \`${INPUT_IMAGE}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Reports artifact: \`${INPUT_ARTIFACT_NAME}\` (sarif + json)" >> "$GITHUB_STEP_SUMMARY" - if [ -f "${{ inputs.report-json }}" ]; then - python3 "$GITHUB_ACTION_PATH/grype_summary.py" --json "${{ inputs.report-json }}" --max-top 10 >> "$GITHUB_STEP_SUMMARY" || true + if [ -f "${INPUT_REPORT_JSON}" ]; then + python3 "$GITHUB_ACTION_PATH/grype_summary.py" --json "${INPUT_REPORT_JSON}" --max-top 10 >> "$GITHUB_STEP_SUMMARY" || true fi - name: Finalize status/outputs id: final if: always() shell: bash + env: + IMAGE_EXISTS: ${{ steps.precheck.outputs.image_exists }} + SBOM_OUTCOME: ${{ steps.sbom.outcome }} + GRYPE_STATUS: ${{ steps.grype.outputs.status }} + GRYPE_EXIT: ${{ steps.grype.outputs.exit_code }} + INPUT_GENERATE_SBOM: ${{ inputs.generate-sbom }} + INPUT_FAIL_ON: ${{ inputs.fail-on }} + INPUT_REPORT_JSON: ${{ inputs.report-json }} + INPUT_REPORT_SARIF: ${{ inputs.report-sarif }} + INPUT_FAIL_BUILD: ${{ inputs.fail-build }} run: | set -euo pipefail - IMAGE_EXISTS="${{ steps.precheck.outputs.image_exists }}" - SBOM_OUTCOME="${{ steps.sbom.outcome }}" - GRYPE_STATUS="${{ steps.grype.outputs.status }}" - GRYPE_EXIT="${{ steps.grype.outputs.exit_code }}" - status="ok" detail="ok" if [ "${IMAGE_EXISTS}" != "true" ]; then status="image_not_found" detail="local docker image not found" - elif [ "${{ inputs.generate-sbom }}" = "true" ] && [ "${SBOM_OUTCOME}" != "success" ]; then + elif [ "${INPUT_GENERATE_SBOM}" = "true" ] && [ "${SBOM_OUTCOME}" != "success" ]; then status="sbom_failed" detail="sbom-action failed" elif [ -z "${GRYPE_STATUS}" ]; then @@ -184,10 +195,10 @@ runs: detail="grype step did not produce status" elif [ "${GRYPE_STATUS}" = "ok" ]; then status="ok" - detail="no vulnerabilities at/above fail-on=${{ inputs.fail-on }}" + detail="no vulnerabilities at/above fail-on=${INPUT_FAIL_ON}" elif [ "${GRYPE_STATUS}" = "high_or_error" ]; then status="high_or_error" - detail="grype exit_code=${GRYPE_EXIT} (fail-on=${{ inputs.fail-on }})" + detail="grype exit_code=${GRYPE_EXIT} (fail-on=${INPUT_FAIL_ON})" else status="${GRYPE_STATUS}" detail="grype exit_code=${GRYPE_EXIT}" @@ -195,10 +206,10 @@ runs: echo "status=${status}" >> "$GITHUB_OUTPUT" echo "detail=${detail}" >> "$GITHUB_OUTPUT" - echo "report_json=${{ inputs.report-json }}" >> "$GITHUB_OUTPUT" - echo "report_sarif=${{ inputs.report-sarif }}" >> "$GITHUB_OUTPUT" + echo "report_json=${INPUT_REPORT_JSON}" >> "$GITHUB_OUTPUT" + echo "report_sarif=${INPUT_REPORT_SARIF}" >> "$GITHUB_OUTPUT" - if [ "${{ inputs.fail-build }}" = "true" ] && [ "${status}" != "ok" ]; then + if [ "${INPUT_FAIL_BUILD}" = "true" ] && [ "${status}" != "ok" ]; then echo "Failing build due to status=${status}: ${detail}" 1>&2 exit 1 fi diff --git a/.github/actions/slack-notify/action.yml b/.github/actions/slack-notify/action.yml index b64c2e6..2a2f90f 100644 --- a/.github/actions/slack-notify/action.yml +++ b/.github/actions/slack-notify/action.yml @@ -56,28 +56,23 @@ runs: - name: Prepare Slack Payload id: prepare-payload shell: bash + env: + INPUT_CHANNEL_ID: ${{ inputs.channel-id }} + INPUT_PAYLOAD: ${{ inputs.payload }} + INPUT_MESSAGE: ${{ inputs.message }} run: | - CHANNEL_ID="${{ inputs.channel-id }}" - # If custom payload is provided, merge channel into it - if [ -n '${{ inputs.payload }}' ]; then + if [ -n "${INPUT_PAYLOAD}" ]; then # Parse the JSON and add/update channel field - PAYLOAD=$(cat <<'PAYLOAD_EOF' | jq -c ". + {channel: \"$CHANNEL_ID\"}" - ${{ inputs.payload }} - PAYLOAD_EOF - ) + PAYLOAD=$(echo "${INPUT_PAYLOAD}" | jq -c ". + {channel: \"${INPUT_CHANNEL_ID}\"}") echo "payload<> $GITHUB_OUTPUT echo "$PAYLOAD" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - elif [ -n '${{ inputs.message }}' ]; then + elif [ -n "${INPUT_MESSAGE}" ]; then # Simple message mode - construct basic payload - MESSAGE=$(cat <<'MESSAGE_EOF' - ${{ inputs.message }} - MESSAGE_EOF - ) PAYLOAD=$(jq -n \ - --arg channel "$CHANNEL_ID" \ - --arg text "$MESSAGE" \ + --arg channel "${INPUT_CHANNEL_ID}" \ + --arg text "${INPUT_MESSAGE}" \ '{channel: $channel, text: $text}') echo "payload<> $GITHUB_OUTPUT echo "$PAYLOAD" >> $GITHUB_OUTPUT @@ -100,23 +95,28 @@ runs: - name: Display Notification Info if: steps.slack.outcome == 'success' shell: bash + env: + SLACK_TS: ${{ steps.slack.outputs.ts }} + SLACK_THREAD_TS: ${{ steps.slack.outputs.thread_ts }} + INPUT_CHANNEL_ID: ${{ inputs.channel-id }} + INPUT_ERRORS: ${{ inputs.errors }} run: | - if [ -n "${{ steps.slack.outputs.ts }}" ]; then + if [ -n "${SLACK_TS}" ]; then echo "โœ… Slack notification sent successfully!" - echo "๐Ÿ“ฌ Channel ID: ${{ inputs.channel-id }}" - echo "โฐ Timestamp: ${{ steps.slack.outputs.ts }}" - if [ -n "${{ steps.slack.outputs.thread_ts }}" ]; then - echo "๐Ÿงต Thread Timestamp: ${{ steps.slack.outputs.thread_ts }}" + echo "๐Ÿ“ฌ Channel ID: ${INPUT_CHANNEL_ID}" + echo "โฐ Timestamp: ${SLACK_TS}" + if [ -n "${SLACK_THREAD_TS}" ]; then + echo "๐Ÿงต Thread Timestamp: ${SLACK_THREAD_TS}" fi else echo "โš ๏ธ Slack API call completed but returned no timestamp!" echo "This usually means the bot doesn't have permission to post to the channel." echo "" echo "Common solutions:" - echo " 1. Invite the bot to channel ${{ inputs.channel-id }}: /invite @YourBotName" + echo " 1. Invite the bot to channel ${INPUT_CHANNEL_ID}: /invite @YourBotName" echo " 2. Add 'chat:write.public' scope to your bot (for public channels)" - echo " 3. Verify the channel ID is correct: ${{ inputs.channel-id }}" - if [ "${{ inputs.errors }}" == "true" ]; then + echo " 3. Verify the channel ID is correct: ${INPUT_CHANNEL_ID}" + if [ "${INPUT_ERRORS}" == "true" ]; then exit 1 fi fi @@ -124,8 +124,10 @@ runs: - name: Display Error Info if: steps.slack.outcome == 'failure' shell: bash + env: + INPUT_ERRORS: ${{ inputs.errors }} run: | echo "โŒ Slack notification failed!" - if [ "${{ inputs.errors }}" == "false" ]; then + if [ "${INPUT_ERRORS}" == "false" ]; then echo "โš ๏ธ Continuing workflow despite failure (errors: false)" fi diff --git a/.github/workflows/build-cds-containers.yml b/.github/workflows/build-cds-containers.yml index aefabd4..2e8c3f0 100644 --- a/.github/workflows/build-cds-containers.yml +++ b/.github/workflows/build-cds-containers.yml @@ -16,14 +16,12 @@ env: IMAGE_NAMESPACE: nvidia IMAGE_PREFIX: dsx-cds- # Prefix to identify CDS container images -permissions: - contents: read - packages: write # Required to push to GHCR - jobs: # Job 1: Read version from VERSION.md get-version: runs-on: ubuntu-latest + permissions: + contents: read outputs: version: ${{ steps.extract-version.outputs.version }} @@ -41,6 +39,9 @@ jobs: # Job 2: Build and push all container images build-and-push-images: runs-on: ubuntu-latest + permissions: + contents: read + packages: write # Required to push to GHCR needs: get-version strategy: fail-fast: false @@ -122,6 +123,9 @@ jobs: # Job 3: Test using the built go-dev image test-go-dev-image: runs-on: ubuntu-latest + permissions: + contents: read + packages: read needs: [get-version, build-and-push-images] # Only run tests when images are pushed (main only) if: github.ref == 'refs/heads/main' @@ -161,6 +165,9 @@ jobs: # Job 4: Test using tools container test-tools-image: runs-on: ubuntu-latest + permissions: + contents: read + packages: read needs: [get-version, build-and-push-images] # Only run tests when images are pushed (main only) if: github.ref == 'refs/heads/main' @@ -196,6 +203,8 @@ jobs: # Job 5: Summary summary: runs-on: ubuntu-latest + permissions: + contents: read needs: [get-version, build-and-push-images, test-go-dev-image, test-tools-image] if: always() diff --git a/.github/workflows/promote-image.yml b/.github/workflows/promote-image.yml index ef8784e..09e303e 100644 --- a/.github/workflows/promote-image.yml +++ b/.github/workflows/promote-image.yml @@ -62,18 +62,24 @@ jobs: - name: Prepare refs id: refs shell: bash + env: + INPUT_DIGEST: ${{ inputs.digest }} + INPUT_SOURCE: ${{ inputs.source }} + INPUT_SOURCE_TAG: ${{ inputs.source_tag }} + INPUT_DESTINATION: ${{ inputs.destination }} + INPUT_DESTINATION_TAG: ${{ inputs.destination_tag }} run: | set -euo pipefail # Assemble source reference - if [[ -n "${{ inputs.digest }}" ]]; then - source_ref="${{ inputs.source }}@${{ inputs.digest }}" + if [[ -n "${INPUT_DIGEST}" ]]; then + source_ref="${INPUT_SOURCE}@${INPUT_DIGEST}" else - source_ref="${{ inputs.source }}:${{ inputs.source_tag }}" + source_ref="${INPUT_SOURCE}:${INPUT_SOURCE_TAG}" fi # Assemble destination reference - destination_ref="${{ inputs.destination }}:${{ inputs.destination_tag }}" + destination_ref="${INPUT_DESTINATION}:${INPUT_DESTINATION_TAG}" echo "source_ref=${source_ref}" >> "$GITHUB_OUTPUT" echo "destination_ref=${destination_ref}" >> "$GITHUB_OUTPUT"