Skip to content

Commit 939a48b

Browse files
cronikVlatombe
andauthored
Add KubernetesCloudTrait extension point (#1701)
Co-authored-by: Kyle Cronin <[email protected]> Co-authored-by: Vincent Latombe <[email protected]>
1 parent b47977d commit 939a48b

File tree

7 files changed

+217
-8
lines changed

7 files changed

+217
-8
lines changed

src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
import hudson.model.Item;
2323
import hudson.model.ItemGroup;
2424
import hudson.model.Label;
25+
import hudson.model.Saveable;
2526
import hudson.security.ACL;
2627
import hudson.security.AccessControlled;
2728
import hudson.security.Permission;
2829
import hudson.slaves.Cloud;
2930
import hudson.slaves.NodeProvisioner;
31+
import hudson.util.DescribableList;
3032
import hudson.util.FormApply;
3133
import hudson.util.FormValidation;
3234
import hudson.util.ListBoxModel;
@@ -59,6 +61,7 @@
5961
import java.util.List;
6062
import java.util.Map;
6163
import java.util.Objects;
64+
import java.util.Optional;
6265
import java.util.Set;
6366
import java.util.concurrent.ConcurrentHashMap;
6467
import java.util.concurrent.TimeUnit;
@@ -170,6 +173,10 @@ public class KubernetesCloud extends Cloud implements PodTemplateGroup {
170173
@CheckForNull
171174
private GarbageCollection garbageCollection;
172175

176+
@NonNull
177+
private DescribableList<KubernetesCloudTrait, KubernetesCloudTraitDescriptor> traits =
178+
new DescribableList<>(Saveable.NOOP);
179+
173180
/**
174181
* namespace -> informer
175182
* Use to watch pod events per namespace.
@@ -370,6 +377,35 @@ public void setGarbageCollection(GarbageCollection garbageCollection) {
370377
this.garbageCollection = garbageCollection;
371378
}
372379

380+
/**
381+
* Get list of traits enabled for this cloud.
382+
* @return configured traits, never null
383+
*/
384+
@NonNull
385+
public List<KubernetesCloudTrait> getTraits() {
386+
return traits;
387+
}
388+
389+
/**
390+
* Replace the traits enabled for this cloud.
391+
* @param traits configured traits, if {@code null} traits will be cleared.
392+
*/
393+
@DataBoundSetter
394+
public void setTraits(List<KubernetesCloudTrait> traits) {
395+
this.traits = new DescribableList<>(Saveable.NOOP, Util.fixNull(traits));
396+
}
397+
398+
/**
399+
* Find configuration trait by type.
400+
* @param traitType trait type class
401+
* @return configuration trait or empty if not configured
402+
* @param <T> trait type
403+
*/
404+
@NonNull
405+
public <T extends KubernetesCloudTrait> Optional<T> getTrait(Class<T> traitType) {
406+
return Optional.ofNullable(this.traits.get(traitType));
407+
}
408+
373409
/**
374410
* @return same as {@link #getJenkinsUrlOrNull}, if set
375411
* @throws IllegalStateException if no Jenkins URL could be computed.
@@ -1284,6 +1320,16 @@ public int getDefaultRetentionTimeout() {
12841320
public int getDefaultWaitForPod() {
12851321
return DEFAULT_WAIT_FOR_POD_SEC;
12861322
}
1323+
1324+
@SuppressWarnings("unused") // used by jelly
1325+
public List<? extends KubernetesCloudTraitDescriptor> getAllTraits() {
1326+
return KubernetesCloudTrait.all();
1327+
}
1328+
1329+
@SuppressWarnings("unused") // used by jelly
1330+
public DescribableList<KubernetesCloudTrait, KubernetesCloudTraitDescriptor> getDefaultTraits() {
1331+
return new DescribableList<>(Saveable.NOOP, KubernetesCloudTrait.getDefaultTraits());
1332+
}
12871333
}
12881334

12891335
@Override
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.csanchez.jenkins.plugins.kubernetes;
2+
3+
import hudson.ExtensionList;
4+
import hudson.ExtensionPoint;
5+
import hudson.model.AbstractDescribableImpl;
6+
import java.util.List;
7+
import java.util.Optional;
8+
9+
/**
10+
* Extension point for {@link KubernetesCloud} configuration traits.
11+
*/
12+
public abstract class KubernetesCloudTrait extends AbstractDescribableImpl<KubernetesCloudTrait>
13+
implements ExtensionPoint {
14+
15+
/**
16+
* @return all the {@link KubernetesCloudTrait} descriptor instances.
17+
*/
18+
public static ExtensionList<KubernetesCloudTraitDescriptor> all() {
19+
return ExtensionList.lookup(KubernetesCloudTraitDescriptor.class);
20+
}
21+
22+
public static List<KubernetesCloudTrait> getDefaultTraits() {
23+
return all().stream()
24+
.map(KubernetesCloudTraitDescriptor::getDefaultTrait)
25+
.filter(Optional::isPresent)
26+
.map(Optional::get)
27+
.toList();
28+
}
29+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.csanchez.jenkins.plugins.kubernetes;
2+
3+
import hudson.model.Descriptor;
4+
import java.util.Optional;
5+
6+
/**
7+
* Descriptor base type for {@link KubernetesCloudTrait} implementations.
8+
*/
9+
public abstract class KubernetesCloudTraitDescriptor extends Descriptor<KubernetesCloudTrait> {
10+
11+
/**
12+
* Get default trait configuration for a new {@link KubernetesCloud} instance.
13+
*
14+
* @return optional default trait configuration
15+
*/
16+
public Optional<KubernetesCloudTrait> getDefaultTrait() {
17+
return Optional.empty();
18+
}
19+
}

src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud/config.jelly

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,9 @@ THE SOFTWARE.
9191
deleteCaption="${%Delete Pod Label}" />
9292
</f:entry>
9393

94-
<f:advanced title="${%Pod Retention}">
95-
<f:dropdownDescriptorSelector title="${%Pod Retention}" field="podRetention"
96-
descriptors="${descriptor.allowedPodRetentions}" default="${descriptor.defaultPodRetention}" />
97-
</f:advanced>
94+
<f:dropdownDescriptorSelector title="${%Pod Retention}" field="podRetention"
95+
descriptors="${descriptor.allowedPodRetentions}"
96+
default="${descriptor.defaultPodRetention}" />
9897

9998
<f:entry title="${%Max connections to Kubernetes API}" field="maxRequestsPerHostStr">
10099
<f:number default="32" checkMethod="post"/>
@@ -119,5 +118,9 @@ THE SOFTWARE.
119118
<f:entry title="${%Defaults Provider Template Name}" field="defaultsProviderTemplate">
120119
<f:textbox default=""/>
121120
</f:entry>
121+
122122
<f:optionalProperty field="garbageCollection" title="${%Enable garbage collection}"/>
123+
124+
<f:descriptorList field="traits" descriptors="${descriptor.allTraits}" />
125+
123126
</j:jelly>

src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloudTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ public void copyConstructor() throws Exception {
220220
pt.setName("podTemplate");
221221

222222
KubernetesCloud cloud = new KubernetesCloud("name");
223-
var objectProperties =
224-
Set.of("templates", "podRetention", "podLabels", "labels", "serverCertificate", "garbageCollection");
223+
var objectProperties = Set.of(
224+
"templates", "podRetention", "podLabels", "labels", "serverCertificate", "garbageCollection", "traits");
225225
for (String property : PropertyUtils.describe(cloud).keySet()) {
226226
if (PropertyUtils.isWriteable(cloud, property)) {
227227
Class<?> propertyType = PropertyUtils.getPropertyType(cloud, property);
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package org.csanchez.jenkins.plugins.kubernetes;
2+
3+
import static org.hamcrest.MatcherAssert.assertThat;
4+
import static org.hamcrest.Matchers.*;
5+
import static org.junit.Assert.assertEquals;
6+
import static org.junit.Assert.assertSame;
7+
import static org.junit.Assert.assertTrue;
8+
9+
import edu.umd.cs.findbugs.annotations.NonNull;
10+
import hudson.ExtensionList;
11+
import java.util.List;
12+
import java.util.Optional;
13+
import org.junit.Rule;
14+
import org.junit.Test;
15+
import org.jvnet.hudson.test.JenkinsRule;
16+
import org.jvnet.hudson.test.TestExtension;
17+
import org.jvnet.hudson.test.WithoutJenkins;
18+
import org.kohsuke.stapler.DataBoundConstructor;
19+
20+
public class KubernetesCloudTraitTest {
21+
22+
@Rule
23+
public JenkinsRule j = new JenkinsRule();
24+
25+
@Test
26+
public void testKubernetesCloudAll() {
27+
assertEquals(2, KubernetesCloudTrait.all().size());
28+
}
29+
30+
@Test
31+
public void testKubernetesCloudDescriptorAllTraits() {
32+
var descriptors = ExtensionList.lookup(KubernetesCloud.DescriptorImpl.class);
33+
assertThat(descriptors.size(), greaterThanOrEqualTo(1));
34+
assertEquals(2, descriptors.get(0).getAllTraits().size());
35+
}
36+
37+
@Test
38+
public void testKubernetesCloudDescriptorDefaultTraits() {
39+
var descriptors = ExtensionList.lookup(KubernetesCloud.DescriptorImpl.class);
40+
assertThat(descriptors.size(), greaterThanOrEqualTo(1));
41+
var traits = descriptors.get(0).getDefaultTraits();
42+
assertEquals(1, traits.size());
43+
var traitB = traits.get(TraitB.class);
44+
assertThat(traitB, notNullValue());
45+
assertThat(traitB.getValue(), equalTo("default"));
46+
}
47+
48+
@WithoutJenkins
49+
@Test
50+
public void testKubernetesCloudTraits() {
51+
var cloud = new KubernetesCloud("Foo");
52+
53+
// empty optional when trait instance not found
54+
assertTrue(cloud.getTrait(TraitA.class).isEmpty());
55+
56+
// set traits
57+
var traitA = new TraitA();
58+
cloud.setTraits(List.of(traitA, new TraitB("foo")));
59+
assertEquals(2, cloud.getTraits().size());
60+
61+
// get trait by class
62+
var maybeTraitA = cloud.getTrait(TraitA.class);
63+
assertTrue(maybeTraitA.isPresent());
64+
assertSame(traitA, maybeTraitA.get());
65+
66+
// handle null values
67+
cloud.setTraits(null);
68+
assertTrue(cloud.getTraits().isEmpty());
69+
}
70+
71+
public static class TraitA extends KubernetesCloudTrait {
72+
@DataBoundConstructor
73+
public TraitA() {}
74+
75+
@TestExtension
76+
public static class DescriptorImpl extends KubernetesCloudTraitDescriptor {
77+
@NonNull
78+
@Override
79+
public String getDisplayName() {
80+
return "Trait A";
81+
}
82+
}
83+
}
84+
85+
public static class TraitB extends KubernetesCloudTrait {
86+
87+
private final String value;
88+
89+
@DataBoundConstructor
90+
public TraitB(String value) {
91+
this.value = value;
92+
}
93+
94+
public String getValue() {
95+
return value;
96+
}
97+
98+
@TestExtension
99+
public static class DescriptorImpl extends KubernetesCloudTraitDescriptor {
100+
@NonNull
101+
@Override
102+
public String getDisplayName() {
103+
return "Trait B";
104+
}
105+
106+
@Override
107+
public Optional<KubernetesCloudTrait> getDefaultTrait() {
108+
return Optional.of(new TraitB("default"));
109+
}
110+
}
111+
}
112+
}

src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodContainerSourceTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import static org.junit.Assert.*;
44

55
import edu.umd.cs.findbugs.annotations.NonNull;
6-
import hudson.Extension;
76
import io.fabric8.kubernetes.api.model.ContainerStatus;
87
import io.fabric8.kubernetes.api.model.EphemeralContainer;
98
import io.fabric8.kubernetes.api.model.Pod;
@@ -14,6 +13,7 @@
1413
import org.junit.Rule;
1514
import org.junit.Test;
1615
import org.jvnet.hudson.test.JenkinsRule;
16+
import org.jvnet.hudson.test.TestExtension;
1717
import org.jvnet.hudson.test.WithoutJenkins;
1818

1919
public class PodContainerSourceTest {
@@ -155,7 +155,7 @@ public void defaultPodContainerSourceGetContainerStatus() {
155155
assertFalse(status.isPresent());
156156
}
157157

158-
@Extension
158+
@TestExtension
159159
public static class TestPodContainerSource extends PodContainerSource {
160160

161161
@Override

0 commit comments

Comments
 (0)