13 changed files with 610 additions and 13 deletions
@ -0,0 +1,130 @@
@@ -0,0 +1,130 @@
|
||||
/* |
||||
* Copyright 2002-2021 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.File; |
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Paths; |
||||
|
||||
import org.gradle.api.Action; |
||||
import org.gradle.api.DefaultTask; |
||||
import org.gradle.api.Project; |
||||
import org.gradle.api.tasks.Input; |
||||
import org.gradle.api.tasks.Optional; |
||||
import org.gradle.api.tasks.TaskAction; |
||||
|
||||
import org.springframework.gradle.github.RepositoryRef; |
||||
import org.springframework.gradle.github.changelog.GitHubChangelogPlugin; |
||||
|
||||
/** |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public class CreateGitHubReleaseTask extends DefaultTask { |
||||
@Input |
||||
private RepositoryRef repository = new RepositoryRef(); |
||||
|
||||
@Input @Optional |
||||
private String gitHubAccessToken; |
||||
|
||||
@Input |
||||
private String version; |
||||
|
||||
@Input @Optional |
||||
private String branch = "main"; |
||||
|
||||
@Input |
||||
private boolean createRelease = false; |
||||
|
||||
@TaskAction |
||||
public void createGitHubRelease() { |
||||
String body = readReleaseNotes(); |
||||
Release release = Release.tag(this.version) |
||||
.commit(this.branch) |
||||
.name(this.version) |
||||
.body(body) |
||||
.preRelease(this.version.contains("-")) |
||||
.build(); |
||||
|
||||
System.out.printf("%sCreating GitHub release for %s/%s@%s\n", |
||||
this.createRelease ? "" : "[DRY RUN] ", |
||||
this.repository.getOwner(), |
||||
this.repository.getName(), |
||||
this.version |
||||
); |
||||
System.out.printf(" Release Notes:\n\n----\n%s\n----\n\n", body.trim()); |
||||
|
||||
if (this.createRelease) { |
||||
GitHubReleaseApi github = new GitHubReleaseApi(this.gitHubAccessToken); |
||||
github.publishRelease(this.repository, release); |
||||
} |
||||
} |
||||
|
||||
private String readReleaseNotes() { |
||||
Project project = getProject(); |
||||
File inputFile = project.file(Paths.get(project.getBuildDir().getPath(), GitHubChangelogPlugin.RELEASE_NOTES_PATH)); |
||||
try { |
||||
return Files.readString(inputFile.toPath()); |
||||
} catch (IOException ex) { |
||||
throw new RuntimeException("Unable to read release notes from " + inputFile, ex); |
||||
} |
||||
} |
||||
|
||||
public RepositoryRef getRepository() { |
||||
return repository; |
||||
} |
||||
|
||||
public void repository(Action<RepositoryRef> repository) { |
||||
repository.execute(this.repository); |
||||
} |
||||
|
||||
public void setRepository(RepositoryRef repository) { |
||||
this.repository = repository; |
||||
} |
||||
|
||||
public String getGitHubAccessToken() { |
||||
return gitHubAccessToken; |
||||
} |
||||
|
||||
public void setGitHubAccessToken(String gitHubAccessToken) { |
||||
this.gitHubAccessToken = gitHubAccessToken; |
||||
} |
||||
|
||||
public String getVersion() { |
||||
return version; |
||||
} |
||||
|
||||
public void setVersion(String version) { |
||||
this.version = version; |
||||
} |
||||
|
||||
public String getBranch() { |
||||
return branch; |
||||
} |
||||
|
||||
public void setBranch(String branch) { |
||||
this.branch = branch; |
||||
} |
||||
|
||||
public boolean isCreateRelease() { |
||||
return createRelease; |
||||
} |
||||
|
||||
public void setCreateRelease(boolean createRelease) { |
||||
this.createRelease = createRelease; |
||||
} |
||||
} |
||||
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 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 releases. |
||||
* |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public class GitHubReleaseApi { |
||||
private String baseUrl = "https://api.github.com"; |
||||
|
||||
private final OkHttpClient httpClient; |
||||
private Gson gson = new Gson(); |
||||
|
||||
public GitHubReleaseApi(String gitHubAccessToken) { |
||||
this.httpClient = new OkHttpClient.Builder() |
||||
.addInterceptor(new AuthorizationInterceptor(gitHubAccessToken)) |
||||
.build(); |
||||
} |
||||
|
||||
public void setBaseUrl(String baseUrl) { |
||||
this.baseUrl = baseUrl; |
||||
} |
||||
|
||||
/** |
||||
* Publish a release with no binary attachments. |
||||
* |
||||
* @param repository The repository owner/name |
||||
* @param release The contents of the release |
||||
*/ |
||||
public void publishRelease(RepositoryRef repository, Release release) { |
||||
String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/releases"; |
||||
String json = this.gson.toJson(release); |
||||
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json); |
||||
Request request = new Request.Builder().url(url).post(body).build(); |
||||
try { |
||||
Response response = this.httpClient.newCall(request).execute(); |
||||
if (!response.isSuccessful()) { |
||||
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s. Got response %s", |
||||
release.getName(), repository.getOwner(), repository.getName(), response)); |
||||
} |
||||
} catch (IOException ex) { |
||||
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s", |
||||
release.getName(), 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); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 groovy.lang.MissingPropertyException; |
||||
import org.gradle.api.Action; |
||||
import org.gradle.api.Plugin; |
||||
import org.gradle.api.Project; |
||||
|
||||
/** |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public class GitHubReleasePlugin implements Plugin<Project> { |
||||
@Override |
||||
public void apply(Project project) { |
||||
project.getTasks().register("createGitHubRelease", CreateGitHubReleaseTask.class, new Action<CreateGitHubReleaseTask>() { |
||||
@Override |
||||
public void execute(CreateGitHubReleaseTask 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=..."); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
@ -0,0 +1,156 @@
@@ -0,0 +1,156 @@
|
||||
/* |
||||
* Copyright 2002-2021 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 com.google.gson.annotations.SerializedName; |
||||
|
||||
/** |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public class Release { |
||||
@SerializedName("tag_name") |
||||
private final String tag; |
||||
|
||||
@SerializedName("target_commitish") |
||||
private final String commit; |
||||
|
||||
@SerializedName("name") |
||||
private final String name; |
||||
|
||||
@SerializedName("body") |
||||
private final String body; |
||||
|
||||
@SerializedName("draft") |
||||
private final boolean draft; |
||||
|
||||
@SerializedName("prerelease") |
||||
private final boolean preRelease; |
||||
|
||||
@SerializedName("generate_release_notes") |
||||
private final boolean generateReleaseNotes; |
||||
|
||||
private Release(String tag, String commit, String name, String body, boolean draft, boolean preRelease, boolean generateReleaseNotes) { |
||||
this.tag = tag; |
||||
this.commit = commit; |
||||
this.name = name; |
||||
this.body = body; |
||||
this.draft = draft; |
||||
this.preRelease = preRelease; |
||||
this.generateReleaseNotes = generateReleaseNotes; |
||||
} |
||||
|
||||
public String getTag() { |
||||
return tag; |
||||
} |
||||
|
||||
public String getCommit() { |
||||
return commit; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public String getBody() { |
||||
return body; |
||||
} |
||||
|
||||
public boolean isDraft() { |
||||
return draft; |
||||
} |
||||
|
||||
public boolean isPreRelease() { |
||||
return preRelease; |
||||
} |
||||
|
||||
public boolean isGenerateReleaseNotes() { |
||||
return generateReleaseNotes; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "Release{" + |
||||
"tag='" + tag + '\'' + |
||||
", commit='" + commit + '\'' + |
||||
", name='" + name + '\'' + |
||||
", body='" + body + '\'' + |
||||
", draft=" + draft + |
||||
", preRelease=" + preRelease + |
||||
", generateReleaseNotes=" + generateReleaseNotes + |
||||
'}'; |
||||
} |
||||
|
||||
public static Builder tag(String tag) { |
||||
return new Builder().tag(tag); |
||||
} |
||||
|
||||
public static Builder commit(String commit) { |
||||
return new Builder().commit(commit); |
||||
} |
||||
|
||||
public static final class Builder { |
||||
private String tag; |
||||
private String commit; |
||||
private String name; |
||||
private String body; |
||||
private boolean draft; |
||||
private boolean preRelease; |
||||
private boolean generateReleaseNotes; |
||||
|
||||
private Builder() { |
||||
} |
||||
|
||||
public Builder tag(String tag) { |
||||
this.tag = tag; |
||||
return this; |
||||
} |
||||
|
||||
public Builder commit(String commit) { |
||||
this.commit = commit; |
||||
return this; |
||||
} |
||||
|
||||
public Builder name(String name) { |
||||
this.name = name; |
||||
return this; |
||||
} |
||||
|
||||
public Builder body(String body) { |
||||
this.body = body; |
||||
return this; |
||||
} |
||||
|
||||
public Builder draft(boolean draft) { |
||||
this.draft = draft; |
||||
return this; |
||||
} |
||||
|
||||
public Builder preRelease(boolean preRelease) { |
||||
this.preRelease = preRelease; |
||||
return this; |
||||
} |
||||
|
||||
public Builder generateReleaseNotes(boolean generateReleaseNotes) { |
||||
this.generateReleaseNotes = generateReleaseNotes; |
||||
return this; |
||||
} |
||||
|
||||
public Release build() { |
||||
return new Release(tag, commit, name, body, draft, preRelease, generateReleaseNotes); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,151 @@
@@ -0,0 +1,151 @@
|
||||
/* |
||||
* Copyright 2002-2021 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.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.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 GitHubReleaseApiTests { |
||||
private GitHubReleaseApi github; |
||||
|
||||
private RepositoryRef repository = new RepositoryRef("spring-projects", "spring-security"); |
||||
|
||||
private MockWebServer server; |
||||
|
||||
private String baseUrl; |
||||
|
||||
@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); |
||||
} |
||||
|
||||
@AfterEach |
||||
public void cleanup() throws Exception { |
||||
this.server.shutdown(); |
||||
} |
||||
|
||||
@Test |
||||
public void publishReleaseWhenValidParametersThenSuccess() throws Exception { |
||||
String responseJson = "{\n" + |
||||
" \"url\": \"https://api.github.com/spring-projects/spring-security/releases/1\",\n" + |
||||
" \"html_url\": \"https://github.com/spring-projects/spring-security/releases/tags/v1.0.0\",\n" + |
||||
" \"assets_url\": \"https://api.github.com/spring-projects/spring-security/releases/1/assets\",\n" + |
||||
" \"upload_url\": \"https://uploads.github.com/spring-projects/spring-security/releases/1/assets{?name,label}\",\n" + |
||||
" \"tarball_url\": \"https://api.github.com/spring-projects/spring-security/tarball/v1.0.0\",\n" + |
||||
" \"zipball_url\": \"https://api.github.com/spring-projects/spring-security/zipball/v1.0.0\",\n" + |
||||
" \"discussion_url\": \"https://github.com/spring-projects/spring-security/discussions/90\",\n" + |
||||
" \"id\": 1,\n" + |
||||
" \"node_id\": \"MDc6UmVsZWFzZTE=\",\n" + |
||||
" \"tag_name\": \"v1.0.0\",\n" + |
||||
" \"target_commitish\": \"main\",\n" + |
||||
" \"name\": \"v1.0.0\",\n" + |
||||
" \"body\": \"Description of the release\",\n" + |
||||
" \"draft\": false,\n" + |
||||
" \"prerelease\": false,\n" + |
||||
" \"created_at\": \"2013-02-27T19:35:32Z\",\n" + |
||||
" \"published_at\": \"2013-02-27T19:35:32Z\",\n" + |
||||
" \"author\": {\n" + |
||||
" \"login\": \"sjohnr\",\n" + |
||||
" \"id\": 1,\n" + |
||||
" \"node_id\": \"MDQ6VXNlcjE=\",\n" + |
||||
" \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" + |
||||
" \"gravatar_id\": \"\",\n" + |
||||
" \"url\": \"https://api.github.com/users/sjohnr\",\n" + |
||||
" \"html_url\": \"https://github.com/sjohnr\",\n" + |
||||
" \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" + |
||||
" \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" + |
||||
" \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" + |
||||
" \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" + |
||||
" \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" + |
||||
" \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" + |
||||
" \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" + |
||||
" \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" + |
||||
" \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" + |
||||
" \"type\": \"User\",\n" + |
||||
" \"site_admin\": false\n" + |
||||
" },\n" + |
||||
" \"assets\": [\n" + |
||||
" {\n" + |
||||
" \"url\": \"https://api.github.com/spring-projects/spring-security/releases/assets/1\",\n" + |
||||
" \"browser_download_url\": \"https://github.com/spring-projects/spring-security/releases/download/v1.0.0/example.zip\",\n" + |
||||
" \"id\": 1,\n" + |
||||
" \"node_id\": \"MDEyOlJlbGVhc2VBc3NldDE=\",\n" + |
||||
" \"name\": \"example.zip\",\n" + |
||||
" \"label\": \"short description\",\n" + |
||||
" \"state\": \"uploaded\",\n" + |
||||
" \"content_type\": \"application/zip\",\n" + |
||||
" \"size\": 1024,\n" + |
||||
" \"download_count\": 42,\n" + |
||||
" \"created_at\": \"2013-02-27T19:35:32Z\",\n" + |
||||
" \"updated_at\": \"2013-02-27T19:35:32Z\",\n" + |
||||
" \"uploader\": {\n" + |
||||
" \"login\": \"sjohnr\",\n" + |
||||
" \"id\": 1,\n" + |
||||
" \"node_id\": \"MDQ6VXNlcjE=\",\n" + |
||||
" \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" + |
||||
" \"gravatar_id\": \"\",\n" + |
||||
" \"url\": \"https://api.github.com/users/sjohnr\",\n" + |
||||
" \"html_url\": \"https://github.com/sjohnr\",\n" + |
||||
" \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" + |
||||
" \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" + |
||||
" \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" + |
||||
" \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" + |
||||
" \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" + |
||||
" \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" + |
||||
" \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" + |
||||
" \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" + |
||||
" \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" + |
||||
" \"type\": \"User\",\n" + |
||||
" \"site_admin\": false\n" + |
||||
" }\n" + |
||||
" }\n" + |
||||
" ]\n" + |
||||
"}"; |
||||
this.server.enqueue(new MockResponse().setBody(responseJson)); |
||||
this.github.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\"}"); |
||||
} |
||||
|
||||
@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())); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue