Skip to content

Commit c501819

Browse files
authored
Merge pull request #770 from jglick/upgrade
2.2234 serial form incompatibility
2 parents ab88cee + cc9f467 commit c501819

File tree

9 files changed

+1453
-0
lines changed

9 files changed

+1453
-0
lines changed

pipeline-model-definition/pom.xml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,49 @@
6161
</systemPropertyVariables>
6262
</configuration>
6363
</plugin>
64+
<plugin>
65+
<groupId>org.apache.maven.plugins</groupId>
66+
<artifactId>maven-dependency-plugin</artifactId>
67+
<executions>
68+
<execution>
69+
<id>old-releases</id>
70+
<goals>
71+
<goal>copy</goal>
72+
</goals>
73+
<phase>generate-test-resources</phase>
74+
<configuration>
75+
<outputDirectory>${project.build.testOutputDirectory}/old-releases</outputDirectory>
76+
<stripVersion>true</stripVersion>
77+
<artifactItems>
78+
<artifactItem>
79+
<groupId>org.jenkinsci.plugins</groupId>
80+
<artifactId>pipeline-stage-tags-metadata</artifactId>
81+
<version>${old-release.version}</version>
82+
<type>hpi</type>
83+
</artifactItem>
84+
<artifactItem>
85+
<groupId>org.jenkinsci.plugins</groupId>
86+
<artifactId>pipeline-model-api</artifactId>
87+
<version>${old-release.version}</version>
88+
<type>hpi</type>
89+
</artifactItem>
90+
<artifactItem>
91+
<groupId>org.jenkinsci.plugins</groupId>
92+
<artifactId>pipeline-model-extensions</artifactId>
93+
<version>${old-release.version}</version>
94+
<type>hpi</type>
95+
</artifactItem>
96+
<artifactItem>
97+
<groupId>org.jenkinsci.plugins</groupId>
98+
<artifactId>pipeline-model-definition</artifactId>
99+
<version>${old-release.version}</version>
100+
<type>hpi</type>
101+
</artifactItem>
102+
</artifactItems>
103+
</configuration>
104+
</execution>
105+
</executions>
106+
</plugin>
64107
</plugins>
65108
</build>
66109

