Skip to content

Commit e87c7a5

Browse files
authored
Merge pull request #1554 from PratikMane0112/feature/add-enable-cd-recipe
Add recipe Enable CD (JEP-229)
2 parents 866bac3 + 33978e3 commit e87c7a5

File tree

7 files changed

+1240
-0
lines changed

7 files changed

+1240
-0
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
package io.jenkins.tools.pluginmodernizer.core.recipes;
2+
3+
import io.jenkins.tools.pluginmodernizer.core.extractor.ArchetypeCommonFile;
4+
import io.jenkins.tools.pluginmodernizer.core.visitors.EnableCDVisitor;
5+
import java.util.ArrayList;
6+
import java.util.Collection;
7+
import java.util.Collections;
8+
import java.util.stream.Collectors;
9+
import org.intellij.lang.annotations.Language;
10+
import org.openrewrite.ExecutionContext;
11+
import org.openrewrite.ScanningRecipe;
12+
import org.openrewrite.SourceFile;
13+
import org.openrewrite.Tree;
14+
import org.openrewrite.TreeVisitor;
15+
import org.openrewrite.text.PlainTextParser;
16+
import org.openrewrite.xml.tree.Xml;
17+
import org.openrewrite.yaml.YamlParser;
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
21+
/**
22+
* Recipe to enable CD (JEP-229) in a Jenkins plugin.
23+
* Sets up continuous delivery workflow, updates Maven configuration, and transforms POM.
24+
*/
25+
public class EnableCD extends ScanningRecipe<EnableCD.ConfigState> {
26+
27+
private static final Logger LOG = LoggerFactory.getLogger(EnableCD.class);
28+
29+
@Language("yaml")
30+
private static final String CD_WORKFLOW_TEMPLATE = """
31+
# Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins
32+
#
33+
# Please find additional hints for individual trigger use case
34+
# configuration options inline this script below.
35+
#
36+
---
37+
name: cd
38+
on:
39+
workflow_dispatch:
40+
inputs:
41+
validate_only:
42+
required: false
43+
type: boolean
44+
description: |
45+
Run validation with release drafter only
46+
→ Skip the release job
47+
# Note: Change this default to true,
48+
# if the checkbox should be checked by default.
49+
default: false
50+
# If you don't want any automatic trigger in general, then
51+
# the following check_run trigger lines should all be commented.
52+
# Note: Consider the use case #2 config for 'validate_only' below
53+
# as an alternative option!
54+
check_run:
55+
types:
56+
- completed
57+
58+
permissions:
59+
checks: read
60+
contents: write
61+
62+
jobs:
63+
maven-cd:
64+
uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1
65+
with:
66+
# Comment / uncomment the validate_only config appropriate to your preference:
67+
#
68+
# Use case #1 (automatic release):
69+
# - Let any successful Jenkins build trigger another release,
70+
# if there are merged pull requests of interest
71+
# - Perform a validation only run with drafting a release note,
72+
# if manually triggered AND inputs.validate_only has been checked.
73+
#
74+
validate_only: ${{ inputs.validate_only == true }}
75+
#
76+
# Alternative use case #2 (no automatic release):
77+
# - Same as use case #1 - but:
78+
# - Let any check_run trigger a validate_only run.
79+
# => enforce the release job to be skipped.
80+
#
81+
#validate_only: ${{ inputs.validate_only == true || github.event_name == 'check_run' }}
82+
secrets:
83+
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
84+
MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
85+
""";
86+
87+
@Language("yaml")
88+
public static final String DEPENDABOT_UPDATE_TEMPLATE = """
89+
- package-ecosystem: github-actions
90+
directory: /
91+
schedule:
92+
interval: monthly
93+
""";
94+
95+
@Override
96+
public String getDisplayName() {
97+
return "Enable CD (JEP-229)";
98+
}
99+
100+
@Override
101+
public String getDescription() {
102+
return "Enables continuous delivery (CD) using JEP-229 for automated plugin releases from pull requests.";
103+
}
104+
105+
@Override
106+
public ConfigState getInitialValue(ExecutionContext ctx) {
107+
return new ConfigState();
108+
}
109+
110+
@Override
111+
public TreeVisitor<?, ExecutionContext> getScanner(ConfigState state) {
112+
return new TreeVisitor<>() {
113+
@Override
114+
public Tree visit(Tree tree, ExecutionContext ctx) {
115+
if (tree instanceof SourceFile sourceFile) {
116+
if (ArchetypeCommonFile.WORKFLOW_CD.same(sourceFile.getSourcePath())) {
117+
LOG.debug(".github/workflows/cd.yaml already exists. Marking as present.");
118+
state.setCdWorkflowExists(true);
119+
}
120+
if (ArchetypeCommonFile.MAVEN_CONFIG.same(sourceFile.getSourcePath())) {
121+
LOG.debug(".mvn/maven.config already exists. Will be updated.");
122+
state.setMavenConfigExists(true);
123+
}
124+
if (ArchetypeCommonFile.DEPENDABOT.same(sourceFile.getSourcePath())) {
125+
LOG.debug(".github/dependabot.yml already exists. Will be updated.");
126+
state.setDependabotExists(true);
127+
}
128+
if (ArchetypeCommonFile.RELEASE_DRAFTER_WORKFLOW.same(sourceFile.getSourcePath())) {
129+
LOG.debug("Release drafter workflow exists. Will be deleted.");
130+
state.setReleaseDrafterWorkflowExists(true);
131+
}
132+
if (ArchetypeCommonFile.POM.same(sourceFile.getSourcePath())) {
133+
LOG.debug("POM file found. Will be processed by visitor.");
134+
state.setPomExists(true);
135+
136+
// Check if POM has properties section
137+
if (sourceFile instanceof Xml.Document) {
138+
Xml.Document doc = (Xml.Document) sourceFile;
139+
Xml.Tag root = doc.getRoot();
140+
if (root != null && root.getChild("properties").isPresent()) {
141+
LOG.debug("POM has properties section.");
142+
state.setPomHasProperties(true);
143+
144+
// Check if already using CD format
145+
root.getChild("version").ifPresent(versionTag -> {
146+
String currentVersion =
147+
versionTag.getValue().orElse("");
148+
149+
boolean isValidCDFormat = currentVersion.equals("${changelist}")
150+
|| currentVersion.equals("${revision}.${changelist}")
151+
|| currentVersion.equals("${revision}-${changelist}");
152+
153+
if (isValidCDFormat) {
154+
LOG.debug("POM version '{}' already uses valid CD format.", currentVersion);
155+
156+
// Verify changelist property exists with SNAPSHOT value
157+
root.getChild("properties").ifPresent(props -> {
158+
if (!props.getChildren("changelist").isEmpty()) {
159+
String changelistValue = props.getChildren("changelist")
160+
.get(0)
161+
.getValue()
162+
.orElse("");
163+
if (changelistValue.contains("SNAPSHOT")) {
164+
LOG.debug(
165+
"POM has valid CD format with SNAPSHOT changelist. Marking as already using CD.");
166+
state.setPomAlreadyUsesCDFormat(true);
167+
}
168+
}
169+
});
170+
}
171+
});
172+
} else {
173+
LOG.debug("POM lacks properties section.");
174+
state.setPomHasProperties(false);
175+
}
176+
}
177+
}
178+
}
179+
return tree;
180+
}
181+
};
182+
}
183+
184+
@Override
185+
public TreeVisitor<?, ExecutionContext> getVisitor(ConfigState state) {
186+
return new EnableCDVisitor(state);
187+
}
188+
189+
@Override
190+
public Collection<SourceFile> generate(ConfigState state, ExecutionContext ctx) {
191+
if (!state.isPomExists()) {
192+
LOG.warn("No pom.xml found. Cannot generate CD workflow files.");
193+
return Collections.emptyList();
194+
}
195+
196+
if (!state.isPomHasProperties()) {
197+
LOG.warn("POM lacks a properties section. Cannot generate CD workflow files.");
198+
return Collections.emptyList();
199+
}
200+
201+
Collection<SourceFile> generatedFiles = new ArrayList<>();
202+
203+
// Generate CD workflow if it doesn't exist
204+
if (!state.isCdWorkflowExists()) {
205+
LOG.debug("Generating .github/workflows/cd.yaml");
206+
generatedFiles.addAll(YamlParser.builder()
207+
.build()
208+
.parse(CD_WORKFLOW_TEMPLATE)
209+
.map(brandNewFile ->
210+
(SourceFile) brandNewFile.withSourcePath(ArchetypeCommonFile.WORKFLOW_CD.getPath()))
211+
.collect(Collectors.toList()));
212+
}
213+
214+
// Generate maven.config if it doesn't exist
215+
if (!state.isMavenConfigExists()) {
216+
LOG.debug("Generating .mvn/maven.config");
217+
String mavenConfig = "-Dchangelist.format=%d.v%s";
218+
generatedFiles.addAll(PlainTextParser.builder()
219+
.build()
220+
.parse(mavenConfig)
221+
.map(brandNewFile ->
222+
(SourceFile) brandNewFile.withSourcePath(ArchetypeCommonFile.MAVEN_CONFIG.getPath()))
223+
.collect(Collectors.toList()));
224+
}
225+
226+
return generatedFiles;
227+
}
228+
229+
/**
230+
* Configuration state for the recipe
231+
*/
232+
public static class ConfigState {
233+
private boolean cdWorkflowExists = false;
234+
private boolean mavenConfigExists = false;
235+
private boolean dependabotExists = false;
236+
private boolean releaseDrafterWorkflowExists = false;
237+
private boolean pomExists = false;
238+
private boolean pomHasProperties = false;
239+
private boolean pomAlreadyUsesCDFormat = false;
240+
241+
public boolean isCdWorkflowExists() {
242+
return cdWorkflowExists;
243+
}
244+
245+
public void setCdWorkflowExists(boolean value) {
246+
cdWorkflowExists = value;
247+
}
248+
249+
public boolean isMavenConfigExists() {
250+
return mavenConfigExists;
251+
}
252+
253+
public void setMavenConfigExists(boolean value) {
254+
mavenConfigExists = value;
255+
}
256+
257+
public boolean isDependabotExists() {
258+
return dependabotExists;
259+
}
260+
261+
public void setDependabotExists(boolean value) {
262+
dependabotExists = value;
263+
}
264+
265+
public boolean isReleaseDrafterWorkflowExists() {
266+
return releaseDrafterWorkflowExists;
267+
}
268+
269+
public void setReleaseDrafterWorkflowExists(boolean value) {
270+
releaseDrafterWorkflowExists = value;
271+
}
272+
273+
public boolean isPomExists() {
274+
return pomExists;
275+
}
276+
277+
public void setPomExists(boolean value) {
278+
pomExists = value;
279+
}
280+
281+
public boolean isPomHasProperties() {
282+
return pomHasProperties;
283+
}
284+
285+
public void setPomHasProperties(boolean value) {
286+
pomHasProperties = value;
287+
}
288+
289+
public boolean isPomAlreadyUsesCDFormat() {
290+
return pomAlreadyUsesCDFormat;
291+
}
292+
293+
public void setPomAlreadyUsesCDFormat(boolean value) {
294+
pomAlreadyUsesCDFormat = value;
295+
}
296+
}
297+
}

0 commit comments

Comments
 (0)