Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public class KubernetesCloud extends Cloud implements PodTemplateGroup {
private transient Map<String, String> labels;

private List<PodLabel> podLabels = new ArrayList<>();
private List<PodAnnotation> podAnnotations = new ArrayList<>();
private boolean usageRestricted;

private int maxRequestsPerHost;
Expand Down Expand Up @@ -569,6 +570,37 @@ Map<String, String> getPodLabelsMap() {
return PodLabel.toMap(getPodLabels());
}

/**
* Annotations for all pods started by the plugin
*/
@NonNull
public List<PodAnnotation> getPodAnnotations() {
return podAnnotations == null ? Collections.emptyList() : podAnnotations;
}

/**
* Set Pod annotations for all pods started by the plugin.
*/
@DataBoundSetter
public void setPodAnnotations(@CheckForNull List<PodAnnotation> annotations) {
this.podAnnotations = new ArrayList<>();
if (annotations != null) {
this.podAnnotations.addAll(annotations);
}
}

/**
* Map of annotations to add to all pods started by the plugin
* @return immutable map of pod annotations
*/
Map<String, String> getPodAnnotationsMap() {
Map<String, String> result = new HashMap<>();
for (PodAnnotation annotation : getPodAnnotations()) {
result.put(annotation.getKey(), annotation.getValue());
}
return Collections.unmodifiableMap(result);
}

@DataBoundSetter
public void setMaxRequestsPerHostStr(String maxRequestsPerHostStr) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,11 @@ public Pod build() {
metadataBuilder.addToLabels(LABEL_KUBERNETES_CONTROLLER, sanitizeLabel(cloud.getJenkinsUrlOrNull()));
}

Map<String, String> annotations = getAnnotationsMap(template.getAnnotations());
Map<String, String> annotations = new HashMap<>();
if (agent != null) {
annotations.putAll(agent.getKubernetesCloud().getPodAnnotationsMap());
}
annotations.putAll(getAnnotationsMap(template.getAnnotations()));
if (!annotations.isEmpty()) {
metadataBuilder.withAnnotations(annotations);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ THE SOFTWARE.
deleteCaption="${%Delete Pod Label}" />
</f:entry>

<f:entry title="${%Pod Annotations}" field="podAnnotations">
<f:repeatableHeteroProperty field="podAnnotations" hasHeader="true" addCaption="${%Add Pod Annotation}"
deleteCaption="${%Delete Pod Annotation}" />
</f:entry>

<f:dropdownDescriptorSelector title="${%Pod Retention}" field="podRetention"
descriptors="${descriptor.allowedPodRetentions}"
default="${descriptor.defaultPodRetention}" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div>
<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations">Annotations</a>
applied to all Pods started by this cloud. These act as a baseline; annotations defined at the pipeline or
pod template level override them, and system annotations (such as the build URL) always take precedence.
<p>Examples:
<ul>
<li><code>app.kubernetes.io/managed-by=jenkins</code></li>
<li><code>cost-center=platform</code></li>
<li><code>team=backend</code></li>
</ul>
</p>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,35 @@ public void testPodLabels() {
assertEquals(defaultPodLabelsList, cloud.getPodLabels());
}

@Test
public void testPodAnnotations() {
KubernetesCloud cloud = new KubernetesCloud("name");

// default is empty list
assertEquals(Collections.emptyList(), cloud.getPodAnnotations());
assertEquals(Collections.emptyMap(), cloud.getPodAnnotationsMap());

// set annotations and verify list and map
List<PodAnnotation> annotations =
Arrays.asList(new PodAnnotation("team", "backend"), new PodAnnotation("cost-center", "platform"));
cloud.setPodAnnotations(annotations);
Map<String, String> expected = new LinkedHashMap<>();
expected.put("team", "backend");
expected.put("cost-center", "platform");
assertEquals(expected, cloud.getPodAnnotationsMap());
assertEquals(annotations, cloud.getPodAnnotations());

// null resets to empty
cloud.setPodAnnotations(null);
assertEquals(Collections.emptyList(), cloud.getPodAnnotations());
assertEquals(Collections.emptyMap(), cloud.getPodAnnotationsMap());

// empty list also results in empty
cloud.setPodAnnotations(new ArrayList<>());
assertEquals(Collections.emptyList(), cloud.getPodAnnotations());
assertEquals(Collections.emptyMap(), cloud.getPodAnnotationsMap());
}

@Test
public void testLabels() {
KubernetesCloud cloud = new KubernetesCloud("name");
Expand Down Expand Up @@ -228,7 +257,14 @@ public void copyConstructor() throws Exception {

KubernetesCloud cloud = new KubernetesCloud("name");
var objectProperties = Set.of(
"templates", "podRetention", "podLabels", "labels", "serverCertificate", "garbageCollection", "traits");
"templates",
"podRetention",
"podLabels",
"podAnnotations",
"labels",
"serverCertificate",
"garbageCollection",
"traits");
for (String property : PropertyUtils.describe(cloud).keySet()) {
if (PropertyUtils.isWriteable(cloud, property)) {
Class<?> propertyType = PropertyUtils.getPropertyType(cloud, property);
Expand Down Expand Up @@ -256,6 +292,7 @@ public void copyConstructor() throws Exception {
cloud.setPodRetention(new Always());
cloud.setPodLabels(PodLabel.listOf("foo", "bar", "cat", "dog"));
cloud.setLabels(Collections.singletonMap("foo", "bar"));
cloud.setPodAnnotations(Arrays.asList(new PodAnnotation("team", "backend")));

KubernetesCloud copy = new KubernetesCloud("copy", cloud);
assertEquals("copy", copy.name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,33 @@ public void testBuildFromYaml(boolean directConnection) throws Exception {
assertThat(container0.getResources().getLimits(), hasEntry("example.com/dongle", new Quantity("42")));
}

@Test
public void testCloudAnnotationsAppliedToPod() throws Exception {
cloud.setPodAnnotations(
Arrays.asList(new PodAnnotation("team", "backend"), new PodAnnotation("cost-center", "platform")));
PodTemplate template = new PodTemplate();
template.setYaml(loadYamlFile("pod-busybox.yaml"));
setupStubs();
Pod pod = new PodTemplateBuilder(template, slave).build();
Map<String, String> annotations = pod.getMetadata().getAnnotations();
assertThat(annotations, hasEntry("team", "backend"));
assertThat(annotations, hasEntry("cost-center", "platform"));
// system GC annotation is always present
assertThat(annotations, hasKey(GarbageCollection.ANNOTATION_LAST_REFRESH));
}

@Test
public void testTemplateAnnotationsOverrideCloudAnnotations() throws Exception {
cloud.setPodAnnotations(Arrays.asList(new PodAnnotation("owner", "cloud-value")));
PodTemplate template = new PodTemplate();
template.setYaml(loadYamlFile("pod-busybox.yaml"));
template.setAnnotations(Arrays.asList(new PodAnnotation("owner", "template-value")));
setupStubs();
Pod pod = new PodTemplateBuilder(template, slave).build();
Map<String, String> annotations = pod.getMetadata().getAnnotations();
assertThat("template annotation overrides cloud annotation", annotations, hasEntry("owner", "template-value"));
}

@Test
public void testBuildJnlpFromYamlWithNullEnv() throws Exception {
PodTemplate template = new PodTemplate();
Expand Down
Loading