@@ -252,5 +295,6 @@
252295
<properties>
253296
<no-test-jar>false</no-test-jar>
254297
<useBeta>true</useBeta>
298+
<old-release.version>2.2221.vc657003fb_d93</old-release.version>
255299
</properties>
256300
</project>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2025 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package org.jenkinsci.plugins.pipeline.modeldefinition;
26+
27+
import groovy.lang.GroovyShell;
28+
import hudson.Extension;
29+
import hudson.ExtensionList;
30+
import hudson.util.VersionNumber;
31+
import java.io.File;
32+
import java.net.URL;
33+
import java.util.Set;
34+
import java.util.concurrent.atomic.AtomicBoolean;
35+
import java.util.logging.Level;
36+
import java.util.logging.Logger;
37+
import javax.xml.XMLConstants;
38+
import javax.xml.parsers.SAXParserFactory;
39+
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.DeclarativeAgentScript2;
40+
import org.jenkinsci.plugins.pipeline.modeldefinition.parser.CompatibilityLoader;
41+
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
42+
import org.jenkinsci.plugins.workflow.cps.GroovyShellDecorator;
43+
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
44+
import org.xml.sax.Attributes;
45+
import org.xml.sax.SAXException;
46+
import org.xml.sax.helpers.DefaultHandler;
47+
48+
/**
49+
* Detects old builds (predating for example {@link DeclarativeAgentScript2}) and loads old Groovy resources to match.
50+
*/
51+
@Extension public final class Upgrade extends GroovyShellDecorator {
52+
53+
private static final Logger LOGGER = Logger.getLogger(Upgrade.class.getName());
54+
55+
@Override public void configureShell(CpsFlowExecution context, GroovyShell shell) {
56+
if (context == null) {
57+
return;
58+
}
59+
var owner = context.getOwner();
60+
if (owner == null) {
61+
return;
62+
}
63+
try {
64+
if (!isOld(owner)) {
65+
LOGGER.fine(() -> context + " does not seem to be old");
66+
return;
67+
}
68+
} catch (Exception x) {
69+
LOGGER.log(Level.WARNING, "failed to check " + context, x);
70+
return;
71+
}
72+
var cl = shell.getClassLoader();
73+
var base = cl.getResourceLoader();
74+
cl.setResourceLoader(filename -> {
75+
for (var loader : ExtensionList.lookup(CompatibilityLoader.class)) {
76+
var url = loader.loadGroovySource(filename);
77+
if (url != null) {
78+
LOGGER.fine(() -> "for " + context + " loading " + filename + " via " + loader + " ⇒ " + url);
79+
return url;
80+
}
81+
}
82+
var url = base.loadGroovySource(filename);
83+
if (url != null) {
84+
LOGGER.finer(() -> "for " + context + " loading " + filename + " ⇒ " + url);
85+
}
86+
return url;
87+
});
88+
}
89+
90+
@Override public GroovyShellDecorator forTrusted() {
91+
return this;
92+
}
93+
94+
@Extension public static final class Loader implements CompatibilityLoader {
95+
private static final Set<String> CLASSES = Set.of(
96+
"org.jenkinsci.plugins.pipeline.modeldefinition.ModelInterpreter",
97+
"org.jenkinsci.plugins.pipeline.modeldefinition.agent.CheckoutScript",
98+
"org.jenkinsci.plugins.pipeline.modeldefinition.agent.impl.AnyScript",
99+
"org.jenkinsci.plugins.pipeline.modeldefinition.agent.impl.LabelScript",
100+
"org.jenkinsci.plugins.pipeline.modeldefinition.agent.impl.NoneScript");
101+
@Override public URL loadGroovySource(String clazz) {
102+
if (CLASSES.contains(clazz)) {
103+
return Upgrade.class.getResource("compat/" + clazz.replaceFirst(".+[.]", "") + ".groovy");
104+
} else {
105+
return null;
106+
}
107+
}
108+
}
109+
110+
private static boolean isOld(FlowExecutionOwner owner) throws Exception {
111+
var factory = SAXParserFactory.newDefaultInstance();
112+
// TODO XMLUtils does not support SAX parsing:
113+
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
114+
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
115+
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
116+
var parser = factory.newSAXParser();
117+
var old = new AtomicBoolean();
118+
var buildXml = new File(owner.getRootDir(), "build.xml");
119+
parser.parse(buildXml, new DefaultHandler() {
120+
@Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
121+
var plugin = attributes.getValue("plugin");
122+
if (plugin != null) {
123+
int at = plugin.indexOf('@');
124+
if (at != -1 && plugin.substring(0, at).equals("pipeline-model-definition")) {
125+
var version = new VersionNumber(plugin.substring(at + 1));
126+
LOGGER.fine(() -> "got " + version + " off " + qName + " in " + buildXml);
127+
if (version.isOlderThan(new VersionNumber("2.2234"))) {
128+
old.set(true);
129+
}
130+
}
131+
}
132+
}
133+
});
134+
return old.get();
135+
}
136+
137+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2016, CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
26+
package org.jenkinsci.plugins.pipeline.modeldefinition.agent.impl
27+
28+
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.CheckoutScript
29+
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.DeclarativeAgentScript
30+
import org.jenkinsci.plugins.workflow.cps.CpsScript
31+
32+
class AnyScript extends DeclarativeAgentScript<Any> {
33+
34+
AnyScript(CpsScript s, Any a) {
35+
super(s, a)
36+
}
37+
38+
@Override
39+
Closure run(Closure body) {
40+
return {
41+
script.node {
42+
CheckoutScript.doCheckout(script, describable, null, body).call()
43+
}
44+
}
45+
}
46+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2017, CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
26+
package org.jenkinsci.plugins.pipeline.modeldefinition.agent
27+
28+
import org.jenkinsci.plugins.pipeline.modeldefinition.SyntheticStageNames
29+
import org.jenkinsci.plugins.workflow.cps.CpsScript
30+
31+
class CheckoutScript implements Serializable {
32+
33+
static Closure doCheckout(CpsScript script, DeclarativeAgent agent, String customWorkspace = null, Closure body) {
34+
return {
35+
if (customWorkspace) {
36+
script.ws(customWorkspace) {
37+
checkoutAndRun(script, agent, body).call()
38+
}
39+
} else {
40+
checkoutAndRun(script, agent, body).call()
41+
}
42+
}
43+
}
44+
45+
private static Closure checkoutAndRun(CpsScript script, DeclarativeAgent agent, Closure body) {
46+
return {
47+
def checkoutMap = [:]
48+
49+
if (agent.isDoCheckout() && agent.hasScmContext(script)) {
50+
String subDir = agent.subdirectory
51+
if (subDir != null && subDir != "") {
52+
script.dir(subDir) {
53+
checkoutMap.putAll(performCheckout(script, agent))
54+
}
55+
} else {
56+
checkoutMap.putAll(performCheckout(script, agent))
57+
}
58+
}
59+
if (checkoutMap) {
60+
script.withEnv(checkoutMap.collect { k, v -> "${k}=${v}" }) {
61+
body.call()
62+
}
63+
} else {
64+
body.call()
65+
}
66+
}
67+
}
68+
69+
private static Map performCheckout(CpsScript script, DeclarativeAgent agent) {
70+
def checkoutMap = [:]
71+
if (!agent.inStage) {
72+
script.stage(SyntheticStageNames.checkout()) {
73+
checkoutMap.putAll(script.checkout(script.scm) ?: [:])
74+
}
75+
} else {
76+
// No stage when we're in a nested stage already
77+
checkoutMap.putAll(script.checkout(script.scm) ?: [:])
78+
}
79+
80+
return checkoutMap
81+
}
82+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2016, CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
26+
package org.jenkinsci.plugins.pipeline.modeldefinition.agent.impl
27+
28+
29+
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.CheckoutScript
30+
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.DeclarativeAgentScript
31+
import org.jenkinsci.plugins.workflow.cps.CpsScript
32+
33+
class LabelScript extends DeclarativeAgentScript<Label> {
34+
35+
LabelScript(CpsScript s, Label a) {
36+
super(s, a)
37+
}
38+
39+
@Override
40+
Closure run(Closure body) {
41+
Closure run = {
42+
script.node(describable?.label) {
43+
CheckoutScript.doCheckout(script, describable, describable.customWorkspace, body).call()
44+
}
45+
}
46+
if (describable.retries > 1) {
47+
return {
48+
script.retry(count: describable.retries, conditions: [script.agent(), script.nonresumable()]) {
49+
run.call()
50+
}
51+
}
52+
} else {
53+
run
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)