├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── test-linux.yml │ └── publish-oci.yml ├── Makefile ├── README.md ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── Dockerfile ├── src ├── main │ └── java │ │ └── com │ │ └── rabbitmq │ │ └── actions │ │ ├── Utils.java │ │ └── DeleteReleaseAction.java └── test │ └── java │ └── com │ └── rabbitmq │ └── actions │ ├── DeleteReleaseActionTest.java │ └── ReleaseTest.java ├── pom.xml ├── mvnw.cmd ├── mvnw └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .sw? 3 | .*.sw? 4 | *.beam 5 | *.class 6 | *.dat 7 | *.dump 8 | *.iml 9 | *.ipr 10 | *.iws 11 | .DS_Store 12 | \#~ 13 | /.idea/ 14 | /deps/ 15 | /target/ 16 | /.classpath 17 | /.project 18 | /.settings 19 | 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | version: 2 4 | updates: 5 | - package-ecosystem: "maven" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | open-pull-requests-limit: 20 -------------------------------------------------------------------------------- /.github/workflows/test-linux.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Build (Linux) 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Set up JDK 21 22 | uses: actions/setup-java@v3 23 | with: 24 | java-version: '21' 25 | distribution: 'zulu' 26 | cache: maven 27 | - name: Build with Maven 28 | run: ./mvnw test 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := bash# we want bash behaviour in all shell invocations 2 | 3 | .DEFAULT_GOAL = package 4 | 5 | ### VARIABLES ### 6 | # 7 | 8 | ### TARGETS ### 9 | # 10 | 11 | .PHONY: package 12 | package: clean ## Build the binary distribution 13 | ./mvnw package -Dmaven.test.skip 14 | 15 | .PHONY: docker-image 16 | docker-image: ## Build Docker image 17 | @docker build --tag pivotalrabbitmq/delete-release-action:latest . 18 | 19 | .PHONY: push-docker-image 20 | push-docker-image: docker-image ## Push Docker image 21 | @docker push pivotalrabbitmq/delete-release-action:latest 22 | 23 | .PHONY: clean 24 | clean: ## Clean all build artefacts 25 | ./mvnw clean 26 | 27 | .PHONY: compile 28 | compile: ## Compile the source code 29 | ./mvnw compile 30 | 31 | .PHONY: test 32 | test: ## Run tests 33 | ./mvnw test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This is a GitHub Action to delete old releases. 4 | 5 | # Usage 6 | 7 | ```yaml 8 | - name: Delete old releases 9 | uses: docker://pivotalrabbitmq/delete-release-action:latest 10 | with: 11 | repository: rabbitmq/rabbitmq-java-tools-binaries-dev 12 | token: ${{ secrets.CI_GITHUB_TOKEN }} 13 | tag-filter: '^v-stream-perf-test-0.[0-9]+.0-SNAPSHOT-[0-9]{8}-[0-9]{6}$' 14 | keep-last-n: 2 15 | ``` 16 | 17 | The filtering can be used on the tag name (`tag-filter`) or on the release name (`name-filter`). 18 | 19 | # License and Copyright 20 | 21 | (c) 2022-2024 Broadcom. All Rights Reserved. 22 | The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. 23 | 24 | This package, the Concourse GitHub Release Delete Resource, is licensed 25 | under the Mozilla Public License 2.0 ("MPL"). 26 | 27 | See [LICENSE](./LICENSE). 28 | -------------------------------------------------------------------------------- /.github/workflows/publish-oci.yml: -------------------------------------------------------------------------------- 1 | name: Rebuild and publish OCI 2 | 3 | on: 4 | push: 5 | paths: ['src/**', '.github/workflows/publish-oci.yml'] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Set up QEMU 13 | uses: docker/setup-qemu-action@v3 14 | - name: Set up Docker Buildx 15 | uses: docker/setup-buildx-action@v3 16 | - name: Login to Docker Hub 17 | uses: docker/login-action@v3 18 | with: 19 | username: ${{ secrets.DOCKERHUB_USERNAME }} 20 | password: ${{ secrets.DOCKERHUB_TOKEN }} 21 | - name: Build and push 22 | uses: docker/build-push-action@v6 23 | with: 24 | file: Dockerfile 25 | platforms: linux/amd64 26 | push: true 27 | tags: rabbitmqdevenv/delete-release-action:latest 28 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 AS builder 2 | 3 | RUN set -eux; \ 4 | apt-get update; \ 5 | apt-get -y upgrade; \ 6 | apt-get install --yes --no-install-recommends \ 7 | ca-certificates \ 8 | wget \ 9 | jq 10 | 11 | ARG JAVA_VERSION="21" 12 | 13 | RUN ARCH="x86"; BUNDLE="jdk"; \ 14 | wget "https://api.azul.com/zulu/download/community/v1.0/bundles/latest/?java_version=$JAVA_VERSION&ext=tar.gz&os=linux&arch=$ARCH&hw_bitness=64&release_status=ga&bundle_type=$BUNDLE" -O jdk-info.json 15 | 16 | RUN wget --progress=bar:force:noscroll -O "jdk.tar.gz" $(cat jdk-info.json | jq --raw-output .url) 17 | RUN echo "$(cat jdk-info.json | jq --raw-output .sha256_hash) *jdk.tar.gz" | sha256sum --check --strict - 18 | 19 | ENV JAVA_HOME="/usr/lib/jdk-$JAVA_VERSION" 20 | 21 | RUN set -eux; \ 22 | mkdir $JAVA_HOME && \ 23 | tar --extract --file jdk.tar.gz --directory "$JAVA_HOME" --strip-components 1; \ 24 | $JAVA_HOME/bin/jlink --compress=zip-6 --output /jre --add-modules java.base,jdk.crypto.cryptoki,java.net.http; \ 25 | /jre/bin/java -version \ 26 | ; \ 27 | mkdir -p /app 28 | 29 | RUN mkdir -p /project/src /project/.mvn 30 | COPY src /project/src 31 | COPY .mvn /project/.mvn 32 | COPY pom.xml /project 33 | COPY mvnw /project 34 | 35 | WORKDIR /project 36 | RUN set -eux; \ 37 | ./mvnw package -Dmaven.test.skip --no-transfer-progress 38 | 39 | FROM ubuntu:24.04 40 | 41 | RUN set -eux; \ 42 | \ 43 | apt-get update; \ 44 | apt-get -y upgrade; \ 45 | apt-get install --yes --no-install-recommends \ 46 | ca-certificates \ 47 | ; \ 48 | rm -rf /var/lib/apt/lists/* 49 | 50 | ENV LANG=en_US.UTF-8 51 | ENV LANGUAGE=en_US:en 52 | 53 | ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk/jre 54 | RUN mkdir -p $JAVA_HOME 55 | COPY --from=builder /jre $JAVA_HOME/ 56 | RUN ln -svT $JAVA_HOME/bin/java /usr/local/bin/java 57 | 58 | RUN mkdir -p /app 59 | 60 | COPY --from=builder /project/target/delete-release-action.jar /app 61 | RUN set -eux; \ 62 | java -jar /app/delete-release-action.jar test 63 | 64 | RUN groupadd --gid 1042 github 65 | RUN useradd --uid 1042 --gid github --comment "github user" github 66 | 67 | USER github:github 68 | 69 | ENTRYPOINT ["java", "-jar", "/app/delete-release-action.jar"] 70 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/actions/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | package com.rabbitmq.actions; 7 | 8 | import static java.lang.String.format; 9 | 10 | import java.net.URI; 11 | import java.net.http.HttpClient; 12 | import java.net.http.HttpRequest; 13 | import java.net.http.HttpResponse; 14 | import java.time.Duration; 15 | import java.util.function.Consumer; 16 | 17 | abstract class Utils { 18 | 19 | static void testSequence() { 20 | Consumer display = Utils::logGreen; 21 | String message; 22 | int exitCode = 0; 23 | try { 24 | String testUri = "https://www.wikipedia.org/"; 25 | logYellow("Starting test sequence, trying to reach " + testUri); 26 | HttpRequest request = HttpRequest.newBuilder().uri(new URI(testUri)).GET().build(); 27 | try (HttpClient client = 28 | HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(60)).build()) { 29 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.discarding()); 30 | int statusClass = response.statusCode() - response.statusCode() % 100; 31 | message = "Response code is " + response.statusCode(); 32 | if (statusClass != 200) { 33 | display = Utils::logRed; 34 | exitCode = 1; 35 | } 36 | } 37 | } catch (Exception e) { 38 | message = "Error during test sequence: " + e.getMessage(); 39 | display = Utils::logRed; 40 | exitCode = 1; 41 | } 42 | display.accept(message); 43 | System.exit(exitCode); 44 | } 45 | 46 | static void logGreen(String message, Object... args) { 47 | log("\u001B[32m" + format(message, args) + "\u001B[0m"); 48 | } 49 | 50 | static void logYellow(String message, Object... args) { 51 | log("\u001B[33m" + format(message, args) + "\u001B[0m"); 52 | } 53 | 54 | static void logRed(String message, Object... args) { 55 | log("\u001B[31m" + format(message, args) + "\u001B[0m"); 56 | } 57 | 58 | static void log(String message, Object... args) { 59 | System.out.printf((message) + "%n", args); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/actions/DeleteReleaseActionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | package com.rabbitmq.actions; 7 | 8 | import static com.rabbitmq.actions.DeleteReleaseAction.*; 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | import com.rabbitmq.actions.DeleteReleaseAction.Release; 12 | import java.time.ZonedDateTime; 13 | import java.time.format.DateTimeFormatter; 14 | import java.util.Arrays; 15 | import java.util.Collections; 16 | import java.util.List; 17 | import org.junit.jupiter.api.Test; 18 | 19 | public class DeleteReleaseActionTest { 20 | 21 | static Release rTag(long id, String tag) { 22 | return new Release(id, tag, null); 23 | } 24 | 25 | static Release rName(long id, String name) { 26 | return new Release(id, null, name); 27 | } 28 | 29 | static Release rDate(long id, String date) { 30 | ZonedDateTime dateTime = null; 31 | if (date != null) { 32 | dateTime = ZonedDateTime.parse(date + "T08:38:25Z", DateTimeFormatter.ISO_ZONED_DATE_TIME); 33 | } 34 | return new Release(id, dateTime); 35 | } 36 | 37 | @Test 38 | void filterByTagTest() { 39 | List releases = 40 | Arrays.asList( 41 | rTag(1, "v3.9.0-alpha-stream.1"), 42 | rTag(2, "v3.9.0-alpha-stream.2"), 43 | rTag(10, "v3.8.14-alpha.2"), 44 | rTag(3, "v3.9.0-alpha-stream.3"), 45 | rTag(11, "v3.8.9-alpha.8"), 46 | rTag(12, "v3.9.0-alpha.470"), 47 | rTag(4, "v3.9.0-alpha-stream.4"), 48 | rTag(13, "v4.0.0-alpha.51")); 49 | 50 | List filtered = filterByTag(releases, "^v(3.9.0-alpha-stream.[0-9]+)$"); 51 | 52 | assertThat(filtered.stream().mapToLong(Release::id)) 53 | .hasSize(4) 54 | .containsExactlyInAnyOrder(1L, 2L, 3L, 4L); 55 | 56 | filtered = filterByTag(releases, "^v([0-9].[0-9].[0-9]+-alpha.[0-9]+)$"); 57 | 58 | assertThat(filtered.stream().mapToLong(Release::id)) 59 | .hasSize(4) 60 | .containsExactlyInAnyOrder(10L, 11L, 12L, 13L); 61 | 62 | filtered = filterByTag(releases, "^v(3.8.[0-9]+-alpha.[0-9]+)$"); 63 | 64 | assertThat(filtered.stream().mapToLong(Release::id)) 65 | .hasSize(2) 66 | .containsExactlyInAnyOrder(10L, 11L); 67 | } 68 | 69 | @Test 70 | void filterByNameTest() { 71 | List releases = 72 | new java.util.ArrayList<>( 73 | List.of( 74 | rName(1, "RabbitMQ 4.0.5-alpha.c0e9eb0a (from 1732907131)"), 75 | rName(2, "RabbitMQ 4.0.5-alpha.5b90d1aa (from 1732900728)"), 76 | rName(10, "RabbitMQ 4.1.0-alpha.cead668b (from 1732900443)"), 77 | rName(3, "RabbitMQ 4.1.0-alpha.84607b7d (from 1732880145)"), 78 | rName(11, "RabbitMQ 4.0.5-alpha.1edb0477 (from 1732832426)"), 79 | rName(12, "RabbitMQ 4.1.0-alpha.534e4f18 (from 1732830884)"), 80 | rName(4, "RabbitMQ 4.1.0-alpha.d6366a3c (from 1732822478)"), 81 | rName(20, "RabbitMQ 4.0.0-beta (from 1732822478)"), 82 | rName(21, "RabbitMQ 4.1.0-beta (from 1732822478)"), 83 | rName(13, "RabbitMQ 4.1.0-alpha.7b2f5fbb (from 1732822415)"))); 84 | 85 | Collections.shuffle(releases); 86 | 87 | List filtered = filterByName(releases, ".*4.0.[0-9]+-alpha.*"); 88 | 89 | assertThat(filtered.stream().mapToLong(Release::id)) 90 | .hasSize(3) 91 | .containsExactlyInAnyOrder(1L, 2L, 11L); 92 | 93 | filtered = filterByName(releases, ".*4.1.[0-9]+-alpha.*"); 94 | 95 | assertThat(filtered.stream().mapToLong(Release::id)) 96 | .hasSize(5) 97 | .containsExactlyInAnyOrder(10L, 3L, 12L, 4L, 13L); 98 | } 99 | 100 | @Test 101 | void filterForDeletionTest() { 102 | List releases = 103 | Arrays.asList( 104 | rDate(1L, "2021-01-01"), 105 | rDate(3L, "2021-01-03"), 106 | rDate(4L, "2021-01-04"), 107 | rDate(2L, "2021-01-02"), 108 | rDate(42L, null), 109 | rDate(8L, "2021-01-08"), 110 | rDate(6L, "2021-01-06"), 111 | rDate(9L, "2021-01-09"), 112 | rDate(7L, "2021-01-07"), 113 | rDate(5L, "2021-01-05")); 114 | 115 | assertThat(filterForDeletion(releases, 3).stream().mapToLong(r -> r.id())) 116 | .hasSize(7) 117 | .containsExactlyInAnyOrder(42L, 1L, 2L, 3L, 4L, 5L, 6L); 118 | 119 | assertThat(filterForDeletion(releases, releases.size() - 1).stream().mapToLong(r -> r.id())) 120 | .hasSize(1) 121 | .containsExactlyInAnyOrder(42L); 122 | 123 | assertThat(filterForDeletion(releases, releases.size() - 2).stream().mapToLong(r -> r.id())) 124 | .hasSize(2) 125 | .containsExactlyInAnyOrder(42L, 1L); 126 | 127 | assertThat(filterForDeletion(releases, 0)).hasSameSizeAs(releases).hasSameElementsAs(releases); 128 | 129 | assertThat(filterForDeletion(releases, releases.size() + 1)).isEmpty(); 130 | } 131 | 132 | private static List filterByTag(List releases, String regex) { 133 | return DeleteReleaseAction.filter(releases, tagPredicate(regex)); 134 | } 135 | 136 | private static List filterByName(List releases, String regex) { 137 | return DeleteReleaseAction.filter(releases, namePredicate(regex)); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.rabbitmq 8 | delete-release-action 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | MPL 2.0 14 | https://www.mozilla.org/en-US/MPL/2.0/ 15 | repo 16 | 17 | 18 | 19 | 20 | 21 | info@rabbitmq.com 22 | Team RabbitMQ 23 | Broadcom Inc. and/or its subsidiaries. 24 | https://rabbitmq.com 25 | 26 | 27 | 28 | 29 | UTF-8 30 | UTF-8 31 | 2.13.2 32 | 6.0.1 33 | 4.0.1 34 | 3.27.6 35 | 5.21.0 36 | 3.1.0 37 | 1.33.0 38 | 3.14.1 39 | 3.5.4 40 | 3.4.0 41 | 3.4.2 42 | 21 43 | 21 44 | 45 | 46 | 47 | 48 | 49 | com.google.code.gson 50 | gson 51 | ${gson.version} 52 | 53 | 54 | 55 | org.junit.jupiter 56 | junit-jupiter-engine 57 | ${junit.jupiter.version} 58 | test 59 | 60 | 61 | 62 | org.assertj 63 | assertj-core 64 | ${assertj.version} 65 | test 66 | 67 | 68 | 69 | org.mockito 70 | mockito-core 71 | ${mockito.version} 72 | test 73 | 74 | 75 | 76 | 77 | com.google.googlejavaformat 78 | google-java-format 79 | ${google-java-format.version} 80 | test 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | ${project.artifactId} 89 | 90 | 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-compiler-plugin 95 | ${maven.compiler.plugin.version} 96 | 97 | 21 98 | 21 99 | 100 | 101 | 102 | 103 | maven-surefire-plugin 104 | ${maven-surefire-plugin.version} 105 | 106 | 107 | 108 | maven-resources-plugin 109 | ${maven-resources-plugin.version} 110 | 111 | 112 | 113 | org.springframework.boot 114 | spring-boot-maven-plugin 115 | ${spring-boot-maven-plugin.version} 116 | 117 | 118 | 119 | repackage 120 | 121 | 122 | 123 | 124 | com.rabbitmq.actions.DeleteReleaseAction 125 | 126 | 127 | 128 | 129 | com.diffplug.spotless 130 | spotless-maven-plugin 131 | ${spotless.version} 132 | 133 | 134 | 135 | ${google-java-format.version} 136 | 137 | 138 | 139 | 140 | /* 141 | * This Source Code Form is subject to the terms of the Mozilla Public 142 | * License, v. 2.0. If a copy of the MPL was not distributed with this 143 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 144 | */ 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/actions/ReleaseTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | package com.rabbitmq.actions; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | import com.rabbitmq.actions.DeleteReleaseAction.Release; 11 | import java.time.ZonedDateTime; 12 | import java.time.format.DateTimeFormatter; 13 | import org.junit.jupiter.api.Test; 14 | 15 | public class ReleaseTest { 16 | 17 | static final String SAMPLE = 18 | "{\n" 19 | + " \"url\": \"https://api.github.com/repos/rabbitmq/rabbitmq-server-binaries-dev/releases/39045306\",\n" 20 | + " \"assets_url\": \"https://api.github.com/repos/rabbitmq/rabbitmq-server-binaries-dev/releases/39045306/assets\",\n" 21 | + " \"upload_url\": \"https://uploads.github.com/repos/rabbitmq/rabbitmq-server-binaries-dev/releases/39045306/assets{?name,label}\",\n" 22 | + " \"html_url\": \"https://github.com/rabbitmq/rabbitmq-server-binaries-dev/releases/tag/v3.9.0-alpha-stream.4\",\n" 23 | + " \"id\": 39045306,\n" 24 | + " \"author\": {\n" 25 | + " \"login\": \"rabbitmq-ci\",\n" 26 | + " \"id\": 71012429,\n" 27 | + " \"node_id\": \"MDQ6VXNlcjcxMDEyNDI5\",\n" 28 | + " \"avatar_url\": \"https://avatars.githubusercontent.com/u/71012429?v=4\",\n" 29 | + " \"gravatar_id\": \"\",\n" 30 | + " \"url\": \"https://api.github.com/users/rabbitmq-ci\",\n" 31 | + " \"html_url\": \"https://github.com/rabbitmq-ci\",\n" 32 | + " \"followers_url\": \"https://api.github.com/users/rabbitmq-ci/followers\",\n" 33 | + " \"following_url\": \"https://api.github.com/users/rabbitmq-ci/following{/other_user}\",\n" 34 | + " \"gists_url\": \"https://api.github.com/users/rabbitmq-ci/gists{/gist_id}\",\n" 35 | + " \"starred_url\": \"https://api.github.com/users/rabbitmq-ci/starred{/owner}{/repo}\",\n" 36 | + " \"subscriptions_url\": \"https://api.github.com/users/rabbitmq-ci/subscriptions\",\n" 37 | + " \"organizations_url\": \"https://api.github.com/users/rabbitmq-ci/orgs\",\n" 38 | + " \"repos_url\": \"https://api.github.com/users/rabbitmq-ci/repos\",\n" 39 | + " \"events_url\": \"https://api.github.com/users/rabbitmq-ci/events{/privacy}\",\n" 40 | + " \"received_events_url\": \"https://api.github.com/users/rabbitmq-ci/received_events\",\n" 41 | + " \"type\": \"User\",\n" 42 | + " \"site_admin\": false\n" 43 | + " },\n" 44 | + " \"node_id\": \"MDc6UmVsZWFzZTM5MDQ1MzA2\",\n" 45 | + " \"tag_name\": \"v3.9.0-alpha-stream.4\",\n" 46 | + " \"target_commitish\": \"master\",\n" 47 | + " \"name\": \"3.9.0-alpha-stream.4\",\n" 48 | + " \"draft\": false,\n" 49 | + " \"prerelease\": true,\n" 50 | + " \"created_at\": \"2021-03-01T08:38:25Z\",\n" 51 | + " \"published_at\": \"2021-03-01T10:37:58Z\",\n" 52 | + " \"assets\": [\n" 53 | + " {\n" 54 | + " \"url\": \"https://api.github.com/repos/rabbitmq/rabbitmq-server-binaries-dev/releases/assets/32772090\",\n" 55 | + " \"id\": 32772090,\n" 56 | + " \"node_id\": \"MDEyOlJlbGVhc2VBc3NldDMyNzcyMDkw\",\n" 57 | + " \"name\": \"rabbitmq-server-generic-unix-3.9.0-alpha-stream.4.tar.xz\",\n" 58 | + " \"label\": \"\",\n" 59 | + " \"uploader\": {\n" 60 | + " \"login\": \"rabbitmq-ci\",\n" 61 | + " \"id\": 71012429,\n" 62 | + " \"node_id\": \"MDQ6VXNlcjcxMDEyNDI5\",\n" 63 | + " \"avatar_url\": \"https://avatars.githubusercontent.com/u/71012429?v=4\",\n" 64 | + " \"gravatar_id\": \"\",\n" 65 | + " \"url\": \"https://api.github.com/users/rabbitmq-ci\",\n" 66 | + " \"html_url\": \"https://github.com/rabbitmq-ci\",\n" 67 | + " \"followers_url\": \"https://api.github.com/users/rabbitmq-ci/followers\",\n" 68 | + " \"following_url\": \"https://api.github.com/users/rabbitmq-ci/following{/other_user}\",\n" 69 | + " \"gists_url\": \"https://api.github.com/users/rabbitmq-ci/gists{/gist_id}\",\n" 70 | + " \"starred_url\": \"https://api.github.com/users/rabbitmq-ci/starred{/owner}{/repo}\",\n" 71 | + " \"subscriptions_url\": \"https://api.github.com/users/rabbitmq-ci/subscriptions\",\n" 72 | + " \"organizations_url\": \"https://api.github.com/users/rabbitmq-ci/orgs\",\n" 73 | + " \"repos_url\": \"https://api.github.com/users/rabbitmq-ci/repos\",\n" 74 | + " \"events_url\": \"https://api.github.com/users/rabbitmq-ci/events{/privacy}\",\n" 75 | + " \"received_events_url\": \"https://api.github.com/users/rabbitmq-ci/received_events\",\n" 76 | + " \"type\": \"User\",\n" 77 | + " \"site_admin\": false\n" 78 | + " },\n" 79 | + " \"content_type\": \"application/octet-stream\",\n" 80 | + " \"state\": \"uploaded\",\n" 81 | + " \"size\": 21,\n" 82 | + " \"download_count\": 2,\n" 83 | + " \"created_at\": \"2021-03-01T10:37:58Z\",\n" 84 | + " \"updated_at\": \"2021-03-01T10:37:58Z\",\n" 85 | + " \"browser_download_url\": \"https://github.com/rabbitmq/rabbitmq-server-binaries-dev/releases/download/v3.9.0-alpha-stream.4/rabbitmq-server-generic-unix-3.9.0-alpha-stream.4.tar.xz\"\n" 86 | + " }\n" 87 | + " ],\n" 88 | + " \"tarball_url\": \"https://api.github.com/repos/rabbitmq/rabbitmq-server-binaries-dev/tarball/v3.9.0-alpha-stream.4\",\n" 89 | + " \"zipball_url\": \"https://api.github.com/repos/rabbitmq/rabbitmq-server-binaries-dev/zipball/v3.9.0-alpha-stream.4\",\n" 90 | + " \"body\": \"\"\n" 91 | + " }"; 92 | 93 | @Test 94 | void deserialize() { 95 | Release release = DeleteReleaseAction.GSON.fromJson(SAMPLE, Release.class); 96 | assertThat(release.url()) 97 | .isEqualTo( 98 | "https://api.github.com/repos/rabbitmq/rabbitmq-server-binaries-dev/releases/39045306"); 99 | assertThat(release.id()).isEqualTo(39045306); 100 | assertThat(release.tag()).isEqualTo("v3.9.0-alpha-stream.4"); 101 | assertThat(release.publication()) 102 | .isEqualTo( 103 | ZonedDateTime.parse("2021-03-01T10:37:58Z", DateTimeFormatter.ISO_ZONED_DATE_TIME)); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/actions/DeleteReleaseAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | package com.rabbitmq.actions; 7 | 8 | import static com.rabbitmq.actions.Utils.*; 9 | import static java.util.stream.Collectors.joining; 10 | import static java.util.stream.Collectors.toList; 11 | 12 | import com.google.gson.Gson; 13 | import com.google.gson.GsonBuilder; 14 | import com.google.gson.JsonDeserializationContext; 15 | import com.google.gson.JsonDeserializer; 16 | import com.google.gson.JsonElement; 17 | import com.google.gson.JsonParseException; 18 | import com.google.gson.reflect.TypeToken; 19 | import java.lang.reflect.Type; 20 | import java.net.URI; 21 | import java.net.http.HttpClient; 22 | import java.net.http.HttpRequest; 23 | import java.net.http.HttpRequest.Builder; 24 | import java.net.http.HttpResponse; 25 | import java.net.http.HttpResponse.BodyHandlers; 26 | import java.time.Duration; 27 | import java.time.ZonedDateTime; 28 | import java.time.format.DateTimeFormatter; 29 | import java.util.ArrayList; 30 | import java.util.Collections; 31 | import java.util.Comparator; 32 | import java.util.LinkedHashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Map.Entry; 36 | import java.util.Objects; 37 | import java.util.Optional; 38 | import java.util.function.Function; 39 | import java.util.function.Predicate; 40 | import java.util.regex.Pattern; 41 | 42 | public class DeleteReleaseAction { 43 | 44 | private static final String GITHUB_API_URL = 45 | System.getenv("GITHUB_API_URL") == null 46 | ? "https://api.github.com" 47 | : System.getenv("GITHUB_API_URL"); 48 | static final Gson GSON = 49 | new GsonBuilder() 50 | .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeDeserializer()) 51 | .create(); 52 | 53 | public static void main(String[] args) { 54 | if (args.length == 1 && "test".equals(args[0])) { 55 | testSequence(); 56 | } 57 | Map envArguments = new LinkedHashMap<>(); 58 | envArguments.put("INPUT_REPOSITORY", "repository"); 59 | envArguments.put("INPUT_TOKEN", "token"); 60 | envArguments.put("INPUT_KEEP-LAST-N", "keep-last-n"); 61 | for (Entry entry : envArguments.entrySet()) { 62 | try { 63 | checkParameter(entry.getKey(), entry.getValue()); 64 | } catch (IllegalArgumentException e) { 65 | logRed(e.getMessage()); 66 | System.exit(1); 67 | } 68 | } 69 | 70 | if (System.getenv("INPUT_TAG-FILTER") == null && System.getenv("INPUT_NAME-FILTER") == null) { 71 | logRed("Parameter tag-filter or name-filter must be set"); 72 | System.exit(1); 73 | } 74 | 75 | String orgRepository = System.getenv("INPUT_REPOSITORY"); 76 | String token = System.getenv("INPUT_TOKEN"); 77 | String tagFilter = System.getenv("INPUT_TAG-FILTER"); 78 | String nameFilter = System.getenv("INPUT_NAME-FILTER"); 79 | int keepLastN = Integer.parseInt(System.getenv("INPUT_KEEP-LAST-N")); 80 | 81 | Input input = 82 | new Input( 83 | new Params(tagFilter, nameFilter, keepLastN), 84 | new Source(orgRepository.split("/")[0], orgRepository.split("/")[1], token)); 85 | 86 | ReleaseAccess access = new GitubRestApiReleaseAccess(input); 87 | 88 | List releases = access.list(); 89 | 90 | if (releases.isEmpty()) { 91 | logGreen("No releases in the repository."); 92 | } else { 93 | List filteredReleases = filter(releases, tagFilter, nameFilter); 94 | if (!filteredReleases.isEmpty()) { 95 | sortByPublication(filteredReleases); 96 | } 97 | List toDeleteReleases = 98 | filterForDeletion(filteredReleases, input.params().keepLastN()); 99 | 100 | if (tagFilter == null) { 101 | logGreen("No tag filter."); 102 | } else { 103 | logGreen("Tag filter: %s.", tagFilter); 104 | } 105 | if (nameFilter == null) { 106 | logGreen("No name filter."); 107 | } else { 108 | logGreen("Name filter: %s.", nameFilter); 109 | } 110 | 111 | Function releaseSummary = r -> r.tag_name + "/" + r.name; 112 | logGreen( 113 | "Repository release(s): %d (%s).", 114 | releases.size(), releases.stream().map(releaseSummary).collect(joining(", "))); 115 | 116 | if (filteredReleases.isEmpty()) { 117 | logGreen("No selected releases."); 118 | } else { 119 | logGreen( 120 | "Selected release(s): %d (%s)", 121 | filteredReleases.size(), 122 | filteredReleases.stream().map(releaseSummary).collect(joining(", "))); 123 | } 124 | 125 | if (toDeleteReleases.isEmpty()) { 126 | logGreen("No releases to delete."); 127 | } else { 128 | logGreen( 129 | "Release(s) to delete: %d (%s)", 130 | toDeleteReleases.size(), 131 | toDeleteReleases.stream().map(releaseSummary).collect(joining(", "))); 132 | } 133 | 134 | filteredReleases.forEach( 135 | r -> { 136 | if (toDeleteReleases.contains(r)) { 137 | logYellow("Removing release '%s'", releaseSummary.apply(r)); 138 | try { 139 | access.delete(r); 140 | access.deleteTag(r); 141 | access.waitForDeletion(r); 142 | } catch (Exception e) { 143 | logRed( 144 | "Error while deleting release '%s': %s", 145 | releaseSummary.apply(r), e.getMessage()); 146 | } 147 | } else { 148 | log(" Keeping release '%s'", releaseSummary.apply(r)); 149 | } 150 | }); 151 | } 152 | } 153 | 154 | private static void checkParameter(String env, String arg) { 155 | if (System.getenv(env) == null) { 156 | throw new IllegalArgumentException("Parameter " + arg + " must be set"); 157 | } 158 | } 159 | 160 | static Predicate releaseRegexPredicate( 161 | Function accessor, String regex) { 162 | Pattern pattern = Pattern.compile(regex); 163 | return r -> pattern.matcher(accessor.apply(r)).matches(); 164 | } 165 | 166 | static Predicate tagPredicate(String tagRegex) { 167 | return releaseRegexPredicate(Release::tag, tagRegex); 168 | } 169 | 170 | static Predicate namePredicate(String nameRegex) { 171 | return releaseRegexPredicate(Release::name, nameRegex); 172 | } 173 | 174 | static List filter(List releases, String tagRegex, String nameRegex) { 175 | Predicate predicate = r -> true; 176 | if (tagRegex != null) { 177 | predicate = predicate.and(tagPredicate(tagRegex)); 178 | } 179 | if (nameRegex != null) { 180 | predicate = predicate.and(namePredicate(nameRegex)); 181 | } 182 | return filter(releases, predicate); 183 | } 184 | 185 | static List filter(List releases, Predicate predicate) { 186 | return releases.stream().filter(predicate).collect(toList()); 187 | } 188 | 189 | static List filterForDeletion(List releases, int keepLastN) { 190 | if (releases.isEmpty()) { 191 | return Collections.emptyList(); 192 | } else if (keepLastN <= 0) { 193 | // do not want to keep any, return all 194 | return releases; 195 | } else if (keepLastN >= releases.size()) { 196 | // we want to keep more than we have, so nothing to delete 197 | return Collections.emptyList(); 198 | } else { 199 | releases = new ArrayList<>(releases); 200 | sortByPublication(releases); 201 | return releases.subList(0, releases.size() - keepLastN); 202 | } 203 | } 204 | 205 | interface ReleaseAccess { 206 | 207 | List list(); 208 | 209 | void delete(Release release); 210 | 211 | void deleteTag(Release release); 212 | 213 | void waitForDeletion(Release release); 214 | } 215 | 216 | static class GitubRestApiReleaseAccess implements ReleaseAccess { 217 | 218 | private static final Duration DELETION_TIMEOUT = Duration.ofSeconds(5); 219 | 220 | private final HttpClient client = 221 | HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(60)).build(); 222 | 223 | private final Input input; 224 | 225 | GitubRestApiReleaseAccess(Input input) { 226 | this.input = input; 227 | } 228 | 229 | static String nextLink(String linkHeader) { 230 | String nextLink = null; 231 | for (String link : linkHeader.split(",")) { 232 | // e.g. 233 | // ; rel="next" 234 | String[] urlRel = link.split(";"); 235 | if ("rel=\"next\"".equals(urlRel[1].trim())) { 236 | String url = urlRel[0].trim(); 237 | // removing the < and > 238 | nextLink = url.substring(1, url.length() - 1); 239 | } 240 | } 241 | return nextLink; 242 | } 243 | 244 | @Override 245 | public List list() { 246 | HttpRequest request = requestBuilder("/releases").GET().build(); 247 | try { 248 | Type type = TypeToken.getParameterized(List.class, Release.class).getType(); 249 | List releases = new ArrayList<>(); 250 | boolean hasMore = true; 251 | while (hasMore) { 252 | HttpResponse response = 253 | client.send(request, HttpResponse.BodyHandlers.ofString()); 254 | releases.addAll(GSON.fromJson(response.body(), type)); 255 | Optional link = response.headers().firstValue("link"); 256 | String nextLink; 257 | if (link.isPresent() && (nextLink = nextLink(link.get())) != null) { 258 | request = requestBuilder().uri(URI.create(nextLink)).GET().build(); 259 | } else { 260 | hasMore = false; 261 | } 262 | } 263 | return releases; 264 | } catch (Exception e) { 265 | throw new RuntimeException(e); 266 | } 267 | } 268 | 269 | @Override 270 | public void delete(Release release) { 271 | HttpRequest request = requestBuilder().DELETE().uri(URI.create(release.url())).build(); 272 | try { 273 | HttpResponse response = client.send(request, BodyHandlers.discarding()); 274 | int statusCode = response.statusCode(); 275 | if (statusClass(statusCode) != 200) { 276 | logYellow("Unexpected response code (release deletion):" + response.statusCode()); 277 | } 278 | } catch (Exception e) { 279 | throw new RuntimeException(e); 280 | } 281 | } 282 | 283 | @Override 284 | public void deleteTag(Release release) { 285 | HttpRequest request = requestBuilder().uri(tagUri(release)).DELETE().build(); 286 | try { 287 | HttpResponse response = client.send(request, BodyHandlers.discarding()); 288 | int statusCode = response.statusCode(); 289 | if (statusClass(statusCode) != 200) { 290 | logYellow("Unexpected response code (tag deletion):" + response.statusCode()); 291 | } 292 | } catch (Exception e) { 293 | throw new RuntimeException(e); 294 | } 295 | } 296 | 297 | private URI tagUri(Release release) { 298 | // https://api.github.com/repos/rabbitmq/rabbitmq-server-binaries-dev/git/refs/tags/v3.9.0-alpha-test.1 299 | String path = "/git/refs/tags/" + release.tag(); 300 | return URI.create( 301 | GITHUB_API_URL 302 | + "/repos/" 303 | + input.source().owner() 304 | + "/" 305 | + input.source().repository() 306 | + path); 307 | } 308 | 309 | @Override 310 | public void waitForDeletion(Release release) { 311 | if (!getUntilNotFound(URI.create(release.url()))) { 312 | logYellow( 313 | " Release has not been deleted after " 314 | + DELETION_TIMEOUT.getSeconds() 315 | + " second(s)."); 316 | } 317 | if (!getUntilNotFound(tagUri(release))) { 318 | logYellow( 319 | " Tag has not been deleted after " + DELETION_TIMEOUT.getSeconds() + " second(s)."); 320 | } 321 | } 322 | 323 | private boolean getUntilNotFound(URI uri) { 324 | Duration increment = Duration.ofSeconds(1); 325 | boolean keepGoing = true; 326 | Duration elapsed = Duration.ZERO; 327 | while (keepGoing && elapsed.compareTo(DELETION_TIMEOUT) < 0) { 328 | HttpRequest request = requestBuilder().GET().uri(uri).build(); 329 | try { 330 | HttpResponse response = client.send(request, BodyHandlers.discarding()); 331 | if (response.statusCode() == 404) { 332 | keepGoing = false; 333 | } else { 334 | Thread.sleep(increment.toMillis()); 335 | elapsed = elapsed.plus(increment); 336 | } 337 | } catch (Exception e) { 338 | throw new RuntimeException(e); 339 | } 340 | } 341 | 342 | return !keepGoing; 343 | } 344 | 345 | private Builder requestBuilder() { 346 | return auth(HttpRequest.newBuilder()); 347 | } 348 | 349 | private Builder requestBuilder(String path) { 350 | return auth( 351 | HttpRequest.newBuilder() 352 | .uri( 353 | URI.create( 354 | GITHUB_API_URL 355 | + "/repos/" 356 | + input.source().owner() 357 | + "/" 358 | + input.source().repository() 359 | + path))); 360 | } 361 | 362 | private Builder auth(Builder builder) { 363 | return builder.setHeader("Authorization", "token " + input.source().token()); 364 | } 365 | 366 | private static int statusClass(int statusCode) { 367 | return statusCode - statusCode % 100; 368 | } 369 | } 370 | 371 | static class ZonedDateTimeDeserializer implements JsonDeserializer { 372 | 373 | @Override 374 | public ZonedDateTime deserialize( 375 | JsonElement json, Type typeOfT, JsonDeserializationContext context) 376 | throws JsonParseException { 377 | return ZonedDateTime.parse(json.getAsString(), DateTimeFormatter.ISO_ZONED_DATE_TIME); 378 | } 379 | } 380 | 381 | static class Release { 382 | 383 | private long id; 384 | private String url; 385 | private ZonedDateTime published_at; 386 | private String tag_name; 387 | private String name; 388 | 389 | Release() {} 390 | 391 | Release(long id, String tag, String name) { 392 | this.id = id; 393 | this.tag_name = tag; 394 | this.name = name; 395 | } 396 | 397 | Release(long id, ZonedDateTime published_at) { 398 | this.id = id; 399 | this.published_at = published_at; 400 | } 401 | 402 | long id() { 403 | return this.id; 404 | } 405 | 406 | String url() { 407 | return this.url; 408 | } 409 | 410 | String tag() { 411 | return this.tag_name; 412 | } 413 | 414 | String name() { 415 | return this.name; 416 | } 417 | 418 | ZonedDateTime publication() { 419 | return this.published_at; 420 | } 421 | 422 | @Override 423 | public String toString() { 424 | return "Release{" 425 | + "id=" 426 | + id 427 | + ", url='" 428 | + url 429 | + '\'' 430 | + ", published_at=" 431 | + published_at 432 | + ", tag_name='" 433 | + tag_name 434 | + '\'' 435 | + '}'; 436 | } 437 | 438 | @Override 439 | public boolean equals(Object o) { 440 | if (this == o) { 441 | return true; 442 | } 443 | if (o == null || getClass() != o.getClass()) { 444 | return false; 445 | } 446 | Release release = (Release) o; 447 | return id == release.id; 448 | } 449 | 450 | @Override 451 | public int hashCode() { 452 | return Objects.hash(id); 453 | } 454 | } 455 | 456 | static class Input { 457 | 458 | private final Params params; 459 | private final Source source; 460 | 461 | Input(Params params, Source source) { 462 | this.params = params; 463 | this.source = source; 464 | } 465 | 466 | Params params() { 467 | return params; 468 | } 469 | 470 | Source source() { 471 | return source; 472 | } 473 | 474 | @Override 475 | public String toString() { 476 | return "Input{" + "params=" + params + ", source=" + source + '}'; 477 | } 478 | } 479 | 480 | static class Params { 481 | 482 | private final String tag_filter; 483 | private final String name_filter; 484 | private final int keep_last_n; 485 | 486 | Params(String tag_filter, String name_filter, int keep_last_n) { 487 | this.tag_filter = tag_filter; 488 | this.name_filter = name_filter; 489 | this.keep_last_n = keep_last_n; 490 | } 491 | 492 | String tagFilter() { 493 | return tag_filter; 494 | } 495 | 496 | String nameFilter() { 497 | return name_filter; 498 | } 499 | 500 | int keepLastN() { 501 | return keep_last_n; 502 | } 503 | 504 | @Override 505 | public String toString() { 506 | return "Params{" 507 | + "tag_filter='" 508 | + tag_filter 509 | + '\'' 510 | + ", name_filter='" 511 | + name_filter 512 | + '\'' 513 | + ", keep_last_n=" 514 | + keep_last_n 515 | + '}'; 516 | } 517 | } 518 | 519 | static class Source { 520 | 521 | private final String owner; 522 | private final String repository; 523 | private final String token; 524 | 525 | Source(String owner, String repository, String token) { 526 | this.owner = owner; 527 | this.repository = repository; 528 | this.token = token; 529 | } 530 | 531 | String owner() { 532 | return owner; 533 | } 534 | 535 | String repository() { 536 | return repository; 537 | } 538 | 539 | String token() { 540 | return token; 541 | } 542 | 543 | @Override 544 | public String toString() { 545 | return "Source{" + "owner='" + owner + '\'' + ", repository='" + repository + '\'' + '}'; 546 | } 547 | } 548 | 549 | static void sortByPublication(List releases) { 550 | releases.sort( 551 | Comparator.comparing( 552 | Release::publication, Comparator.nullsFirst(Comparator.naturalOrder()))); 553 | } 554 | } 555 | --------------------------------------------------------------------------------