diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 76302a4..24494c3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,14 +21,14 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - name: Lint Codebase id: super-linter - uses: super-linter/super-linter/slim@12150456a73e248bdc94d0794898f94e23127c88 # v7.4.0 + uses: super-linter/super-linter/slim@61abc07d755095a68f4987d1c2c3d1d64408f1f9 # v8.5.0 env: DEFAULT_BRANCH: main FILTER_REGEX_EXCLUDE: dist/**/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0bfe952..01aded0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive fetch-depth: 0 @@ -30,24 +30,24 @@ jobs: - name: Setup wash CLI uses: ./setup-wash-action with: - wash-version: wash-v1.0.0-beta.8 + wash-version: wash-v2.0.0-rc.7 - # create a test working directory + # create a test working directory with a Rust project - name: Create test directory run: | mkdir test-dir - cd test-dir + + - name: Create test Rust project + working-directory: test-dir + run: | + cargo init --lib --name test-component + printf '\n[lib]\ncrate-type = ["cdylib"]\n' >> Cargo.toml - name: Setup cargo-auditable uses: ./setup-wash-cargo-auditable with: working-directory: test-dir - - name: Create test Rust project - working-directory: test-dir - run: | - cargo init --name test-component - - name: Test wash-build action id: build uses: ./wash-build @@ -66,17 +66,8 @@ jobs: env: STEPS_BUILD_OUTPUTS_COMPONENT_PATH: ${{ steps.build.outputs.component_path }} - - name: Verify wash config updated with cargo-auditable - shell: bash - working-directory: test-dir - run: | - jq . .wash/config.json - if ! jq -e '.build.rust.custom_command | arrays | contains(["auditable"])' .wash/config.json; then - echo "Error: .wash/config.json does not contain 'auditable' in custom_command" - exit 1 - fi - - name: Test wash-oci-publish action (multiple tags) + if: github.event_name == 'push' uses: ./wash-oci-publish with: component_path: ${{ steps.build.outputs.component_path }} diff --git a/setup-wash-action b/setup-wash-action index 49c62a9..f27efbc 160000 --- a/setup-wash-action +++ b/setup-wash-action @@ -1 +1 @@ -Subproject commit 49c62a9834282ebac849a3043d37306a625820a3 +Subproject commit f27efbc3b7f728027a859df2b873b86789107ca1 diff --git a/setup-wash-cargo-auditable/action.yml b/setup-wash-cargo-auditable/action.yml index 17fb184..e2d9449 100644 --- a/setup-wash-cargo-auditable/action.yml +++ b/setup-wash-cargo-auditable/action.yml @@ -6,6 +6,10 @@ description: | wash CLI must be installed and available in PATH. We recommend using the wasmcloud/setup-wash-action prior to this action to install wash. + A Cargo project (Cargo.toml) must already exist in the working directory + before calling this action, as it reads the package name to determine + the component output path. + inputs: working-directory: description: "Directory containing the WebAssembly project" @@ -15,36 +19,22 @@ inputs: runs: using: "composite" steps: - - uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # v2.62.6 + - uses: taiki-e/install-action@1cf3de8de323df92fe08c793e53eaef58799aec4 # v2.68.3 with: tool: cargo-auditable,cargo-audit - - name: Set auditable as custom build command in .wash/config.json + - name: Set auditable as custom build command in .wash/config.yaml shell: bash working-directory: ${{ inputs.working-directory }} run: | + set -euo pipefail + + # Derive the wasm artifact path from the Cargo package name + PACKAGE_NAME=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].name' | tr '-' '_') + COMPONENT_PATH="target/wasm32-wasip2/release/${PACKAGE_NAME}.wasm" + mkdir -p .wash - # Create or update config with jq, merging with existing config - if [ -f .wash/config.json ]; then - # Merge with existing config, ensuring nested objects exist - jq '(.build // {}) as $build | - (.build.rust // {}) as $rust | - .build = ($build | .rust = ($rust | - .custom_command = ["cargo", "auditable", "build", "--release", "--target", "wasm32-wasip2", "--message-format", "json"] | - .target = "wasm32-wasip2" | - .release = true - ))' .wash/config.json > .wash/config.json.tmp - mv .wash/config.json.tmp .wash/config.json - else - # Create new config - jq -n '{ - "build": { - "rust": { - "target": "wasm32-wasip2", - "release": true, - "custom_command": ["cargo", "auditable", "build", "--release", "--target", "wasm32-wasip2", "--message-format", "json"] - } - } - }' > .wash/config.json - fi + # Write .wash/config.yaml in wash v2.0 format + printf 'build:\n command: cargo auditable build --release --target wasm32-wasip2\n component_path: "%s"\n' \ + "$COMPONENT_PATH" > .wash/config.yaml diff --git a/wash-build/action.yml b/wash-build/action.yml index 26b7577..d33682c 100644 --- a/wash-build/action.yml +++ b/wash-build/action.yml @@ -62,8 +62,10 @@ runs: - name: Verify Wasm binary exists shell: bash working-directory: ${{ inputs.working-directory }} + env: + COMPONENT_PATH: ${{ steps.build.outputs.component_path }} run: | - if [ ! -f "${{ steps.build.outputs.component_path }}" ]; then - echo "Error: ${{ steps.build.outputs.component_path }} not found!" >&2 + if [ ! -f "$COMPONENT_PATH" ]; then + echo "Error: $COMPONENT_PATH not found!" >&2 exit 1 fi diff --git a/wash-oci-publish/action.yml b/wash-oci-publish/action.yml index 96c28a1..9cb521e 100644 --- a/wash-oci-publish/action.yml +++ b/wash-oci-publish/action.yml @@ -37,40 +37,45 @@ runs: using: "composite" steps: - name: Login to container registry - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ${{ inputs.registry }} username: ${{ github.actor }} password: ${{ inputs.token }} - - name: Set IMAGE_NAME env as lower-case repository name + - name: Set image name and SBOM filename + id: image-info shell: bash run: | - echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV - # Create a sanitized filename for SBOM (replace / with -) - echo "SBOM_FILENAME=$(echo "${GITHUB_REPOSITORY,,}" | tr '/' '-').spdx.json" >> $GITHUB_ENV + echo "image_name=${GITHUB_REPOSITORY,,}" >> $GITHUB_OUTPUT + echo "sbom_filename=$(echo "${GITHUB_REPOSITORY,,}" | tr '/' '-').spdx.json" >> $GITHUB_OUTPUT - name: Push Wasm component to container registry id: push shell: bash + env: + IMAGE_TAGS: ${{ inputs.image_tags }} + REGISTRY: ${{ inputs.registry }} + IMAGE_NAME: ${{ steps.image-info.outputs.image_name }} + COMPONENT_PATH: ${{ inputs.component_path }} run: | # Split comma-separated tags and push each one - IFS=',' read -ra TAGS <<< "${{ inputs.image_tags }}" + IFS=',' read -ra TAGS <<< "$IMAGE_TAGS" digests=() for tag in "${TAGS[@]}"; do # Trim whitespace tag=$(echo "$tag" | xargs) - echo "Pushing ${{ inputs.registry }}/${{ env.IMAGE_NAME }}:$tag" - - push_output=$(wash oci push --output json "${{ inputs.registry }}/${{ env.IMAGE_NAME }}:$tag" "${{ inputs.component_path }}") + echo "Pushing $REGISTRY/$IMAGE_NAME:$tag" + + push_output=$(wash oci push --output json "$REGISTRY/$IMAGE_NAME:$tag" "$COMPONENT_PATH") digest=$(echo "$push_output" | jq -r .data.digest) - + if [ -z "$digest" ] || [ "$digest" = "null" ]; then echo "Failed to determine pushed component digest for tag $tag: $push_output" >&2 exit 1 fi - + echo "Component pushed with digest: $digest for tag: $tag" digests+=("$digest") done @@ -81,20 +86,23 @@ runs: - name: Generate artifact attestation if: ${{ inputs.attestation == 'true' }} - uses: actions/attest-build-provenance@v3 + uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 with: - subject-name: ${{ inputs.registry }}/${{ env.IMAGE_NAME }} + subject-name: ${{ inputs.registry }}/${{ steps.image-info.outputs.image_name }} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true - - uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # v2.62.6 + - uses: taiki-e/install-action@1cf3de8de323df92fe08c793e53eaef58799aec4 # v2.68.3 if: ${{ inputs.attestation == 'true' }} with: tool: auditable2cdx - name: Install CycloneDX CLI + id: install-cyclonedx if: ${{ inputs.attestation == 'true' }} shell: bash + env: + CYCLONEDX_VERSION: ${{ inputs.cyclonedx-version }} run: | # Detect OS and architecture for cyclonedx-cli binary case "$(uname -s)" in @@ -124,41 +132,48 @@ runs: *) echo "Unsupported OS: $(uname -s)"; exit 1 ;; esac - # Pin to known good version 0.29.1 + # Pin to known good version echo "Downloading cyclonedx-cli binary: $BINARY_NAME" - curl -LO "https://github.com/CycloneDX/cyclonedx-cli/releases/download/v${{ inputs.cyclonedx-version }}/$BINARY_NAME" - chmod +x "$BINARY_NAME" + curl -LO "https://github.com/CycloneDX/cyclonedx-cli/releases/download/v${CYCLONEDX_VERSION}/${BINARY_NAME}" - # Create a directory for the binary and add to PATH mkdir -p "$HOME/.local/bin" mv "$BINARY_NAME" "$HOME/.local/bin/cyclonedx" - echo "$HOME/.local/bin" >> $GITHUB_PATH + chmod +x "$HOME/.local/bin/cyclonedx" + + echo "cyclonedx_bin=$HOME/.local/bin/cyclonedx" >> $GITHUB_OUTPUT - name: Extract SBOM from component + id: extract-sbom if: ${{ inputs.attestation == 'true' }} shell: bash + env: + COMPONENT_PATH: ${{ inputs.component_path }} run: | # Create absolute path for CycloneDX SBOM file CYCLONEDX_PATH="$(pwd)/$(echo "${GITHUB_REPOSITORY,,}" | tr '/' '-').cyclonedx.json" # Extract CycloneDX SBOM - auditable2cdx ${{ inputs.component_path }} > "$CYCLONEDX_PATH" + auditable2cdx "$COMPONENT_PATH" > "$CYCLONEDX_PATH" echo "CycloneDX SBOM created at: $CYCLONEDX_PATH" - # Store path for next step - echo "CYCLONEDX_ABS_PATH=$CYCLONEDX_PATH" >> $GITHUB_ENV + echo "cyclonedx_path=$CYCLONEDX_PATH" >> $GITHUB_OUTPUT - name: Convert SBOM to SPDX + id: convert-sbom if: ${{ inputs.attestation == 'true' }} shell: bash + env: + SBOM_FILENAME: ${{ steps.image-info.outputs.sbom_filename }} + CYCLONEDX_ABS_PATH: ${{ steps.extract-sbom.outputs.cyclonedx_path }} + CYCLONEDX_BIN: ${{ steps.install-cyclonedx.outputs.cyclonedx_bin }} run: | # Create absolute path for SPDX SBOM file - SBOM_PATH="$(pwd)/${{ env.SBOM_FILENAME }}" + SBOM_PATH="$(pwd)/$SBOM_FILENAME" # Convert CycloneDX to SPDX format for GitHub attestation - cyclonedx convert --input-file "${{ env.CYCLONEDX_ABS_PATH }}" --output-file "$SBOM_PATH" --output-format spdxjson + "$CYCLONEDX_BIN" convert --input-file "$CYCLONEDX_ABS_PATH" --output-file "$SBOM_PATH" --output-format spdxjson - echo "SBOM_ABS_PATH=$SBOM_PATH" >> $GITHUB_ENV + echo "sbom_abs_path=$SBOM_PATH" >> $GITHUB_OUTPUT # Debug: Print SBOM file info and contents echo "SPDX SBOM file created at: $SBOM_PATH" @@ -168,9 +183,9 @@ runs: echo "SPDX SBOM file format detection:" file "$SBOM_PATH" - - uses: actions/attest-sbom@v3 + - uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 # v3.0.0 if: ${{ inputs.attestation == 'true' }} with: - subject-name: ${{ inputs.registry }}/${{ env.IMAGE_NAME }} + subject-name: ${{ inputs.registry }}/${{ steps.image-info.outputs.image_name }} subject-digest: ${{ steps.push.outputs.digest }} - sbom-path: ${{ env.SBOM_ABS_PATH }} + sbom-path: ${{ steps.convert-sbom.outputs.sbom_abs_path }}