47 changed files with 141 additions and 5531 deletions
@ -1,66 +0,0 @@
@@ -1,66 +0,0 @@
|
||||
package org.springframework.gradle.antora; |
||||
|
||||
import org.gradle.api.Action; |
||||
import org.gradle.api.GradleException; |
||||
import org.gradle.api.Plugin; |
||||
import org.gradle.api.Project; |
||||
import org.gradle.api.tasks.TaskProvider; |
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin; |
||||
|
||||
public class AntoraVersionPlugin implements Plugin<Project> { |
||||
public static final String ANTORA_CHECK_VERSION_TASK_NAME = "antoraCheckVersion"; |
||||
|
||||
@Override |
||||
public void apply(Project project) { |
||||
TaskProvider<CheckAntoraVersionTask> antoraCheckVersion = project.getTasks().register(ANTORA_CHECK_VERSION_TASK_NAME, CheckAntoraVersionTask.class, new Action<CheckAntoraVersionTask>() { |
||||
@Override |
||||
public void execute(CheckAntoraVersionTask antoraCheckVersion) { |
||||
antoraCheckVersion.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); |
||||
antoraCheckVersion.setDescription("Checks the antora.yml version properties match the Gradle version"); |
||||
antoraCheckVersion.getAntoraVersion().convention(project.provider(() -> getDefaultAntoraVersion(project))); |
||||
antoraCheckVersion.getAntoraPrerelease().convention(project.provider(() -> getDefaultAntoraPrerelease(project))); |
||||
antoraCheckVersion.getAntoraDisplayVersion().convention(project.provider(() -> getDefaultAntoraDisplayVersion(project))); |
||||
antoraCheckVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml"))); |
||||
} |
||||
}); |
||||
project.getPlugins().withType(LifecycleBasePlugin.class, new Action<LifecycleBasePlugin>() { |
||||
@Override |
||||
public void execute(LifecycleBasePlugin lifecycleBasePlugin) { |
||||
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME) |
||||
.configure(check -> check.dependsOn(antoraCheckVersion)); |
||||
} |
||||
}); |
||||
project.getTasks().register("antoraUpdateVersion", UpdateAntoraVersionTask.class, new Action<UpdateAntoraVersionTask>() { |
||||
@Override |
||||
public void execute(UpdateAntoraVersionTask antoraUpdateVersion) { |
||||
antoraUpdateVersion.setGroup("Release"); |
||||
antoraUpdateVersion.setDescription("Updates the antora.yml version properties to match the Gradle version"); |
||||
antoraUpdateVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml"))); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private static String getDefaultAntoraVersion(Project project) { |
||||
String projectVersion = getProjectVersion(project); |
||||
return AntoraVersionUtils.getDefaultAntoraVersion(projectVersion); |
||||
} |
||||
|
||||
private static String getDefaultAntoraPrerelease(Project project) { |
||||
String projectVersion = getProjectVersion(project); |
||||
return AntoraVersionUtils.getDefaultAntoraPrerelease(projectVersion); |
||||
} |
||||
|
||||
private static String getDefaultAntoraDisplayVersion(Project project) { |
||||
String projectVersion = getProjectVersion(project); |
||||
return AntoraVersionUtils.getDefaultAntoraDisplayVersion(projectVersion); |
||||
} |
||||
|
||||
private static String getProjectVersion(Project project) { |
||||
Object projectVersion = project.getVersion(); |
||||
if (projectVersion == null) { |
||||
throw new GradleException("Please define antoraVersion and antoraPrerelease on " + ANTORA_CHECK_VERSION_TASK_NAME + " or provide a Project version so they can be defaulted"); |
||||
} |
||||
return String.valueOf(projectVersion); |
||||
} |
||||
|
||||
} |
||||
@ -1,55 +0,0 @@
@@ -1,55 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.antora; |
||||
|
||||
public class AntoraVersionUtils { |
||||
|
||||
public static String getDefaultAntoraVersion(String projectVersion) { |
||||
int preReleaseIndex = getSnapshotIndex(projectVersion); |
||||
return isSnapshot(projectVersion) ? projectVersion.substring(0, preReleaseIndex) : projectVersion; |
||||
} |
||||
|
||||
public static String getDefaultAntoraPrerelease(String projectVersion) { |
||||
if (isSnapshot(projectVersion)) { |
||||
int preReleaseIndex = getSnapshotIndex(projectVersion); |
||||
return projectVersion.substring(preReleaseIndex); |
||||
} |
||||
if (isPreRelease(projectVersion)) { |
||||
return Boolean.TRUE.toString(); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public static String getDefaultAntoraDisplayVersion(String projectVersion) { |
||||
if (!isSnapshot(projectVersion) && isPreRelease(projectVersion)) { |
||||
return getDefaultAntoraVersion(projectVersion); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private static boolean isSnapshot(String projectVersion) { |
||||
return getSnapshotIndex(projectVersion) >= 0; |
||||
} |
||||
|
||||
private static int getSnapshotIndex(String projectVersion) { |
||||
return projectVersion.lastIndexOf("-SNAPSHOT"); |
||||
} |
||||
|
||||
private static boolean isPreRelease(String projectVersion) { |
||||
return projectVersion.lastIndexOf("-") >= 0; |
||||
} |
||||
} |
||||
@ -1,96 +0,0 @@
@@ -1,96 +0,0 @@
|
||||
package org.springframework.gradle.antora; |
||||
|
||||
import org.gradle.api.DefaultTask; |
||||
import org.gradle.api.GradleException; |
||||
import org.gradle.api.file.RegularFileProperty; |
||||
import org.gradle.api.provider.Property; |
||||
import org.gradle.api.tasks.Input; |
||||
import org.gradle.api.tasks.InputFile; |
||||
import org.gradle.api.tasks.Optional; |
||||
import org.gradle.api.tasks.TaskAction; |
||||
import org.yaml.snakeyaml.Yaml; |
||||
import org.yaml.snakeyaml.constructor.Constructor; |
||||
import org.yaml.snakeyaml.representer.Representer; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.FileNotFoundException; |
||||
|
||||
public abstract class CheckAntoraVersionTask extends DefaultTask { |
||||
|
||||
@TaskAction |
||||
public void check() throws FileNotFoundException { |
||||
File antoraYmlFile = getAntoraYmlFile().getAsFile().get(); |
||||
String expectedAntoraVersion = getAntoraVersion().get(); |
||||
String expectedAntoraPrerelease = getAntoraPrerelease().getOrElse(null); |
||||
String expectedAntoraDisplayVersion = getAntoraDisplayVersion().getOrElse(null); |
||||
|
||||
Representer representer = new Representer(); |
||||
representer.getPropertyUtils().setSkipMissingProperties(true); |
||||
|
||||
Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer); |
||||
AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile)); |
||||
|
||||
String actualAntoraPrerelease = antoraYml.getPrerelease(); |
||||
boolean preReleaseMatches = antoraYml.getPrerelease() == null && expectedAntoraPrerelease == null || |
||||
(actualAntoraPrerelease != null && actualAntoraPrerelease.equals(expectedAntoraPrerelease)); |
||||
String actualAntoraDisplayVersion = antoraYml.getDisplay_version(); |
||||
boolean displayVersionMatches = antoraYml.getDisplay_version() == null && expectedAntoraDisplayVersion == null || |
||||
(actualAntoraDisplayVersion != null && actualAntoraDisplayVersion.equals(expectedAntoraDisplayVersion)); |
||||
String actualAntoraVersion = antoraYml.getVersion(); |
||||
if (!preReleaseMatches || |
||||
!displayVersionMatches || |
||||
!expectedAntoraVersion.equals(actualAntoraVersion)) { |
||||
throw new GradleException("The Gradle version of '" + getProject().getVersion() + "' should have version: '" |
||||
+ expectedAntoraVersion + "' prerelease: '" + expectedAntoraPrerelease + "' display_version: '" |
||||
+ expectedAntoraDisplayVersion + "' defined in " + antoraYmlFile + " but got version: '" |
||||
+ actualAntoraVersion + "' prerelease: '" + actualAntoraPrerelease + "' display_version: '" + actualAntoraDisplayVersion + "'"); |
||||
} |
||||
} |
||||
|
||||
@InputFile |
||||
public abstract RegularFileProperty getAntoraYmlFile(); |
||||
|
||||
@Input |
||||
public abstract Property<String> getAntoraVersion(); |
||||
|
||||
@Input |
||||
@Optional |
||||
public abstract Property<String> getAntoraPrerelease(); |
||||
|
||||
@Input |
||||
@Optional |
||||
public abstract Property<String> getAntoraDisplayVersion(); |
||||
|
||||
public static class AntoraYml { |
||||
private String version; |
||||
|
||||
private String prerelease; |
||||
|
||||
private String display_version; |
||||
|
||||
public String getVersion() { |
||||
return version; |
||||
} |
||||
|
||||
public void setVersion(String version) { |
||||
this.version = version; |
||||
} |
||||
|
||||
public String getPrerelease() { |
||||
return prerelease; |
||||
} |
||||
|
||||
public void setPrerelease(String prerelease) { |
||||
this.prerelease = prerelease; |
||||
} |
||||
|
||||
public String getDisplay_version() { |
||||
return display_version; |
||||
} |
||||
|
||||
public void setDisplay_version(String display_version) { |
||||
this.display_version = display_version; |
||||
} |
||||
} |
||||
} |
||||
@ -1,138 +0,0 @@
@@ -1,138 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.antora; |
||||
|
||||
import org.gradle.api.DefaultTask; |
||||
import org.gradle.api.GradleException; |
||||
import org.gradle.api.Project; |
||||
import org.gradle.api.file.RegularFileProperty; |
||||
import org.gradle.api.provider.Property; |
||||
import org.gradle.api.tasks.Input; |
||||
import org.gradle.api.tasks.InputFile; |
||||
import org.gradle.api.tasks.Optional; |
||||
import org.gradle.api.tasks.OutputFile; |
||||
import org.gradle.api.tasks.TaskAction; |
||||
import org.yaml.snakeyaml.DumperOptions; |
||||
import org.yaml.snakeyaml.Yaml; |
||||
import org.yaml.snakeyaml.constructor.Constructor; |
||||
import org.yaml.snakeyaml.nodes.NodeTuple; |
||||
import org.yaml.snakeyaml.nodes.Tag; |
||||
import org.yaml.snakeyaml.representer.Representer; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.FileWriter; |
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.gradle.github.milestones.NextVersionYml; |
||||
|
||||
public abstract class UpdateAntoraVersionTask extends DefaultTask { |
||||
|
||||
@TaskAction |
||||
public void update() throws IOException { |
||||
String projectVersion = getProject().getVersion().toString(); |
||||
File antoraYmlFile = getAntoraYmlFile().getAsFile().get(); |
||||
String updatedAntoraVersion = AntoraVersionUtils.getDefaultAntoraVersion(projectVersion); |
||||
String updatedAntoraPrerelease = AntoraVersionUtils.getDefaultAntoraPrerelease(projectVersion); |
||||
String updatedAntoraDisplayVersion = AntoraVersionUtils.getDefaultAntoraDisplayVersion(projectVersion); |
||||
|
||||
Representer representer = new Representer(); |
||||
representer.getPropertyUtils().setSkipMissingProperties(true); |
||||
|
||||
Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer); |
||||
AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile)); |
||||
|
||||
System.out.println("Updating the version parameters in " + antoraYmlFile.getName() + " to version: " |
||||
+ updatedAntoraVersion + ", prerelease: " + updatedAntoraPrerelease + ", display_version: " |
||||
+ updatedAntoraDisplayVersion); |
||||
antoraYml.setVersion(updatedAntoraVersion); |
||||
antoraYml.setPrerelease(updatedAntoraPrerelease); |
||||
antoraYml.setDisplay_version(updatedAntoraDisplayVersion); |
||||
|
||||
FileWriter outputWriter = new FileWriter(antoraYmlFile); |
||||
getYaml().dump(antoraYml, outputWriter); |
||||
} |
||||
|
||||
@InputFile |
||||
public abstract RegularFileProperty getAntoraYmlFile(); |
||||
|
||||
public static class AntoraYml { |
||||
|
||||
private String name; |
||||
|
||||
private String version; |
||||
|
||||
private String prerelease; |
||||
|
||||
private String display_version; |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getVersion() { |
||||
return version; |
||||
} |
||||
|
||||
public void setVersion(String version) { |
||||
this.version = version; |
||||
} |
||||
|
||||
public String getPrerelease() { |
||||
return prerelease; |
||||
} |
||||
|
||||
public void setPrerelease(String prerelease) { |
||||
this.prerelease = prerelease; |
||||
} |
||||
|
||||
public String getDisplay_version() { |
||||
return display_version; |
||||
} |
||||
|
||||
public void setDisplay_version(String display_version) { |
||||
this.display_version = display_version; |
||||
} |
||||
|
||||
} |
||||
|
||||
private Yaml getYaml() { |
||||
Representer representer = new Representer() { |
||||
@Override |
||||
protected NodeTuple representJavaBeanProperty(Object javaBean, |
||||
org.yaml.snakeyaml.introspector.Property property, Object propertyValue, Tag customTag) { |
||||
// Don't write out null values
|
||||
if (propertyValue == null) { |
||||
return null; |
||||
} |
||||
else { |
||||
return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); |
||||
} |
||||
} |
||||
}; |
||||
representer.addClassTag(AntoraYml.class, Tag.MAP); |
||||
DumperOptions ymlOptions = new DumperOptions(); |
||||
ymlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); |
||||
ymlOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED); |
||||
return new Yaml(representer, ymlOptions); |
||||
} |
||||
|
||||
} |
||||
@ -1,70 +0,0 @@
@@ -1,70 +0,0 @@
|
||||
package org.springframework.gradle.github; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
public class RepositoryRef implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 7151218536746822797L; |
||||
|
||||
private String owner; |
||||
|
||||
private String name; |
||||
|
||||
public RepositoryRef() { |
||||
} |
||||
|
||||
public RepositoryRef(String owner, String name) { |
||||
this.owner = owner; |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getOwner() { |
||||
return owner; |
||||
} |
||||
|
||||
public void setOwner(String owner) { |
||||
this.owner = owner; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "RepositoryRef{" + |
||||
"owner='" + owner + '\'' + |
||||
", name='" + name + '\'' + |
||||
'}'; |
||||
} |
||||
|
||||
public static RepositoryRefBuilder owner(String owner) { |
||||
return new RepositoryRefBuilder().owner(owner); |
||||
} |
||||
|
||||
public static final class RepositoryRefBuilder { |
||||
private String owner; |
||||
private String repository; |
||||
|
||||
private RepositoryRefBuilder() { |
||||
} |
||||
|
||||
private RepositoryRefBuilder owner(String owner) { |
||||
this.owner = owner; |
||||
return this; |
||||
} |
||||
|
||||
public RepositoryRefBuilder repository(String repository) { |
||||
this.repository = repository; |
||||
return this; |
||||
} |
||||
|
||||
public RepositoryRef build() { |
||||
return new RepositoryRef(owner, repository); |
||||
} |
||||
} |
||||
} |
||||
@ -1,97 +0,0 @@
@@ -1,97 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-2020 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.changelog; |
||||
|
||||
import java.io.File; |
||||
import java.nio.file.Paths; |
||||
|
||||
import org.gradle.api.Action; |
||||
import org.gradle.api.Plugin; |
||||
import org.gradle.api.Project; |
||||
import org.gradle.api.Task; |
||||
import org.gradle.api.artifacts.Configuration; |
||||
import org.gradle.api.artifacts.DependencySet; |
||||
import org.gradle.api.artifacts.repositories.ExclusiveContentRepository; |
||||
import org.gradle.api.artifacts.repositories.IvyArtifactRepository; |
||||
import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout; |
||||
import org.gradle.api.tasks.JavaExec; |
||||
|
||||
public class GitHubChangelogPlugin implements Plugin<Project> { |
||||
|
||||
public static final String CHANGELOG_GENERATOR_CONFIGURATION_NAME = "changelogGenerator"; |
||||
public static final String RELEASE_NOTES_PATH = "changelog/release-notes.md"; |
||||
|
||||
@Override |
||||
public void apply(Project project) { |
||||
createRepository(project); |
||||
createChangelogGeneratorConfiguration(project); |
||||
project.getTasks().register("generateChangelog", JavaExec.class, new Action<JavaExec>() { |
||||
@Override |
||||
public void execute(JavaExec generateChangelog) { |
||||
File outputFile = project.file(Paths.get(project.getBuildDir().getPath(), RELEASE_NOTES_PATH)); |
||||
outputFile.getParentFile().mkdirs(); |
||||
generateChangelog.setGroup("Release"); |
||||
generateChangelog.setDescription("Generates the changelog"); |
||||
generateChangelog.setWorkingDir(project.getRootDir()); |
||||
generateChangelog.classpath(project.getConfigurations().getAt(CHANGELOG_GENERATOR_CONFIGURATION_NAME)); |
||||
generateChangelog.doFirst(new Action<Task>() { |
||||
@Override |
||||
public void execute(Task task) { |
||||
generateChangelog.args("--spring.config.location=scripts/release/release-notes-sections.yml", project.property("nextVersion"), outputFile.toString()); |
||||
} |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void createChangelogGeneratorConfiguration(Project project) { |
||||
project.getConfigurations().create(CHANGELOG_GENERATOR_CONFIGURATION_NAME, new Action<Configuration>() { |
||||
@Override |
||||
public void execute(Configuration configuration) { |
||||
configuration.defaultDependencies(new Action<DependencySet>() { |
||||
@Override |
||||
public void execute(DependencySet dependencies) { |
||||
dependencies.add(project.getDependencies().create("spring-io:github-changelog-generator:0.0.6")); |
||||
} |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void createRepository(Project project) { |
||||
IvyArtifactRepository repository = project.getRepositories().ivy(new Action<IvyArtifactRepository>() { |
||||
@Override |
||||
public void execute(IvyArtifactRepository repository) { |
||||
repository.setUrl("https://github.com/"); |
||||
repository.patternLayout(new Action<IvyPatternRepositoryLayout>() { |
||||
@Override |
||||
public void execute(IvyPatternRepositoryLayout layout) { |
||||
layout.artifact("[organization]/[artifact]/releases/download/v[revision]/[artifact].[ext]"); |
||||
} |
||||
}); |
||||
repository.getMetadataSources().artifact(); |
||||
} |
||||
}); |
||||
project.getRepositories().exclusiveContent(new Action<ExclusiveContentRepository>() { |
||||
@Override |
||||
public void execute(ExclusiveContentRepository exclusiveContentRepository) { |
||||
exclusiveContentRepository.forRepositories(repository); |
||||
exclusiveContentRepository.filter(descriptor -> descriptor.includeGroup("spring-io")); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
@ -1,271 +0,0 @@
@@ -1,271 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.milestones; |
||||
|
||||
import java.io.IOException; |
||||
import java.time.Instant; |
||||
import java.time.LocalDate; |
||||
import java.time.LocalDateTime; |
||||
import java.time.ZoneId; |
||||
import java.time.ZoneOffset; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.TimeZone; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import com.google.common.reflect.TypeToken; |
||||
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; |
||||
|
||||
public class GitHubMilestoneApi { |
||||
private String baseUrl = "https://api.github.com"; |
||||
|
||||
private OkHttpClient client; |
||||
|
||||
private final Gson gson = new GsonBuilder() |
||||
.registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe()) |
||||
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter().nullSafe()) |
||||
.create(); |
||||
|
||||
public GitHubMilestoneApi() { |
||||
this.client = new OkHttpClient.Builder().build(); |
||||
} |
||||
|
||||
public GitHubMilestoneApi(String gitHubToken) { |
||||
this.client = new OkHttpClient.Builder() |
||||
.addInterceptor(new AuthorizationInterceptor(gitHubToken)) |
||||
.build(); |
||||
} |
||||
|
||||
public void setBaseUrl(String baseUrl) { |
||||
this.baseUrl = baseUrl; |
||||
} |
||||
|
||||
public long findMilestoneNumberByTitle(RepositoryRef repositoryRef, String milestoneTitle) { |
||||
List<Milestone> milestones = this.getMilestones(repositoryRef); |
||||
for (Milestone milestone : milestones) { |
||||
if (milestoneTitle.equals(milestone.getTitle())) { |
||||
return milestone.getNumber(); |
||||
} |
||||
} |
||||
if (milestones.size() <= 100) { |
||||
throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones); |
||||
} |
||||
throw new RuntimeException("It is possible there are too many open milestones (only 100 are supported). Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones); |
||||
} |
||||
|
||||
public List<Milestone> getMilestones(RepositoryRef repositoryRef) { |
||||
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/milestones?per_page=100"; |
||||
Request request = new Request.Builder().get().url(url) |
||||
.build(); |
||||
try { |
||||
Response response = this.client.newCall(request).execute(); |
||||
if (!response.isSuccessful()) { |
||||
throw new RuntimeException("Could not retrieve milestones for repository " + repositoryRef + ". Response " + response); |
||||
} |
||||
return this.gson.fromJson(response.body().charStream(), new TypeToken<List<Milestone>>(){}.getType()); |
||||
} catch (IOException e) { |
||||
throw new RuntimeException("Could not retrieve milestones for repository " + repositoryRef, e); |
||||
} |
||||
} |
||||
|
||||
public boolean isOpenIssuesForMilestoneNumber(RepositoryRef repositoryRef, long milestoneNumber) { |
||||
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/issues?per_page=1&milestone=" + milestoneNumber; |
||||
Request request = new Request.Builder().get().url(url) |
||||
.build(); |
||||
try { |
||||
Response response = this.client.newCall(request).execute(); |
||||
if (!response.isSuccessful()) { |
||||
throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef + ". Response " + response); |
||||
} |
||||
List<Object> issues = this.gson.fromJson(response.body().charStream(), new TypeToken<List<Object>>(){}.getType()); |
||||
return !issues.isEmpty(); |
||||
} catch (IOException e) { |
||||
throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef, e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Check if the given milestone is due today or past due. |
||||
* |
||||
* @param repositoryRef The repository owner/name |
||||
* @param milestoneTitle The title of the milestone whose due date should be checked |
||||
* @return true if the given milestone is due today or past due, false otherwise |
||||
*/ |
||||
public boolean isMilestoneDueToday(RepositoryRef repositoryRef, String milestoneTitle) { |
||||
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() |
||||
+ "/milestones?per_page=100"; |
||||
Request request = new Request.Builder().get().url(url).build(); |
||||
try { |
||||
Response response = this.client.newCall(request).execute(); |
||||
if (!response.isSuccessful()) { |
||||
throw new RuntimeException("Could not find milestone with title " + milestoneTitle + " for repository " |
||||
+ repositoryRef + ". Response " + response); |
||||
} |
||||
List<Milestone> milestones = this.gson.fromJson(response.body().charStream(), |
||||
new TypeToken<List<Milestone>>() { |
||||
}.getType()); |
||||
for (Milestone milestone : milestones) { |
||||
if (milestoneTitle.equals(milestone.getTitle())) { |
||||
LocalDate today = LocalDate.now(); |
||||
return milestone.getDueOn() != null && today.compareTo(milestone.getDueOn().toLocalDate()) >= 0; |
||||
} |
||||
} |
||||
if (milestones.size() <= 100) { |
||||
throw new RuntimeException("Could not find open milestone with title " + milestoneTitle |
||||
+ " for repository " + repositoryRef + " Got " + milestones); |
||||
} |
||||
throw new RuntimeException( |
||||
"It is possible there are too many open milestones open (only 100 are supported). Could not find open milestone with title " |
||||
+ milestoneTitle + " for repository " + repositoryRef + " Got " + milestones); |
||||
} |
||||
catch (IOException e) { |
||||
throw new RuntimeException( |
||||
"Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef, |
||||
e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Calculate the next release version based on the current version. |
||||
* |
||||
* The current version must conform to the pattern MAJOR.MINOR.PATCH-SNAPSHOT. If the |
||||
* current version is a snapshot of a patch release, then the patch release will be |
||||
* returned. For example, if the current version is 5.6.1-SNAPSHOT, then 5.6.1 will be |
||||
* returned. If the current version is a snapshot of a version that is not GA (i.e the |
||||
* PATCH segment is 0), then GitHub will be queried to find the next milestone or |
||||
* release candidate. If no pre-release versions are found, then the next version will |
||||
* be assumed to be the GA. |
||||
* @param repositoryRef The repository owner/name |
||||
* @param currentVersion The current project version |
||||
* @return the next matching milestone/release candidate or null if none exist |
||||
*/ |
||||
public String getNextReleaseMilestone(RepositoryRef repositoryRef, String currentVersion) { |
||||
Pattern snapshotPattern = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)-SNAPSHOT$"); |
||||
Matcher snapshotVersion = snapshotPattern.matcher(currentVersion); |
||||
|
||||
if (snapshotVersion.find()) { |
||||
String patchSegment = snapshotVersion.group(3); |
||||
String currentVersionNoIdentifier = currentVersion.replace("-SNAPSHOT", ""); |
||||
if (patchSegment.equals("0")) { |
||||
String nextPreRelease = getNextPreRelease(repositoryRef, currentVersionNoIdentifier); |
||||
return nextPreRelease != null ? nextPreRelease : currentVersionNoIdentifier; |
||||
} |
||||
else { |
||||
return currentVersionNoIdentifier; |
||||
} |
||||
} |
||||
else { |
||||
throw new IllegalStateException( |
||||
"Cannot calculate next release version because the current project version does not conform to the expected format"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Calculate the next pre-release version (milestone or release candidate) based on |
||||
* the current version. |
||||
* |
||||
* The current version must conform to the pattern MAJOR.MINOR.PATCH. If no matching |
||||
* milestone or release candidate is found in GitHub then it will return null. |
||||
* @param repositoryRef The repository owner/name |
||||
* @param currentVersionNoIdentifier The current project version without any |
||||
* identifier |
||||
* @return the next matching milestone/release candidate or null if none exist |
||||
*/ |
||||
private String getNextPreRelease(RepositoryRef repositoryRef, String currentVersionNoIdentifier) { |
||||
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() |
||||
+ "/milestones?per_page=100"; |
||||
Request request = new Request.Builder().get().url(url).build(); |
||||
try { |
||||
Response response = this.client.newCall(request).execute(); |
||||
if (!response.isSuccessful()) { |
||||
throw new RuntimeException( |
||||
"Could not get milestones for repository " + repositoryRef + ". Response " + response); |
||||
} |
||||
List<Milestone> milestones = this.gson.fromJson(response.body().charStream(), |
||||
new TypeToken<List<Milestone>>() { |
||||
}.getType()); |
||||
Optional<String> nextPreRelease = milestones.stream().map(Milestone::getTitle) |
||||
.filter(m -> m.startsWith(currentVersionNoIdentifier + "-")) |
||||
.min((m1, m2) -> { |
||||
Pattern preReleasePattern = Pattern.compile("^.*-([A-Z]+)([0-9]+)$"); |
||||
Matcher matcher1 = preReleasePattern.matcher(m1); |
||||
Matcher matcher2 = preReleasePattern.matcher(m2); |
||||
matcher1.find(); |
||||
matcher2.find(); |
||||
if (!matcher1.group(1).equals(matcher2.group(1))) { |
||||
return m1.compareTo(m2); |
||||
} |
||||
else { |
||||
return Integer.valueOf(matcher1.group(2)).compareTo(Integer.valueOf(matcher2.group(2))); |
||||
} |
||||
}); |
||||
return nextPreRelease.orElse(null); |
||||
} |
||||
catch (IOException e) { |
||||
throw new RuntimeException("Could not find open milestones with for repository " + repositoryRef, e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Create a milestone. |
||||
* |
||||
* @param repository The repository owner/name |
||||
* @param milestone The milestone containing a title and due date |
||||
*/ |
||||
public void createMilestone(RepositoryRef repository, Milestone milestone) { |
||||
String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/milestones"; |
||||
String json = this.gson.toJson(milestone); |
||||
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 milestone %s for repository %s/%s. Got response %s", |
||||
milestone.getTitle(), repository.getOwner(), repository.getName(), response)); |
||||
} |
||||
} catch (IOException ex) { |
||||
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s", |
||||
milestone.getTitle(), repository.getOwner(), repository.getName()), ex); |
||||
} |
||||
} |
||||
|
||||
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); |
||||
} |
||||
} |
||||
} |
||||
@ -1,110 +0,0 @@
@@ -1,110 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.milestones; |
||||
|
||||
import org.gradle.api.Action; |
||||
import org.gradle.api.DefaultTask; |
||||
import org.gradle.api.file.RegularFileProperty; |
||||
import org.gradle.api.tasks.Input; |
||||
import org.gradle.api.tasks.InputFile; |
||||
import org.gradle.api.tasks.Optional; |
||||
import org.gradle.api.tasks.OutputFile; |
||||
import org.gradle.api.tasks.TaskAction; |
||||
import org.gradle.work.DisableCachingByDefault; |
||||
import org.yaml.snakeyaml.Yaml; |
||||
import org.yaml.snakeyaml.constructor.Constructor; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
|
||||
import org.springframework.gradle.github.RepositoryRef; |
||||
|
||||
@DisableCachingByDefault(because = "the due date needs to be checked every time in case it changes") |
||||
public abstract class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask { |
||||
@Input |
||||
private RepositoryRef repository = new RepositoryRef(); |
||||
|
||||
@Input @Optional |
||||
private String milestoneTitle; |
||||
|
||||
@InputFile @Optional |
||||
public abstract RegularFileProperty getNextVersionFile(); |
||||
|
||||
@Input @Optional |
||||
private String gitHubAccessToken; |
||||
|
||||
@OutputFile |
||||
public abstract RegularFileProperty getIsOpenIssuesFile(); |
||||
|
||||
private GitHubMilestoneApi milestones = new GitHubMilestoneApi(); |
||||
|
||||
@TaskAction |
||||
public void checkHasNoOpenIssues() throws IOException { |
||||
if (this.milestoneTitle == null) { |
||||
File nextVersionFile = getNextVersionFile().getAsFile().get(); |
||||
Yaml yaml = new Yaml(new Constructor(NextVersionYml.class)); |
||||
NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile)); |
||||
String nextVersion = nextVersionYml.getVersion(); |
||||
if (nextVersion == null) { |
||||
throw new IllegalArgumentException( |
||||
"Could not find version property in provided file " + nextVersionFile.getName()); |
||||
} |
||||
this.milestoneTitle = nextVersion; |
||||
} |
||||
long milestoneNumber = this.milestones.findMilestoneNumberByTitle(this.repository, this.milestoneTitle); |
||||
boolean isOpenIssues = this.milestones.isOpenIssuesForMilestoneNumber(this.repository, milestoneNumber); |
||||
Path isOpenIssuesPath = getIsOpenIssuesFile().getAsFile().get().toPath(); |
||||
Files.writeString(isOpenIssuesPath, String.valueOf(isOpenIssues)); |
||||
if (isOpenIssues) { |
||||
System.out.println("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber); |
||||
} |
||||
else { |
||||
System.out.println("The repository " + this.repository + " has no open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber); |
||||
} |
||||
} |
||||
|
||||
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 getMilestoneTitle() { |
||||
return milestoneTitle; |
||||
} |
||||
|
||||
public void setMilestoneTitle(String milestoneTitle) { |
||||
this.milestoneTitle = milestoneTitle; |
||||
} |
||||
|
||||
public String getGitHubAccessToken() { |
||||
return gitHubAccessToken; |
||||
} |
||||
|
||||
public void setGitHubAccessToken(String gitHubAccessToken) { |
||||
this.gitHubAccessToken = gitHubAccessToken; |
||||
this.milestones = new GitHubMilestoneApi(gitHubAccessToken); |
||||
} |
||||
} |
||||
@ -1,93 +0,0 @@
@@ -1,93 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.milestones; |
||||
|
||||
import org.gradle.api.Action; |
||||
import org.gradle.api.DefaultTask; |
||||
import org.gradle.api.file.RegularFileProperty; |
||||
import org.gradle.api.tasks.Input; |
||||
import org.gradle.api.tasks.Optional; |
||||
import org.gradle.api.tasks.OutputFile; |
||||
import org.gradle.api.tasks.TaskAction; |
||||
import org.yaml.snakeyaml.DumperOptions; |
||||
import org.yaml.snakeyaml.Yaml; |
||||
import org.yaml.snakeyaml.nodes.Tag; |
||||
import org.yaml.snakeyaml.representer.Representer; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileWriter; |
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.gradle.github.RepositoryRef; |
||||
|
||||
public abstract class GitHubMilestoneNextReleaseTask extends DefaultTask { |
||||
|
||||
@Input |
||||
private RepositoryRef repository = new RepositoryRef(); |
||||
|
||||
@Input |
||||
@Optional |
||||
private String gitHubAccessToken; |
||||
|
||||
private GitHubMilestoneApi milestones = new GitHubMilestoneApi(); |
||||
|
||||
@TaskAction |
||||
public void calculateNextReleaseMilestone() throws IOException { |
||||
String currentVersion = getProject().getVersion().toString(); |
||||
String nextPreRelease = this.milestones.getNextReleaseMilestone(this.repository, currentVersion); |
||||
System.out.println("The next release milestone is: " + nextPreRelease); |
||||
NextVersionYml nextVersionYml = new NextVersionYml(); |
||||
nextVersionYml.setVersion(nextPreRelease); |
||||
File outputFile = getNextReleaseFile().get().getAsFile(); |
||||
FileWriter outputWriter = new FileWriter(outputFile); |
||||
Yaml yaml = getYaml(); |
||||
yaml.dump(nextVersionYml, outputWriter); |
||||
} |
||||
|
||||
@OutputFile |
||||
public abstract RegularFileProperty getNextReleaseFile(); |
||||
|
||||
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; |
||||
this.milestones = new GitHubMilestoneApi(gitHubAccessToken); |
||||
} |
||||
|
||||
private Yaml getYaml() { |
||||
Representer representer = new Representer(); |
||||
representer.addClassTag(NextVersionYml.class, Tag.MAP); |
||||
DumperOptions ymlOptions = new DumperOptions(); |
||||
ymlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); |
||||
return new Yaml(representer, ymlOptions); |
||||
} |
||||
|
||||
} |
||||
@ -1,102 +0,0 @@
@@ -1,102 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.milestones; |
||||
|
||||
import org.gradle.api.Action; |
||||
import org.gradle.api.DefaultTask; |
||||
import org.gradle.api.file.RegularFileProperty; |
||||
import org.gradle.api.tasks.Input; |
||||
import org.gradle.api.tasks.InputFile; |
||||
import org.gradle.api.tasks.Optional; |
||||
import org.gradle.api.tasks.OutputFile; |
||||
import org.gradle.api.tasks.TaskAction; |
||||
import org.gradle.work.DisableCachingByDefault; |
||||
import org.yaml.snakeyaml.Yaml; |
||||
import org.yaml.snakeyaml.constructor.Constructor; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
|
||||
import org.springframework.gradle.github.RepositoryRef; |
||||
|
||||
@DisableCachingByDefault(because = "the due date needs to be checked every time in case it changes") |
||||
public abstract class GitHubMilestoneNextVersionDueTodayTask extends DefaultTask { |
||||
|
||||
@Input |
||||
private RepositoryRef repository = new RepositoryRef(); |
||||
|
||||
@Input |
||||
@Optional |
||||
private String gitHubAccessToken; |
||||
|
||||
@InputFile |
||||
public abstract RegularFileProperty getNextVersionFile(); |
||||
|
||||
@OutputFile |
||||
public abstract RegularFileProperty getIsDueTodayFile(); |
||||
|
||||
private GitHubMilestoneApi milestones = new GitHubMilestoneApi(); |
||||
|
||||
@TaskAction |
||||
public void checkReleaseDueToday() throws IOException { |
||||
File nextVersionFile = getNextVersionFile().getAsFile().get(); |
||||
Yaml yaml = new Yaml(new Constructor(NextVersionYml.class)); |
||||
NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile)); |
||||
String nextVersion = nextVersionYml.getVersion(); |
||||
if (nextVersion == null) { |
||||
throw new IllegalArgumentException( |
||||
"Could not find version property in provided file " + nextVersionFile.getName()); |
||||
} |
||||
boolean milestoneDueToday = this.milestones.isMilestoneDueToday(this.repository, nextVersion); |
||||
Path isDueTodayPath = getIsDueTodayFile().getAsFile().get().toPath(); |
||||
Files.writeString(isDueTodayPath, String.valueOf(milestoneDueToday)); |
||||
if (milestoneDueToday) { |
||||
System.out.println("The milestone with the title " + nextVersion + " in the repository " + this.repository |
||||
+ " is due today"); |
||||
} |
||||
else { |
||||
System.out.println("The milestone with the title " + nextVersion + " in the repository " |
||||
+ this.repository + " is not due yet"); |
||||
} |
||||
|
||||
} |
||||
|
||||
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; |
||||
this.milestones = new GitHubMilestoneApi(gitHubAccessToken); |
||||
} |
||||
|
||||
} |
||||
@ -1,73 +0,0 @@
@@ -1,73 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.milestones; |
||||
|
||||
import org.gradle.api.Action; |
||||
import org.gradle.api.Plugin; |
||||
import org.gradle.api.Project; |
||||
import org.gradle.api.tasks.TaskProvider; |
||||
|
||||
import org.springframework.gradle.github.RepositoryRef; |
||||
|
||||
public class GitHubMilestonePlugin implements Plugin<Project> { |
||||
@Override |
||||
public void apply(Project project) { |
||||
TaskProvider<GitHubMilestoneNextReleaseTask> nextReleaseMilestoneTask = project.getTasks().register("gitHubNextReleaseMilestone", GitHubMilestoneNextReleaseTask.class, (gitHubMilestoneNextReleaseTask) -> { |
||||
gitHubMilestoneNextReleaseTask.doNotTrackState("API call to GitHub needs to check for new milestones every time"); |
||||
gitHubMilestoneNextReleaseTask.setGroup("Release"); |
||||
gitHubMilestoneNextReleaseTask.setDescription("Calculates the next release version based on the current version and outputs it to a yaml file"); |
||||
gitHubMilestoneNextReleaseTask.getNextReleaseFile() |
||||
.fileProvider(project.provider(() -> project.file("next-release.yml"))); |
||||
if (project.hasProperty("gitHubAccessToken")) { |
||||
gitHubMilestoneNextReleaseTask |
||||
.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); |
||||
} |
||||
}); |
||||
project.getTasks().register("gitHubCheckMilestoneHasNoOpenIssues", GitHubMilestoneHasNoOpenIssuesTask.class, (githubCheckMilestoneHasNoOpenIssues) -> { |
||||
githubCheckMilestoneHasNoOpenIssues.setGroup("Release"); |
||||
githubCheckMilestoneHasNoOpenIssues.setDescription("Checks if there are any open issues for the specified repository and milestone"); |
||||
githubCheckMilestoneHasNoOpenIssues.getIsOpenIssuesFile().value(project.getLayout().getBuildDirectory().file("github/milestones/is-open-issues")); |
||||
githubCheckMilestoneHasNoOpenIssues.setMilestoneTitle((String) project.findProperty("nextVersion")); |
||||
if (!project.hasProperty("nextVersion")) { |
||||
githubCheckMilestoneHasNoOpenIssues.getNextVersionFile().convention( |
||||
nextReleaseMilestoneTask.flatMap(GitHubMilestoneNextReleaseTask::getNextReleaseFile)); |
||||
} |
||||
if (project.hasProperty("gitHubAccessToken")) { |
||||
githubCheckMilestoneHasNoOpenIssues.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); |
||||
} |
||||
}); |
||||
project.getTasks().register("gitHubCheckNextVersionDueToday", GitHubMilestoneNextVersionDueTodayTask.class, (gitHubMilestoneNextVersionDueTodayTask) -> { |
||||
gitHubMilestoneNextVersionDueTodayTask.setGroup("Release"); |
||||
gitHubMilestoneNextVersionDueTodayTask.setDescription("Checks if the next release version is due today or past due, will fail if the next version is not due yet"); |
||||
gitHubMilestoneNextVersionDueTodayTask.getIsDueTodayFile().value(project.getLayout().getBuildDirectory().file("github/milestones/is-due-today")); |
||||
gitHubMilestoneNextVersionDueTodayTask.getNextVersionFile().convention( |
||||
nextReleaseMilestoneTask.flatMap(GitHubMilestoneNextReleaseTask::getNextReleaseFile)); |
||||
if (project.hasProperty("gitHubAccessToken")) { |
||||
gitHubMilestoneNextVersionDueTodayTask |
||||
.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); |
||||
} |
||||
}); |
||||
project.getTasks().register("scheduleNextRelease", ScheduleNextReleaseTask.class, (scheduleNextRelease) -> { |
||||
scheduleNextRelease.doNotTrackState("API call to GitHub needs to check for new milestones every time"); |
||||
scheduleNextRelease.setGroup("Release"); |
||||
scheduleNextRelease.setDescription("Schedule the next release (even months only) or release train (series of milestones starting in January or July) based on the current version"); |
||||
|
||||
scheduleNextRelease.setVersion((String) project.findProperty("nextVersion")); |
||||
scheduleNextRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); |
||||
}); |
||||
} |
||||
} |
||||
@ -1,23 +0,0 @@
@@ -1,23 +0,0 @@
|
||||
package org.springframework.gradle.github.milestones; |
||||
|
||||
import java.io.IOException; |
||||
import java.time.LocalDate; |
||||
|
||||
import com.google.gson.TypeAdapter; |
||||
import com.google.gson.stream.JsonReader; |
||||
import com.google.gson.stream.JsonWriter; |
||||
|
||||
/** |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
class LocalDateAdapter extends TypeAdapter<LocalDate> { |
||||
@Override |
||||
public void write(JsonWriter jsonWriter, LocalDate localDate) throws IOException { |
||||
jsonWriter.value(localDate.toString()); |
||||
} |
||||
|
||||
@Override |
||||
public LocalDate read(JsonReader jsonReader) throws IOException { |
||||
return LocalDate.parse(jsonReader.nextString()); |
||||
} |
||||
} |
||||
@ -1,25 +0,0 @@
@@ -1,25 +0,0 @@
|
||||
package org.springframework.gradle.github.milestones; |
||||
|
||||
import java.io.IOException; |
||||
import java.time.LocalDateTime; |
||||
import java.time.ZoneOffset; |
||||
import java.time.format.DateTimeFormatter; |
||||
|
||||
import com.google.gson.TypeAdapter; |
||||
import com.google.gson.stream.JsonReader; |
||||
import com.google.gson.stream.JsonWriter; |
||||
|
||||
/** |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
class LocalDateTimeAdapter extends TypeAdapter<LocalDateTime> { |
||||
@Override |
||||
public void write(JsonWriter jsonWriter, LocalDateTime localDateTime) throws IOException { |
||||
jsonWriter.value(localDateTime.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); |
||||
} |
||||
|
||||
@Override |
||||
public LocalDateTime read(JsonReader jsonReader) throws IOException { |
||||
return LocalDateTime.parse(jsonReader.nextString(), DateTimeFormatter.ISO_ZONED_DATE_TIME); |
||||
} |
||||
} |
||||
@ -1,67 +0,0 @@
@@ -1,67 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.milestones; |
||||
|
||||
import com.google.gson.annotations.SerializedName; |
||||
|
||||
import java.time.LocalDateTime; |
||||
import java.util.Date; |
||||
|
||||
/** |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public class Milestone { |
||||
private String title; |
||||
|
||||
private Long number; |
||||
|
||||
@SerializedName("due_on") |
||||
private LocalDateTime dueOn; |
||||
|
||||
public String getTitle() { |
||||
return title; |
||||
} |
||||
|
||||
public void setTitle(String title) { |
||||
this.title = title; |
||||
} |
||||
|
||||
public Long getNumber() { |
||||
return number; |
||||
} |
||||
|
||||
public void setNumber(Long number) { |
||||
this.number = number; |
||||
} |
||||
|
||||
public LocalDateTime getDueOn() { |
||||
return dueOn; |
||||
} |
||||
|
||||
public void setDueOn(LocalDateTime dueOn) { |
||||
this.dueOn = dueOn; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "Milestone{" + |
||||
"title='" + title + '\'' + |
||||
", number='" + number + '\'' + |
||||
", dueOn='" + dueOn + '\'' + |
||||
'}'; |
||||
} |
||||
} |
||||
@ -1,29 +0,0 @@
@@ -1,29 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.milestones; |
||||
|
||||
public class NextVersionYml { |
||||
private String version; |
||||
|
||||
public String getVersion() { |
||||
return version; |
||||
} |
||||
|
||||
public void setVersion(String version) { |
||||
this.version = version; |
||||
} |
||||
} |
||||
@ -1,147 +0,0 @@
@@ -1,147 +0,0 @@
|
||||
/* |
||||
* 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.milestones; |
||||
|
||||
import java.time.LocalDate; |
||||
import java.time.LocalTime; |
||||
|
||||
import org.gradle.api.Action; |
||||
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 ScheduleNextReleaseTask extends DefaultTask { |
||||
@Input |
||||
private RepositoryRef repository = new RepositoryRef(); |
||||
|
||||
@Input |
||||
private String gitHubAccessToken; |
||||
|
||||
@Input |
||||
private String version; |
||||
|
||||
@Input |
||||
private Integer weekOfMonth; |
||||
|
||||
@Input |
||||
private Integer dayOfWeek; |
||||
|
||||
@TaskAction |
||||
public void scheduleNextRelease() { |
||||
GitHubMilestoneApi gitHubMilestoneApi = new GitHubMilestoneApi(this.gitHubAccessToken); |
||||
String nextReleaseMilestone = gitHubMilestoneApi.getNextReleaseMilestone(this.repository, this.version); |
||||
|
||||
// If the next release contains a dash (e.g. 5.6.0-RC1), it is already scheduled
|
||||
if (nextReleaseMilestone.contains("-")) { |
||||
return; |
||||
} |
||||
|
||||
// Check to see if a scheduled GA version already exists
|
||||
boolean hasExistingMilestone = gitHubMilestoneApi.getMilestones(this.repository).stream() |
||||
.anyMatch(milestone -> nextReleaseMilestone.equals(milestone.getTitle())); |
||||
if (hasExistingMilestone) { |
||||
return; |
||||
} |
||||
|
||||
// Next milestone is either a patch version or minor version
|
||||
// Note: Major versions will be handled like minor and get a release
|
||||
// train which can be manually updated to match the desired schedule.
|
||||
if (nextReleaseMilestone.endsWith(".0")) { |
||||
// Create M1, M2, M3, RC1 and GA milestones for release train
|
||||
getReleaseTrain(nextReleaseMilestone).getTrainDates().forEach((milestoneTitle, dueOn) -> { |
||||
Milestone milestone = new Milestone(); |
||||
milestone.setTitle(milestoneTitle); |
||||
// Note: GitHub seems to store full date/time as UTC then displays
|
||||
// as a date (no time) in your timezone, which means the date will
|
||||
// not always be the same date as we intend.
|
||||
// Using 12pm/noon UTC allows GitHub to schedule and display the
|
||||
// correct date.
|
||||
milestone.setDueOn(dueOn.atTime(LocalTime.NOON)); |
||||
gitHubMilestoneApi.createMilestone(this.repository, milestone); |
||||
}); |
||||
} else { |
||||
// Create GA milestone for patch release on the next even month
|
||||
LocalDate startDate = LocalDate.now(); |
||||
LocalDate dueOn = getReleaseTrain(nextReleaseMilestone).getNextReleaseDate(startDate); |
||||
Milestone milestone = new Milestone(); |
||||
milestone.setTitle(nextReleaseMilestone); |
||||
milestone.setDueOn(dueOn.atTime(LocalTime.NOON)); |
||||
gitHubMilestoneApi.createMilestone(this.repository, milestone); |
||||
} |
||||
} |
||||
|
||||
private SpringReleaseTrain getReleaseTrain(String nextReleaseMilestone) { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.nextTrain() |
||||
.version(nextReleaseMilestone) |
||||
.weekOfMonth(this.weekOfMonth) |
||||
.dayOfWeek(this.dayOfWeek) |
||||
.build(); |
||||
|
||||
return new SpringReleaseTrain(releaseTrainSpec); |
||||
} |
||||
|
||||
public RepositoryRef getRepository() { |
||||
return this.repository; |
||||
} |
||||
|
||||
public void repository(Action<RepositoryRef> repository) { |
||||
repository.execute(this.repository); |
||||
} |
||||
|
||||
public void setRepository(RepositoryRef repository) { |
||||
this.repository = repository; |
||||
} |
||||
|
||||
public String getGitHubAccessToken() { |
||||
return this.gitHubAccessToken; |
||||
} |
||||
|
||||
public void setGitHubAccessToken(String gitHubAccessToken) { |
||||
this.gitHubAccessToken = gitHubAccessToken; |
||||
} |
||||
|
||||
public String getVersion() { |
||||
return this.version; |
||||
} |
||||
|
||||
public void setVersion(String version) { |
||||
this.version = version; |
||||
} |
||||
|
||||
public Integer getWeekOfMonth() { |
||||
return weekOfMonth; |
||||
} |
||||
|
||||
public void setWeekOfMonth(Integer weekOfMonth) { |
||||
this.weekOfMonth = weekOfMonth; |
||||
} |
||||
|
||||
public Integer getDayOfWeek() { |
||||
return dayOfWeek; |
||||
} |
||||
|
||||
public void setDayOfWeek(Integer dayOfWeek) { |
||||
this.dayOfWeek = dayOfWeek; |
||||
} |
||||
} |
||||
@ -1,136 +0,0 @@
@@ -1,136 +0,0 @@
|
||||
/* |
||||
* 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.milestones; |
||||
|
||||
import java.time.DayOfWeek; |
||||
import java.time.LocalDate; |
||||
import java.time.Month; |
||||
import java.time.Year; |
||||
import java.time.temporal.TemporalAdjuster; |
||||
import java.time.temporal.TemporalAdjusters; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Spring release train generator based on rules contained in a specification. |
||||
* <p> |
||||
* The rules are: |
||||
* <ol> |
||||
* <li>Train 1 (January-May) or 2 (July-November)</li> |
||||
* <li>Version number (e.g. 0.1.2, 1.0.0, etc.)</li> |
||||
* <li>Week of month (1st, 2nd, 3rd, 4th)</li> |
||||
* <li>Day of week (Monday-Friday)</li> |
||||
* <li>Year (e.g. 2020, 2021, etc.)</li> |
||||
* </ol> |
||||
* |
||||
* The release train generated will contain M1, M2, M3, RC1 and GA versions |
||||
* mapped to their respective dates in the train. |
||||
* |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public final class SpringReleaseTrain { |
||||
private final SpringReleaseTrainSpec releaseTrainSpec; |
||||
|
||||
public SpringReleaseTrain(SpringReleaseTrainSpec releaseTrainSpec) { |
||||
this.releaseTrainSpec = releaseTrainSpec; |
||||
} |
||||
|
||||
/** |
||||
* Calculate release train dates based on the release train specification. |
||||
* |
||||
* @return A mapping of release milestones to scheduled release dates |
||||
*/ |
||||
public Map<String, LocalDate> getTrainDates() { |
||||
Map<String, LocalDate> releaseDates = new LinkedHashMap<>(); |
||||
switch (this.releaseTrainSpec.getTrain()) { |
||||
case ONE: |
||||
addTrainDate(releaseDates, "M1", Month.JANUARY); |
||||
addTrainDate(releaseDates, "M2", Month.FEBRUARY); |
||||
addTrainDate(releaseDates, "M3", Month.MARCH); |
||||
addTrainDate(releaseDates, "RC1", Month.APRIL); |
||||
addTrainDate(releaseDates, null, Month.MAY); |
||||
break; |
||||
case TWO: |
||||
addTrainDate(releaseDates, "M1", Month.JULY); |
||||
addTrainDate(releaseDates, "M2", Month.AUGUST); |
||||
addTrainDate(releaseDates, "M3", Month.SEPTEMBER); |
||||
addTrainDate(releaseDates, "RC1", Month.OCTOBER); |
||||
addTrainDate(releaseDates, null, Month.NOVEMBER); |
||||
break; |
||||
} |
||||
|
||||
return releaseDates; |
||||
} |
||||
|
||||
/** |
||||
* Determine if a given date matches the due date of given version. |
||||
* |
||||
* @param version The version number (e.g. 5.6.0-M1, 5.6.0, etc.) |
||||
* @param expectedDate The expected date |
||||
* @return true if the given date matches the due date of the given version, false otherwise |
||||
*/ |
||||
public boolean isTrainDate(String version, LocalDate expectedDate) { |
||||
return expectedDate.isEqual(getTrainDates().get(version)); |
||||
} |
||||
|
||||
/** |
||||
* Calculate the next release date following the given date. |
||||
* <p> |
||||
* The next release date is always on an even month so that a patch release |
||||
* is the month after the GA version of a release train. This method does |
||||
* not consider the year of the release train, only the given start date. |
||||
* |
||||
* @param startDate The start date |
||||
* @return The next release date following the given date |
||||
*/ |
||||
public LocalDate getNextReleaseDate(LocalDate startDate) { |
||||
LocalDate trainDate; |
||||
LocalDate currentDate = startDate; |
||||
do { |
||||
trainDate = calculateReleaseDate( |
||||
Year.of(currentDate.getYear()), |
||||
currentDate.getMonth(), |
||||
this.releaseTrainSpec.getDayOfWeek().getDayOfWeek(), |
||||
this.releaseTrainSpec.getWeekOfMonth().getDayOffset() |
||||
); |
||||
currentDate = currentDate.plusMonths(1); |
||||
} while (!trainDate.isAfter(startDate) || trainDate.getMonthValue() % 2 != 0); |
||||
|
||||
return trainDate; |
||||
} |
||||
|
||||
private void addTrainDate(Map<String, LocalDate> releaseDates, String milestone, Month month) { |
||||
LocalDate releaseDate = calculateReleaseDate( |
||||
this.releaseTrainSpec.getYear(), |
||||
month, |
||||
this.releaseTrainSpec.getDayOfWeek().getDayOfWeek(), |
||||
this.releaseTrainSpec.getWeekOfMonth().getDayOffset() |
||||
); |
||||
String suffix = (milestone == null) ? "" : "-" + milestone; |
||||
releaseDates.put(this.releaseTrainSpec.getVersion() + suffix, releaseDate); |
||||
} |
||||
|
||||
private static LocalDate calculateReleaseDate(Year year, Month month, DayOfWeek dayOfWeek, int dayOffset) { |
||||
TemporalAdjuster nextMonday = TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY); |
||||
TemporalAdjuster nextDayOfWeek = TemporalAdjusters.nextOrSame(dayOfWeek); |
||||
|
||||
LocalDate firstDayOfMonth = year.atMonth(month).atDay(1); |
||||
LocalDate firstMondayOfMonth = firstDayOfMonth.with(nextMonday); |
||||
|
||||
return firstMondayOfMonth.with(nextDayOfWeek).plusDays(dayOffset); |
||||
} |
||||
} |
||||
@ -1,205 +0,0 @@
@@ -1,205 +0,0 @@
|
||||
/* |
||||
* 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.milestones; |
||||
|
||||
import java.time.LocalDate; |
||||
import java.time.Month; |
||||
import java.time.Year; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A specification for a release train. |
||||
* |
||||
* @author Steve Riesenberg |
||||
* @see SpringReleaseTrain |
||||
*/ |
||||
public final class SpringReleaseTrainSpec { |
||||
private final Train train; |
||||
private final String version; |
||||
private final WeekOfMonth weekOfMonth; |
||||
private final DayOfWeek dayOfWeek; |
||||
private final Year year; |
||||
|
||||
public SpringReleaseTrainSpec(Train train, String version, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek, Year year) { |
||||
this.train = train; |
||||
this.version = version; |
||||
this.weekOfMonth = weekOfMonth; |
||||
this.dayOfWeek = dayOfWeek; |
||||
this.year = year; |
||||
} |
||||
|
||||
public Train getTrain() { |
||||
return train; |
||||
} |
||||
|
||||
public String getVersion() { |
||||
return version; |
||||
} |
||||
|
||||
public WeekOfMonth getWeekOfMonth() { |
||||
return weekOfMonth; |
||||
} |
||||
|
||||
public DayOfWeek getDayOfWeek() { |
||||
return dayOfWeek; |
||||
} |
||||
|
||||
public Year getYear() { |
||||
return year; |
||||
} |
||||
|
||||
public static Builder builder() { |
||||
return new Builder(); |
||||
} |
||||
|
||||
public enum WeekOfMonth { |
||||
FIRST(0), SECOND(7), THIRD(14), FOURTH(21); |
||||
|
||||
private final int dayOffset; |
||||
|
||||
WeekOfMonth(int dayOffset) { |
||||
this.dayOffset = dayOffset; |
||||
} |
||||
|
||||
public int getDayOffset() { |
||||
return dayOffset; |
||||
} |
||||
} |
||||
|
||||
public enum DayOfWeek { |
||||
MONDAY(java.time.DayOfWeek.MONDAY), |
||||
TUESDAY(java.time.DayOfWeek.TUESDAY), |
||||
WEDNESDAY(java.time.DayOfWeek.WEDNESDAY), |
||||
THURSDAY(java.time.DayOfWeek.THURSDAY), |
||||
FRIDAY(java.time.DayOfWeek.FRIDAY); |
||||
|
||||
private final java.time.DayOfWeek dayOfWeek; |
||||
|
||||
DayOfWeek(java.time.DayOfWeek dayOfWeek) { |
||||
this.dayOfWeek = dayOfWeek; |
||||
} |
||||
|
||||
public java.time.DayOfWeek getDayOfWeek() { |
||||
return dayOfWeek; |
||||
} |
||||
} |
||||
|
||||
public enum Train { |
||||
ONE, TWO |
||||
} |
||||
|
||||
public static class Builder { |
||||
private Train train; |
||||
private String version; |
||||
private WeekOfMonth weekOfMonth; |
||||
private DayOfWeek dayOfWeek; |
||||
private Year year; |
||||
|
||||
private Builder() { |
||||
} |
||||
|
||||
public Builder train(int train) { |
||||
this.train = switch (train) { |
||||
case 1 -> Train.ONE; |
||||
case 2 -> Train.TWO; |
||||
default -> throw new IllegalArgumentException("Invalid train: " + train); |
||||
}; |
||||
return this; |
||||
} |
||||
|
||||
public Builder train(Train train) { |
||||
this.train = train; |
||||
return this; |
||||
} |
||||
|
||||
public Builder nextTrain() { |
||||
// Search for next train starting with this month
|
||||
return nextTrain(LocalDate.now().withDayOfMonth(1)); |
||||
} |
||||
|
||||
public Builder nextTrain(LocalDate startDate) { |
||||
Train nextTrain = null; |
||||
|
||||
// Search for next train from a given start date
|
||||
LocalDate currentDate = startDate; |
||||
while (nextTrain == null) { |
||||
if (currentDate.getMonth() == Month.JANUARY) { |
||||
nextTrain = Train.ONE; |
||||
} else if (currentDate.getMonth() == Month.JULY) { |
||||
nextTrain = Train.TWO; |
||||
} |
||||
|
||||
currentDate = currentDate.plusMonths(1); |
||||
} |
||||
|
||||
return train(nextTrain).year(currentDate.getYear()); |
||||
} |
||||
|
||||
public Builder version(String version) { |
||||
this.version = version; |
||||
return this; |
||||
} |
||||
|
||||
public Builder weekOfMonth(int weekOfMonth) { |
||||
this.weekOfMonth = switch (weekOfMonth) { |
||||
case 1 -> WeekOfMonth.FIRST; |
||||
case 2 -> WeekOfMonth.SECOND; |
||||
case 3 -> WeekOfMonth.THIRD; |
||||
case 4 -> WeekOfMonth.FOURTH; |
||||
default -> throw new IllegalArgumentException("Invalid weekOfMonth: " + weekOfMonth); |
||||
}; |
||||
return this; |
||||
} |
||||
|
||||
public Builder weekOfMonth(WeekOfMonth weekOfMonth) { |
||||
this.weekOfMonth = weekOfMonth; |
||||
return this; |
||||
} |
||||
|
||||
public Builder dayOfWeek(int dayOfWeek) { |
||||
this.dayOfWeek = switch (dayOfWeek) { |
||||
case 1 -> DayOfWeek.MONDAY; |
||||
case 2 -> DayOfWeek.TUESDAY; |
||||
case 3 -> DayOfWeek.WEDNESDAY; |
||||
case 4 -> DayOfWeek.THURSDAY; |
||||
case 5 -> DayOfWeek.FRIDAY; |
||||
default -> throw new IllegalArgumentException("Invalid dayOfWeek: " + dayOfWeek); |
||||
}; |
||||
return this; |
||||
} |
||||
|
||||
public Builder dayOfWeek(DayOfWeek dayOfWeek) { |
||||
this.dayOfWeek = dayOfWeek; |
||||
return this; |
||||
} |
||||
|
||||
public Builder year(int year) { |
||||
this.year = Year.of(year); |
||||
return this; |
||||
} |
||||
|
||||
public SpringReleaseTrainSpec build() { |
||||
Assert.notNull(train, "train cannot be null"); |
||||
Assert.notNull(version, "version cannot be null"); |
||||
Assert.notNull(weekOfMonth, "weekOfMonth cannot be null"); |
||||
Assert.notNull(dayOfWeek, "dayOfWeek cannot be null"); |
||||
Assert.notNull(year, "year cannot be null"); |
||||
return new SpringReleaseTrainSpec(train, version, weekOfMonth, dayOfWeek, year); |
||||
} |
||||
} |
||||
} |
||||
@ -1,130 +0,0 @@
@@ -1,130 +0,0 @@
|
||||
/* |
||||
* 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; |
||||
} |
||||
} |
||||
@ -1,84 +0,0 @@
@@ -1,84 +0,0 @@
|
||||
/* |
||||
* 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.Action; |
||||
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 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 getBranch() { |
||||
return branch; |
||||
} |
||||
|
||||
public void setBranch(String branch) { |
||||
this.branch = branch; |
||||
} |
||||
|
||||
public String getWorkflowId() { |
||||
return workflowId; |
||||
} |
||||
|
||||
public void setWorkflowId(String workflowId) { |
||||
this.workflowId = workflowId; |
||||
} |
||||
} |
||||
@ -1,98 +0,0 @@
@@ -1,98 +0,0 @@
|
||||
/* |
||||
* 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); |
||||
} |
||||
} |
||||
} |
||||
@ -1,91 +0,0 @@
@@ -1,91 +0,0 @@
|
||||
/* |
||||
* 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); |
||||
} |
||||
} |
||||
} |
||||
@ -1,54 +0,0 @@
@@ -1,54 +0,0 @@
|
||||
/* |
||||
* 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.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, (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=..."); |
||||
} |
||||
}); |
||||
|
||||
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.setWorkflowId((String) project.findProperty("workflowId")); |
||||
dispatchGitHubWorkflow.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); |
||||
}); |
||||
} |
||||
} |
||||
@ -1,156 +0,0 @@
@@ -1,156 +0,0 @@
|
||||
/* |
||||
* 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); |
||||
} |
||||
} |
||||
} |
||||
@ -1,51 +0,0 @@
@@ -1,51 +0,0 @@
|
||||
/* |
||||
* 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<String, Object> inputs; |
||||
|
||||
public WorkflowDispatch() { |
||||
} |
||||
|
||||
public WorkflowDispatch(String ref, Map<String, Object> inputs) { |
||||
this.ref = ref; |
||||
this.inputs = inputs; |
||||
} |
||||
|
||||
public String getRef() { |
||||
return ref; |
||||
} |
||||
|
||||
public void setRef(String ref) { |
||||
this.ref = ref; |
||||
} |
||||
|
||||
public Map<String, Object> getInputs() { |
||||
return inputs; |
||||
} |
||||
|
||||
public void setInputs(Map<String, Object> inputs) { |
||||
this.inputs = inputs; |
||||
} |
||||
} |
||||
@ -1,82 +0,0 @@
@@ -1,82 +0,0 @@
|
||||
/* |
||||
* 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); |
||||
} |
||||
} |
||||
} |
||||
@ -1,53 +0,0 @@
@@ -1,53 +0,0 @@
|
||||
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 + '\'' + |
||||
'}'; |
||||
} |
||||
} |
||||
@ -1,123 +0,0 @@
@@ -1,123 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-2020 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.sagan; |
||||
|
||||
import java.util.regex.Pattern; |
||||
|
||||
/** |
||||
* Domain object for creating a new release version. |
||||
*/ |
||||
public class Release { |
||||
private String version; |
||||
|
||||
private ReleaseStatus status; |
||||
|
||||
private boolean current; |
||||
|
||||
private String referenceDocUrl; |
||||
|
||||
private String apiDocUrl; |
||||
|
||||
public String getVersion() { |
||||
return version; |
||||
} |
||||
|
||||
public void setVersion(String version) { |
||||
this.version = version; |
||||
} |
||||
|
||||
public ReleaseStatus getStatus() { |
||||
return status; |
||||
} |
||||
|
||||
public void setStatus(ReleaseStatus status) { |
||||
this.status = status; |
||||
} |
||||
|
||||
public boolean isCurrent() { |
||||
return current; |
||||
} |
||||
|
||||
public void setCurrent(boolean current) { |
||||
this.current = current; |
||||
} |
||||
|
||||
public String getReferenceDocUrl() { |
||||
return referenceDocUrl; |
||||
} |
||||
|
||||
public void setReferenceDocUrl(String referenceDocUrl) { |
||||
this.referenceDocUrl = referenceDocUrl; |
||||
} |
||||
|
||||
public String getApiDocUrl() { |
||||
return apiDocUrl; |
||||
} |
||||
|
||||
public void setApiDocUrl(String apiDocUrl) { |
||||
this.apiDocUrl = apiDocUrl; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "Release{" + |
||||
"version='" + version + '\'' + |
||||
", status=" + status + |
||||
", current=" + current + |
||||
", referenceDocUrl='" + referenceDocUrl + '\'' + |
||||
", apiDocUrl='" + apiDocUrl + '\'' + |
||||
'}'; |
||||
} |
||||
|
||||
public enum ReleaseStatus { |
||||
/** |
||||
* Unstable version with limited support |
||||
*/ |
||||
SNAPSHOT, |
||||
/** |
||||
* Pre-Release version meant to be tested by the community |
||||
*/ |
||||
PRERELEASE, |
||||
/** |
||||
* Release Generally Available on public artifact repositories and enjoying full support from maintainers |
||||
*/ |
||||
GENERAL_AVAILABILITY; |
||||
|
||||
private static final Pattern PRERELEASE_PATTERN = Pattern.compile("[A-Za-z0-9\\.\\-]+?(M|RC)\\d+"); |
||||
|
||||
private static final String SNAPSHOT_SUFFIX = "SNAPSHOT"; |
||||
|
||||
/** |
||||
* Parse the ReleaseStatus from a String |
||||
* @param version a project version |
||||
* @return the release status for this version |
||||
*/ |
||||
public static ReleaseStatus parse(String version) { |
||||
if (version == null) { |
||||
throw new IllegalArgumentException("version cannot be null"); |
||||
} |
||||
if (version.endsWith(SNAPSHOT_SUFFIX)) { |
||||
return SNAPSHOT; |
||||
} |
||||
if (PRERELEASE_PATTERN.matcher(version).matches()) { |
||||
return PRERELEASE; |
||||
} |
||||
return GENERAL_AVAILABILITY; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,93 +0,0 @@
@@ -1,93 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-2020 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.sagan; |
||||
|
||||
import com.google.gson.Gson; |
||||
import okhttp3.*; |
||||
|
||||
import java.io.IOException; |
||||
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://api.spring.io"; |
||||
|
||||
private OkHttpClient client; |
||||
private Gson gson = new Gson(); |
||||
|
||||
public SaganApi(String username, String gitHubToken) { |
||||
this.client = new OkHttpClient.Builder() |
||||
.addInterceptor(new BasicInterceptor(username, gitHubToken)) |
||||
.build(); |
||||
} |
||||
|
||||
public void setBaseUrl(String baseUrl) { |
||||
this.baseUrl = baseUrl; |
||||
} |
||||
|
||||
public void createReleaseForProject(Release release, String projectName) { |
||||
String url = this.baseUrl + "/projects/" + projectName + "/releases"; |
||||
String releaseJsonString = gson.toJson(release); |
||||
RequestBody body = RequestBody.create(MediaType.parse("application/json"), releaseJsonString); |
||||
Request request = new Request.Builder() |
||||
.url(url) |
||||
.post(body) |
||||
.build(); |
||||
try { |
||||
Response response = this.client.newCall(request).execute(); |
||||
if (!response.isSuccessful()) { |
||||
throw new RuntimeException("Could not create release " + release + ". Got response " + response); |
||||
} |
||||
} catch (IOException fail) { |
||||
throw new RuntimeException("Could not create release " + release, fail); |
||||
} |
||||
} |
||||
|
||||
public void deleteReleaseForProject(String release, String projectName) { |
||||
String url = this.baseUrl + "/projects/" + projectName + "/releases/" + release; |
||||
Request request = new Request.Builder() |
||||
.url(url) |
||||
.delete() |
||||
.build(); |
||||
try { |
||||
Response response = this.client.newCall(request).execute(); |
||||
if (!response.isSuccessful()) { |
||||
throw new RuntimeException("Could not delete release " + release + ". Got response " + response); |
||||
} |
||||
} catch (IOException fail) { |
||||
throw new RuntimeException("Could not delete release " + release, fail); |
||||
} |
||||
} |
||||
|
||||
private static class BasicInterceptor implements Interceptor { |
||||
|
||||
private final String token; |
||||
|
||||
public BasicInterceptor(String username, String token) { |
||||
this.token = Base64.getEncoder().encodeToString((username + ":" + token).getBytes()); |
||||
} |
||||
|
||||
@Override |
||||
public okhttp3.Response intercept(Chain chain) throws IOException { |
||||
Request request = chain.request().newBuilder() |
||||
.addHeader("Authorization", "Basic " + this.token).build(); |
||||
return chain.proceed(request); |
||||
} |
||||
} |
||||
} |
||||
@ -1,109 +0,0 @@
@@ -1,109 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-2020 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.sagan; |
||||
|
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
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; |
||||
import org.springframework.util.Assert; |
||||
|
||||
public class SaganCreateReleaseTask extends DefaultTask { |
||||
|
||||
private static final Pattern VERSION_PATTERN = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-.+)?$"); |
||||
|
||||
@Input |
||||
private String gitHubAccessToken; |
||||
@Input |
||||
private String version; |
||||
@Input |
||||
private String apiDocUrl; |
||||
@Input |
||||
private String referenceDocUrl; |
||||
@Input |
||||
private String projectName; |
||||
|
||||
@TaskAction |
||||
public void saganCreateRelease() { |
||||
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")) { |
||||
Matcher versionMatcher = VERSION_PATTERN.matcher(this.version); |
||||
Assert.isTrue(versionMatcher.matches(), "Version " + this.version + " does not match expected pattern"); |
||||
String majorVersion = versionMatcher.group(1); |
||||
String minorVersion = versionMatcher.group(2); |
||||
String majorMinorVersion = String.format("%s.%s-SNAPSHOT", majorVersion, minorVersion); |
||||
referenceDocUrl = this.referenceDocUrl.replace("{version}", majorMinorVersion); |
||||
} |
||||
|
||||
SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken); |
||||
Release release = new Release(); |
||||
release.setVersion(this.version); |
||||
release.setApiDocUrl(this.apiDocUrl); |
||||
release.setReferenceDocUrl(referenceDocUrl); |
||||
sagan.createReleaseForProject(release, this.projectName); |
||||
} |
||||
|
||||
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 getApiDocUrl() { |
||||
return apiDocUrl; |
||||
} |
||||
|
||||
public void setApiDocUrl(String apiDocUrl) { |
||||
this.apiDocUrl = apiDocUrl; |
||||
} |
||||
|
||||
public String getReferenceDocUrl() { |
||||
return referenceDocUrl; |
||||
} |
||||
|
||||
public void setReferenceDocUrl(String referenceDocUrl) { |
||||
this.referenceDocUrl = referenceDocUrl; |
||||
} |
||||
|
||||
public String getProjectName() { |
||||
return projectName; |
||||
} |
||||
|
||||
public void setProjectName(String projectName) { |
||||
this.projectName = projectName; |
||||
} |
||||
|
||||
} |
||||
@ -1,68 +0,0 @@
@@ -1,68 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-2020 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.sagan; |
||||
|
||||
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 |
||||
private String gitHubAccessToken; |
||||
@Input |
||||
private String version; |
||||
@Input |
||||
private String projectName; |
||||
|
||||
@TaskAction |
||||
public void saganCreateRelease() { |
||||
GitHubUserApi github = new GitHubUserApi(this.gitHubAccessToken); |
||||
User user = github.getUser(); |
||||
|
||||
SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken); |
||||
sagan.deleteReleaseForProject(this.version, this.projectName); |
||||
} |
||||
|
||||
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 getProjectName() { |
||||
return projectName; |
||||
} |
||||
|
||||
public void setProjectName(String projectName) { |
||||
this.projectName = projectName; |
||||
} |
||||
|
||||
} |
||||
@ -1,47 +0,0 @@
@@ -1,47 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-2020 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.sagan; |
||||
|
||||
import io.spring.gradle.convention.Utils; |
||||
import org.gradle.api.*; |
||||
|
||||
public class SaganPlugin implements Plugin<Project> { |
||||
@Override |
||||
public void apply(Project project) { |
||||
project.getTasks().register("saganCreateRelease", SaganCreateReleaseTask.class, new Action<SaganCreateReleaseTask>() { |
||||
@Override |
||||
public void execute(SaganCreateReleaseTask saganCreateVersion) { |
||||
saganCreateVersion.setGroup("Release"); |
||||
saganCreateVersion.setDescription("Creates a new version for the specified project on spring.io"); |
||||
saganCreateVersion.setVersion((String) project.findProperty("nextVersion")); |
||||
saganCreateVersion.setProjectName(Utils.getProjectName(project)); |
||||
saganCreateVersion.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); |
||||
} |
||||
}); |
||||
project.getTasks().register("saganDeleteRelease", SaganDeleteReleaseTask.class, new Action<SaganDeleteReleaseTask>() { |
||||
@Override |
||||
public void execute(SaganDeleteReleaseTask saganDeleteVersion) { |
||||
saganDeleteVersion.setGroup("Release"); |
||||
saganDeleteVersion.setDescription("Delete a version for the specified project on spring.io"); |
||||
saganDeleteVersion.setVersion((String) project.findProperty("previousVersion")); |
||||
saganDeleteVersion.setProjectName(Utils.getProjectName(project)); |
||||
saganDeleteVersion.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
} |
||||
@ -1,44 +0,0 @@
@@ -1,44 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.security.convention.versions; |
||||
|
||||
import org.gradle.api.Action; |
||||
import org.gradle.api.Plugin; |
||||
import org.gradle.api.Project; |
||||
|
||||
public class UpdateProjectVersionPlugin implements Plugin<Project> { |
||||
@Override |
||||
public void apply(Project project) { |
||||
project.getTasks().register("updateProjectVersion", UpdateProjectVersionTask.class, new Action<UpdateProjectVersionTask>() { |
||||
@Override |
||||
public void execute(UpdateProjectVersionTask updateProjectVersionTask) { |
||||
updateProjectVersionTask.setGroup("Release"); |
||||
updateProjectVersionTask.setDescription("Updates the project version to the next release in gradle.properties"); |
||||
updateProjectVersionTask.dependsOn("gitHubNextReleaseMilestone"); |
||||
updateProjectVersionTask.getNextVersionFile().fileProvider(project.provider(() -> project.file("next-release.yml"))); |
||||
} |
||||
}); |
||||
project.getTasks().register("updateToSnapshotVersion", UpdateToSnapshotVersionTask.class, new Action<UpdateToSnapshotVersionTask>() { |
||||
@Override |
||||
public void execute(UpdateToSnapshotVersionTask updateToSnapshotVersionTask) { |
||||
updateToSnapshotVersionTask.setGroup("Release"); |
||||
updateToSnapshotVersionTask.setDescription( |
||||
"Updates the project version to the next snapshot in gradle.properties"); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
@ -1,63 +0,0 @@
@@ -1,63 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.security.convention.versions; |
||||
|
||||
import org.gradle.api.DefaultTask; |
||||
import org.gradle.api.Project; |
||||
import org.gradle.api.file.RegularFileProperty; |
||||
import org.gradle.api.tasks.Input; |
||||
import org.gradle.api.tasks.InputFile; |
||||
import org.gradle.api.tasks.Optional; |
||||
import org.gradle.api.tasks.TaskAction; |
||||
import org.yaml.snakeyaml.Yaml; |
||||
import org.yaml.snakeyaml.constructor.Constructor; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.FileNotFoundException; |
||||
|
||||
import org.springframework.gradle.github.milestones.NextVersionYml; |
||||
|
||||
public abstract class UpdateProjectVersionTask extends DefaultTask { |
||||
|
||||
@InputFile |
||||
public abstract RegularFileProperty getNextVersionFile(); |
||||
|
||||
@TaskAction |
||||
public void checkReleaseDueToday() throws FileNotFoundException { |
||||
File nextVersionFile = getNextVersionFile().getAsFile().get(); |
||||
Yaml yaml = new Yaml(new Constructor(NextVersionYml.class)); |
||||
NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile)); |
||||
String nextVersion = nextVersionYml.getVersion(); |
||||
if (nextVersion == null) { |
||||
throw new IllegalArgumentException( |
||||
"Could not find version property in provided file " + nextVersionFile.getName()); |
||||
} |
||||
String currentVersion = getProject().getVersion().toString(); |
||||
File gradlePropertiesFile = getProject().getRootProject().file(Project.GRADLE_PROPERTIES); |
||||
if (!gradlePropertiesFile.exists()) { |
||||
return; |
||||
} |
||||
System.out.println("Updating the project version in " + Project.GRADLE_PROPERTIES + " from " + currentVersion |
||||
+ " to " + nextVersion); |
||||
FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> { |
||||
gradlePropertiesText = gradlePropertiesText.replace("version=" + currentVersion, "version=" + nextVersion); |
||||
return gradlePropertiesText; |
||||
}); |
||||
} |
||||
|
||||
} |
||||
@ -1,68 +0,0 @@
@@ -1,68 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-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.security.convention.versions; |
||||
|
||||
import org.gradle.api.DefaultTask; |
||||
import org.gradle.api.Project; |
||||
import org.gradle.api.tasks.TaskAction; |
||||
|
||||
import java.io.File; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
public abstract class UpdateToSnapshotVersionTask extends DefaultTask { |
||||
|
||||
private static final String RELEASE_VERSION_PATTERN = "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-M\\d+|-RC\\d+)?$"; |
||||
|
||||
@TaskAction |
||||
public void updateToSnapshotVersion() { |
||||
String currentVersion = getProject().getVersion().toString(); |
||||
File gradlePropertiesFile = getProject().getRootProject().file(Project.GRADLE_PROPERTIES); |
||||
if (!gradlePropertiesFile.exists()) { |
||||
return; |
||||
} |
||||
String nextVersion = calculateNextSnapshotVersion(currentVersion); |
||||
System.out.println("Updating the project version in " + Project.GRADLE_PROPERTIES + " from " + currentVersion |
||||
+ " to " + nextVersion); |
||||
FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> { |
||||
gradlePropertiesText = gradlePropertiesText.replace("version=" + currentVersion, "version=" + nextVersion); |
||||
return gradlePropertiesText; |
||||
}); |
||||
} |
||||
|
||||
private String calculateNextSnapshotVersion(String currentVersion) { |
||||
Pattern releaseVersionPattern = Pattern.compile(RELEASE_VERSION_PATTERN); |
||||
Matcher releaseVersion = releaseVersionPattern.matcher(currentVersion); |
||||
|
||||
if (releaseVersion.find()) { |
||||
String majorSegment = releaseVersion.group(1); |
||||
String minorSegment = releaseVersion.group(2); |
||||
String patchSegment = releaseVersion.group(3); |
||||
String modifier = releaseVersion.group(4); |
||||
if (modifier == null) { |
||||
patchSegment = String.valueOf(Integer.parseInt(patchSegment) + 1); |
||||
} |
||||
System.out.println("modifier = " + modifier); |
||||
return String.format("%s.%s.%s-SNAPSHOT", majorSegment, minorSegment, patchSegment); |
||||
} |
||||
else { |
||||
throw new IllegalStateException( |
||||
"Cannot calculate next snapshot version because the current project version does not conform to the expected format"); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,85 +0,0 @@
@@ -1,85 +0,0 @@
|
||||
/* |
||||
* Copyright 2019-2020 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 io.spring.gradle.convention.sagan; |
||||
|
||||
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.sagan.Release; |
||||
import org.springframework.gradle.sagan.SaganApi; |
||||
|
||||
import java.nio.charset.Charset; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
|
||||
public class SaganApiTests { |
||||
private MockWebServer server; |
||||
|
||||
private SaganApi sagan; |
||||
|
||||
private String baseUrl; |
||||
|
||||
@BeforeEach |
||||
public void setup() throws Exception { |
||||
this.server = new MockWebServer(); |
||||
this.server.start(); |
||||
this.sagan = new SaganApi("user", "mock-oauth-token"); |
||||
this.baseUrl = this.server.url("/api").toString(); |
||||
this.sagan.setBaseUrl(this.baseUrl); |
||||
} |
||||
|
||||
@AfterEach |
||||
public void cleanup() throws Exception { |
||||
this.server.shutdown(); |
||||
} |
||||
|
||||
@Test |
||||
public void createWhenValidThenNoException() throws Exception { |
||||
this.server.enqueue(new MockResponse()); |
||||
Release release = new Release(); |
||||
release.setVersion("5.6.0-SNAPSHOT"); |
||||
release.setApiDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/api/"); |
||||
release.setReferenceDocUrl("https://docs.spring.io/spring-security/reference/{version}/index.html"); |
||||
this.sagan.createReleaseForProject(release, "spring-security"); |
||||
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 dXNlcjptb2NrLW9hdXRoLXRva2Vu"); |
||||
assertThat(request.getBody().readString(Charset.defaultCharset())).isEqualToIgnoringWhitespace("{\n" + |
||||
" \"version\":\"5.6.0-SNAPSHOT\",\n" + |
||||
" \"current\":false,\n" + |
||||
" \"referenceDocUrl\":\"https://docs.spring.io/spring-security/reference/{version}/index.html\",\n" + |
||||
" \"apiDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/api/\"\n" + |
||||
"}"); |
||||
} |
||||
|
||||
@Test |
||||
public void deleteWhenValidThenNoException() throws Exception { |
||||
this.server.enqueue(new MockResponse()); |
||||
this.sagan.deleteReleaseForProject("5.6.0-SNAPSHOT", "spring-security"); |
||||
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 dXNlcjptb2NrLW9hdXRoLXRva2Vu"); |
||||
assertThat(request.getBody().readString(Charset.defaultCharset())).isEmpty(); |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -1,245 +0,0 @@
@@ -1,245 +0,0 @@
|
||||
/* |
||||
* 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.milestones; |
||||
|
||||
import java.time.LocalDate; |
||||
import java.time.Year; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.CsvSource; |
||||
|
||||
import org.springframework.gradle.github.milestones.SpringReleaseTrainSpec.Train; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public class SpringReleaseTrainTests { |
||||
@ParameterizedTest |
||||
@CsvSource({ |
||||
"2019-12-31, ONE, 2020", |
||||
"2020-01-01, ONE, 2020", |
||||
"2020-01-31, ONE, 2020", |
||||
"2020-02-01, TWO, 2020", |
||||
"2020-07-31, TWO, 2020", |
||||
"2020-08-01, ONE, 2021" |
||||
}) |
||||
public void nextTrainWhenBoundaryConditionsThenSuccess(LocalDate startDate, Train expectedTrain, Year expectedYear) { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.nextTrain(startDate) |
||||
.version("1.0.0") |
||||
.weekOfMonth(2) |
||||
.dayOfWeek(2) |
||||
.build(); |
||||
assertThat(releaseTrainSpec.getTrain()).isEqualTo(expectedTrain); |
||||
assertThat(releaseTrainSpec.getYear()).isEqualTo(expectedYear); |
||||
} |
||||
|
||||
@Test |
||||
public void getTrainDatesWhenTrainOneIsSecondTuesdayOf2020ThenSuccess() { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.train(1) |
||||
.version("1.0.0") |
||||
.weekOfMonth(2) |
||||
.dayOfWeek(2) |
||||
.year(2020) |
||||
.build(); |
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec); |
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates(); |
||||
assertThat(trainDates).hasSize(5); |
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2020, 1, 14)); |
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2020, 2, 11)); |
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2020, 3, 10)); |
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2020, 4, 14)); |
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2020, 5, 12)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTrainDatesWhenTrainTwoIsSecondTuesdayOf2020ThenSuccess() { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.train(2) |
||||
.version("1.0.0") |
||||
.weekOfMonth(2) |
||||
.dayOfWeek(2) |
||||
.year(2020) |
||||
.build(); |
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec); |
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates(); |
||||
assertThat(trainDates).hasSize(5); |
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2020, 7, 14)); |
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2020, 8, 11)); |
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2020, 9, 15)); |
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2020, 10, 13)); |
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2020, 11, 10)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTrainDatesWhenTrainOneIsSecondTuesdayOf2022ThenSuccess() { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.train(1) |
||||
.version("1.0.0") |
||||
.weekOfMonth(2) |
||||
.dayOfWeek(2) |
||||
.year(2022) |
||||
.build(); |
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec); |
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates(); |
||||
assertThat(trainDates).hasSize(5); |
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 1, 11)); |
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 2, 15)); |
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 3, 15)); |
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 4, 12)); |
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 5, 10)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTrainDatesWhenTrainTwoIsSecondTuesdayOf2022ThenSuccess() { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.train(2) |
||||
.version("1.0.0") |
||||
.weekOfMonth(2) |
||||
.dayOfWeek(2) |
||||
.year(2022) |
||||
.build(); |
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec); |
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates(); |
||||
assertThat(trainDates).hasSize(5); |
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 7, 12)); |
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 8, 9)); |
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 9, 13)); |
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 10, 11)); |
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 11, 15)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTrainDatesWhenTrainOneIsThirdMondayOf2022ThenSuccess() { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.train(1) |
||||
.version("1.0.0") |
||||
.weekOfMonth(3) |
||||
.dayOfWeek(1) |
||||
.year(2022) |
||||
.build(); |
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec); |
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates(); |
||||
assertThat(trainDates).hasSize(5); |
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 1, 17)); |
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 2, 21)); |
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 3, 21)); |
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 4, 18)); |
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 5, 16)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTrainDatesWhenTrainTwoIsThirdMondayOf2022ThenSuccess() { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.train(2) |
||||
.version("1.0.0") |
||||
.weekOfMonth(3) |
||||
.dayOfWeek(1) |
||||
.year(2022) |
||||
.build(); |
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec); |
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates(); |
||||
assertThat(trainDates).hasSize(5); |
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 7, 18)); |
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 8, 15)); |
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 9, 19)); |
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 10, 17)); |
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 11, 21)); |
||||
} |
||||
|
||||
@Test |
||||
public void isTrainDateWhenTrainOneIsThirdMondayOf2022ThenSuccess() { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.train(1) |
||||
.version("1.0.0") |
||||
.weekOfMonth(3) |
||||
.dayOfWeek(1) |
||||
.year(2022) |
||||
.build(); |
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec); |
||||
for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) { |
||||
assertThat(releaseTrain.isTrainDate("1.0.0-M1", LocalDate.of(2022, 1, dayOfMonth))).isEqualTo(dayOfMonth == 17); |
||||
} |
||||
for (int dayOfMonth = 1; dayOfMonth <= 28; dayOfMonth++) { |
||||
assertThat(releaseTrain.isTrainDate("1.0.0-M2", LocalDate.of(2022, 2, dayOfMonth))).isEqualTo(dayOfMonth == 21); |
||||
} |
||||
for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) { |
||||
assertThat(releaseTrain.isTrainDate("1.0.0-M3", LocalDate.of(2022, 3, dayOfMonth))).isEqualTo(dayOfMonth == 21); |
||||
} |
||||
for (int dayOfMonth = 1; dayOfMonth <= 30; dayOfMonth++) { |
||||
assertThat(releaseTrain.isTrainDate("1.0.0-RC1", LocalDate.of(2022, 4, dayOfMonth))).isEqualTo(dayOfMonth == 18); |
||||
} |
||||
for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) { |
||||
assertThat(releaseTrain.isTrainDate("1.0.0", LocalDate.of(2022, 5, dayOfMonth))).isEqualTo(dayOfMonth == 16); |
||||
} |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@CsvSource({ |
||||
"2022-01-01, 2022-02-21", |
||||
"2022-02-01, 2022-02-21", |
||||
"2022-02-21, 2022-04-18", |
||||
"2022-03-01, 2022-04-18", |
||||
"2022-04-01, 2022-04-18", |
||||
"2022-04-18, 2022-06-20", |
||||
"2022-05-01, 2022-06-20", |
||||
"2022-06-01, 2022-06-20", |
||||
"2022-06-20, 2022-08-15", |
||||
"2022-07-01, 2022-08-15", |
||||
"2022-08-01, 2022-08-15", |
||||
"2022-08-15, 2022-10-17", |
||||
"2022-09-01, 2022-10-17", |
||||
"2022-10-01, 2022-10-17", |
||||
"2022-10-17, 2022-12-19", |
||||
"2022-11-01, 2022-12-19", |
||||
"2022-12-01, 2022-12-19", |
||||
"2022-12-19, 2023-02-20" |
||||
}) |
||||
public void getNextReleaseDateWhenBoundaryConditionsThenSuccess(LocalDate startDate, LocalDate expectedDate) { |
||||
SpringReleaseTrainSpec releaseTrainSpec = |
||||
SpringReleaseTrainSpec.builder() |
||||
.train(1) |
||||
.version("1.0.0") |
||||
.weekOfMonth(3) |
||||
.dayOfWeek(1) |
||||
.year(2022) |
||||
.build(); |
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec); |
||||
assertThat(releaseTrain.getNextReleaseDate(startDate)).isEqualTo(expectedDate); |
||||
} |
||||
} |
||||
@ -1,89 +0,0 @@
@@ -1,89 +0,0 @@
|
||||
/* |
||||
* 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<String, Object> 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)); |
||||
} |
||||
} |
||||
@ -1,155 +0,0 @@
@@ -1,155 +0,0 @@
|
||||
/* |
||||
* 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.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 GitHubReleaseApiTests { |
||||
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.baseUrl = this.server.url("/api").toString(); |
||||
this.gitHubReleaseApi = new GitHubReleaseApi("mock-oauth-token"); |
||||
this.gitHubReleaseApi.setBaseUrl(this.baseUrl); |
||||
this.repository = new RepositoryRef("spring-projects", "spring-security"); |
||||
} |
||||
|
||||
@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.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().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.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build())); |
||||
} |
||||
} |
||||
@ -1,106 +0,0 @@
@@ -1,106 +0,0 @@
|
||||
/* |
||||
* 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
|
||||
} |
||||
} |
||||
Loading…
Reference in new issue