diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/user/GitHubUserApi.java b/buildSrc/src/main/java/org/springframework/gradle/github/user/GitHubUserApi.java new file mode 100644 index 0000000000..b1beac71dc --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/user/GitHubUserApi.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020-2023 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.user; + +import java.io.IOException; + +import com.google.gson.Gson; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * @author Steve Riesenberg + */ +public class GitHubUserApi { + private String baseUrl = "https://api.github.com"; + + private final OkHttpClient httpClient; + private Gson gson = new Gson(); + + public GitHubUserApi(String gitHubAccessToken) { + this.httpClient = new OkHttpClient.Builder() + .addInterceptor(new AuthorizationInterceptor(gitHubAccessToken)) + .build(); + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + /** + * Retrieve a GitHub user by the personal access token. + * + * @return The GitHub user + */ + public User getUser() { + String url = this.baseUrl + "/user"; + Request request = new Request.Builder().url(url).get().build(); + try (Response response = this.httpClient.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new RuntimeException( + String.format("Unable to retrieve GitHub user." + + " Please check the personal access token and try again." + + " Got response %s", response)); + } + return this.gson.fromJson(response.body().charStream(), User.class); + } catch (IOException ex) { + throw new RuntimeException("Unable to retrieve GitHub user.", 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/user/User.java b/buildSrc/src/main/java/org/springframework/gradle/github/user/User.java new file mode 100644 index 0000000000..69dc7e8acf --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/user/User.java @@ -0,0 +1,53 @@ +package org.springframework.gradle.github.user; + +/** + * @author Steve Riesenberg + */ +public class User { + private Long id; + private String login; + private String name; + private String url; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return this.login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", login='" + login + '\'' + + ", name='" + name + '\'' + + ", url='" + url + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java index 24eeb3111c..96f885ca44 100644 --- a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java +++ b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java @@ -26,14 +26,14 @@ import java.util.Base64; * Implements necessary calls to the Sagan API See https://spring.io/restdocs/index.html */ public class SaganApi { - private String baseUrl = "https://spring.io/api"; + private String baseUrl = "https://api.spring.io"; private OkHttpClient client; private Gson gson = new Gson(); - public SaganApi(String gitHubToken) { + public SaganApi(String username, String gitHubToken) { this.client = new OkHttpClient.Builder() - .addInterceptor(new BasicInterceptor("not-used", gitHubToken)) + .addInterceptor(new BasicInterceptor(username, gitHubToken)) .build(); } diff --git a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java index 6592544b1f..c4064407fe 100644 --- a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java +++ b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java @@ -20,6 +20,9 @@ import org.gradle.api.DefaultTask; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction; +import org.springframework.gradle.github.user.GitHubUserApi; +import org.springframework.gradle.github.user.User; + public class SaganCreateReleaseTask extends DefaultTask { @Input @@ -35,11 +38,22 @@ public class SaganCreateReleaseTask extends DefaultTask { @TaskAction public void saganCreateRelease() { - SaganApi sagan = new SaganApi(this.gitHubAccessToken); + GitHubUserApi github = new GitHubUserApi(this.gitHubAccessToken); + User user = github.getUser(); + + // Antora reference docs URLs for snapshots do not contain -SNAPSHOT + String referenceDocUrl = this.referenceDocUrl; + if (this.version.endsWith("-SNAPSHOT")) { + referenceDocUrl = this.referenceDocUrl + .replace("{version}", this.version) + .replace("-SNAPSHOT", ""); + } + + SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken); Release release = new Release(); release.setVersion(this.version); release.setApiDocUrl(this.apiDocUrl); - release.setReferenceDocUrl(this.referenceDocUrl); + release.setReferenceDocUrl(referenceDocUrl); sagan.createReleaseForProject(release, this.projectName); } diff --git a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java index 49a3885226..dfebc860b7 100644 --- a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java +++ b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java @@ -20,6 +20,9 @@ import org.gradle.api.DefaultTask; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction; +import org.springframework.gradle.github.user.GitHubUserApi; +import org.springframework.gradle.github.user.User; + public class SaganDeleteReleaseTask extends DefaultTask { @Input @@ -31,7 +34,10 @@ public class SaganDeleteReleaseTask extends DefaultTask { @TaskAction public void saganCreateRelease() { - SaganApi sagan = new SaganApi(this.gitHubAccessToken); + GitHubUserApi github = new GitHubUserApi(this.gitHubAccessToken); + User user = github.getUser(); + + SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken); sagan.deleteReleaseForProject(this.version, this.projectName); } diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java b/buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java index da50eef986..495e898a2f 100644 --- a/buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java +++ b/buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java @@ -42,7 +42,7 @@ public class SaganApiTests { public void setup() throws Exception { this.server = new MockWebServer(); this.server.start(); - this.sagan = new SaganApi("mock-oauth-token"); + this.sagan = new SaganApi("user", "mock-oauth-token"); this.baseUrl = this.server.url("/api").toString(); this.sagan.setBaseUrl(this.baseUrl); } @@ -63,7 +63,7 @@ public class SaganApiTests { RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS); assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases"); assertThat(request.getMethod()).isEqualToIgnoringCase("post"); - assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic bm90LXVzZWQ6bW9jay1vYXV0aC10b2tlbg=="); + assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic dXNlcjptb2NrLW9hdXRoLXRva2Vu"); assertThat(request.getBody().readString(Charset.defaultCharset())).isEqualToIgnoringWhitespace("{\n" + " \"version\":\"5.6.0-SNAPSHOT\",\n" + " \"current\":false,\n" + @@ -79,7 +79,7 @@ public class SaganApiTests { RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS); assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases/5.6.0-SNAPSHOT"); assertThat(request.getMethod()).isEqualToIgnoringCase("delete"); - assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic bm90LXVzZWQ6bW9jay1vYXV0aC10b2tlbg=="); + assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic dXNlcjptb2NrLW9hdXRoLXRva2Vu"); assertThat(request.getBody().readString(Charset.defaultCharset())).isEmpty(); } } diff --git a/buildSrc/src/test/java/org/springframework/gradle/github/user/GitHubUserApiTests.java b/buildSrc/src/test/java/org/springframework/gradle/github/user/GitHubUserApiTests.java new file mode 100644 index 0000000000..801b1748d1 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/gradle/github/user/GitHubUserApiTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2020-2023 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.user; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * @author Steve Riesenberg + */ +public class GitHubUserApiTests { + private GitHubUserApi gitHubUserApi; + + private MockWebServer server; + + private String baseUrl; + + @BeforeEach + public void setup() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + this.baseUrl = this.server.url("/api").toString(); + this.gitHubUserApi = new GitHubUserApi("mock-oauth-token"); + this.gitHubUserApi.setBaseUrl(this.baseUrl); + } + + @AfterEach + public void cleanup() throws Exception { + this.server.shutdown(); + } + + @Test + public void getUserWhenValidParametersThenSuccess() { + // @formatter:off + String responseJson = "{\n" + + " \"avatar_url\": \"https://avatars.githubusercontent.com/u/583231?v=4\",\n" + + " \"bio\": null,\n" + + " \"blog\": \"https://github.blog\",\n" + + " \"company\": \"@github\",\n" + + " \"created_at\": \"2011-01-25T18:44:36Z\",\n" + + " \"email\": null,\n" + + " \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\",\n" + + " \"followers\": 8481,\n" + + " \"followers_url\": \"https://api.github.com/users/octocat/followers\",\n" + + " \"following\": 9,\n" + + " \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\",\n" + + " \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\",\n" + + " \"gravatar_id\": \"\",\n" + + " \"hireable\": null,\n" + + " \"html_url\": \"https://github.com/octocat\",\n" + + " \"id\": 583231,\n" + + " \"location\": \"San Francisco\",\n" + + " \"login\": \"octocat\",\n" + + " \"name\": \"The Octocat\",\n" + + " \"node_id\": \"MDQ6VXNlcjU4MzIzMQ==\",\n" + + " \"organizations_url\": \"https://api.github.com/users/octocat/orgs\",\n" + + " \"public_gists\": 8,\n" + + " \"public_repos\": 8,\n" + + " \"received_events_url\": \"https://api.github.com/users/octocat/received_events\",\n" + + " \"repos_url\": \"https://api.github.com/users/octocat/repos\",\n" + + " \"site_admin\": false,\n" + + " \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\",\n" + + " \"twitter_username\": null,\n" + + " \"type\": \"User\",\n" + + " \"updated_at\": \"2023-02-25T12:14:58Z\",\n" + + " \"url\": \"https://api.github.com/users/octocat\"\n" + + "}"; + // @formatter:on + this.server.enqueue(new MockResponse().setBody(responseJson)); + + User user = this.gitHubUserApi.getUser(); + assertThat(user.getId()).isEqualTo(583231); + assertThat(user.getLogin()).isEqualTo("octocat"); + assertThat(user.getName()).isEqualTo("The Octocat"); + assertThat(user.getUrl()).isEqualTo("https://api.github.com/users/octocat"); + } + + @Test + public void getUserWhenErrorResponseThenException() { + this.server.enqueue(new MockResponse().setResponseCode(400)); + // @formatter:off + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> this.gitHubUserApi.getUser()); + // @formatter:on + } +}