@@ -30,4 +30,4 @@ gpg.executable=/usr/local/bin/gpg1
|
||||
|
||||
# A GitHub token with user:email, read:user and read:org scopes.
|
||||
# User needs to be part of the Spring team on GitHub as well.
|
||||
sagan.key=
|
||||
project-service.key=
|
||||
|
||||
@@ -80,8 +80,8 @@ A job should have started. Click on the active job, and then click on *Open Blue
|
||||
|Create new release versions and tickets for upcoming version
|
||||
|`$ tracker setup-next $trainIteration.next`
|
||||
|
||||
|Update versions in Sagan. `$targets` is given as comma separated lists of code names, without spaces. E.g. `Moore,Neumann`
|
||||
|`$ sagan update $releasetrains`
|
||||
|Update versions in the Projects Service. `$targets` is given as comma separated lists of code names, without spaces. E.g. `Moore,Neumann`
|
||||
|`$ projects update $releasetrains`
|
||||
|
||||
|Create list of docs for release announcements
|
||||
|`$ announcement $trainIteration`
|
||||
@@ -120,7 +120,7 @@ Add an `application-local.properties` to the project root and add the following
|
||||
* `gpg.passphrase` - The password of your GPG key.
|
||||
* `gpg.executable` - Path to your GPG executable, typically `/usr/local/MacGPG2/bin/gpg2`
|
||||
or `/usr/local/bin/gpg`.
|
||||
* `sagan.key` - Sagan authentication token. Must be a valid GitHub token. Can be the same
|
||||
* `project-service.key` - Project Service authentication token. Must be a valid GitHub token. Can be the same
|
||||
as `git.password` when using a GitHub token as password.
|
||||
|
||||
After that, run the `verify` command (`$ verify`) to verify your settings (authentication,
|
||||
@@ -146,7 +146,7 @@ See `application-local.template` for details.
|
||||
2+| *Post-release tasks*
|
||||
|Close JIRA tickets and GitHub release tickets. |`$ tracker close $trainIteration`
|
||||
|Create new release versions and tickets for upcoming version |`$ tracker setup-next $trainIteration.next`
|
||||
|Update versions in Sagan. `$targets` is given as comma separated lists of code names, without spaces. E.g. `Moore,Neumann` |`$ sagan update $releasetrains`
|
||||
|Update versions in Projects Service. `$targets` is given as comma separated lists of code names, without spaces. E.g. `Moore,Neumann` |`$ projects update $releasetrains`
|
||||
|Create list of docs for release announcements |`$ announcement $trainIteration`
|
||||
|===
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.springframework.data.release.build.BuildOperations;
|
||||
import org.springframework.data.release.deployment.DeploymentOperations;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.issues.github.GitHub;
|
||||
import org.springframework.data.release.sagan.SaganClient;
|
||||
import org.springframework.data.release.projectservice.ProjectService;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
@@ -46,7 +46,7 @@ class VerifyCommands extends TimedCommand {
|
||||
@NonNull GitHub github;
|
||||
@NonNull DeploymentOperations deployment;
|
||||
@NonNull BuildOperations build;
|
||||
@NonNull SaganClient saganClient;
|
||||
@NonNull ProjectService projectService;
|
||||
@NonNull Logger logger;
|
||||
|
||||
@CliCommand("verify")
|
||||
@@ -72,9 +72,9 @@ class VerifyCommands extends TimedCommand {
|
||||
github.verifyAuthentication();
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(module) || "sagan".equals(module)) {
|
||||
// Sagan Verification
|
||||
saganClient.verifyAuthentication();
|
||||
if (ObjectUtils.isEmpty(module) || "projects".equals(module)) {
|
||||
// Projects Service Verification
|
||||
projectService.verifyAuthentication();
|
||||
}
|
||||
|
||||
logger.log("Verify", "All settings are verified. You can ship a release now.");
|
||||
|
||||
@@ -25,21 +25,8 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -54,6 +41,7 @@ import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.LogCommand;
|
||||
import org.eclipse.jgit.api.ResetCommand.ResetType;
|
||||
import org.eclipse.jgit.api.errors.EmptyCommitException;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.api.errors.RefNotFoundException;
|
||||
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
@@ -75,16 +63,7 @@ import org.springframework.data.release.issues.IssueTracker;
|
||||
import org.springframework.data.release.issues.Ticket;
|
||||
import org.springframework.data.release.issues.TicketReference;
|
||||
import org.springframework.data.release.issues.TicketStatus;
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.Gpg;
|
||||
import org.springframework.data.release.model.Iteration;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.ProjectAware;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
import org.springframework.data.release.model.ReleaseTrains;
|
||||
import org.springframework.data.release.model.Train;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.model.*;
|
||||
import org.springframework.data.release.utils.ExecutionUtils;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.data.util.Pair;
|
||||
@@ -151,6 +130,17 @@ public class GitOperations {
|
||||
* @param update whether to fetch an update from origin.
|
||||
*/
|
||||
public void checkout(Train train, boolean update) {
|
||||
checkout(train, update, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks out all projects of the given {@link Train}.
|
||||
*
|
||||
* @param train
|
||||
* @param update whether to fetch an update from origin.
|
||||
* @param reset whether to reset HARD.
|
||||
*/
|
||||
public void checkout(Train train, boolean update, boolean reset) {
|
||||
|
||||
Assert.notNull(train, "Train must not be null!");
|
||||
|
||||
@@ -158,47 +148,52 @@ public class GitOperations {
|
||||
update(train);
|
||||
}
|
||||
|
||||
AtomicBoolean mainSwitch = new AtomicBoolean();
|
||||
ExecutionUtils.run(executor, train, module -> {
|
||||
|
||||
Project project = module.getProject();
|
||||
|
||||
doWithGit(project, git -> {
|
||||
|
||||
ModuleIteration gaIteration = train.getModuleIteration(project, Iteration.GA);
|
||||
Optional<Tag> gaTag = findTagFor(project, ArtifactVersion.of(gaIteration));
|
||||
Branch branch = getBranch(train, module, project);
|
||||
checkoutBranch(project, git, branch);
|
||||
|
||||
if (!gaTag.isPresent()) {
|
||||
logger.log(project, "Checking out main branch as no GA release tag could be found!");
|
||||
if (reset) {
|
||||
reset(project, branch);
|
||||
}
|
||||
|
||||
Branch branch = gaTag.isPresent() ? Branch.from(module) : Branch.MAIN;
|
||||
|
||||
CheckoutCommand command = git.checkout().setName(branch.toString());
|
||||
|
||||
if (!branchExists(project, branch)) {
|
||||
|
||||
logger.log(project, "git checkout -b %s --track origin/%s", branch, branch);
|
||||
command.setCreateBranch(true)//
|
||||
.setStartPoint("origin/".concat(branch.toString()))//
|
||||
.call();
|
||||
} else {
|
||||
|
||||
logger.log(project, "git checkout %s", branch);
|
||||
command.call();
|
||||
}
|
||||
|
||||
reset(project, branch);
|
||||
});
|
||||
});
|
||||
|
||||
if (mainSwitch.get()) {
|
||||
logger.warn(train,
|
||||
"Successfully checked out projects. There were switches to main for certain projects. This happens if the train has no branches yet.");
|
||||
} else {
|
||||
logger.log(train, "Successfully checked out projects.");
|
||||
logger.log(train, "Successfully checked out projects.");
|
||||
|
||||
}
|
||||
|
||||
private Branch getBranch(Train train, Module module, Project project) {
|
||||
|
||||
ModuleIteration gaIteration = train.getModuleIteration(project, Iteration.GA);
|
||||
Optional<Tag> gaTag = findTagFor(project, ArtifactVersion.of(gaIteration));
|
||||
|
||||
if (!gaTag.isPresent()) {
|
||||
logger.log(project, "Checking out main branch as no GA release tag could be found!");
|
||||
}
|
||||
|
||||
return gaTag.isPresent() ? Branch.from(module) : Branch.MAIN;
|
||||
}
|
||||
|
||||
private void checkoutBranch(Project project, Git git, Branch branch) throws GitAPIException {
|
||||
|
||||
CheckoutCommand command = git.checkout().setName(branch.toString());
|
||||
|
||||
if (!branchExists(project, branch)) {
|
||||
|
||||
logger.log(project, "git checkout -b %s --track origin/%s", branch, branch);
|
||||
command.setCreateBranch(true)//
|
||||
.setStartPoint("origin/".concat(branch.toString()))//
|
||||
.call();
|
||||
} else {
|
||||
|
||||
logger.log(project, "git checkout %s", branch);
|
||||
command.call();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.springframework.data.util.Streamable;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class Modules implements Streamable<Module> {
|
||||
public class Modules implements Streamable<Module> {
|
||||
|
||||
private final Collection<Module> modules;
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ public class ReleaseTrains {
|
||||
return TRAINS.stream() //
|
||||
.filter(it -> it.getName().equalsIgnoreCase(name)) //
|
||||
.findFirst() //
|
||||
.orElse(null);
|
||||
.orElseThrow(() -> new IllegalArgumentException("Cannot resolve " + name + " to a Release Train"));
|
||||
}
|
||||
|
||||
public static Train getTrainByCalver(Version calver) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 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
|
||||
* http://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,
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -25,6 +25,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
@@ -42,10 +43,10 @@ import com.jayway.jsonpath.JsonPath;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class DefaultSaganClient implements SaganClient {
|
||||
class DefaultProjectClient implements ProjectService {
|
||||
|
||||
RestOperations operations;
|
||||
SaganProperties properties;
|
||||
ProjectServiceResources configuration;
|
||||
Logger logger;
|
||||
|
||||
/*
|
||||
@@ -55,7 +56,7 @@ class DefaultSaganClient implements SaganClient {
|
||||
@Override
|
||||
public String getProjectMetadata(MaintainedVersion version) {
|
||||
|
||||
URI resource = properties.getProjectMetadataResource(version);
|
||||
URI resource = configuration.getProjectMetadataResource(version);
|
||||
|
||||
logger.log(version.getProject(), "Getting project metadata for version %s from %s…", version.getVersion(),
|
||||
resource);
|
||||
@@ -70,9 +71,9 @@ class DefaultSaganClient implements SaganClient {
|
||||
@Override
|
||||
public String getProjectMetadata(Project project) {
|
||||
|
||||
URI resource = properties.getProjectReleasesResource(project);
|
||||
URI resource = configuration.getProjectReleasesResource(project);
|
||||
|
||||
logger.log(project, "Getting project releases from %s…", resource);
|
||||
logger.log(project, "Getting listed project versions from %s…", resource);
|
||||
|
||||
return operations.getForObject(resource, String.class);
|
||||
}
|
||||
@@ -84,26 +85,38 @@ class DefaultSaganClient implements SaganClient {
|
||||
@Override
|
||||
public void updateProjectMetadata(Project project, MaintainedVersions versions) {
|
||||
|
||||
URI resource = properties.getProjectReleasesResource(project);
|
||||
URI resource = configuration.getProjectReleasesResource(project);
|
||||
|
||||
String versionsString = versions.stream()//
|
||||
.map(MaintainedVersion::getVersion)//
|
||||
.map(Object::toString) //
|
||||
.collect(Collectors.joining(", "));
|
||||
List<String> versionsToRetain = versions.stream() //
|
||||
.map(version -> new ProjectMetadata(version, versions)).map(ProjectMetadata::getVersion)
|
||||
List<String> versionsToRetain = getVersionsToWrite(versions).map(ProjectMetadata::getVersion)
|
||||
.collect(Collectors.toList());
|
||||
List<String> versionsInSagan = new ArrayList<>();
|
||||
|
||||
logger.log(project, "Updating project version to %s via %s…", versionsString, resource);
|
||||
|
||||
// Delete all existing versions first
|
||||
deleteExistingVersions(project, versionsToRetain, versionsInSagan);
|
||||
|
||||
logger.log(project, "Writing project versions %s.", versionsString);
|
||||
boolean requiresDelete = requiresDeleteVersions(project, versionsToRetain, versionsInSagan);
|
||||
boolean requiresWrite = requiresWriteVersions(versions, versionsInSagan);
|
||||
|
||||
// Write new ones
|
||||
createVersions(versions, resource, versionsInSagan);
|
||||
if (requiresDelete || requiresWrite) {
|
||||
logger.log(project, "Updating project versions at %s…", resource);
|
||||
}
|
||||
|
||||
if (requiresDelete) {
|
||||
|
||||
logger.log(project, "Deleting outdated project versions…", versionsString);
|
||||
deleteExistingVersions(project, versionsToRetain);
|
||||
}
|
||||
|
||||
if (requiresWrite) {
|
||||
|
||||
logger.log(project, "Writing project versions %s.", versionsString);
|
||||
createVersions(project, versions, resource, versionsInSagan);
|
||||
}
|
||||
|
||||
logger.log(project, "Project versions up to date: %s", versionsString);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -113,7 +126,7 @@ class DefaultSaganClient implements SaganClient {
|
||||
@Override
|
||||
public void verifyAuthentication() {
|
||||
|
||||
URI resource = properties.getProjectReleasesResource(Projects.BUILD);
|
||||
URI resource = configuration.getProjectReleasesResource(Projects.BUILD);
|
||||
|
||||
logger.log("Sagan", "Verifying Sagan Authentication…");
|
||||
|
||||
@@ -126,22 +139,42 @@ class DefaultSaganClient implements SaganClient {
|
||||
logger.log("Sagan", "Authentication verified!");
|
||||
}
|
||||
|
||||
private void createVersions(MaintainedVersions versions, URI resource, List<String> versionsInSagan) {
|
||||
private void createVersions(Project project, MaintainedVersions versions, URI resource,
|
||||
List<String> versionsInSagan) {
|
||||
|
||||
versions.stream() //
|
||||
.map(it -> new ProjectMetadata(it, versions)) //
|
||||
getVersionsToWrite(versions) //
|
||||
.filter(version -> !versionsInSagan.contains(version.getVersion())) //
|
||||
.peek(metadata -> logger.log(project, "Creating project version %s…", metadata.getVersion())) //
|
||||
.forEach(payload -> operations.postForObject(resource, payload, String.class));
|
||||
}
|
||||
|
||||
private void deleteExistingVersions(Project project, List<String> versionsToRetain, List<String> versionsInSagan) {
|
||||
private static boolean requiresWriteVersions(MaintainedVersions versions, List<String> versionsInSagan) {
|
||||
return getVersionsToWrite(versions) //
|
||||
.anyMatch(version -> !versionsInSagan.contains(version.getVersion()));
|
||||
}
|
||||
|
||||
Arrays.stream(JsonPath.compile("$..version").<JSONArray> read(getProjectMetadata(project)).toArray())//
|
||||
.map(Object::toString) //
|
||||
private static Stream<ProjectMetadata> getVersionsToWrite(MaintainedVersions versions) {
|
||||
return versions.stream() //
|
||||
.map(it -> new ProjectMetadata(it, versions));
|
||||
}
|
||||
|
||||
private boolean requiresDeleteVersions(Project project, List<String> versionsToRetain, List<String> versionsInSagan) {
|
||||
return getVersionsToDelete(project) //
|
||||
.peek(versionsInSagan::add) //
|
||||
.anyMatch(version -> !versionsToRetain.contains(version));
|
||||
}
|
||||
|
||||
private void deleteExistingVersions(Project project, List<String> versionsToRetain) {
|
||||
|
||||
getVersionsToDelete(project) //
|
||||
.filter(version -> !versionsToRetain.contains(version)) //
|
||||
.map(version -> properties.getProjectReleaseResource(project, version))//
|
||||
.map(version -> configuration.getProjectReleaseResource(project, version))//
|
||||
.peek(uri -> logger.log(project, "Deleting existing project version at %s…", uri)) //
|
||||
.forEach(operations::delete);
|
||||
}
|
||||
|
||||
private Stream<String> getVersionsToDelete(Project project) {
|
||||
return Arrays.stream(JsonPath.compile("$..version").<JSONArray> read(getProjectMetadata(project)).toArray())//
|
||||
.map(Object::toString);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 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
|
||||
* http://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,
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -30,7 +30,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
|
||||
/**
|
||||
* Dummy implementation of {@link SaganClient} to just dump the request payloads into {@link System#out} instead of
|
||||
* Dummy implementation of {@link ProjectService} to just dump the request payloads into {@link System#out} instead of
|
||||
* communicating with a real server.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
@@ -38,7 +38,7 @@ import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class DummySaganClient implements SaganClient {
|
||||
class DummyProjectClient implements ProjectService {
|
||||
|
||||
Logger logger;
|
||||
ObjectWriter mapper;
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 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
|
||||
* http://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,
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +27,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -59,7 +75,7 @@ class MaintainedVersions implements Streamable<MaintainedVersion> {
|
||||
* @return
|
||||
*/
|
||||
static MaintainedVersions of(MaintainedVersion... versions) {
|
||||
return MaintainedVersions.of(Arrays.asList(versions));
|
||||
return of(Arrays.asList(versions));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 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
|
||||
* http://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,
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import org.springframework.data.release.model.DocumentationMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* http://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.data.release.projectservice;
|
||||
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class ProjectPaths {
|
||||
|
||||
static String getProjectPathSegment(Project project) {
|
||||
|
||||
if (Projects.BUILD.equals(project)) {
|
||||
return "spring-data";
|
||||
}
|
||||
|
||||
if (Projects.RELATIONAL.equals(project) || Projects.JDBC.equals(project)) {
|
||||
return "spring-data-jdbc";
|
||||
}
|
||||
|
||||
return project.getFolderName();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 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
|
||||
* http://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,
|
||||
@@ -13,17 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import org.springframework.data.release.model.Project;
|
||||
|
||||
/**
|
||||
* Interface to abstract the actual interaction with the Sagan server.
|
||||
* Interface to abstract the actual interaction with the Projects service server.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface SaganClient {
|
||||
public interface ProjectService {
|
||||
|
||||
/**
|
||||
* Returns the project metadata for the given {@link MaintainedVersion}.
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 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
|
||||
* http://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,
|
||||
@@ -13,27 +13,33 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.ReleaseTrains;
|
||||
import org.springframework.data.release.model.Train;
|
||||
import org.springframework.data.release.utils.ExecutionUtils;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Operations for Sagan interaction.
|
||||
* Operations for Projects Service interaction.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
@@ -42,12 +48,18 @@ import org.springframework.stereotype.Component;
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class SaganCommands extends TimedCommand {
|
||||
class ProjectServiceCommands extends TimedCommand {
|
||||
|
||||
SaganOperations sagan;
|
||||
ProjectServiceOperations projects;
|
||||
GitOperations git;
|
||||
ExecutorService executor;
|
||||
|
||||
@CliCommand("sagan update")
|
||||
public void saganUpdateProjectInformation(@CliOption(key = "", mandatory = true) String trainNames) {
|
||||
updateProjectInformation(trainNames);
|
||||
}
|
||||
|
||||
@CliCommand("projects update")
|
||||
public void updateProjectInformation(@CliOption(key = "", mandatory = true) String trainNames) {
|
||||
|
||||
List<Train> trains = Stream.of(trainNames.split(","))//
|
||||
@@ -55,8 +67,12 @@ class SaganCommands extends TimedCommand {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// ensure we have all git repositories available
|
||||
trains.forEach(git::checkout);
|
||||
Set<Project> affectedProjects = new HashSet<>();
|
||||
trains.stream() //
|
||||
.flatMap(Streamable::stream) //
|
||||
.forEach(it -> affectedProjects.add(it.getProject()));
|
||||
ExecutionUtils.run(executor, Streamable.of(affectedProjects), git::update);
|
||||
|
||||
sagan.updateProjectMetadata(trains);
|
||||
projects.updateProjectMetadata(trains);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 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
|
||||
* http://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,
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -33,23 +33,23 @@ import org.springframework.web.client.RestTemplate;
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class SaganConfiguration {
|
||||
class ProjectServiceConfiguration {
|
||||
|
||||
@Autowired GitProperties gitProperties;
|
||||
@Autowired SaganProperties properties;
|
||||
@Autowired Logger logger;
|
||||
|
||||
@Bean
|
||||
public SaganOperations saganOperations(GitOperations operations, SaganClient saganClient, Executor executor) {
|
||||
return new SaganOperations(operations, executor, saganClient, logger);
|
||||
public ProjectServiceOperations saganOperations(GitOperations operations, ProjectService projectService,
|
||||
Executor executor) {
|
||||
return new ProjectServiceOperations(operations, executor, projectService, logger);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SaganClient saganClient() {
|
||||
ProjectService projectsService(ProjectServiceProperties properties) {
|
||||
|
||||
RestTemplate restTemplate = new RestTemplateBuilder()
|
||||
.basicAuthentication(gitProperties.getUsername(), properties.key).build();
|
||||
|
||||
return new DefaultSaganClient(restTemplate, properties, logger);
|
||||
return new DefaultProjectClient(restTemplate, properties, logger);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 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
|
||||
* http://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,
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -49,14 +49,14 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class SaganOperations {
|
||||
class ProjectServiceOperations {
|
||||
|
||||
private static final List<Project> TO_FILTER = Arrays.asList(Projects.COMMONS, //
|
||||
Projects.KEY_VALUE);
|
||||
|
||||
GitOperations git;
|
||||
Executor executor;
|
||||
SaganClient client;
|
||||
ProjectService client;
|
||||
Logger logger;
|
||||
|
||||
/**
|
||||
@@ -77,13 +77,33 @@ class SaganOperations {
|
||||
|
||||
Map<Project, MaintainedVersions> versions = findVersions(trains);
|
||||
|
||||
ExecutionUtils.run(executor, Streamable.of(versions.entrySet()), entry -> {
|
||||
Streamable.of(versions.entrySet()).forEach(entry -> {
|
||||
|
||||
if (entry.getKey() == Projects.BOM) {
|
||||
return;
|
||||
}
|
||||
|
||||
client.updateProjectMetadata(entry.getKey(), entry.getValue());
|
||||
if(entry.getKey() == Projects.JDBC){
|
||||
return;
|
||||
}
|
||||
|
||||
// Sometimes we see 404 Not Found: [no body], sometimes
|
||||
// 400 Bad Request: "Release '2.2.13-SNAPSHOT' already present
|
||||
RuntimeException rethrow = null;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
|
||||
try {
|
||||
client.updateProjectMetadata(entry.getKey(), entry.getValue());
|
||||
return;
|
||||
} catch (RuntimeException e) {
|
||||
rethrow = e;
|
||||
logger.warn(entry.getKey(), e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (rethrow != null) {
|
||||
throw rethrow;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
* Copyright 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
|
||||
* http://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,
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -21,26 +21,21 @@ import java.net.URI;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Configuration properties for the Sagan instance to talk to.
|
||||
* Configuration properties for the Projects Service instance to talk to.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "sagan")
|
||||
class SaganProperties {
|
||||
|
||||
private static String SAGAN_BASE = "https://spring.io/api/";
|
||||
private static String SAGAN_RELEASES = SAGAN_BASE.concat("projects/{project}/releases");
|
||||
private static String SAGAN_GENERATIONS = SAGAN_BASE.concat("projects/{project}/generations");
|
||||
private static String SAGAN_RELEASE_VERSION = SAGAN_RELEASES.concat("/{version}");
|
||||
@ConfigurationProperties(prefix = "project-service")
|
||||
class ProjectServiceProperties implements ProjectServiceResources {
|
||||
|
||||
@Setter String key;
|
||||
@Setter String base = "https://api.spring.io/";
|
||||
|
||||
/**
|
||||
* Returns the URI to the resource exposing the project releases for the given {@link Project}.
|
||||
@@ -48,11 +43,11 @@ class SaganProperties {
|
||||
* @param project must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
URI getProjectReleasesResource(Project project) {
|
||||
public URI getProjectReleasesResource(Project project) {
|
||||
|
||||
Assert.notNull(project, "Project must not be null!");
|
||||
|
||||
return new UriTemplate(SAGAN_RELEASES).expand(getProjectPathSegment(project));
|
||||
return new UriTemplate(base + "projects/{project}/releases").expand(ProjectPaths.getProjectPathSegment(project));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,11 +56,11 @@ class SaganProperties {
|
||||
* @param project must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
URI getProjectGenerationsResource(Project project) {
|
||||
public URI getProjectGenerationsResource(Project project) {
|
||||
|
||||
Assert.notNull(project, "Project must not be null!");
|
||||
|
||||
return new UriTemplate(SAGAN_GENERATIONS).expand(getProjectPathSegment(project));
|
||||
return new UriTemplate(base + "projects/{project}/generations").expand(ProjectPaths.getProjectPathSegment(project));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,12 +71,13 @@ class SaganProperties {
|
||||
* @param version must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
URI getProjectReleaseResource(Project project, String version) {
|
||||
public URI getProjectReleaseResource(Project project, String version) {
|
||||
|
||||
Assert.notNull(project, "Project must not be null!");
|
||||
Assert.hasText(version, "Version must not be null!");
|
||||
|
||||
return new UriTemplate(SAGAN_RELEASE_VERSION).expand(getProjectPathSegment(project), version);
|
||||
return new UriTemplate(base + "projects/{project}/releases/{version}")
|
||||
.expand(ProjectPaths.getProjectPathSegment(project), version);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,20 +86,7 @@ class SaganProperties {
|
||||
* @param version must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
URI getProjectMetadataResource(MaintainedVersion version) {
|
||||
public URI getProjectMetadataResource(MaintainedVersion version) {
|
||||
return getProjectReleaseResource(version.getProject(), version.getVersion().toString());
|
||||
}
|
||||
|
||||
private static String getProjectPathSegment(Project project) {
|
||||
|
||||
if (Projects.BUILD.equals(project)) {
|
||||
return "spring-data";
|
||||
}
|
||||
|
||||
if (Projects.RELATIONAL.equals(project) || Projects.JDBC.equals(project)) {
|
||||
return "spring-data-jdbc";
|
||||
}
|
||||
|
||||
return project.getFolderName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 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
|
||||
*
|
||||
* http://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.data.release.projectservice;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.data.release.model.Project;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface ProjectServiceResources {
|
||||
|
||||
URI getProjectReleasesResource(Project project);
|
||||
|
||||
URI getProjectReleaseResource(Project project, String version);
|
||||
|
||||
URI getProjectMetadataResource(MaintainedVersion version);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ deployment.maven-central.gpg.passphrase=${MAVEN_SIGNING_KEY_PASSWORD}
|
||||
deployment.maven-central.gpg.executable=/usr/bin/gpg
|
||||
|
||||
sagan.key=n/a
|
||||
project-service.key=n/a
|
||||
|
||||
maven.maven-home=${MAVEN_HOME}
|
||||
maven.console-logger=false
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.sagan;
|
||||
package org.springframework.data.release.projectservice;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.release.AbstractIntegrationTests;
|
||||
import org.springframework.data.release.model.Project;
|
||||
@@ -30,15 +29,15 @@ import org.springframework.data.release.model.Projects;
|
||||
import org.springframework.data.release.model.ReleaseTrains;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link SaganOperations}.
|
||||
* Integration tests for {@link ProjectServiceOperations}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Disabled("I will write to production systems")
|
||||
class SaganOperationsIntegrationTests extends AbstractIntegrationTests {
|
||||
class ProjectServiceOperationsIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
@Autowired SaganOperations sagan;
|
||||
@Autowired SaganClient client;
|
||||
@Autowired ProjectServiceOperations sagan;
|
||||
@Autowired ProjectService client;
|
||||
|
||||
@Test
|
||||
void detectVersionsToUpdate() {
|
||||
@@ -78,7 +77,7 @@ class SaganOperationsIntegrationTests extends AbstractIntegrationTests {
|
||||
@Test
|
||||
void updateJpa() {
|
||||
|
||||
MaintainedVersions versions = sagan.findVersions(ReleaseTrains.KAY, ReleaseTrains.INGALLS, ReleaseTrains.HOPPER)
|
||||
MaintainedVersions versions = sagan.findVersions(ReleaseTrains.Q, ReleaseTrains.RAJ, ReleaseTrains.TURING)
|
||||
.get(Projects.JPA);
|
||||
|
||||
System.out.println(versions);
|
||||
@@ -15,3 +15,4 @@ git.author=dummy
|
||||
github.api-url=http://localhost:8888
|
||||
#Sagan
|
||||
sagan.key=foo
|
||||
project-service.key=foo
|
||||
|
||||
Reference in New Issue
Block a user