Add support for projects service at api.spring.io.

Closes #25
This commit is contained in:
Mark Paluch
2023-01-23 15:18:19 +01:00
parent c9d583176e
commit bff8e2a9e6
24 changed files with 308 additions and 174 deletions

View File

@@ -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=

View File

@@ -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`
|===

View File

@@ -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.");

View File

@@ -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();
}
}
/**

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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));
}
/**

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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}.

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
});
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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.*;

View File

@@ -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.*;

View File

@@ -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;

View File

@@ -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);

View File

@@ -15,3 +15,4 @@ git.author=dummy
github.api-url=http://localhost:8888
#Sagan
sagan.key=foo
project-service.key=foo