Skip to content

Commit 9ef357b

Browse files
committed
Add command to distribute GitHub workflows.
Closes #96
1 parent b2a57fc commit 9ef357b

File tree

7 files changed

+129
-21
lines changed

7 files changed

+129
-21
lines changed

readme.adoc

+9
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,15 @@ To distribute `ci/pipeline.properties` from Spring Data Build across all modules
223223
$ infra distribute ci-properties $trainIteration
224224
----
225225

226+
===== GitHub Workflow Distribution
227+
228+
To distribute `.github/workflows/project.yml` from Spring Data Build across all modules:
229+
230+
----
231+
$ infra distribute gh-workflow $trainIteration
232+
----
233+
234+
Note that your GitHub token to authenticate against GitHub must have the `workflow` permission.
226235

227236
===== Broken Link Report
228237

src/main/java/org/springframework/data/release/git/GitOperations.java

+30-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@
5858
import org.eclipse.jgit.transport.CredentialItem.CharArrayType;
5959
import org.eclipse.jgit.transport.CredentialItem.InformationalMessage;
6060
import org.eclipse.jgit.transport.CredentialsProvider;
61+
import org.eclipse.jgit.transport.PushResult;
6162
import org.eclipse.jgit.transport.RefSpec;
63+
import org.eclipse.jgit.transport.RemoteRefUpdate;
6264
import org.eclipse.jgit.transport.TagOpt;
6365
import org.eclipse.jgit.transport.URIish;
6466

@@ -76,6 +78,7 @@
7678
import org.springframework.plugin.core.PluginRegistry;
7779
import org.springframework.stereotype.Component;
7880
import org.springframework.util.Assert;
81+
import org.springframework.util.StringUtils;
7982

8083
/**
8184
* Component to execute Git related operations.
@@ -270,10 +273,35 @@ public void push(ModuleIteration module) {
270273

271274
call(git.push() //
272275
.setRemote("origin") //
273-
.setRefSpecs(new RefSpec(ref.getName())));
276+
.setRefSpecs(new RefSpec(ref.getName()))).forEach(pushResult -> {
277+
handlePushResult(module, pushResult);
278+
});
274279
});
275280
}
276281

282+
private void handlePushResult(ModuleIteration module, PushResult pushResult) {
283+
284+
Set<RemoteRefUpdate.Status> success = new HashSet<>(Arrays.asList(RemoteRefUpdate.Status.AWAITING_REPORT,
285+
RemoteRefUpdate.Status.NOT_ATTEMPTED, RemoteRefUpdate.Status.OK, RemoteRefUpdate.Status.UP_TO_DATE));
286+
287+
if (StringUtils.hasText(pushResult.getMessages())) {
288+
logger.log(module, pushResult.getMessages());
289+
}
290+
291+
for (RemoteRefUpdate remoteUpdate : pushResult.getRemoteUpdates()) {
292+
293+
if (success.contains(remoteUpdate.getStatus())) {
294+
295+
logger.log(module, String.format("✅️ Push done: %s %s", remoteUpdate.getStatus(),
296+
StringUtils.hasText(remoteUpdate.getMessage()) ? remoteUpdate.getMessage() : ""));
297+
298+
continue;
299+
}
300+
301+
logger.warn(module, String.format("⚠️ Push failed: %s %s", remoteUpdate.getStatus(), remoteUpdate.getMessage()));
302+
}
303+
}
304+
277305
public void pushTags(Train train) {
278306

279307
ExecutionUtils.run(executor, train.getModules(), module -> {
@@ -793,7 +821,7 @@ public void add(SupportedProject project, String filepattern) {
793821

794822
Assert.notNull(project, "Project must not be null!");
795823

796-
logger.log(project, "git add \"filepattern\"");
824+
logger.log(project, "git add \"%s\"", filepattern);
797825

798826
doWithGit(project, git -> {
799827

src/main/java/org/springframework/data/release/infra/InfrastructureCommands.java

+18
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,22 @@ public void distributeCiProperties(@CliOption(key = "", mandatory = true) TrainI
107107
infra.distributeCiProperties(iteration);
108108
}
109109

110+
/**
111+
* Distribute GH workflows across all modules using Build as template.
112+
*
113+
* @param iteration
114+
* @throws IOException
115+
* @throws InterruptedException
116+
*/
117+
@CliCommand(value = "infra distribute gh-workflow")
118+
public void distributeGhWorkflow(@CliOption(key = "", mandatory = true) TrainIteration iteration)
119+
throws IOException, InterruptedException {
120+
121+
logger.log(iteration, "Distributing GH Workflow for Spring Data…");
122+
123+
git.prepare(iteration);
124+
125+
infra.distributeGhWorkflow(iteration);
126+
}
127+
110128
}

