diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c03cde0b..be89321b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,9 +14,8 @@ jobs: steps: - name: Checkout HiveMQ Helm Charts uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - - name: Fetch history - run: git fetch --prune --unshallow + with: + fetch-depth: 0 - name: Configure Git run: | @@ -26,6 +25,20 @@ jobs: - name: Set up Helm uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4 + - name: Set up JDK 21 + uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4 + with: + gradle-home-cache-includes: | + caches + notifications + jdks + - name: Add dependency chart repos run: | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts @@ -34,7 +47,6 @@ jobs: run: | echo "${{ secrets.SIGNING_KEY }}" | gpg --dearmor > $HOME/secring.gpg echo "${{ secrets.SIGNING_PASSWORD }}" > $HOME/passphrase - echo "CR_KEYRING=$HOME/secring.gpg" >> "$GITHUB_ENV" echo "CR_PASSPHRASE_FILE=$HOME/passphrase" >> "$GITHUB_ENV" @@ -50,3 +62,14 @@ jobs: run: | rm -f $HOME/secring.gpg rm -f $HOME/passphrase + + - name: Set latest release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bash ./release/set-latest-release.sh + + - name: Update GitHub release notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + working-directory: helm-charts + run: bash ./release/update-github-release-notes.sh 10 diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 266f89ca..44b65321 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -4,6 +4,9 @@ on: branches: - "**" workflow_dispatch: +concurrency: + group: verify-${{ github.ref }} + cancel-in-progress: true jobs: verify: runs-on: ubuntu-latest @@ -60,3 +63,8 @@ jobs: helm kubeconform --config .github/configs/kubeconform.yml charts/hivemq-platform-operator -r hivemq-platform-operator-release -n hivemq-platform-operator-namespace helm kubeconform --config .github/configs/kubeconform.yml charts/hivemq-platform -r hivemq-platform-release -n hivemq-platform-namespace --schema-location hivemqplatform.hivemq-v1.json helm kubeconform --config .github/configs/kubeconform.yml charts/hivemq-swarm -r hivemq-swarm-release -n hivemq-swarm-namespace + + - name: Validate branch protection + env: + GH_TOKEN: ${{ secrets.JENKINS_GITHUB_TOKEN }} + run: bash ./release/validate-branch-protection.sh ${{ github.base_ref }} diff --git a/.gitignore b/.gitignore index eb562a2c..84231685 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ charts/**/charts/**.tgz # Folders and files created when enabling debug mode in Helm .debug + +# temporary files for the github-release-note-updater +releases.json +charts.json diff --git a/github-release-note-updater/build.gradle.kts b/github-release-note-updater/build.gradle.kts new file mode 100644 index 00000000..6057735e --- /dev/null +++ b/github-release-note-updater/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + application +} + +group = "com.hivemq" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +application { + mainClass = "com.hivemq.release.GitHubReleaseNotesUpdater" +} + +repositories { + mavenCentral() +} + +dependencies { + compileOnly(libs.jetbrains.annotations) + implementation(libs.jackson.databind) + implementation(libs.java.semver) + implementation(libs.jcommander) +} diff --git a/github-release-note-updater/gradle/libs.versions.toml b/github-release-note-updater/gradle/libs.versions.toml new file mode 100644 index 00000000..51ef54f3 --- /dev/null +++ b/github-release-note-updater/gradle/libs.versions.toml @@ -0,0 +1,11 @@ +[versions] +jackson = "2.18.1" +java-semver = "0.10.2" +jcommander = "1.82" +jetbrains-annotations = "26.0.1" + +[libraries] +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } +java-semver = { module = "com.github.zafarkhaja:java-semver", version.ref = "java-semver" } +jcommander = { module = "com.beust:jcommander", version.ref = "jcommander" } +jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } diff --git a/github-release-note-updater/settings.gradle.kts b/github-release-note-updater/settings.gradle.kts new file mode 100644 index 00000000..aa5fb46b --- /dev/null +++ b/github-release-note-updater/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "github-release-note-updater" diff --git a/github-release-note-updater/src/main/java/com/hivemq/release/Arguments.java b/github-release-note-updater/src/main/java/com/hivemq/release/Arguments.java new file mode 100644 index 00000000..63caf32d --- /dev/null +++ b/github-release-note-updater/src/main/java/com/hivemq/release/Arguments.java @@ -0,0 +1,18 @@ +package com.hivemq.release; + +import com.beust.jcommander.Parameter; +import org.jetbrains.annotations.NotNull; + +class Arguments { + + @Parameter(names = {"--help", "-h"}, description = "Prints the usage.", help = true) + boolean help; + + @Parameter(names = {"--github-cli", "-g"}, description = "Path to the GitHub CLI binary.", required = true) + @SuppressWarnings("NotNullFieldNotInitialized") + @NotNull String gitHubCliPath; + + @Parameter(names = {"--path", "-p"}, description = "Path to input files.", required = true) + @SuppressWarnings("NotNullFieldNotInitialized") + @NotNull String path; +} diff --git a/github-release-note-updater/src/main/java/com/hivemq/release/Chart.java b/github-release-note-updater/src/main/java/com/hivemq/release/Chart.java new file mode 100644 index 00000000..9ffe13a8 --- /dev/null +++ b/github-release-note-updater/src/main/java/com/hivemq/release/Chart.java @@ -0,0 +1,23 @@ +package com.hivemq.release; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.zafarkhaja.semver.Version; +import org.jetbrains.annotations.NotNull; + +record Chart(String name, Version version, Version appVersion, String description) implements Comparable { + + @JsonCreator + Chart( + @JsonProperty(value = "name") final @NotNull String name, + @JsonProperty(value = "version") final @NotNull String version, + @JsonProperty(value = "app_version") final @NotNull String appVersion, + @JsonProperty(value = "description") final @NotNull String description) { + this(name.split("/")[1], Version.parse(version), Version.parse(appVersion), description); + } + + @Override + public int compareTo(final @NotNull Chart o) { + return version.compareTo(o.version); + } +} diff --git a/github-release-note-updater/src/main/java/com/hivemq/release/GitHubReleaseNotesUpdater.java b/github-release-note-updater/src/main/java/com/hivemq/release/GitHubReleaseNotesUpdater.java new file mode 100644 index 00000000..cd61a177 --- /dev/null +++ b/github-release-note-updater/src/main/java/com/hivemq/release/GitHubReleaseNotesUpdater.java @@ -0,0 +1,230 @@ +package com.hivemq.release; + +import com.beust.jcommander.JCommander; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.zafarkhaja.semver.Version; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Function; +import java.util.regex.Pattern; + +public class GitHubReleaseNotesUpdater { + + private static final @NotNull String PLATFORM_RELEASE_NOTE_TEMPLATE = """ + %s + + [Updated to HiveMQ Platform %s](%s) + """; + private static final @NotNull String OPERATOR_RELEASE_NOTE_TEMPLATE = """ + %s + + [Updated to HiveMQ Platform Operator %s](%s) + """; + + private static final @NotNull String PLATFORM_MAINTENANCE_RELEASE_URL = + "https://www.hivemq.com/changelog/hivemq-%s-%s-%s-released/"; + private static final @NotNull String PLATFORM_FEATURE_RELEASE_URL = + "https://www.hivemq.com/changelog/whats-new-in-hivemq-%s-%s/"; + private static final @NotNull String OPERATOR_RELEASE_URL = + "https://www.hivemq.com/changelog/hivemq-platform-operator-%s-%s-%s-release/"; + + private static final @NotNull Pattern PLATFORM_RELEASE_PATTERN = + Pattern.compile("^hivemq-platform-(\\d+\\.\\d+\\.\\d+)$"); + private static final @NotNull Pattern OPERATOR_RELEASE_PATTERN = + Pattern.compile("^hivemq-platform-operator-(\\d+\\.\\d+\\.\\d+)$"); + + public static void main(final @NotNull String @NotNull [] args) throws Exception { + final var arguments = new Arguments(); + final var jCommander = JCommander.newBuilder().addObject(arguments).build(); + jCommander.parse(args); + if (arguments.help) { + jCommander.usage(); + System.exit(0); + } + + final var chartsPath = Path.of(arguments.path, "charts.json"); + final var releasesPath = Path.of(arguments.path, "releases.json"); + if (Files.notExists(chartsPath)) { + System.err.printf("Charts path '%s' does not exist%n", chartsPath); + System.exit(1); + } + if (Files.notExists(releasesPath)) { + System.err.printf("Releases path '%s' does not exist%n", releasesPath); + System.exit(1); + } + + final var objectMapper = new ObjectMapper(); + final var charts = Arrays.stream(objectMapper.readValue(Files.readString(chartsPath), Chart[].class)).toList(); + final var releases = + Arrays.stream(objectMapper.readValue(Files.readString(releasesPath), Release[].class)).toList(); + + // sort the released Helm charts + final var platformOperatorCharts = + charts.stream().filter(chart -> chart.name().equals("hivemq-platform-operator")).sorted().toList(); + final var platformCharts = + charts.stream().filter(chart -> chart.name().equals("hivemq-platform")).sorted().toList(); + final var legacyOperatorCharts = + charts.stream().filter(chart -> chart.name().equals("hivemq-operator")).sorted().toList(); + final var swarmCharts = charts.stream().filter(chart -> chart.name().equals("hivemq-swarm")).sorted().toList(); + + // prepare the release notes + final var releaseNotes = new HashMap(); + setReleaseNotes(releaseNotes, + releases, + OPERATOR_RELEASE_PATTERN, + platformCharts, + PLATFORM_RELEASE_NOTE_TEMPLATE, + GitHubReleaseNotesUpdater::getPlatformReleaseUrl, + platformOperatorCharts, + OPERATOR_RELEASE_NOTE_TEMPLATE, + GitHubReleaseNotesUpdater::getOperatorReleaseUrl); + setReleaseNotes(releaseNotes, + releases, + OPERATOR_RELEASE_PATTERN, + legacyOperatorCharts, + PLATFORM_RELEASE_NOTE_TEMPLATE, + GitHubReleaseNotesUpdater::getPlatformReleaseUrl, + platformOperatorCharts, + OPERATOR_RELEASE_NOTE_TEMPLATE, + GitHubReleaseNotesUpdater::getOperatorReleaseUrl); + setReleaseNotes(releaseNotes, + releases, + OPERATOR_RELEASE_PATTERN, + swarmCharts, + PLATFORM_RELEASE_NOTE_TEMPLATE, + GitHubReleaseNotesUpdater::getPlatformReleaseUrl, + platformOperatorCharts, + OPERATOR_RELEASE_NOTE_TEMPLATE, + GitHubReleaseNotesUpdater::getOperatorReleaseUrl); + setReleaseNotes(releaseNotes, + releases, + PLATFORM_RELEASE_PATTERN, + platformOperatorCharts, + OPERATOR_RELEASE_NOTE_TEMPLATE, + GitHubReleaseNotesUpdater::getOperatorReleaseUrl, + platformCharts, + PLATFORM_RELEASE_NOTE_TEMPLATE, + GitHubReleaseNotesUpdater::getPlatformReleaseUrl); + + // update the GitHub release notes + try (var executorService = Executors.newSingleThreadExecutor()) { + var success = true; + for (final var release : releases) { + final var releaseTag = release.tagName(); + final var releaseNote = releaseNotes.get(releaseTag); + if (releaseNote == null) { + System.out.println("Skipping release " + releaseTag); + continue; + } + final var exitCode = execute(executorService, + arguments.gitHubCliPath, + "release", + "edit", + releaseTag, + "--repo", + "hivemq/helm-charts", + "--notes", + releaseNote); + if (exitCode != 0) { + success = false; + } + } + if (!success) { + System.err.println("Not all release notes were updated successfully!"); + System.exit(1); + } + } + } + + private static void setReleaseNotes( + final @NotNull Map releaseNotes, + final @NotNull List releases, + final @NotNull Pattern matchingReleasePattern, + final @NotNull List charts, + final @NotNull String releaseNoteTemplate, + final @NotNull Function releaseUrlFunction, + final @NotNull List otherCharts, + final @NotNull String otherReleaseNoteTemplate, + final @NotNull Function otherReleaseUrlFunction) { + for (int i = 0; i < charts.size(); i++) { + // we get the previous chart and check if the appVersion has changed in the current chart + // (if so we generate a release note for this chart, otherwise for the matching otherChart) + final var chart = charts.get(i); + final var previousChart = i == 0 ? null : charts.get(i - 1); + final var wasChartUpdated = previousChart == null || !previousChart.appVersion().equals(chart.appVersion()); + final var releaseTag = String.format("%s-%s", chart.name(), chart.version()); + final var otherReleaseOptional = getMatchingRelease(releases, releaseTag, matchingReleasePattern); + if (wasChartUpdated || otherReleaseOptional.isEmpty()) { + // this release was triggered by an update of this chart + final var releaseNote = String.format(releaseNoteTemplate, + chart.description(), + chart.appVersion(), + releaseUrlFunction.apply(chart.appVersion())); + releaseNotes.put(releaseTag, releaseNote); + } else { + // this release was triggered by an update of the other chart + final var otherRelease = otherReleaseOptional.get(); + final var otherChartVersion = + Version.parse(otherRelease.tagName().substring(otherRelease.tagName().lastIndexOf('-') + 1)); + final var otherChart = otherCharts.stream() + .filter(c -> c.version().equals(otherChartVersion)) + .findFirst() + .orElseThrow(); + final var releaseNote = String.format(otherReleaseNoteTemplate, + chart.description(), + otherChart.appVersion(), + otherReleaseUrlFunction.apply(otherChart.appVersion())); + releaseNotes.put(releaseTag, releaseNote); + } + } + } + + private static @NotNull Optional getMatchingRelease( + final @NotNull List releases, + final @NotNull String releaseTag, + final @NotNull Pattern matchingReleasePattern) { + return releases.stream() + .filter(release -> release.tagName().equals(releaseTag)) + .findFirst() + .flatMap(value -> releases.stream() + .filter(release -> matchingReleasePattern.matcher(release.tagName()).matches()) + .filter(release -> release.publishedAt().equals(value.publishedAt())) + .findFirst()); + } + + private static @NotNull String getPlatformReleaseUrl(final @NotNull Version version) { + if (version.patchVersion() != 0) { + return String.format(PLATFORM_MAINTENANCE_RELEASE_URL, + version.majorVersion(), + version.minorVersion(), + version.patchVersion()); + } + return String.format(PLATFORM_FEATURE_RELEASE_URL, version.majorVersion(), version.minorVersion()); + } + + private static @NotNull String getOperatorReleaseUrl(final @NotNull Version version) { + return String.format(OPERATOR_RELEASE_URL, + version.majorVersion(), + version.minorVersion(), + version.patchVersion()); + } + + private static int execute(final @NotNull ExecutorService executorService, final @NotNull String... command) + throws Exception { + final var process = new ProcessBuilder().command(command).start(); + final var inputStreamGobbler = new StreamGobbler(process.getInputStream(), System.out::println); + final var errorStreamGobbler = new StreamGobbler(process.getErrorStream(), System.err::println); + executorService.submit(inputStreamGobbler); + executorService.submit(errorStreamGobbler); + return process.waitFor(); + } +} diff --git a/github-release-note-updater/src/main/java/com/hivemq/release/Release.java b/github-release-note-updater/src/main/java/com/hivemq/release/Release.java new file mode 100644 index 00000000..323b1f1d --- /dev/null +++ b/github-release-note-updater/src/main/java/com/hivemq/release/Release.java @@ -0,0 +1,22 @@ +package com.hivemq.release; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +record Release(String name, String tagName, ZonedDateTime publishedAt) { + + private static final ZoneId ZONE_ID = ZoneId.systemDefault(); + + @JsonCreator + Release( + @JsonProperty(value = "name") final @NotNull String name, + @JsonProperty(value = "tagName") final @NotNull String tagName, + @JsonProperty(value = "publishedAt") final @NotNull String publishedAt) { + this(name, tagName, Instant.parse(publishedAt).atZone(ZONE_ID).toLocalDate().atStartOfDay(ZONE_ID)); + } +} diff --git a/github-release-note-updater/src/main/java/com/hivemq/release/StreamGobbler.java b/github-release-note-updater/src/main/java/com/hivemq/release/StreamGobbler.java new file mode 100644 index 00000000..0e881e5c --- /dev/null +++ b/github-release-note-updater/src/main/java/com/hivemq/release/StreamGobbler.java @@ -0,0 +1,27 @@ +package com.hivemq.release; + +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.function.Consumer; + +/** + * Consumes an {@link InputStream}. + */ +class StreamGobbler implements Runnable { + + private final @NotNull InputStream inputStream; + private final @NotNull Consumer consumer; + + StreamGobbler(final @NotNull InputStream inputStream, final @NotNull Consumer consumer) { + this.inputStream = inputStream; + this.consumer = consumer; + } + + @Override + public void run() { + new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumer); + } +} diff --git a/release/set-latest-release.sh b/release/set-latest-release.sh new file mode 100755 index 00000000..13d39792 --- /dev/null +++ b/release/set-latest-release.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -e + +# configuration +REPO="hivemq/helm-charts" + +# check if binaries are installed +IS_GH_INSTALLED=$(which gh >/dev/null 2>&1 || echo "GitHub CLI is not installed") +if [ -n "$IS_GH_INSTALLED" ]; then + echo "$IS_GH_INSTALLED" + exit 1 +fi + +# check if gh is not authenticated +if ! gh auth status &>/dev/null; then + echo "GitHub CLI is not logged in" + echo "Please run 'gh auth login' to authenticate" + exit 1 +fi + +# get a list of releases with names matching "hivemq-platform-" +echo "Searching the latest hivemq-platform release" +releases=$(gh release list --repo "$REPO" --limit 100 | grep -oP '^hivemq-platform-\K[0-9]+\.[0-9]+\.[0-9]+') +latest_version=$(echo "$releases" | sort -V | tail -n 1) +if [ -z "$latest_version" ]; then + echo "No hivemq-platform releases found" + exit 1 +fi + +# mark the found release as latest +latest_release="hivemq-platform-$latest_version" +gh release edit "$latest_release" --repo "$REPO" --latest +echo "Marked $latest_release as the latest release" diff --git a/release/update-github-release-notes.sh b/release/update-github-release-notes.sh new file mode 100755 index 00000000..2c44f0c1 --- /dev/null +++ b/release/update-github-release-notes.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -e + +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) +cd "${SCRIPT_DIR}" || exit 1 + +# configuration +REPO="hivemq/helm-charts" +RELEASE_COUNT=${1:-200} + +# check if binaries are installed +IS_HELM_INSTALLED=$(which helm >/dev/null 2>&1 || echo "Helm is not installed") +if [ -n "$IS_HELM_INSTALLED" ]; then + echo "$IS_HELM_INSTALLED" + exit 1 +fi +IS_GH_INSTALLED=$(which gh >/dev/null 2>&1 || echo "GitHub CLI is not installed") +if [ -n "$IS_GH_INSTALLED" ]; then + echo "$IS_GH_INSTALLED" + exit 1 +fi + +# check if gh is not authenticated +if ! gh auth status &>/dev/null; then + echo "GitHub CLI is not logged in" + echo "Please run 'gh auth login' to authenticate" + exit 1 +fi + +cd .. || exit 1 + +helm repo add hivemq https://hivemq.github.io/helm-charts +helm repo update +helm search repo hivemq --versions -o json > charts.json +gh release list --repo "$REPO" --limit "$RELEASE_COUNT" --json name,tagName,publishedAt > releases.json + +GH_PATH=$(which gh) +PWD=$(pwd) +./gradlew :github-release-note-updater:run --args " -g $GH_PATH -p $PWD" + +rm releases.json charts.json diff --git a/release/validate-branch-protection.sh b/release/validate-branch-protection.sh new file mode 100755 index 00000000..e0ad8ce4 --- /dev/null +++ b/release/validate-branch-protection.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -e + +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) +cd "${SCRIPT_DIR}" || exit 1 + +# configuration +OWNER="hivemq" +REPO="helm-charts" +BRANCH="${1:-develop}" +WORKFLOW_FILES=( + "../.github/workflows/hivemq-operator-integration-test.yml" + "../.github/workflows/hivemq-platform-operator-integration-test.yml" +) +MANUAL_CHECKS=("continuous-integration/jenkins/branch" "smoke-test-legacy" "smoke-test-platform" "verify" "verification/cla-signed") + +# check bash version +if ((BASH_VERSINFO < 4)); then + echo "Bash >= 4.x must be installed" + exit 1 +fi + +# check if binaries are installed +IS_GH_INSTALLED=$(which gh >/dev/null 2>&1 || echo "GitHub CLI is not installed") +if [ -n "$IS_GH_INSTALLED" ]; then + echo "$IS_GH_INSTALLED" + exit 1 +fi +IS_JQ_INSTALLED=$(which jq >/dev/null 2>&1 || echo "jq is not installed") +if [ -n "$IS_JQ_INSTALLED" ]; then + echo "$IS_JQ_INSTALLED" + exit 1 +fi +IS_YQ_INSTALLED=$(which yq >/dev/null 2>&1 || echo "yq is not installed") +if [ -n "$IS_YQ_INSTALLED" ]; then + echo "$IS_YQ_INSTALLED" + exit 1 +fi +IS_COMM_INSTALLED=$(which comm >/dev/null 2>&1 || echo "comm is not installed") +if [ -n "$IS_COMM_INSTALLED" ]; then + echo "$IS_COMM_INSTALLED" + exit 1 +fi + +# check if gh is not authenticated +if ! gh auth status &>/dev/null; then + echo "GitHub CLI is not logged in" + echo "Please run 'gh auth login' to authenticate" + exit 1 +fi + +# process each specified workflow file +EXPECTED_CHECKS=("${MANUAL_CHECKS[@]}") +for WORKFLOW_FILE in "${WORKFLOW_FILES[@]}"; do + echo "Analyzing $WORKFLOW_FILE..." + # parse each job's name and its specific test-plan matrix + declare -A JOB_TEST_PLANS=() + while IFS= read -r job_name; do + test_plans=$(yq eval ".jobs[\"$job_name\"].strategy.matrix.test-plan[]" "$WORKFLOW_FILE" 2>/dev/null) + JOB_TEST_PLANS["$job_name"]="$test_plans" + done < <(yq eval '.jobs | keys | .[]' "$WORKFLOW_FILE" 2>/dev/null) + + # generate expected check names based on each job and its test plans + for job_name in "${!JOB_TEST_PLANS[@]}"; do + while IFS= read -r test_plan; do + echo "Found test: $job_name ($test_plan)" + EXPECTED_CHECKS+=("$job_name ($test_plan)") + done <<< "${JOB_TEST_PLANS[$job_name]}" + done +done +echo + +# convert array to newline-separated string and sort it for comparison +EXPECTED_CHECKS_STRING=$(printf "%s\n" "${EXPECTED_CHECKS[@]}" | sort) + +# get required checks +REQUIRED_CHECKS=$(gh api -H "Accept: application/vnd.github.v3+json" "/repos/$OWNER/$REPO/branches/$BRANCH/protection" | jq -r '.required_status_checks.contexts | @csv' | tr ',' '\n' | tr -d '"' | sort) + +# compare expected checks with branch required checks +DIFF=$(comm -23 <(echo "$EXPECTED_CHECKS_STRING") <(echo "$REQUIRED_CHECKS")) +if [ -n "$DIFF" ]; then + echo "Missing checks in $BRANCH branch protection:" + echo "$DIFF" + exit 1 +fi +echo "No checks are missing in $BRANCH branch protection" diff --git a/settings.gradle.kts b/settings.gradle.kts index 6299959f..484f9b3d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,5 @@ rootProject.name = "helm-charts" +includeBuild("github-release-note-updater") includeBuild("tests-hivemq-operator") includeBuild("tests-hivemq-platform-operator")