From 2c93a92afa14c08b839cb3e910a08cd8bc24ebd9 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg Date: Wed, 4 May 2022 14:03:32 -0500 Subject: [PATCH] Add dispatchGitHubWorkflow gradle task --- build.gradle | 7 ++ .../release/DispatchGitHubWorkflowTask.java | 79 +++++++++++++++ .../github/release/GitHubActionsApi.java | 98 +++++++++++++++++++ .../github/release/GitHubReleasePlugin.java | 37 ++++--- .../github/release/WorkflowDispatch.java | 51 ++++++++++ .../github/release/GitHubActionsApiTests.java | 89 +++++++++++++++++ .../github/release/GitHubReleaseApiTests.java | 26 ++--- 7 files changed, 360 insertions(+), 27 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/release/DispatchGitHubWorkflowTask.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubActionsApi.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/release/WorkflowDispatch.java create mode 100644 buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubActionsApiTests.java diff --git a/build.gradle b/build.gradle index 19ae8d4d61..7df5874cd2 100644 --- a/build.gradle +++ b/build.gradle @@ -69,6 +69,13 @@ tasks.named("createGitHubRelease") { } } +tasks.named("dispatchGitHubWorkflow") { + repository { + owner = "spring-projects" + name = "spring-security" + } +} + tasks.named("updateDependencies") { // we aren't Gradle 7 compatible yet checkForGradleUpdate = false diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/release/DispatchGitHubWorkflowTask.java b/buildSrc/src/main/java/org/springframework/gradle/github/release/DispatchGitHubWorkflowTask.java new file mode 100644 index 0000000000..3da737ec18 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/release/DispatchGitHubWorkflowTask.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.gradle.github.release; + +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.TaskAction; + +import org.springframework.gradle.github.RepositoryRef; + +/** + * @author Steve Riesenberg + */ +public class DispatchGitHubWorkflowTask extends DefaultTask { + @Input + private RepositoryRef repository = new RepositoryRef(); + + @Input + private String gitHubAccessToken; + + @Input + private String branch; + + @Input + private String workflowId; + + @TaskAction + public void dispatchGitHubWorkflow() { + GitHubActionsApi gitHubActionsApi = new GitHubActionsApi(this.gitHubAccessToken); + WorkflowDispatch workflowDispatch = new WorkflowDispatch(this.branch, null); + gitHubActionsApi.dispatchWorkflow(this.repository, this.workflowId, workflowDispatch); + } + + public RepositoryRef getRepository() { + return repository; + } + + public void setRepository(RepositoryRef repository) { + this.repository = repository; + } + + public String getGitHubAccessToken() { + return gitHubAccessToken; + } + + public void setGitHubAccessToken(String gitHubAccessToken) { + this.gitHubAccessToken = gitHubAccessToken; + } + + public String getBranch() { + return branch; + } + + public void setBranch(String branch) { + this.branch = branch; + } + + public String getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(String workflowId) { + this.workflowId = workflowId; + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubActionsApi.java b/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubActionsApi.java new file mode 100644 index 0000000000..3fb2034c1d --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubActionsApi.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.gradle.github.release; + +import java.io.IOException; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import org.springframework.gradle.github.RepositoryRef; + +/** + * Manage GitHub Actions. + * + * @author Steve Riesenberg + */ +public class GitHubActionsApi { + private String baseUrl = "https://api.github.com"; + + private final OkHttpClient client; + + private final Gson gson = new GsonBuilder().create(); + + public GitHubActionsApi() { + this.client = new OkHttpClient.Builder().build(); + } + + public GitHubActionsApi(String gitHubToken) { + this.client = new OkHttpClient.Builder() + .addInterceptor(new AuthorizationInterceptor(gitHubToken)) + .build(); + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + /** + * Create a workflow dispatch event. + * + * @param repository The repository owner/name + * @param workflowId The ID of the workflow or the name of the workflow file name + * @param workflowDispatch The workflow dispatch containing a ref (branch) and optional inputs + */ + public void dispatchWorkflow(RepositoryRef repository, String workflowId, WorkflowDispatch workflowDispatch) { + String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/actions/workflows/" + workflowId + "/dispatches"; + String json = this.gson.toJson(workflowDispatch); + RequestBody body = RequestBody.create(MediaType.parse("application/json"), json); + Request request = new Request.Builder().url(url).post(body).build(); + try { + Response response = this.client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new RuntimeException(String.format("Could not create workflow dispatch %s for repository %s/%s. Got response %s", + workflowId, repository.getOwner(), repository.getName(), response)); + } + } catch (IOException ex) { + throw new RuntimeException(String.format("Could not create workflow dispatch %s for repository %s/%s", + workflowId, repository.getOwner(), repository.getName()), ex); + } + } + + private static class AuthorizationInterceptor implements Interceptor { + private final String token; + + public AuthorizationInterceptor(String token) { + this.token = token; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request().newBuilder() + .addHeader("Authorization", "Bearer " + this.token) + .build(); + + return chain.proceed(request); + } + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleasePlugin.java b/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleasePlugin.java index ae2c44a769..ddd5ccf23e 100644 --- a/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleasePlugin.java +++ b/buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleasePlugin.java @@ -17,7 +17,6 @@ package org.springframework.gradle.github.release; import groovy.lang.MissingPropertyException; -import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; @@ -27,23 +26,29 @@ import org.gradle.api.Project; public class GitHubReleasePlugin implements Plugin { @Override public void apply(Project project) { - project.getTasks().register("createGitHubRelease", CreateGitHubReleaseTask.class, new Action() { - @Override - public void execute(CreateGitHubReleaseTask createGitHubRelease) { - createGitHubRelease.setGroup("Release"); - createGitHubRelease.setDescription("Create a github release"); - createGitHubRelease.dependsOn("generateChangelog"); + project.getTasks().register("createGitHubRelease", CreateGitHubReleaseTask.class, (createGitHubRelease) -> { + createGitHubRelease.setGroup("Release"); + createGitHubRelease.setDescription("Create a github release"); + createGitHubRelease.dependsOn("generateChangelog"); - createGitHubRelease.setCreateRelease("true".equals(project.findProperty("createRelease"))); - createGitHubRelease.setVersion((String) project.findProperty("nextVersion")); - if (project.hasProperty("branch")) { - createGitHubRelease.setBranch((String) project.findProperty("branch")); - } - createGitHubRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); - if (createGitHubRelease.isCreateRelease() && createGitHubRelease.getGitHubAccessToken() == null) { - throw new MissingPropertyException("Please provide an access token with -PgitHubAccessToken=..."); - } + createGitHubRelease.setCreateRelease("true".equals(project.findProperty("createRelease"))); + createGitHubRelease.setVersion((String) project.findProperty("nextVersion")); + if (project.hasProperty("branch")) { + createGitHubRelease.setBranch((String) project.findProperty("branch")); } + createGitHubRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); + if (createGitHubRelease.isCreateRelease() && createGitHubRelease.getGitHubAccessToken() == null) { + throw new MissingPropertyException("Please provide an access token with -PgitHubAccessToken=..."); + } + }); + + project.getTasks().register("dispatchGitHubWorkflow", DispatchGitHubWorkflowTask.class, (dispatchGitHubWorkflow) -> { + dispatchGitHubWorkflow.setGroup("Release"); + dispatchGitHubWorkflow.setDescription("Create a workflow_dispatch event on a given branch"); + + dispatchGitHubWorkflow.setBranch((String) project.findProperty("branch")); + dispatchGitHubWorkflow.setBranch((String) project.findProperty("workflowId")); + dispatchGitHubWorkflow.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); }); } } diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/release/WorkflowDispatch.java b/buildSrc/src/main/java/org/springframework/gradle/github/release/WorkflowDispatch.java new file mode 100644 index 0000000000..531531bebf --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/release/WorkflowDispatch.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.gradle.github.release; + +import java.util.Map; + +/** + * @author Steve Riesenberg + */ +public class WorkflowDispatch { + private String ref; + private Map inputs; + + public WorkflowDispatch() { + } + + public WorkflowDispatch(String ref, Map inputs) { + this.ref = ref; + this.inputs = inputs; + } + + public String getRef() { + return ref; + } + + public void setRef(String ref) { + this.ref = ref; + } + + public Map getInputs() { + return inputs; + } + + public void setInputs(Map inputs) { + this.inputs = inputs; + } +} diff --git a/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubActionsApiTests.java b/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubActionsApiTests.java new file mode 100644 index 0000000000..51372480c0 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubActionsApiTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.gradle.github.release; + +import java.nio.charset.Charset; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.gradle.github.RepositoryRef; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * @author Steve Riesenberg + */ +public class GitHubActionsApiTests { + private GitHubActionsApi gitHubActionsApi; + + private MockWebServer server; + + private String baseUrl; + + private RepositoryRef repository; + + @BeforeEach + public void setup() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + this.baseUrl = this.server.url("/api").toString(); + this.gitHubActionsApi = new GitHubActionsApi("mock-oauth-token"); + this.gitHubActionsApi.setBaseUrl(this.baseUrl); + this.repository = new RepositoryRef("spring-projects", "spring-security"); + } + + @AfterEach + public void cleanup() throws Exception { + this.server.shutdown(); + } + + @Test + public void dispatchWorkflowWhenValidParametersThenSuccess() throws Exception { + this.server.enqueue(new MockResponse().setResponseCode(204)); + + Map inputs = new LinkedHashMap<>(); + inputs.put("input-1", "value"); + inputs.put("input-2", false); + WorkflowDispatch workflowDispatch = new WorkflowDispatch("main", inputs); + this.gitHubActionsApi.dispatchWorkflow(this.repository, "test-workflow.yml", workflowDispatch); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post"); + assertThat(recordedRequest.getRequestUrl().toString()) + .isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/actions/workflows/test-workflow.yml/dispatches"); + assertThat(recordedRequest.getBody().readString(Charset.defaultCharset())) + .isEqualTo("{\"ref\":\"main\",\"inputs\":{\"input-1\":\"value\",\"input-2\":false}}"); + } + + @Test + public void dispatchWorkflowWhenErrorResponseThenException() throws Exception { + this.server.enqueue(new MockResponse().setResponseCode(400)); + + WorkflowDispatch workflowDispatch = new WorkflowDispatch("main", null); + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> this.gitHubActionsApi.dispatchWorkflow(this.repository, "test-workflow.yml", workflowDispatch)); + } +} diff --git a/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubReleaseApiTests.java b/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubReleaseApiTests.java index 6ac7955722..3d91574d5b 100644 --- a/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubReleaseApiTests.java +++ b/buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubReleaseApiTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,15 @@ package org.springframework.gradle.github.release; +import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import org.junit.Test; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.gradle.github.RepositoryRef; @@ -34,21 +35,22 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * @author Steve Riesenberg */ public class GitHubReleaseApiTests { - private GitHubReleaseApi github; - - private RepositoryRef repository = new RepositoryRef("spring-projects", "spring-security"); + private GitHubReleaseApi gitHubReleaseApi; private MockWebServer server; private String baseUrl; + private RepositoryRef repository; + @BeforeEach public void setup() throws Exception { this.server = new MockWebServer(); this.server.start(); - this.github = new GitHubReleaseApi("mock-oauth-token"); this.baseUrl = this.server.url("/api").toString(); - this.github.setBaseUrl(this.baseUrl); + this.gitHubReleaseApi = new GitHubReleaseApi("mock-oauth-token"); + this.gitHubReleaseApi.setBaseUrl(this.baseUrl); + this.repository = new RepositoryRef("spring-projects", "spring-security"); } @AfterEach @@ -134,18 +136,20 @@ public class GitHubReleaseApiTests { " ]\n" + "}"; this.server.enqueue(new MockResponse().setBody(responseJson)); - this.github.publishRelease(this.repository, Release.tag("1.0.0").build()); + this.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build()); RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post"); - assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/releases"); - assertThat(recordedRequest.getBody().toString()).isEqualTo("{\"tag_name\":\"1.0.0\"}"); + assertThat(recordedRequest.getRequestUrl().toString()) + .isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/releases"); + assertThat(recordedRequest.getBody().readString(Charset.defaultCharset())) + .isEqualTo("{\"tag_name\":\"1.0.0\",\"draft\":false,\"prerelease\":false,\"generate_release_notes\":false}"); } @Test public void publishReleaseWhenErrorResponseThenException() throws Exception { this.server.enqueue(new MockResponse().setResponseCode(400)); assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> this.github.publishRelease(this.repository, Release.tag("1.0.0").build())); + .isThrownBy(() -> this.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build())); } }