src/main/java/org/springframework/data/release/infra/InfrastructureOperations.java

+26-12
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
import java.util.Optional;
2929
import java.util.Properties;
3030
import java.util.concurrent.ExecutorService;
31+
import java.util.function.Predicate;
3132

3233
import org.apache.commons.io.FileUtils;
34+
3335
import org.springframework.data.release.TimedCommand;
3436
import org.springframework.data.release.git.Branch;
3537
import org.springframework.data.release.git.GitOperations;
@@ -39,10 +41,10 @@
3941
import org.springframework.data.release.model.Project;
4042
import org.springframework.data.release.model.Projects;
4143
import org.springframework.data.release.model.SupportedProject;
42-
import org.springframework.data.release.model.Train;
4344
import org.springframework.data.release.model.TrainIteration;
4445
import org.springframework.data.release.utils.ExecutionUtils;
4546
import org.springframework.data.release.utils.Logger;
47+
import org.springframework.data.util.Predicates;
4648
import org.springframework.data.util.Streamable;
4749
import org.springframework.stereotype.Component;
4850

@@ -70,11 +72,19 @@ public class InfrastructureOperations extends TimedCommand {
7072
* @param iteration
7173
*/
7274
void distributeCiProperties(TrainIteration iteration) {
75+
distributeFile(iteration, "ci/pipeline.properties", "CI Properties", Predicates.isTrue());
76+
}
7377

74-
File master = workspace.getFile(CI_PROPERTIES, iteration.getSupportedProject(Projects.BUILD));
78+
void distributeGhWorkflow(TrainIteration iteration) {
79+
distributeFile(iteration, ".github/workflows/project.yml", "GitHub Actions", project -> project != Projects.BOM);
80+
}
81+
82+
private void distributeFile(TrainIteration iteration, String file, String description,
83+
Predicate<Project> projectFilter) {
84+
File master = workspace.getFile(file, iteration.getSupportedProject(Projects.BUILD));
7585

7686
if (!master.exists()) {
77-
throw new IllegalStateException(String.format("CI Properties file %s does not exist", master));
87+
throw new IllegalStateException(String.format("%s file %s does not exist", description, master));
7888
}
7989

8090
ExecutionUtils.run(executor, iteration, module -> {
@@ -86,29 +96,33 @@ void distributeCiProperties(TrainIteration iteration) {
8696
git.checkout(project, branch);
8797
});
8898

89-
verifyExistingPropertyFiles(iteration.getTrain(), master);
99+
Streamable<ModuleIteration> projects = Streamable.of(iteration.getModulesExcept(Projects.BUILD))
100+
.filter(it -> projectFilter.test(it.getProject())).filter(it -> it.getProject().getMaintainer().isCore());
101+
102+
verifyExistingFiles(projects, file, description);
90103

91-
ExecutionUtils.run(executor, Streamable.of(iteration.getModulesExcept(Projects.BUILD)), module -> {
104+
ExecutionUtils.run(executor, projects, module -> {
92105

93-
File target = workspace.getFile(CI_PROPERTIES, module.getSupportedProject());
106+
File target = workspace.getFile(file, module.getSupportedProject());
94107
target.delete();
95108

96109
FileUtils.copyFile(master, target);
97110

98-
git.add(module.getSupportedProject(), CI_PROPERTIES);
99-
git.commit(module, "Update CI properties.", Optional.empty(), false);
111+
git.add(module.getSupportedProject(), file);
112+
git.commit(module, String.format("Update %s.", description), Optional.empty(), false);
100113
git.push(module);
101114
});
102115
}
103116

104-
private void verifyExistingPropertyFiles(Train train, File master) {
117+
private void verifyExistingFiles(Streamable<ModuleIteration> train, String file, String description) {
105118

106-
for (SupportedProject project : train) {
119+
for (ModuleIteration moduleIteration : train) {
107120

108-
File target = workspace.getFile(CI_PROPERTIES, project);
121+
File target = workspace.getFile(file, moduleIteration.getSupportedProject());
109122

110123
if (!target.exists()) {
111-
throw new IllegalStateException(String.format("CI Properties file %s does not exist", master));
124+
throw new IllegalStateException(
125+
String.format("%s file %s does not exist in %s", description, file, moduleIteration.getSupportedProject()));
112126
}
113127
}
114128
}

src/main/java/org/springframework/data/release/model/Project.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class Project implements Comparable<Project>, Named {
4545
private final @With ArtifactCoordinates additionalArtifacts;
4646
private final @With boolean skipTests;
4747
private final @Getter @With boolean useShortVersionMilestones; // use a short version 2.3.0-RC1 instead of 2.3 RC1 if
48+
private final @Getter @With ProjectMaintainer maintainer;
4849
// true
4950

5051
Project(String key, String name, Tracker tracker) {
@@ -53,13 +54,14 @@ public class Project implements Comparable<Project>, Named {
5354

5455
private Project(String key, String name, String fullName, Tracker tracker) {
5556
this(new ProjectKey(key), name, fullName, Collections.emptySet(), tracker, ArtifactCoordinates.SPRING_DATA, true,
56-
false);
57+
false, ProjectMaintainer.CORE);
5758
}
5859

5960
@java.beans.ConstructorProperties({ "key", "name", "fullName", "dependencies", "tracker", "additionalArtifacts",
60-
"skipTests", "plainVersionMilestones" })
61+
"skipTests", "plainVersionMilestones", "owner" })
6162
private Project(ProjectKey key, String name, String fullName, Collection<Project> dependencies, Tracker tracker,
62-
ArtifactCoordinates additionalArtifacts, boolean skipTests, boolean useShortVersionMilestones) {
63+
ArtifactCoordinates additionalArtifacts, boolean skipTests, boolean useShortVersionMilestones,
64+
ProjectMaintainer maintainer) {
6365

6466
this.key = key;
6567
this.name = name;
@@ -69,6 +71,7 @@ private Project(ProjectKey key, String name, String fullName, Collection<Project
6971
this.additionalArtifacts = additionalArtifacts;
7072
this.skipTests = skipTests;
7173
this.useShortVersionMilestones = useShortVersionMilestones;
74+
this.maintainer = maintainer;
7275
}
7376

7477
public boolean uses(Tracker tracker) {
@@ -110,7 +113,7 @@ public boolean skipTests() {
110113

111114
public Project withDependencies(Project... project) {
112115
return new Project(key, name, fullName, Arrays.asList(project), tracker, additionalArtifacts, skipTests,
113-
useShortVersionMilestones);
116+
useShortVersionMilestones, maintainer);
114117
}
115118

116119
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.release.model;
17+
18+
/**
19+
* @author Mark Paluch
20+
*/
21+
public enum ProjectMaintainer {
22+
23+
CORE, COMMUNITY;
24+
25+
public boolean isCore() {
26+
return this == CORE;
27+
}
28+
29+
public boolean isCommunity() {
30+
return this == COMMUNITY;
31+
}
32+
33+
}

src/main/java/org/springframework/data/release/model/Projects.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,23 @@ public class Projects {
6767
.withAdditionalArtifacts(
6868
ArtifactCoordinates.SPRING_DATA.artifacts("spring-data-mongodb-cross-store", "spring-data-mongodb-log4j"));
6969

70-
NEO4J = new Project("DATAGRAPH", "Neo4j", Tracker.GITHUB).withDependencies(COMMONS);
70+
NEO4J = new Project("DATAGRAPH", "Neo4j", Tracker.GITHUB).withDependencies(COMMONS)
71+
.withMaintainer(ProjectMaintainer.COMMUNITY);
7172

7273
SOLR = new Project("DATASOLR", "Solr", Tracker.GITHUB) //
7374
.withDependencies(COMMONS) //
7475
.withFullName("Spring Data for Apache Solr");
7576

76-
COUCHBASE = new Project("DATACOUCH", "Couchbase", Tracker.GITHUB).withDependencies(COMMONS);
77+
COUCHBASE = new Project("DATACOUCH", "Couchbase", Tracker.GITHUB).withDependencies(COMMONS)
78+
.withMaintainer(ProjectMaintainer.COMMUNITY);
7779

7880
CASSANDRA = new Project("DATACASS", "Cassandra", Tracker.GITHUB) //
7981
.withDependencies(COMMONS) //
8082
.withAdditionalArtifacts(ArtifactCoordinates.SPRING_DATA.artifacts("spring-cql"))
8183
.withFullName("Spring Data for Apache Cassandra");
8284

83-
ELASTICSEARCH = new Project("DATAES", "Elasticsearch", Tracker.GITHUB).withDependencies(COMMONS);
85+
ELASTICSEARCH = new Project("DATAES", "Elasticsearch", Tracker.GITHUB).withDependencies(COMMONS)
86+
.withMaintainer(ProjectMaintainer.COMMUNITY);
8487

8588
KEY_VALUE = new Project("DATAKV", "KeyValue", Tracker.GITHUB).withDependencies(COMMONS);
8689

0 commit comments

Comments
 (0)