diff --git a/.github/actions/combine-results/action.yaml b/.github/actions/combine-results/action.yaml new file mode 100644 index 00000000000..35eb5dfe217 --- /dev/null +++ b/.github/actions/combine-results/action.yaml @@ -0,0 +1,37 @@ +name: Combine results +description: | + Determines a single result from one or more results. A single skipped or + failed will cause the action to fail. Details for each job will be output + as part of a results summary. + +inputs: + needs-json: + description: Job outputs in JSON format + required: true + type: string + +runs: + using: composite + steps: + - name: Summarise and combine results + shell: bash + run: | + needs_results=$(echo '${{ inputs.needs-json }}' | jq -r 'to_entries[] | "\(.key) \(.value.result)"') + + echo "Summary:" + failed="false" + while IFS=" " read -r job result; do + if [[ "$result" != "success" ]]; then + failed="true" + echo "- ❌ Job \"$job\" returned result \"$result\"" + else + echo "- ✅ Job \"$job\" returned result \"$result\"" + fi + done <<< "$needs_results" + + if [[ "$failed" == "false" ]]; then + echo "Overall result: success" + else + echo "Overall result: failure" + exit 1 + fi diff --git a/.github/actions/resolve-go-channels/action.yaml b/.github/actions/resolve-go-channels/action.yaml new file mode 100644 index 00000000000..cd7eaefa80e --- /dev/null +++ b/.github/actions/resolve-go-channels/action.yaml @@ -0,0 +1,158 @@ +name: Resolve Go snap channels +description: | + Compiles a list of Go snap channels with unique versions according to the + given optional input flags and specific channels. Looks up the snapd build + and snapd FIPS build Go channels from build-aux/snap/snapcraft.yaml. Assumes + risk stable if not specified. + +inputs: + include-snapd-build-go-channel: + description: Flag instructing to include the channel of Go snap used to build Snapd snap + required: false + type: boolean + include-snapd-build-fips-go-channel: + description: Flag instructing to include the channel of Go snap used to build Snapd snap for FIPS + required: false + type: string + include-latest-go-channel: + description: Flag instructing to include the latest channel of Go snap + required: false + type: boolean + specific-go-channels: + description: Space separated list of required Go snap channels + required: false + type: string + +outputs: + go-channels: + description: JSON list of Go snap channels + value: ${{ steps.resolve-go-channels.outputs.go-channels }} + +runs: + using: composite + steps: + - name: Resolve Go snap channels + id: resolve-go-channels + shell: bash + run: | + # Get the Go snap version corresponding to a channel format [/] + # - If optional risk is omitted, default stable will be assumed + # - Assumes both device and snap architecture amd64 + go_version_from_channel() { + channel=$1 + risk_default="stable" + arch_default="amd64" + + # channel=/ + if [[ "$channel" =~ ^([0-9]+\.[0-9]+|[0-9]+\.[0-9]+-fips|latest)/(stable|candidate|beta|edge)$ ]]; then + track=${channel%%/*} + risk=${channel##*/} + # channel= + elif [[ "$channel" =~ ^([0-9]+\.[0-9]+|[0-9]+\.[0-9]+-fips|latest)$ ]]; then + track=$channel + risk=$risk_default + # Not supported + else + echo "Cannot use Go channel \"$channel\"" + return 1 + fi + + # Query params + device_arch="Snap-Device-Architecture: $arch_default" + channel_arch="$arch_default" + device_series="Snap-Device-Series: 16" + endpoint="https://api.snapcraft.io/v2/snaps/info/go" + + # Query store + if ! result="$(curl -s --fail -H "$device_arch" -H "$device_series" -X GET "$endpoint")"; then + echo "Cannot use endpoint \"$endpoint\": $result" + return 1 + else + version="$(jq -r ".\"channel-map\"[] \ + | select ( .channel.track == \"$track\" and .channel.risk == \"$risk\" and .channel.architecture == \"$channel_arch\" ) \ + | .version" <<< "$result")" + if [ -z "$version" ] || [ "$version" = "null" ]; then + echo "Cannot find version corresponding to: arch=$channel_arch, track=$track, risk=$track" + return 1 + else + # Return the version + echo "$version" + fi + fi + } + + go_channels=() + echo "Gathering required Go channels" + + # Optional Go channel used to build Snapd snap + if [ "${{ inputs.include-snapd-build-go-channel }}" = "true" ]; then + echo "> Require Go channel used to build Snapd snap" + yaml="build-aux/snap/snapcraft.yaml" + if ! channel="$(yq '.parts.snapd.build-snaps[]' $yaml | grep "go/.*/.*")"; then + echo "Error: Cannot find valid Snapd build Go channel" + exit 1 + fi + channel="$(yq '.parts.snapd.build-snaps[] | select(. == "go/*/*") | sub("^go/", "")' $yaml)" + echo "> Adding Go channel \"$channel\"" + go_channels+=("$channel") + fi + + # Optional Go channel used to build Snapd snap for FIPS + if [ "${{ inputs.include-snapd-build-fips-go-channel }}" = "true" ]; then + echo "> Require Go channel used to build Snapd snap for FIPS" + yaml="build-aux/snap/snapcraft.yaml" + if ! channel="$(yq '.parts.snapd.override-build' $yaml | grep "GO_TOOLCHAIN_FIPS_CHANNEL=\".*\"")"; then + echo "Error: Cannot find valid Snapd FIPS build Go channel" + exit 1 + fi + channel="$(echo "$channel" | sed -n 's/^GO_TOOLCHAIN_FIPS_CHANNEL="\([^"]*\)"/\1/p')" + echo "> Adding Go channel \"$channel\"" + go_channels+=("$channel") + fi + + # Optional latest stable Go channel + if [ "${{ inputs.include-latest-go-channel }}" = "true" ]; then + echo "> Require latest stable Go channel" + channel="latest/stable" + echo "> Adding Go channel \"$channel\"" + go_channels+=("$channel") + fi + + # Optional specific Go channel(s) + if [ -n "${{ inputs.specific-go-channels }}" ]; then + echo "> Require specific Go channel(s)" + for channel in ${{ inputs.specific-go-channels }}; do + echo "> Adding Go channel \"$channel\"" + go_channels+=("$channel") + done + fi + + declare -A go_versions + go_channels_with_unique_version=() + echo "Dropping Go channels that duplicates Go versions" + + # Iterate all the required channels and create list of + # channels with unique versions. + for channel in "${go_channels[@]}"; do + if ! output="$(go_version_from_channel "$channel")"; then + echo "Error: $output" + else + if [[ -v go_versions["$output"] ]]; then + echo "> Dropping channel \"$channel\": same Go version as channel \"${go_versions[$output]}\"" + else + echo "> Keeping channel \"$channel\" with unique Go version \"$output\"" + go_versions["$output"]="$channel" + go_channels_with_unique_version+=("$channel") + fi + fi + done + + # Convert to single line JSON array and remove duplicates + go_channels_output="[]" + if [[ ${#go_channels_with_unique_version[@]} -gt 0 ]]; then + go_channels_output="$(printf '%s\n' "${go_channels_with_unique_version[@]}" | jq -R . | jq -s -c .)" + fi + echo "Unique Go channels: $go_channels_output" + + # Output the single line JSON array + echo "go-channels=$go_channels_output" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1458d6d5388..41c1d4d7f8a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,6 +10,22 @@ concurrency: cancel-in-progress: true jobs: + go-channels: + runs-on: ubuntu-latest + outputs: + go-channels: ${{ steps.resolve-go-channels.outputs.go-channels }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Resolve Go snap channels + id: resolve-go-channels + uses: ./.github/actions/resolve-go-channels + with: + include-snapd-build-go-channel: true + include-snapd-build-fips-go-channel: true + include-latest-go-channel: true + snap-builds: uses: ./.github/workflows/snap-builds.yaml with: @@ -68,7 +84,9 @@ jobs: static-checks: uses: ./.github/workflows/static-checks.yaml - needs: [cache-build-deps] + needs: + - go-channels + - cache-build-deps with: runs-on: ubuntu-latest gochannel: ${{ matrix.gochannel }} @@ -77,16 +95,13 @@ jobs: # we cache successful runs so it's fine to keep going fail-fast: false matrix: - gochannel: - - 1.18 - - latest/stable + gochannel: ${{ fromJson(needs.go-channels.outputs.go-channels) }} branch-static-checks: runs-on: ubuntu-latest needs: [cache-build-deps] if: github.ref != 'refs/heads/master' steps: - - name: Checkout code uses: actions/checkout@v4 with: @@ -102,9 +117,30 @@ jobs: test "$system_daily" == "$current_daily" shell: bash + # The required-static-checks job was introduced to maintain a consistent + # status check name, regardless of changes to the Go channel used for static + # checks. This avoids the need to update required status checks whenever the + # Go channel changes. + required-static-checks: + runs-on: ubuntu-latest + needs: + - static-checks + - branch-static-checks + if: always() + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Confirm required static checks passed + uses: ./.github/actions/combine-results + with: + needs-json: ${{ toJSON(needs) }} + unit-tests: uses: ./.github/workflows/unit-tests.yaml - needs: [static-checks] + needs: + - go-channels + - static-checks name: "unit-tests default ${{ matrix.gochannel }}" with: runs-on: ubuntu-22.04 @@ -114,14 +150,14 @@ jobs: # we cache successful runs so it's fine to keep going fail-fast: false matrix: - gochannel: - - 1.18 - - latest/stable + gochannel: ${{ fromJson(needs.go-channels.outputs.go-channels) }} # TODO run unit tests of C code unit-tests-special: uses: ./.github/workflows/unit-tests.yaml - needs: [static-checks] + needs: + - go-channels + - static-checks name: "unit-tests (${{ matrix.gochannel }} ${{ matrix.test-case.go-build-tags }} ${{ matrix.test-case.go-test-race && ' test-race' || ''}} ${{ matrix.test-case.snapd-debug && ' snapd-debug' || ''}})" @@ -136,9 +172,7 @@ jobs: # we cache successful runs so it's fine to keep going fail-fast: false matrix: - gochannel: - - 1.18 - - latest/stable + gochannel: ${{ fromJson(needs.go-channels.outputs.go-channels) }} test-case: - { go-build-tags: snapd_debug, skip-coverage: false, snapd-debug: true, go-test-race: false} - { go-build-tags: withbootassetstesting, skip-coverage: false, snapd-debug: false, go-test-race: false} @@ -161,6 +195,26 @@ jobs: # TODO add arch? - fedora:latest - opensuse/tumbleweed + + # The required-unit-tests job was introduced to maintain a consistent + # status check name, regardless of changes to the Go channel used for unit + # tests. This avoids the need to update required status checks whenever the + # Go channel changes. + required-unit-tests: + runs-on: ubuntu-latest + needs: + - unit-tests + - unit-tests-special + - unit-tests-cross-distro + if: always() + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Confirm required unit tests passed + uses: ./.github/actions/combine-results + with: + needs-json: ${{ toJSON(needs) }} code-coverage: needs: [unit-tests, unit-tests-special] diff --git a/build-aux/snap/snapcraft.yaml b/build-aux/snap/snapcraft.yaml index f374ebac8d1..6c3a0ee6278 100644 --- a/build-aux/snap/snapcraft.yaml +++ b/build-aux/snap/snapcraft.yaml @@ -195,7 +195,7 @@ parts: plugin: nil source: . build-snaps: - - go/1.18/stable # the default Go toolchain + - go/1.23/stable # the default Go toolchain after: - apparmor build-packages: @@ -261,7 +261,7 @@ parts: snap refresh --channel "$GO_TOOLCHAIN_FIPS_CHANNEL" go # make sure it is really the Go FIPS toolchain if ! test -f /snap/go/current/src/crypto/internal/backend/openssl_linux.go; then - echo "Go 1.21 FIPS toolchain not found" + echo "Go FIPS toolschain verification failed: cannot find /snap/go/current/src/crypto/internal/backend/openssl_linux.go" exit 1 fi fi