Skip to content

Commit 9b6e622

Browse files
authored
[JENKINS-76076] ServerURL not accepting environment variable via CasC (#1107)
Fix the way how to retrieve attribute value from CasC context using the opportune configurator.
1 parent 84f32d2 commit 9b6e622

File tree

5 files changed

+132
-26
lines changed

5 files changed

+132
-26
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@
168168
<version>4.1.1</version>
169169
<scope>test</scope>
170170
</dependency>
171+
<dependency>
172+
<groupId>org.junit-pioneer</groupId>
173+
<artifactId>junit-pioneer</artifactId>
174+
<version>2.1.0</version>
175+
<scope>test</scope>
176+
</dependency>
171177
<dependency>
172178
<groupId>org.jenkins-ci.plugins</groupId>
173179
<artifactId>git</artifactId>

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/casc/BitbucketCloudEndpointConfigurator.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,18 @@
2626
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookConfiguration;
2727
import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint;
2828
import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud.CloudWebhookConfiguration;
29+
import edu.umd.cs.findbugs.annotations.NonNull;
2930
import hudson.Extension;
3031
import io.jenkins.plugins.casc.ConfigurationAsCode;
3132
import io.jenkins.plugins.casc.ConfigurationContext;
33+
import io.jenkins.plugins.casc.Configurator;
3234
import io.jenkins.plugins.casc.ConfiguratorException;
3335
import io.jenkins.plugins.casc.impl.configurators.DataBoundConfigurator;
3436
import io.jenkins.plugins.casc.model.Mapping;
3537
import java.util.logging.Logger;
3638
import org.kohsuke.accmod.Restricted;
3739
import org.kohsuke.accmod.restrictions.NoExternalUse;
3840

39-
import static org.apache.commons.lang3.StringUtils.trimToNull;
40-
4141
/**
4242
* Specialised class to configure how to build a new instance of
4343
* BitbucketServerEndpoint using {@link ConfigurationAsCode}.
@@ -54,43 +54,56 @@ public BitbucketCloudEndpointConfigurator() {
5454
}
5555

5656
@Override
57-
protected BitbucketCloudEndpoint instance(Mapping mapping, ConfigurationContext context) throws ConfiguratorException {
57+
protected BitbucketCloudEndpoint instance(@NonNull Mapping mapping, @NonNull ConfigurationContext context) throws ConfiguratorException {
58+
final Configurator<Boolean> boolConfigurator = context.lookupOrFail(Boolean.class);
59+
final Configurator<Integer> intConfigurator = context.lookupOrFail(Integer.class);
60+
5861
boolean enableCache = false;
5962
if (mapping.containsKey("enableCache")) {
60-
enableCache = Boolean.parseBoolean(mapping.getScalarValue("enableCache"));
63+
enableCache = boolConfigurator.configure(mapping.get("enableCache"), context);
64+
mapping.remove("enableCache");
6165
}
6266
int teamCacheDuration = 0;
6367
if (mapping.containsKey("teamCacheDuration")) {
64-
teamCacheDuration = Integer.parseInt(mapping.getScalarValue("teamCacheDuration"));
68+
teamCacheDuration = intConfigurator.configure(mapping.get("teamCacheDuration"), context);
69+
mapping.remove("teamCacheDuration");
6570
}
6671
int repositoriesCacheDuration = 0;
6772
if (mapping.containsKey("repositoriesCacheDuration")) {
68-
repositoriesCacheDuration = Integer.parseInt(mapping.getScalarValue("repositoriesCacheDuration"));
73+
repositoriesCacheDuration = intConfigurator.configure(mapping.get("repositoriesCacheDuration"), context);
74+
mapping.remove("repositoriesCacheDuration");
6975
}
70-
BitbucketWebhookConfiguration webhook = getWebhook(mapping);
76+
BitbucketWebhookConfiguration webhook = getWebhook(mapping, context);
7177
return new BitbucketCloudEndpoint(enableCache, teamCacheDuration, repositoriesCacheDuration, webhook);
7278
}
7379

74-
private BitbucketWebhookConfiguration getWebhook(Mapping mapping) {
80+
private BitbucketWebhookConfiguration getWebhook(@NonNull Mapping mapping, @NonNull ConfigurationContext context) {
81+
final Configurator<String> stringConfigurator = context.lookupOrFail(String.class);
82+
final Configurator<Boolean> boolConfigurator = context.lookupOrFail(Boolean.class);
83+
7584
boolean manageHooks = false;
7685
if (mapping.containsKey("manageHooks")) {
7786
logger.warning("manageHooks is deprecated, replace from your CasC definition with the appropriate webhook definition.");
78-
manageHooks = Boolean.parseBoolean(trimToNull(mapping.getScalarValue("manageHooks")));
87+
manageHooks = boolConfigurator.configure(mapping.get("manageHooks"), context);
88+
mapping.remove("manageHooks");
7989
}
8090
String credentialsId = null;
8191
if (mapping.containsKey("credentialsId")) {
8292
logger.warning("credentialsId is deprecated, replace from your CasC definition with the appropriate webhook definition.");
83-
credentialsId = mapping.getScalarValue("credentialsId");
93+
credentialsId = stringConfigurator.configure(mapping.get("credentialsId"), context);
94+
mapping.remove("credentialsId");
8495
}
8596
boolean enableHookSignature = false;
8697
if (mapping.containsKey("enableHookSignature")) {
8798
logger.warning("enableHookSignature is deprecated, replace from your CasC definition with the appropriate webhook definition.");
88-
enableHookSignature = Boolean.parseBoolean(trimToNull(mapping.getScalarValue("enableHookSignature")));
99+
enableHookSignature = boolConfigurator.configure(mapping.get("enableHookSignature"), context);
100+
mapping.remove("enableHookSignature");
89101
}
90102
String hookSignatureCredentialsId = null;
91103
if (mapping.containsKey("hookSignatureCredentialsId")) {
92104
logger.warning("hookSignatureCredentialsId is deprecated, replace from your CasC definition with the appropriate webhook definition.");
93-
hookSignatureCredentialsId = mapping.getScalarValue("hookSignatureCredentialsId");
105+
hookSignatureCredentialsId = stringConfigurator.configure(mapping.get("hookSignatureCredentialsId"), context);
106+
mapping.remove("hookSignatureCredentialsId");
94107
}
95108
return new CloudWebhookConfiguration(manageHooks, credentialsId, enableHookSignature, hookSignatureCredentialsId);
96109
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/casc/BitbucketServerEndpointConfigurator.java

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,18 @@
2727
import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketServerEndpoint;
2828
import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.plugin.PluginWebhookConfiguration;
2929
import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerWebhookConfiguration;
30+
import edu.umd.cs.findbugs.annotations.NonNull;
3031
import hudson.Extension;
3132
import io.jenkins.plugins.casc.ConfigurationAsCode;
3233
import io.jenkins.plugins.casc.ConfigurationContext;
34+
import io.jenkins.plugins.casc.Configurator;
3335
import io.jenkins.plugins.casc.ConfiguratorException;
3436
import io.jenkins.plugins.casc.impl.configurators.DataBoundConfigurator;
3537
import io.jenkins.plugins.casc.model.Mapping;
3638
import java.util.logging.Logger;
3739
import org.kohsuke.accmod.Restricted;
3840
import org.kohsuke.accmod.restrictions.NoExternalUse;
3941

40-
import static org.apache.commons.lang3.StringUtils.trimToNull;
41-
4242
/**
4343
* Specialised class to configure how to build a new instance of
4444
* BitbucketServerEndpoint using {@link ConfigurationAsCode}.
@@ -55,19 +55,26 @@ public BitbucketServerEndpointConfigurator() {
5555
}
5656

5757
@Override
58-
protected BitbucketServerEndpoint instance(Mapping mapping, ConfigurationContext context) throws ConfiguratorException {
59-
final String displayName = mapping.getScalarValue("displayName");
58+
protected BitbucketServerEndpoint instance(@NonNull Mapping mapping, @NonNull ConfigurationContext context) throws ConfiguratorException {
59+
final Configurator<String> stringConfigurator = context.lookupOrFail(String.class);
60+
61+
final String displayName = stringConfigurator.configure(mapping.get("displayName"), context);
62+
mapping.remove("displayName");
63+
6064
final String serverURL;
6165
if (mapping.containsKey("serverUrl")) {
62-
serverURL = mapping.getScalarValue("serverUrl");
66+
serverURL = stringConfigurator.configure(mapping.get("serverUrl"), context);
67+
mapping.remove("serverUrl");
6368
} else {
64-
serverURL = mapping.getScalarValue("serverURL");
69+
serverURL = stringConfigurator.configure(mapping.get("serverURL"), context);
70+
mapping.remove("serverURL");
6571
}
6672
String serverVersion = null;
6773
if (mapping.containsKey("serverVersion")) {
68-
serverVersion = mapping.getScalarValue("serverVersion");
74+
serverVersion = stringConfigurator.configure(mapping.get("serverVersion"), context);
75+
mapping.remove("serverVersion");
6976
}
70-
BitbucketWebhookConfiguration webhook = getWebhook(mapping);
77+
BitbucketWebhookConfiguration webhook = getWebhook(mapping, context);
7178
BitbucketServerEndpoint endpoint = new BitbucketServerEndpoint(displayName, serverURL, webhook );
7279
if (serverVersion != null) {
7380
endpoint.setServerVersion(serverVersion);
@@ -85,31 +92,39 @@ protected BitbucketServerEndpoint instance(Mapping mapping, ConfigurationContext
8592
}
8693

8794
@SuppressWarnings("deprecation")
88-
private BitbucketWebhookConfiguration getWebhook(Mapping mapping) {
95+
private BitbucketWebhookConfiguration getWebhook(@NonNull Mapping mapping, @NonNull ConfigurationContext context) {
96+
final Configurator<String> stringConfigurator = context.lookupOrFail(String.class);
97+
final Configurator<Boolean> boolConfigurator = context.lookupOrFail(Boolean.class);
98+
8999
boolean manageHooks = false;
90100
if (mapping.containsKey("manageHooks")) {
91101
logger.warning("manageHooks is deprecated, replace from your CasC definition with the appropriate webhook definition.");
92-
manageHooks = Boolean.parseBoolean(trimToNull(mapping.getScalarValue("manageHooks")));
102+
manageHooks = boolConfigurator.configure(mapping.get("manageHooks"), context);
103+
mapping.remove("manageHooks");
93104
}
94105
String credentialsId = null;
95106
if (mapping.containsKey("credentialsId")) {
96107
logger.warning("credentialsId is deprecated, replace from your CasC definition with the appropriate webhook definition.");
97-
credentialsId = mapping.getScalarValue("credentialsId");
108+
credentialsId = stringConfigurator.configure(mapping.get("credentialsId"), context);
109+
mapping.remove("credentialsId");
98110
}
99111
boolean enableHookSignature = false;
100112
if (mapping.containsKey("enableHookSignature")) {
101113
logger.warning("enableHookSignature is deprecated, replace from your CasC definition with the appropriate webhook definition.");
102-
enableHookSignature = Boolean.parseBoolean(trimToNull(mapping.getScalarValue("enableHookSignature")));
114+
enableHookSignature = boolConfigurator.configure(mapping.get("enableHookSignature"), context);
115+
mapping.remove("enableHookSignature");
103116
}
104117
String hookSignatureCredentialsId = null;
105118
if (mapping.containsKey("hookSignatureCredentialsId")) {
106119
logger.warning("hookSignatureCredentialsId is deprecated, replace from your CasC definition with the appropriate webhook definition.");
107-
hookSignatureCredentialsId = mapping.getScalarValue("hookSignatureCredentialsId");
120+
hookSignatureCredentialsId = stringConfigurator.configure(mapping.get("hookSignatureCredentialsId"), context);
121+
mapping.remove("hookSignatureCredentialsId");
108122
}
109123
String webhookImplementation = null;
110124
if (mapping.containsKey("webhookImplementation")) {
111125
logger.warning("webhookImplementation is deprecated, replace from your CasC definition with the appropriate webhook definition.");
112-
webhookImplementation = mapping.getScalarValue("webhookImplementation");
126+
webhookImplementation = stringConfigurator.configure(mapping.get("webhookImplementation"), context);
127+
mapping.remove("webhookImplementation");
113128
}
114129
BitbucketWebhookConfiguration webhook;
115130
if ("NATIVE".equals(webhookImplementation)) {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2025, Nikolas Falco
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+
package com.cloudbees.jenkins.plugins.bitbucket.impl.casc;
25+
26+
import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration;
27+
import com.cloudbees.jenkins.plugins.bitbucket.impl.BitbucketPlugin;
28+
import io.jenkins.plugins.casc.ConfigurationAsCode;
29+
import org.junit.jupiter.api.BeforeAll;
30+
import org.junit.jupiter.api.Test;
31+
import org.junitpioneer.jupiter.SetEnvironmentVariable;
32+
import org.jvnet.hudson.test.JenkinsRule;
33+
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
37+
@WithJenkins
38+
class BitbucketServerEndpointConfiguratorTest {
39+
40+
private static JenkinsRule r;
41+
42+
@BeforeAll
43+
static void init(JenkinsRule rule) {
44+
r = rule;
45+
BitbucketPlugin.aliases();
46+
}
47+
48+
@SetEnvironmentVariable(key = "SERVER_URL", value = "https://acme.bitbucket.com")
49+
@Test
50+
void test() {
51+
ConfigurationAsCode.get().configure(getClass().getResource("configuration-as-code-resolve-envvars.yml").toString());
52+
53+
BitbucketEndpointConfiguration instance = BitbucketEndpointConfiguration.get();
54+
assertThat(instance.getEndpoints())
55+
.hasSize(1)
56+
.element(0)
57+
.satisfies(endpoint -> {
58+
assertThat(endpoint.getServerURL()).isEqualTo("https://acme.bitbucket.com");
59+
});
60+
}
61+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
unclassified:
3+
bitbucketendpointconfiguration:
4+
endpoints:
5+
- bitbucketServerEndpoint:
6+
displayName: "Example Inc"
7+
serverUrl: ${SERVER_URL}
8+
callCanMerge: false
9+
callChanges: false
10+
serverVersion: VERSION_7
11+
webhookImplementation: NATIVE

0 commit comments

Comments
 (0)