-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
12 changed files
with
854 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
...Src/src/main/graphql/org/springframework/security/convention/versions/CreateIssue.graphql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
mutation CreateIssueInput($assigneeId: ID!, $labelIds: [ID!], $milestoneId: ID!, $repositoryId: ID!, $title: String!) { | ||
createIssue(input: {assigneeIds: [$assigneeId], labelIds: $labelIds, milestoneId: $milestoneId, projectIds: [], repositoryId: $repositoryId, title: $title}) { | ||
issue { | ||
number | ||
} | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...ain/graphql/org/springframework/security/convention/versions/FindCreateIssueInput.graphql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
query FindCreateIssueInput($owner: String!, $name: String!, $labelQuery: String, $milestoneName: String) { | ||
repository(owner: $owner, name: $name) { | ||
id | ||
labels(query: $labelQuery, first: 1) { | ||
nodes { | ||
id | ||
name | ||
} | ||
} | ||
milestones(query: $milestoneName, states: [OPEN], first: 1) { | ||
nodes { | ||
id | ||
title | ||
} | ||
} | ||
} | ||
viewer { | ||
id | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
buildSrc/src/main/graphql/org/springframework/security/convention/versions/RateLimit.graphql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
query RateLimit { | ||
rateLimit { | ||
limit | ||
cost | ||
remaining | ||
resetAt | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
buildSrc/src/main/graphql/org/springframework/security/convention/versions/schema.json
Large diffs are not rendered by default.
Oops, something went wrong.
49 changes: 49 additions & 0 deletions
49
...dSrc/src/main/java/org/springframework/security/convention/versions/CommandLineUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright 2019-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.security.convention.versions; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.PrintStream; | ||
import java.util.Arrays; | ||
import java.util.Scanner; | ||
|
||
class CommandLineUtils { | ||
static void runCommand(File dir, String... args) { | ||
try { | ||
Process process = new ProcessBuilder() | ||
.directory(dir) | ||
.command(args) | ||
.start(); | ||
writeLinesTo(process.getInputStream(), System.out); | ||
writeLinesTo(process.getErrorStream(), System.out); | ||
if (process.waitFor() != 0) { | ||
new RuntimeException("Failed to run " + Arrays.toString(args)); | ||
} | ||
} catch (IOException | InterruptedException e) { | ||
throw new RuntimeException("Failed to run " + Arrays.toString(args), e); | ||
} | ||
} | ||
|
||
private static void writeLinesTo(InputStream input, PrintStream out) { | ||
Scanner scanner = new Scanner(input); | ||
while(scanner.hasNextLine()) { | ||
out.println(scanner.nextLine()); | ||
} | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
buildSrc/src/main/java/org/springframework/security/convention/versions/FileUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright 2019-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.security.convention.versions; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.util.function.Function; | ||
|
||
class FileUtils { | ||
static void replaceFileText(File file, Function<String, String> replaceText) { | ||
String buildFileText = readString(file); | ||
String updatedBuildFileText = replaceText.apply(buildFileText); | ||
writeString(file, updatedBuildFileText); | ||
} | ||
|
||
static String readString(File file) { | ||
try { | ||
byte[] bytes = Files.readAllBytes(file.toPath()); | ||
return new String(bytes); | ||
} | ||
catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static void writeString(File file, String text) { | ||
try { | ||
Files.write(file.toPath(), text.getBytes()); | ||
} | ||
catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
} |
180 changes: 180 additions & 0 deletions
180
buildSrc/src/main/java/org/springframework/security/convention/versions/GitHubApi.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package org.springframework.security.convention.versions; | ||
|
||
import java.io.IOException; | ||
import java.time.Duration; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
|
||
import com.apollographql.apollo.ApolloCall; | ||
import com.apollographql.apollo.ApolloClient; | ||
import com.apollographql.apollo.api.Input; | ||
import com.apollographql.apollo.api.Response; | ||
import com.apollographql.apollo.exception.ApolloException; | ||
import okhttp3.Interceptor; | ||
import okhttp3.OkHttpClient; | ||
import okhttp3.Request; | ||
import org.jetbrains.annotations.NotNull; | ||
import reactor.core.publisher.Mono; | ||
import reactor.util.retry.RetrySpec; | ||
|
||
public class GitHubApi { | ||
|
||
private final ApolloClient apolloClient; | ||
|
||
public GitHubApi(String githubToken) { | ||
if (githubToken == null) { | ||
throw new IllegalArgumentException("githubToken is required. You can set it using -PgitHubAccessToken="); | ||
} | ||
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); | ||
clientBuilder.addInterceptor(new AuthorizationInterceptor(githubToken)); | ||
this.apolloClient = ApolloClient.builder() | ||
.serverUrl("https://api.github.com/graphql") | ||
.okHttpClient(clientBuilder.build()) | ||
.build(); | ||
} | ||
|
||
public Mono<FindCreateIssueResult> findCreateIssueInput(String owner, String name, String milestone) { | ||
String label = "\"type: dependency-upgrade\""; | ||
FindCreateIssueInputQuery findCreateIssueInputQuery = new FindCreateIssueInputQuery(owner, name, Input.optional(label), Input.optional(milestone)); | ||
return Mono.create( sink -> this.apolloClient.query(findCreateIssueInputQuery) | ||
.enqueue(new ApolloCall.Callback<>() { | ||
@Override | ||
public void onResponse(@NotNull Response<FindCreateIssueInputQuery.Data> response) { | ||
if (response.hasErrors()) { | ||
sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" ")))); | ||
} else { | ||
FindCreateIssueInputQuery.Data data = response.getData(); | ||
FindCreateIssueInputQuery.Repository repository = data.repository(); | ||
List<String> labels = repository.labels().nodes().stream().map(FindCreateIssueInputQuery.Node::id).collect(Collectors.toList()); | ||
if (labels.isEmpty()) { | ||
sink.error(new IllegalArgumentException("Could not find label for " + label)); | ||
return; | ||
} | ||
Optional<String> firstMilestoneId = repository.milestones().nodes().stream().map(FindCreateIssueInputQuery.Node1::id).findFirst(); | ||
if (!firstMilestoneId.isPresent()) { | ||
sink.error(new IllegalArgumentException("Could not find OPEN milestone id for " + milestone)); | ||
return; | ||
} | ||
String milestoneId = firstMilestoneId.get(); | ||
String repositoryId = repository.id(); | ||
String assigneeId = data.viewer().id(); | ||
sink.success(new FindCreateIssueResult(repositoryId, labels, milestoneId, assigneeId)); | ||
} | ||
} | ||
|
||
@Override | ||
public void onFailure(@NotNull ApolloException e) { | ||
sink.error(e); | ||
} | ||
})); | ||
} | ||
|
||
public static class FindCreateIssueResult { | ||
private final String repositoryId; | ||
private final List<String> labelIds; | ||
private final String milestoneId; | ||
private final String assigneeId; | ||
|
||
public FindCreateIssueResult(String repositoryId, List<String> labelIds, String milestoneId, String assigneeId) { | ||
this.repositoryId = repositoryId; | ||
this.labelIds = labelIds; | ||
this.milestoneId = milestoneId; | ||
this.assigneeId = assigneeId; | ||
} | ||
|
||
public String getRepositoryId() { | ||
return repositoryId; | ||
} | ||
|
||
public List<String> getLabelIds() { | ||
return labelIds; | ||
} | ||
|
||
public String getMilestoneId() { | ||
return milestoneId; | ||
} | ||
|
||
public String getAssigneeId() { | ||
return assigneeId; | ||
} | ||
} | ||
|
||
public Mono<RateLimitQuery.RateLimit> findRateLimit() { | ||
return Mono.create( sink -> this.apolloClient.query(new RateLimitQuery()) | ||
.enqueue(new ApolloCall.Callback<>() { | ||
@Override | ||
public void onResponse(@NotNull Response<RateLimitQuery.Data> response) { | ||
if (response.hasErrors()) { | ||
sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" ")))); | ||
} else { | ||
sink.success(response.getData().rateLimit()); | ||
} | ||
} | ||
|
||
@Override | ||
public void onFailure(@NotNull ApolloException e) { | ||
sink.error(e); | ||
} | ||
})); | ||
} | ||
|
||
public Mono<Integer> createIssue(String repositoryId, String title, List<String> labelIds, String milestoneId, String assigneeId) { | ||
CreateIssueInputMutation createIssue = new CreateIssueInputMutation.Builder() | ||
.repositoryId(repositoryId) | ||
.title(title) | ||
.labelIds(labelIds) | ||
.milestoneId(milestoneId) | ||
.assigneeId(assigneeId) | ||
.build(); | ||
return Mono.create( sink -> this.apolloClient.mutate(createIssue) | ||
.enqueue(new ApolloCall.Callback<>() { | ||
@Override | ||
public void onResponse(@NotNull Response<CreateIssueInputMutation.Data> response) { | ||
if (response.hasErrors()) { | ||
String message = response.getErrors().stream().map(e -> e.getMessage() + " " + e.getCustomAttributes() + " " + e.getLocations()).collect(Collectors.joining(" ")); | ||
if (message.contains("was submitted too quickly")) { | ||
sink.error(new SubmittedTooQuick(message)); | ||
} else { | ||
sink.error(new RuntimeException(message)); | ||
} | ||
} else { | ||
sink.success(response.getData().createIssue().issue().number()); | ||
} | ||
} | ||
|
||
@Override | ||
public void onFailure(@NotNull ApolloException e) { | ||
sink.error(e); | ||
} | ||
})) | ||
.retryWhen( | ||
RetrySpec.fixedDelay(3, Duration.ofMinutes(1)) | ||
.filter(SubmittedTooQuick.class::isInstance) | ||
.doBeforeRetry(r -> System.out.println("Pausing for 1 minute and then retrying due to receiving \"submitted too quickly\" error from GitHub API")) | ||
) | ||
.cast(Integer.class); | ||
} | ||
|
||
public static class SubmittedTooQuick extends RuntimeException { | ||
public SubmittedTooQuick(String message) { | ||
super(message); | ||
} | ||
} | ||
|
||
private static class AuthorizationInterceptor implements Interceptor { | ||
|
||
private final String token; | ||
|
||
public AuthorizationInterceptor(String token) { | ||
this.token = token; | ||
} | ||
|
||
@Override | ||
public okhttp3.Response intercept(Chain chain) throws IOException { | ||
Request request = chain.request().newBuilder() | ||
.addHeader("Authorization", "Bearer " + this.token).build(); | ||
return chain.proceed(request); | ||
} | ||
} | ||
} |
Oops, something went wrong.