diff --git a/examples/dind.groovy b/examples/dind.groovy index e5fba67d16..27a81e92ea 100644 --- a/examples/dind.groovy +++ b/examples/dind.groovy @@ -10,6 +10,9 @@ podTemplate(yaml: ''' apiVersion: v1 kind: Pod spec: + volumes: + - name: docker-socket + emptyDir: {} containers: - name: docker image: docker:19.03.1 @@ -17,19 +20,19 @@ spec: - sleep args: - 99d - env: - - name: DOCKER_HOST - value: tcp://localhost:2375 + volumeMounts: + - name: docker-socket + mountPath: /var/run - name: docker-daemon image: docker:19.03.1-dind securityContext: privileged: true - env: - - name: DOCKER_TLS_CERTDIR - value: "" + volumeMounts: + - name: docker-socket + mountPath: /var/run ''') { node(POD_LABEL) { - git 'https://github.com/jenkinsci/docker-jnlp-slave.git' + writeFile file: 'Dockerfile', text: 'FROM scratch' container('docker') { sh 'docker version && DOCKER_BUILDKIT=1 docker build --progress plain -t testing .' } diff --git a/pom.xml b/pom.xml index 4adcfd1306..2906b9dca6 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins plugin - 4.3 + 4.7 @@ -39,7 +39,7 @@ - 1.26.5 + 1.27.1 -SNAPSHOT @@ -49,7 +49,8 @@ 8 2.222.4 false - 1.6.0 + 1.7.2 + 2.81 true @@ -62,7 +63,7 @@ org.jenkins-ci.plugins jackson2-api - 2.11.0 + 2.11.2 org.jenkins-ci.plugins @@ -114,9 +115,17 @@ ${pipeline-model-definition.version} true + + org.jenkinsci.plugins + pipeline-model-definition + ${pipeline-model-definition.version} + tests + test + org.jenkins-ci.plugins.workflow workflow-cps + ${workflow-cps.version} true @@ -151,6 +160,7 @@ org.jenkins-ci.plugins.workflow workflow-cps + ${workflow-cps.version} tests test @@ -180,7 +190,7 @@ org.jenkins-ci.plugins ssh-agent - 1.19 + 1.20 test @@ -223,6 +233,12 @@ test-harness test + + org.jenkins-ci.plugins + docker-workflow + 1.23 + test + org.jenkins-ci.plugins @@ -269,17 +285,10 @@ 0 50 0.85 + ${jenkins.host.address} - - org.codehaus.mojo - versions-maven-plugin - 2.7 - - file://${basedir}/src/test/resources/rules.xml - - diff --git a/src/main/java/io/jenkins/plugins/kubernetes/NoDelayProvisionerStrategy.java b/src/main/java/io/jenkins/plugins/kubernetes/NoDelayProvisionerStrategy.java index f7a055dd12..32d8d33720 100644 --- a/src/main/java/io/jenkins/plugins/kubernetes/NoDelayProvisionerStrategy.java +++ b/src/main/java/io/jenkins/plugins/kubernetes/NoDelayProvisionerStrategy.java @@ -6,12 +6,14 @@ import hudson.slaves.CloudProvisioningListener; import hudson.slaves.NodeProvisioner; import jenkins.model.Jenkins; +import jenkins.util.Timer; import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -28,12 +30,12 @@ public class NoDelayProvisionerStrategy extends NodeProvisioner.Strategy { private static final Logger LOGGER = Logger.getLogger(NoDelayProvisionerStrategy.class.getName()); - private static final boolean DISABLE_NODELAY_PROVISING = Boolean.valueOf( + private static final boolean DISABLE_NO_DELAY_PROVISIONING = Boolean.parseBoolean( System.getProperty("io.jenkins.plugins.kubernetes.disableNoDelayProvisioning")); @Override public NodeProvisioner.StrategyDecision apply(NodeProvisioner.StrategyState strategyState) { - if (DISABLE_NODELAY_PROVISING) { + if (DISABLE_NO_DELAY_PROVISIONING) { LOGGER.log(Level.FINE, "Provisioning not complete, NoDelayProvisionerStrategy is disabled"); return NodeProvisioner.StrategyDecision.CONSULT_REMAINING_STRATEGIES; } @@ -49,6 +51,8 @@ public NodeProvisioner.StrategyDecision apply(NodeProvisioner.StrategyState stra int currentDemand = snapshot.getQueueLength(); LOGGER.log(Level.FINE, "Available capacity={0}, currentDemand={1}", new Object[]{availableCapacity, currentDemand}); + int totalPlannedNodes = 0; + boolean canProvision = false; if (availableCapacity < currentDemand) { List jenkinsClouds = new ArrayList<>(Jenkins.get().clouds); Collections.shuffle(jenkinsClouds); @@ -61,22 +65,29 @@ public NodeProvisioner.StrategyDecision apply(NodeProvisioner.StrategyState stra continue; } } + canProvision = true; Collection plannedNodes = cloud.provision(label, workloadToProvision); LOGGER.log(Level.FINE, "Planned {0} new nodes", plannedNodes.size()); fireOnStarted(cloud, strategyState.getLabel(), plannedNodes); strategyState.recordPendingLaunches(plannedNodes); availableCapacity += plannedNodes.size(); + totalPlannedNodes += plannedNodes.size(); LOGGER.log(Level.FINE, "After provisioning, available capacity={0}, currentDemand={1}", new Object[]{availableCapacity, currentDemand}); break; } } - if (availableCapacity >= currentDemand) { - LOGGER.log(Level.FINE, "Provisioning completed"); - return NodeProvisioner.StrategyDecision.PROVISIONING_COMPLETED; + if (currentDemand - availableCapacity <= 0) { + LOGGER.log(Level.FINE, String.format("Provisioning completed for label: [%s]", label)); } else { - LOGGER.log(Level.FINE, "Provisioning not complete, consulting remaining strategies"); - return NodeProvisioner.StrategyDecision.CONSULT_REMAINING_STRATEGIES; + if (!canProvision) { + return NodeProvisioner.StrategyDecision.CONSULT_REMAINING_STRATEGIES; + } + if (totalPlannedNodes > 0 && label != null) { + LOGGER.log(Level.FINE, "Suggesting NodeProvisioner review"); + Timer.get().schedule(label.nodeProvisioner::suggestReviewNow, 1L, TimeUnit.SECONDS); + } } + return NodeProvisioner.StrategyDecision.PROVISIONING_COMPLETED; } private static void fireOnStarted(final Cloud cloud, final Label label, diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java index afa29cb86b..6d6d5bca38 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java @@ -19,15 +19,14 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.servlet.ServletException; +import hudson.Main; import hudson.model.ItemGroup; import hudson.util.XStream2; -import io.fabric8.openshift.client.OpenShiftClient; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.csanchez.jenkins.plugins.kubernetes.pipeline.PodTemplateMap; @@ -63,7 +62,6 @@ import hudson.slaves.NodeProvisioner; import hudson.util.FormValidation; import hudson.util.ListBoxModel; -import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodList; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; @@ -141,6 +139,7 @@ public class KubernetesCloud extends Cloud { @DataBoundConstructor public KubernetesCloud(String name) { super(name); + setMaxRequestsPerHost(DEFAULT_MAX_REQUESTS_PER_HOST); } /** @@ -540,24 +539,25 @@ public synchronized Collection provision(@CheckForN int toBeProvisioned = Math.max(0, excessWorkload - allInProvisioning.size()); LOGGER.log(Level.INFO, "Excess workload after pending Kubernetes agents: {0}", toBeProvisioned); - List r = new ArrayList(); + List plannedNodes = new ArrayList<>(); - for (PodTemplate t: getTemplatesFor(label)) { - LOGGER.log(Level.INFO, "Template for label {0}: {1}", new Object[] { label, t.getName() }); + for (PodTemplate podTemplate: getTemplatesFor(label)) { + LOGGER.log(Level.INFO, "Template for label {0}: {1}", new Object[] { label, podTemplate.getName() }); for (int i = 0; i < toBeProvisioned; i++) { - if (!addProvisionedSlave(t, label, i)) { + // Check concurrency limits + if (!addProvisionedSlave(podTemplate, label, i + allInProvisioning.size())) { break; } - r.add(PlannedNodeBuilderFactory.createInstance().cloud(this).template(t).label(label).build()); + plannedNodes.add(PlannedNodeBuilderFactory.createInstance().cloud(this).template(podTemplate).label(label).build()); } LOGGER.log(Level.FINEST, "Planned Kubernetes agents for template \"{0}\": {1}", - new Object[] { t.getName(), r.size() }); - if (r.size() > 0) { - // Already found a matching template - return r; + new Object[] { podTemplate.getName(), plannedNodes.size() }); + if (plannedNodes.size() > 0) { + // Return early when a matching template was found and nodes were planned + return plannedNodes; } } - return r; + return plannedNodes; } catch (KubernetesClientException e) { Throwable cause = e.getCause(); if (cause instanceof SocketTimeoutException || cause instanceof ConnectException || cause instanceof UnknownHostException) { @@ -579,9 +579,9 @@ public synchronized Collection provision(@CheckForN * Check not too many already running. * */ - private boolean addProvisionedSlave(@Nonnull PodTemplate template, @CheckForNull Label label, int scheduledCount) throws Exception { + private boolean addProvisionedSlave(@Nonnull PodTemplate template, @CheckForNull Label label, int numProvisioned) throws Exception { if (containerCap == 0) { - return true; + return false; } KubernetesClient client = connect(); @@ -592,23 +592,27 @@ private boolean addProvisionedSlave(@Nonnull PodTemplate template, @CheckForNull templateNamespace = client.getNamespace(); } + // check overall concurrency limit using the default label(s) on all templates Map podLabels = getPodLabelsMap(); - List allActiveSlavePods = getActiveSlavePods(client, templateNamespace, podLabels); - if (allActiveSlavePods != null && containerCap <= allActiveSlavePods.size() + scheduledCount) { + long numRunningOrPending = getNumActiveSlavePods(client, templateNamespace, podLabels); + if (numRunningOrPending + numProvisioned >= containerCap) { LOGGER.log(Level.INFO, - "Maximum number of concurrently running agent pods ({0}) reached for Kubernetes Cloud {4}, not provisioning: {1} running or pending in namespace {2} with Kubernetes labels {3}", - new Object[] { containerCap, allActiveSlavePods.size() + scheduledCount, templateNamespace, getLabels(), name }); + "Maximum number of concurrently running agent pods ({0}) reached for Kubernetes Cloud {4}, " + + "not provisioning: {1} running or pending in namespace {2} with Kubernetes labels {3}", + new Object[] { containerCap, numRunningOrPending, templateNamespace, getLabels(), name }); return false; } - Map labelsMap = new HashMap<>(podLabels); - labelsMap.putAll(template.getLabelsMap()); - List activeTemplateSlavePods = getActiveSlavePods(client, templateNamespace, labelsMap); - if (activeTemplateSlavePods != null && allActiveSlavePods != null && template.getInstanceCap() <= activeTemplateSlavePods.size() + scheduledCount) { + // check template-level concurrency limit using template-level labels + Map templateLabels = new HashMap<>(podLabels); + templateLabels.putAll(template.getLabelsMap()); + numRunningOrPending = getNumActiveSlavePods(client, templateNamespace, podLabels); + if (numRunningOrPending + numProvisioned >= template.getInstanceCap()) { LOGGER.log(Level.INFO, - "Maximum number of concurrently running agent pods ({0}) reached for template {1} in Kubernetes Cloud {6}, not provisioning: {2} running or pending in namespace {3} with label \"{4}\" and Kubernetes labels {5}", - new Object[] { template.getInstanceCap(), template.getName(), activeTemplateSlavePods.size() + scheduledCount, - templateNamespace, label == null ? "" : label.toString(), labelsMap, name }); + "Maximum number of concurrently running agent pods ({0}) reached for template {1} in Kubernetes Cloud {6}, " + + "not provisioning: {2} running or pending in namespace {3} with label \"{4}\" and Kubernetes labels {5}", + new Object[] { template.getInstanceCap(), template.getName(), numRunningOrPending, + templateNamespace, label == null ? "" : label.toString(), templateLabels, name }); return false; } return true; @@ -617,16 +621,15 @@ private boolean addProvisionedSlave(@Nonnull PodTemplate template, @CheckForNull /** * Query for running or pending pods */ - private List getActiveSlavePods(KubernetesClient client, String templateNamespace, Map podLabels) { + private long getNumActiveSlavePods(KubernetesClient client, String templateNamespace, Map podLabels) { PodList slaveList = client.pods().inNamespace(templateNamespace).withLabels(podLabels).list(); - List activeSlavePods = null; // JENKINS-53370 check for nulls if (slaveList != null && slaveList.getItems() != null) { - activeSlavePods = slaveList.getItems().stream() // + return slaveList.getItems().stream() // .filter(x -> x.getStatus().getPhase().toLowerCase().matches("(running|pending)")) - .collect(Collectors.toList()); + .count(); } - return activeSlavePods; + return 0; } @Override @@ -735,7 +738,10 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(defaultsProviderTemplate, templates, serverUrl, serverCertificate, skipTlsVerify, addMasterProxyEnvVars, capOnlyOnAlivePods, namespace, jenkinsUrl, jenkinsTunnel, credentialsId, containerCap, retentionTimeout, connectTimeout, readTimeout, podLabels, usageRestricted, maxRequestsPerHost, podRetention); + return Objects.hash(defaultsProviderTemplate, templates, serverUrl, serverCertificate, skipTlsVerify, + addMasterProxyEnvVars, capOnlyOnAlivePods, namespace, jenkinsUrl, jenkinsTunnel, credentialsId, + containerCap, retentionTimeout, connectTimeout, readTimeout, podLabels, usageRestricted, + maxRequestsPerHost, podRetention); } public Integer getWaitForPodSec() { @@ -990,4 +996,17 @@ public List getList(@Nonnull KubernetesCloud cloud) { return cloud.getTemplates(); } } + + @Initializer(after = InitMilestone.SYSTEM_CONFIG_LOADED) + public static void hpiRunInit() { + if (Main.isDevelopmentMode) { + Jenkins jenkins = Jenkins.get(); + String hostAddress = System.getProperty("jenkins.host.address"); + if (hostAddress != null && jenkins.clouds.getAll(KubernetesCloud.class).isEmpty()) { + KubernetesCloud cloud = new KubernetesCloud("kubernetes"); + cloud.setJenkinsUrl("http://" + hostAddress + ":8080/jenkins/"); + jenkins.clouds.add(cloud); + } + } + } } diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesLauncher.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesLauncher.java index f8785d0f30..01fc463c81 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesLauncher.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesLauncher.java @@ -38,9 +38,7 @@ import javax.annotation.CheckForNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import hudson.model.Queue; import io.fabric8.kubernetes.client.KubernetesClientException; -import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; @@ -56,7 +54,10 @@ import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.dsl.LogWatch; import io.fabric8.kubernetes.client.dsl.PrettyLoggable; + +import static java.util.logging.Level.FINE; import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; /** * Launches on Kubernetes the specified {@link KubernetesComputer} instance. @@ -124,12 +125,27 @@ public synchronized void launch(SlaveComputer computer, TaskListener listener) { .stream().filter(s -> StringUtils.isNotBlank(s)).findFirst().orElse(null); slave.setNamespace(namespace); - LOGGER.log(Level.FINE, "Creating Pod: {0}/{1}", new Object[] { namespace, podName }); - pod = client.pods().inNamespace(namespace).create(pod); + + TaskListener runListener = template.getListener(); + + LOGGER.log(FINE, "Creating Pod: {0}/{1}", new Object[] { namespace, podName }); + try { + pod = client.pods().inNamespace(namespace).create(pod); + } catch (KubernetesClientException e) { + int httpCode = e.getCode(); + if (400 <= httpCode && httpCode < 500) { // 4xx + runListener.getLogger().printf("ERROR: Unable to create pod %s/%s.%n%s%n", namespace, pod.getMetadata().getName(), e.getMessage()); + PodUtils.cancelQueueItemFor(pod, e.getMessage()); + } else if (500 <= httpCode && httpCode < 600) { // 5xx + LOGGER.log(FINE,"Kubernetes returned HTTP code {0} {1}. Retrying...", new Object[] {e.getCode(), e.getStatus()}); + } else { + LOGGER.log(WARNING, "Kubernetes returned unhandled HTTP code {0} {1}", new Object[] {e.getCode(), e.getStatus()}); + } + throw e; + } LOGGER.log(INFO, "Created Pod: {0}/{1}", new Object[] { namespace, podName }); listener.getLogger().printf("Created Pod: %s/%s%n", namespace, podName); - TaskListener runListener = template.getListener(); runListener.getLogger().printf("Created Pod: %s/%s%n", namespace, podName); template.getWorkspaceVolume().createVolume(client, pod.getMetadata()); diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplate.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplate.java index 69217f7224..2980066b71 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplate.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplate.java @@ -14,6 +14,8 @@ import javax.annotation.Nonnull; import com.google.common.annotations.VisibleForTesting; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; @@ -68,6 +70,11 @@ public class PodTemplate extends AbstractDescribableImpl implements public static final Integer DEFAULT_SLAVE_JENKINS_CONNECTION_TIMEOUT = Integer .getInteger(PodTemplate.class.getName() + ".connectionTimeout", 100); + /** + * Digest function that is used to compute the kubernetes label "jenkins/label-digest" + */ + public static final HashFunction LABEL_DIGEST_FUNCTION = Hashing.sha1(); + private String inheritFrom; private String name; @@ -395,7 +402,17 @@ public Set getLabelSet() { } public Map getLabelsMap() { - return ImmutableMap.of("jenkins/label", label == null ? DEFAULT_LABEL : sanitizeLabel(label)); + if (label == null) { + return ImmutableMap.of( + "jenkins/label", DEFAULT_LABEL, + "jenkins/label-digest", "0" + ); + } else { + return ImmutableMap.of( + "jenkins/label", sanitizeLabel(label), + "jenkins/label-digest", LABEL_DIGEST_FUNCTION.hashString(label).toString() + ); + } } static String sanitizeLabel(String input) { @@ -495,8 +512,8 @@ public void setHostNetwork(Boolean hostNetwork) { this.hostNetwork = hostNetwork; } - public Boolean isHostNetwork() { - return hostNetwork; + public boolean isHostNetwork() { + return isHostNetworkSet()?hostNetwork.booleanValue():false; } public boolean isHostNetworkSet() { @@ -906,6 +923,19 @@ public void save() { } @Extension public static class DescriptorImpl extends Descriptor { + static final String[] STRING_FIELDS = { + "activeDeadlineSeconds", + "idleMinutes", + "instanceCap", + "slaveConnectTimeout", + }; + + public DescriptorImpl() { + for (String field : STRING_FIELDS) { + addHelpFileRedirect(field + "Str", PodTemplate.class, field); + } + } + @Override public String getDisplayName() { return "Kubernetes Pod Template"; diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java index e61b461afa..508277a9d5 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateUtils.java @@ -417,7 +417,11 @@ public static PodTemplate combine(PodTemplate parent, PodTemplate template) { podTemplate.setSupplementalGroups(template.getSupplementalGroups() != null ? template.getSupplementalGroups() : parent.getSupplementalGroups()); - podTemplate.setHostNetwork(template.isHostNetworkSet() ? template.isHostNetwork() : parent.isHostNetwork()); + if (template.isHostNetworkSet()) { + podTemplate.setHostNetwork(template.isHostNetwork()); + } else if (parent.isHostNetworkSet()) { + podTemplate.setHostNetwork(parent.isHostNetwork()); + } List yamls = new ArrayList<>(parent.getYamls()); yamls.addAll(template.getYamls()); diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodUtils.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodUtils.java index 95737c8407..30e2636f39 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodUtils.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/PodUtils.java @@ -17,16 +17,22 @@ package org.csanchez.jenkins.plugins.kubernetes; import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.model.Queue; import io.fabric8.kubernetes.api.model.ContainerStatus; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodStatus; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; import java.util.Collections; import java.util.List; import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; public final class PodUtils { + private static final Logger LOGGER = Logger.getLogger(PodUtils.class.getName()); public static final Predicate CONTAINER_IS_TERMINATED = cs -> cs.getState().getTerminated() != null; public static final Predicate CONTAINER_IS_WAITING = cs -> cs.getState().getWaiting() != null; @@ -51,4 +57,32 @@ public static List getContainerStatus(Pod pod) { public static List getContainers(Pod pod, Predicate predicate) { return getContainerStatus(pod).stream().filter(predicate).collect(Collectors.toList()); } + + /** + * Cancel queue items matching the given pod. + * It uses the annotation "runUrl" added to the pod to do the matching. + * + * It uses the current thread context to list item queues, + * so make sure to be in the right context before calling this method. + * + * @param pod The pod to cancel items for. + * @param reason The reason the item are being cancelled. + */ + public static void cancelQueueItemFor(Pod pod, String reason) { + Queue q = Jenkins.get().getQueue(); + boolean cancelled = false; + for (Queue.Item item: q.getItems()) { + Queue.Task task = item.task; + if (task.getUrl().equals(pod.getMetadata().getAnnotations().get("runUrl"))) { + LOGGER.log(Level.FINE, "Cancelling queue item: \"{0}\"\n{1}", + new Object[]{ task.getDisplayName(), !StringUtils.isBlank(reason) ? "due to " + reason : ""}); + q.cancel(item); + cancelled = true; + break; + } + } + if (!cancelled) { + LOGGER.log(Level.FINE, "No queue item found for pod: {0}/{1}", new Object[] {pod.getMetadata().getNamespace(), pod.getMetadata().getName()}); + } + } } diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java index 0b275ce2b5..f20ca9b0ba 100755 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java @@ -93,8 +93,7 @@ public class ContainerExecDecorator extends LauncherDecorator implements Seriali * stdin buffer size for commands sent to Kubernetes exec api. A low value will cause slowness in commands executed. * A higher value will consume more memory */ - private static final int STDIN_BUFFER_SIZE = Integer - .getInteger(ContainerExecDecorator.class.getName() + ".stdinBufferSize", 2 * 1024); + private static final int STDIN_BUFFER_SIZE = Integer.getInteger(ContainerExecDecorator.class.getName() + ".stdinBufferSize", 16 * 1024); @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "not needed on deserialization") private transient List closables; @@ -612,6 +611,7 @@ private boolean isSeparator(int b) { } private static void doExec(PrintStream in, PrintStream out, boolean[] masks, String... statements) { + long start = System.nanoTime(); // For logging ByteArrayOutputStream loggingOutput = new ByteArrayOutputStream(); // Tee both outputs @@ -632,7 +632,7 @@ private static void doExec(PrintStream in, PrintStream out, boolean[] masks, Str .append("\" "); } tee.println(); - LOGGER.log(Level.FINEST, loggingOutput.toString(encoding)); + LOGGER.log(Level.FINEST, loggingOutput.toString(encoding) + "[" + TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - start) + " μs." + "]"); // We need to exit so that we know when the command has finished. tee.println(EXIT); tee.flush(); diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent.java index 2398a76ac2..e42feabd8d 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent.java @@ -1,7 +1,16 @@ package org.csanchez.jenkins.plugins.kubernetes.pipeline; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.ExtensionList; +import hudson.Util; +import hudson.model.Descriptor; +import hudson.model.Label; +import hudson.util.ListBoxModel; import org.apache.commons.lang.StringUtils; import org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate; +import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; +import org.csanchez.jenkins.plugins.kubernetes.PodTemplate; import org.csanchez.jenkins.plugins.kubernetes.pod.retention.PodRetention; import org.csanchez.jenkins.plugins.kubernetes.pod.yaml.YamlMergeStrategy; import org.csanchez.jenkins.plugins.kubernetes.volumes.workspace.WorkspaceVolume; @@ -13,45 +22,59 @@ import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; -import hudson.Util; - +import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; public class KubernetesDeclarativeAgent extends DeclarativeAgent { private static final Logger LOGGER = Logger.getLogger(KubernetesDeclarativeAgent.class.getName()); + @CheckForNull private String label; + @CheckForNull private String customWorkspace; + @CheckForNull private String cloud; + @CheckForNull private String inheritFrom; private int idleMinutes; - private int instanceCap; + private int instanceCap = Integer.MAX_VALUE; + @CheckForNull private String serviceAccount; + @CheckForNull private String nodeSelector; + @CheckForNull private String namespace; + @CheckForNull private String workingDir; private int activeDeadlineSeconds; private int slaveConnectTimeout; + @CheckForNull private PodRetention podRetention; private ContainerTemplate containerTemplate; private List containerTemplates; + @CheckForNull private String defaultContainer; + @CheckForNull private String yaml; + @CheckForNull private String yamlFile; private YamlMergeStrategy yamlMergeStrategy; + @CheckForNull private WorkspaceVolume workspaceVolume; + @CheckForNull private String supplementalGroups; @DataBoundConstructor @@ -68,6 +91,10 @@ public String getLabel() { return label; } + public String getLabelExpression() { + return label != null ? Label.parse(label).stream().map(Objects::toString).sorted().collect(Collectors.joining(" && ")) : null; + } + @DataBoundSetter public void setLabel(String label) { this.label = Util.fixEmpty(label); @@ -89,7 +116,7 @@ public String getCloud() { @DataBoundSetter public void setCloud(String cloud) { - this.cloud = cloud; + this.cloud = Util.fixEmpty(cloud); } public int getIdleMinutes() { @@ -107,7 +134,11 @@ public String getInheritFrom() { @DataBoundSetter public void setInheritFrom(String inheritFrom) { - this.inheritFrom = inheritFrom; + if (PodTemplateStep.DescriptorImpl.defaultInheritFrom.equals(inheritFrom)) { + this.inheritFrom = null; + } else { + this.inheritFrom = inheritFrom; + } } public int getInstanceCap() { @@ -116,7 +147,11 @@ public int getInstanceCap() { @DataBoundSetter public void setInstanceCap(int instanceCap) { - this.instanceCap = instanceCap; + if (instanceCap <= 0) { + this.instanceCap = Integer.MAX_VALUE; + } else { + this.instanceCap = instanceCap; + } } public String getServiceAccount() { @@ -213,12 +248,12 @@ public void setSlaveConnectTimeout(int slaveConnectTimeout) { } public PodRetention getPodRetention() { - return podRetention; + return this.podRetention == null ? PodTemplateStep.DescriptorImpl.defaultPodRetention : this.podRetention; } @DataBoundSetter - public void setPodRetention(PodRetention podRetention) { - this.podRetention = podRetention; + public void setPodRetention(@CheckForNull PodRetention podRetention) { + this.podRetention = (podRetention == null || podRetention.equals(PodTemplateStep.DescriptorImpl.defaultPodRetention)) ? null : podRetention; } public String getYamlFile() { @@ -240,17 +275,17 @@ public void setYamlMergeStrategy(YamlMergeStrategy yamlMergeStrategy) { } public WorkspaceVolume getWorkspaceVolume() { - return workspaceVolume; + return workspaceVolume == null ? PodTemplateStep.DescriptorImpl.defaultWorkspaceVolume : this.workspaceVolume; } @DataBoundSetter public void setWorkspaceVolume(WorkspaceVolume workspaceVolume) { - this.workspaceVolume = workspaceVolume; + this.workspaceVolume = (workspaceVolume == null || workspaceVolume.equals(PodTemplateStep.DescriptorImpl.defaultWorkspaceVolume)) ? null : workspaceVolume; } @DataBoundSetter public void setSupplementalGroups(String supplementalGroups) { - this.supplementalGroups = supplementalGroups; + this.supplementalGroups = Util.fixEmpty(supplementalGroups); } public String getSupplementalGroups() { @@ -274,7 +309,9 @@ public Map getAsArgs() { "Ignoring containerTemplate option as containerTemplates is also defined"); } } - argMap.put("containers", containerTemplates); + if (containerTemplates != null && !containerTemplates.isEmpty()) { + argMap.put("containers", containerTemplates); + } if (!StringUtils.isEmpty(yaml)) { argMap.put("yaml", yaml); @@ -315,7 +352,7 @@ public Map getAsArgs() { if (podRetention != null) { argMap.put("podRetention", podRetention); } - if (instanceCap > 0) { + if (instanceCap > 0 && instanceCap < Integer.MAX_VALUE) { argMap.put("instanceCap", instanceCap); } if (!StringUtils.isEmpty(supplementalGroups)){ @@ -328,5 +365,40 @@ public Map getAsArgs() { @OptionalExtension(requirePlugins = "pipeline-model-extensions") @Symbol("kubernetes") public static class DescriptorImpl extends DeclarativeAgentDescriptor { + + static final String[] POD_TEMPLATE_FIELDS = {"namespace", "inheritFrom", "yaml", "instanceCap", "podRetention", "supplementalGroups", "idleMinutes", "activeDeadlineSeconds", "serviceAccount", "nodeSelector", "workingDir", "workspaceVolume"}; + + public DescriptorImpl() { + for (String field: new String[] {"cloud", "label"}) { + addHelpFileRedirect(field, PodTemplateStep.class, field); + } + for (String field: POD_TEMPLATE_FIELDS) { + addHelpFileRedirect(field, PodTemplate.class, field); + } + } + + @Nonnull + @Override + public String getDisplayName() { + return Messages.KubernetesDeclarativeAgent_displayName(); + } + + @SuppressWarnings("unused") // by stapler/jelly + public ListBoxModel doFillCloudItems() { + return ExtensionList.lookupSingleton(PodTemplateStep.DescriptorImpl.class).doFillCloudItems(); + } + + @SuppressWarnings("unused") // by stapler/jelly + public ListBoxModel doFillInheritFromItems(@QueryParameter("cloud") String cloudName) { + return ExtensionList.lookupSingleton(PodTemplateStep.DescriptorImpl.class).doFillInheritFromItems(cloudName); + } + + public PodRetention getDefaultPodRetention() { + return PodRetention.getPodTemplateDefault(); + } + + public WorkspaceVolume getDefaultWorkspaceVolume() { + return WorkspaceVolume.getDefault(); + } } } diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep.java index 29f80a4310..733f9192b2 100755 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep.java @@ -5,10 +5,17 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import com.google.common.collect.ImmutableSet; +import hudson.model.Job; +import hudson.slaves.Cloud; +import hudson.util.ListBoxModel; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; import org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate; +import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; import org.csanchez.jenkins.plugins.kubernetes.PodAnnotation; import org.csanchez.jenkins.plugins.kubernetes.PodTemplate; import org.csanchez.jenkins.plugins.kubernetes.model.TemplateEnvVar; @@ -28,16 +35,16 @@ import hudson.model.Node; import hudson.model.Run; import hudson.model.TaskListener; +import org.kohsuke.stapler.QueryParameter; + import javax.annotation.CheckForNull; public class PodTemplateStep extends Step implements Serializable { private static final long serialVersionUID = 5588861066775717487L; - private static final String DEFAULT_CLOUD = "kubernetes"; - @CheckForNull - private String cloud = DEFAULT_CLOUD; + private String cloud; @CheckForNull private String inheritFrom; @@ -384,6 +391,47 @@ public void setSupplementalGroups(@CheckForNull String supplementalGroups) { @Extension public static class DescriptorImpl extends StepDescriptor { + static final String[] POD_TEMPLATE_FIELDS = {"name", "namespace", "inheritFrom", "containers", "envVars", "volumes", "annotations", "yaml", "showRawYaml", "instanceCap", "podRetention", "supplementalGroups", "idleMinutes", "activeDeadlineSeconds", "serviceAccount", "nodeSelector", "workingDir", "workspaceVolume"}; + + public DescriptorImpl() { + for (String field : POD_TEMPLATE_FIELDS) { + addHelpFileRedirect(field, PodTemplate.class, field); + } + } + + @SuppressWarnings("unused") // by stapler/jelly + public ListBoxModel doFillCloudItems() { + ListBoxModel result = new ListBoxModel(); + result.add("—any—", ""); + Jenkins.get().clouds + .getAll(KubernetesCloud.class) + .forEach(cloud -> result.add(cloud.name)); + return result; + } + + @SuppressWarnings("unused") // by stapler/jelly + public ListBoxModel doFillInheritFromItems(@QueryParameter("cloud") String cloudName) { + cloudName = Util.fixEmpty(cloudName); + ListBoxModel result = new ListBoxModel(); + result.add("—Default inheritance—", ""); + result.add("—Disable inheritance—", " "); + Cloud cloud; + if (cloudName == null) { + cloud = Jenkins.get().clouds.get(KubernetesCloud.class); + } else { + cloud = Jenkins.get().getCloud(cloudName); + } + if (cloud instanceof KubernetesCloud) { + List templates = ((KubernetesCloud) cloud).getTemplates(); + result.addAll(templates.stream() + .filter(template -> StringUtils.isNotEmpty(template.getName())) + .map(PodTemplate::getName) + .map(ListBoxModel.Option::new) + .collect(Collectors.toList())); + } + return result; + } + @Override public String getFunctionName() { return "podTemplate"; diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecution.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecution.java index 70b738d4ec..e8f0f2fb30 100755 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecution.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecution.java @@ -144,11 +144,6 @@ public boolean start() throws Exception { throw new AbortException(Messages.RFC1123_error(String.join(", ", errors))); } - // Note that after JENKINS-51248 this must be a single label atom, not a space-separated list, unlike PodTemplate.label generally. - if (!PodTemplateUtils.validateLabel(newTemplate.getLabel())) { - throw new AbortException(Messages.label_error(newTemplate.getLabel())); - } - cloud.addDynamicTemplate(newTemplate); BodyInvoker invoker = getContext().newBodyInvoker().withContexts(step, new PodTemplateContext(namespace, name)).withCallback(new PodTemplateCallback(newTemplate)); if (step.getLabel() == null) { @@ -259,12 +254,12 @@ private PodTemplateCallback(PodTemplate podTemplate) { protected void finished(StepContext context) throws Exception { Cloud cloud = Jenkins.get().getCloud(cloudName); if (cloud == null) { - LOGGER.log(Level.WARNING, "Cloud {0} no longer exists, cannot delete pod template {1}", + LOGGER.log(Level.FINE, "Cloud {0} no longer exists, cannot delete pod template {1}", new Object[] { cloudName, podTemplate.getName() }); return; } if (cloud instanceof KubernetesCloud) { - LOGGER.log(Level.INFO, "Removing pod template {1} from cloud {0}", + LOGGER.log(Level.FINE, "Removing pod template {1} from cloud {0}", new Object[] { cloud.name, podTemplate.getName() }); KubernetesCloud kubernetesCloud = (KubernetesCloud) cloud; kubernetesCloud.removeDynamicTemplate(podTemplate); diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/Reaper.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/Reaper.java index 821264feef..4faf27182c 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/Reaper.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pod/retention/Reaper.java @@ -26,6 +26,8 @@ import hudson.model.Result; import hudson.model.TaskListener; import hudson.model.listeners.ItemListener; +import hudson.security.ACL; +import hudson.security.ACLContext; import hudson.slaves.Cloud; import hudson.slaves.ComputerListener; import hudson.slaves.EphemeralNode; @@ -269,13 +271,8 @@ public void onEvent(@NonNull Action action, @NonNull KubernetesSlave node, @NonN TaskListener runListener = node.getTemplate().getListener(); runListener.error("Unable to pull Docker image \""+cs.getImage()+"\". Check if image tag name is spelled correctly."); }); - Queue q = Jenkins.get().getQueue(); - String runUrl = pod.getMetadata().getAnnotations().get("runUrl"); - for (Queue.Item item: q.getItems()) { - if (item.task.getUrl().equals(runUrl)) { - q.cancel(item); - break; - } + try (ACLContext _ = ACL.as(ACL.SYSTEM)) { + PodUtils.cancelQueueItemFor(pod, "ImagePullBackOff"); } node.terminate(); } diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-failureThreshold.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-failureThreshold.html new file mode 100644 index 0000000000..79fe0783c0 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-failureThreshold.html @@ -0,0 +1,5 @@ +

+ When a Pod starts and the probe fails, Kubernetes will try failureThreshold times before giving up.
+ Giving up in case of liveness probe means restarting the container.
+ In case of readiness probe the Pod will be marked Unready. Defaults to 3. Minimum value is 1. +

diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-initialDelaySeconds.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-initialDelaySeconds.html new file mode 100644 index 0000000000..04958b3a23 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-initialDelaySeconds.html @@ -0,0 +1 @@ +Number of seconds after the container has started before liveness or readiness probes are initiated. Defaults to 0 seconds. Minimum value is 0. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-periodSeconds.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-periodSeconds.html new file mode 100644 index 0000000000..ec42d0a935 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-periodSeconds.html @@ -0,0 +1,3 @@ +

+ How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. +

diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-successThreshold.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-successThreshold.html new file mode 100644 index 0000000000..1f7cd6e5d2 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-successThreshold.html @@ -0,0 +1 @@ +Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness. Minimum value is 1. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-timeoutSeconds.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-timeoutSeconds.html new file mode 100644 index 0000000000..9910930063 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerLivenessProbe/help-timeoutSeconds.html @@ -0,0 +1 @@ +Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/config.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/config.jelly index 44008cc4d3..33f73951a2 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/config.jelly +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/config.jelly @@ -18,7 +18,7 @@ - + @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@ - + diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/config_zh_CN.properties b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/config_zh_CN.properties index fb72416bc0..32d471f7c6 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/config_zh_CN.properties +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/config_zh_CN.properties @@ -36,7 +36,7 @@ Request\ Memory=\u5185\u5B58\u9700\u6C42 Limit\ CPU=CPU \u9650\u5236 Limit\ Memory=\u5185\u5B58\u9650\u5236 Liveness\ Probe=\u5065\u5EB7\u68C0\u67E5 -PortMappings=\u7AEF\u53E3\u6620\u5C04 +Port\ Mappings=\u7AEF\u53E3\u6620\u5C04 List\ of\ exposed\ ports=\u66B4\u9732\u7684\u7AEF\u53E3\u5217\u8868 Add\ Port\ Mapping=\u6DFB\u52A0\u7AEF\u53E3\u6620\u5C04 Delete\ Port\ Mapping=\u5220\u9664\u7AEF\u53E3\u6620\u5C04 diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/help-alwaysPullImage.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/help-alwaysPullImage.html new file mode 100644 index 0000000000..f8e3eb1042 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/help-alwaysPullImage.html @@ -0,0 +1,3 @@ +If ticked, the latest version of the image will be pulled every time it is used. + +See Images - Kubernetes for the default Kubernetes behaviour. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/help-ttyEnabled.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/help-ttyEnabled.html new file mode 100644 index 0000000000..5abe303de3 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/help-ttyEnabled.html @@ -0,0 +1 @@ +Whether this container should allocate a TTY for itself. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/help-workingDir.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/help-workingDir.html new file mode 100644 index 0000000000..a55ab18305 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate/help-workingDir.html @@ -0,0 +1 @@ +Path to the root of the workspace from the view point of this container, such as /home/jenkins/agent. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/container.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/container.jelly index e4f4896515..f4c58736c9 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/container.jelly +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/container.jelly @@ -24,7 +24,7 @@ THE SOFTWARE. - +
@@ -34,4 +34,4 @@ THE SOFTWARE.
       
     
   
-
\ No newline at end of file
+
diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/events.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/events.jelly
index 62a837899b..c3ea376d11 100644
--- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/events.jelly
+++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/events.jelly
@@ -24,7 +24,7 @@ THE SOFTWARE.
 
 
 
-  
+  
     
     
       
diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/podLog.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/podLog.jelly
index 7899433981..f1acdbd19f 100644
--- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/podLog.jelly
+++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/podLog.jelly
@@ -24,7 +24,7 @@ THE SOFTWARE.
 
 
-  
+  
     
     
       
@@ -43,4 +43,4 @@ THE SOFTWARE.
-
\ No newline at end of file + diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/sidepanel2.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/sidepanel2.jelly index d64f5f72c0..50d26d96e2 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/sidepanel2.jelly +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesComputer/sidepanel2.jelly @@ -24,9 +24,7 @@ THE SOFTWARE. - - - - - - \ No newline at end of file + + + + diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesFolderProperty/config.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesFolderProperty/config.jelly index ce3da15851..5fb904976e 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesFolderProperty/config.jelly +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/KubernetesFolderProperty/config.jelly @@ -14,7 +14,8 @@ - + + diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config.jelly index 0333b24972..2ad6b78afc 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config.jelly +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config.jelly @@ -22,28 +22,31 @@ ${it.description} - + - + - + - + - + + + + + @@ -53,8 +56,7 @@ - + @@ -62,13 +64,7 @@ - - - - - + @@ -78,14 +74,12 @@ - + - + @@ -97,9 +91,7 @@ - + @@ -107,15 +99,14 @@ - + - + - + diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config_zh_CN.properties b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config_zh_CN.properties index 76f23e39f6..a1a18a8a16 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config_zh_CN.properties +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/config_zh_CN.properties @@ -27,7 +27,7 @@ Usage=\u7528\u6CD5 Pod\ template\ to\ inherit\ from=\u7236\u7EA7\u7684 Pod \u6A21\u677F\u540D\u79F0 Containers=\u5BB9\u5668\u5217\u8868 List\ of\ container\ in\ the\ agent\ pod=Pod \u4EE3\u7406\u4E2D\u7684\u5BB9\u5668\u5217\u8868 -EnvVars=\u73AF\u5883\u53D8\u91CF +Environment\ variables=\u73AF\u5883\u53D8\u91CF List\ of\ environment\ variables\ to\ set\ in\ all\ container\ of\ the\ pod=\u8BE5 Pod \u4E2D\u6240\u6709\u5BB9\u5668\u7684\u73AF\u5883\u53D8\u91CF Volumes=\u5377 List\ of\ volumes\ to\ mount\ in\ agent\ pod=\u6302\u8F7D\u5230 Pod \u4EE3\u7406\u4E2D\u7684\u5377\u5217\u8868 diff --git a/src/main/webapp/help/activeDeadlineSeconds.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-activeDeadlineSeconds.html similarity index 100% rename from src/main/webapp/help/activeDeadlineSeconds.html rename to src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-activeDeadlineSeconds.html diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-annotations.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-annotations.html new file mode 100644 index 0000000000..120bb10dab --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-annotations.html @@ -0,0 +1,4 @@ +

+ Annotations to set on pod metadata
+ Read Annotations - Kubernetes for more information. +

diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-containers.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-containers.html new file mode 100644 index 0000000000..81ae7b0249 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-containers.html @@ -0,0 +1,2 @@ +By default, the pod contains a single container with name jnlp running the Jenkins agent.
+It is possible to override this container by adding a new entry and use jnlp as name. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-envVars.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-envVars.html new file mode 100644 index 0000000000..7c35db8b89 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-envVars.html @@ -0,0 +1 @@ +Environment variables can be hardcoded, or injected from a Kubernetes secret. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-hostNetwork.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-hostNetwork.html new file mode 100644 index 0000000000..312712bb78 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-hostNetwork.html @@ -0,0 +1 @@ +Allows the pod to share the host network namespace. Not recommended. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-idleMinutes.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-idleMinutes.html new file mode 100644 index 0000000000..028fea0c62 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-idleMinutes.html @@ -0,0 +1,8 @@ +

+ By default, agents are terminated as soon as they have completed the task they have been assigned. +

+ +

+ Setting a value for this field will keep agents around for N minutes (N being the defined value).
+ In that case, the agent may be reused by another build. +

diff --git a/src/main/webapp/help/imagePullSecrets.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-imagePullSecrets.html similarity index 82% rename from src/main/webapp/help/imagePullSecrets.html rename to src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-imagePullSecrets.html index abc3b3dcb3..fcff57c8ce 100755 --- a/src/main/webapp/help/imagePullSecrets.html +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-imagePullSecrets.html @@ -13,6 +13,4 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -The list of secrets. Secrets are passed in the form of key/value pairs, where key represents the secret name and value -the mount path. \ No newline at end of file +Name of secrets that can be used to pull the specified image. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-inheritFrom.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-inheritFrom.html new file mode 100644 index 0000000000..4bb769730c --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-inheritFrom.html @@ -0,0 +1,19 @@ +

+ A podTemplate may or may not inherit from an existing template.
+ This means that the podTemplate will inherit node selector, service account, image pull secrets, containerTemplates and volumes from the template it inherits from. +

+

+ Service account and Node selector, when overridden, completely replace any value found on the “parent”. +

+

+ Container templates that are added to the podTemplate, that has a matching containerTemplate (a containerTemplate with the same name) in the parent template, will inherit the configuration of the parent containerTemplate.
+ If no matching containerTemplate is found, the template is added as is. +

+ Volume inheritance works exactly as Container templates. +

+ Image Pull Secrets are combined (all secrets defined both on 'parent' and 'current' template are used). +

+

+ By default, pod template inherits outer pod template definitions.
+ Inheritance can be stopped by using the empty string. +

diff --git a/src/main/webapp/help/instanceCap.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-instanceCap.html similarity index 58% rename from src/main/webapp/help/instanceCap.html rename to src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-instanceCap.html index 82dfc8a9e6..ac1f0c0cf7 100644 --- a/src/main/webapp/help/instanceCap.html +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-instanceCap.html @@ -1,5 +1,5 @@ -
-The maximum number of concurrently running agent pods created from this template that are permitted in the Kubernetes Cloud. -The number of running agents will never exceed the global concurrency limit sets at the Cloud Configuration level. +

+The maximum number of concurrently running agent pods created from this template that are permitted in the Kubernetes Cloud.
+The number of running agents will never exceed the global concurrency limit sets at the Cloud Configuration level.
If set to empty or negative number it means no limit. -

+

diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-label.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-label.html new file mode 100644 index 0000000000..095e88c8a7 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-label.html @@ -0,0 +1 @@ +Labels to put on the created agents diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-name.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-name.html similarity index 100% rename from src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-name.html rename to src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-name.html diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-namespace.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-namespace.html new file mode 100644 index 0000000000..6215d744e2 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-namespace.html @@ -0,0 +1,3 @@ +Namespace in which to schedule the pod.
+ +Leave empty to use the namespace defined at cloud level. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/form/taglib b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-nodeProperties.html similarity index 100% rename from src/main/resources/org/csanchez/jenkins/plugins/kubernetes/form/taglib rename to src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-nodeProperties.html diff --git a/src/main/webapp/help/nodeSelector.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-nodeSelector.html similarity index 100% rename from src/main/webapp/help/nodeSelector.html rename to src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-nodeSelector.html diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-podRetention.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-podRetention.html index 3678135308..881c9b46d9 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-podRetention.html +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-podRetention.html @@ -1,6 +1,6 @@

- This setting controls how slave pods are retained after the Jenkins build completes for this pod template. + This setting controls how agent pods are retained after the Jenkins build completes for this pod template. Values other than "Default" will override the plugin's Pod Retention setting. The following retention policies are provided:

@@ -11,7 +11,7 @@
  • On Failure - keep the slave pod if it fails during the build.
  • - Note: Kubernetes administrators are responsible for managing any kept slave pod. + Note: Kubernetes administrators are responsible for managing any kept agent pod. These will not be deleted by the Jenkins Kubernetes plugin.

    -
    \ No newline at end of file + diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-serviceAccount.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-serviceAccount.html new file mode 100755 index 0000000000..a62439a98e --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-serviceAccount.html @@ -0,0 +1 @@ +The service account to use to run the pod. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-showRawYaml.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-showRawYaml.html new file mode 100644 index 0000000000..c313bd28e9 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-showRawYaml.html @@ -0,0 +1,5 @@ +

    + When checked, the actual pod definition in YAML will be dumped in the build console (secrets redacted).
    + This helps audit builds to understand what was the exact environment when the build ran.
    + If you don't care about this information and want less verbosity in build logs, disable this feature. +

    diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-slaveConnectTimeout.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-slaveConnectTimeout.html new file mode 100644 index 0000000000..716e03c7dc --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-slaveConnectTimeout.html @@ -0,0 +1,4 @@ +

    + Specify time in seconds up to which Jenkins should wait for the JNLP agent to establish a connection.
    + Value must be a positive integer. +

    diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-slaveConnectTimeoutStr.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-slaveConnectTimeoutStr.html deleted file mode 100644 index 10c84bf184..0000000000 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-slaveConnectTimeoutStr.html +++ /dev/null @@ -1,2 +0,0 @@ -Specify time in seconds up to which Jenkins should wait for the JNLP agent to -estabilish a connection. Value should be a positive integer, default being 100. diff --git a/src/main/webapp/help/supplementalGroups.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-supplementalGroups.html similarity index 100% rename from src/main/webapp/help/supplementalGroups.html rename to src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-supplementalGroups.html diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-volumes.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-volumes.html new file mode 100644 index 0000000000..8b01bb7146 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-volumes.html @@ -0,0 +1,4 @@ +

    + Volumes get mounted in all containers with the specified mount path.
    + If you want more fine-grained control, use the raw YAML field. +

    diff --git a/src/main/webapp/help/workingDir.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-workingDir.html similarity index 100% rename from src/main/webapp/help/workingDir.html rename to src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-workingDir.html diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-workspaceVolume.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-workspaceVolume.html new file mode 100644 index 0000000000..d56a637298 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-workspaceVolume.html @@ -0,0 +1 @@ +Specifies the type of volume to be used to mount the agent workspace. diff --git a/src/main/webapp/help/yaml.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-yaml.html similarity index 59% rename from src/main/webapp/help/yaml.html rename to src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-yaml.html index 99f376aed6..fa51bab9e2 100644 --- a/src/main/webapp/help/yaml.html +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/PodTemplate/help-yaml.html @@ -15,25 +15,25 @@ -->

    -The raw yaml of a Pod API Object. Any pod fields set with non-empty values via the configuration fields above will generally take precedence -as they are merged with this yaml, though in the case of arrays (like Volumes or Tolerations) the objects in the yaml will be appeneded to the -existing list. + The raw yaml of a Pod API Object. Any pod fields set with non-empty values via the configuration fields above will generally take precedence + as they are merged with this yaml, though in the case of arrays (like Volumes or Tolerations) the objects in the yaml will be appended to the + existing list.

    -Fragments of a full Pod API Object yaml representation are allowed, but you must specify enough of the structure to give context -for the object construction. + Fragments of a full Pod API Object yaml representation are allowed, but you must specify enough of the structure to give context + for the object construction.

    -For example, you can start with: + For example, you can start with: -

    +    
     apiVersion: v1
     kind: Pod
     spec:
     ....
    -
    +

    diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/form/checkbox.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/form/checkbox.jelly deleted file mode 100644 index e937522b87..0000000000 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/form/checkbox.jelly +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - <input type="checkbox"> tag that takes true/false for @checked, which is more Jelly friendly. - - - - - - Normally, the submitted JSON will be boolean indicating whether the checkbox was checked or not. - This is sometimes inconvenient if you have a UI that lets user select a subset of a set. - If this attribute is present, the submitted JSON will have this as a string value if the checkbox is checked, - and none otherwise, making the subset selection easier. - - - The default value of the check box, in case both @checked and @instance are null. - If this attribute is unspecified or null, it defaults to unchecked, otherwise checked. - - - - - - - If set to true, this will take precedence over the onclick attribute and prevent the state of the checkbox from being changed. - - - Used for databinding. TBD. - - - If specified, this human readable text will follow the checkbox, and clicking this text also - toggles the checkbox. - - - Used as tooltip of the checkbox, and, if a title is specified, of the title - - - - - - - - - - - - - - - - - - - - ${customizedFields.add(name)} - - diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent/config.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent/config.jelly new file mode 100644 index 0000000000..44873cbaa0 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent/config.jelly @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent/help-customWorkspace.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent/help-customWorkspace.html new file mode 100644 index 0000000000..debc7789c3 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgent/help-customWorkspace.html @@ -0,0 +1 @@ +Allows the SCM repository to be checked out in a custom workspace directory. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentScript.groovy b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentScript.groovy index 4371e2b610..d269b7ed96 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentScript.groovy +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentScript.groovy @@ -44,7 +44,7 @@ public class KubernetesDeclarativeAgentScript extends DeclarativeAgentScript - + @@ -22,29 +22,31 @@ ${it.description} - - + + - + - + + + + + - + - + @@ -53,8 +55,7 @@ - + @@ -66,23 +67,20 @@ - + - + - + - + - + diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-cloud.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-cloud.html new file mode 100644 index 0000000000..3afe31fdd2 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-cloud.html @@ -0,0 +1,2 @@ +The Kubernetes cloud to use to schedule the pod.
    +If unset, the first available Kubernetes cloud will be used. diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-label.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-label.html index 862de7a80f..6ee30ed7cb 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-label.html +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-label.html @@ -2,5 +2,14 @@ Jenkins node label to bind. If left blank, one will be generated for you, and inside the step it will be bound to the variable POD_LABEL - so you can use this as the argument to the node step. + so you can use this as the argument to the node step.
    + + Example: +

    +        podTemplate(...) {
    +            node(POD_LABEL) {
    +                // some steps
    +            }
    +        }
    +    
    diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-podRetention.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-podRetention.html deleted file mode 100644 index 4b853a7a37..0000000000 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help-podRetention.html +++ /dev/null @@ -1,17 +0,0 @@ -
    -

    - This setting controls how slave pods are retained after the Jenkins build completes for this pod template. - Values other than "default" will override the plugin's Pod Retention setting. - The following retention policies are supported: -

    -
      -
    1. default - use the Pod Retention setting for the plugin.
    2. -
    3. Never - always delete the slave pod.
    4. -
    5. On Failure - keep the slave pod if it fails during the build.
    6. -
    7. Always - always keep the slave pod.
    8. -
    -

    - Note: Kubernetes administrators are responsible for managing any kept slave pod. - These will not be deleted by the Jenkins Kubernetes plugin. -

    -
    \ No newline at end of file diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help.html index 1fbf9e09ea..2152a094e1 100755 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help.html +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStep/help.html @@ -1,3 +1,13 @@ -
    - Defines a container inside the kubernetes plugin configuration -
    +

    + Defines a Kubernetes pod template that can be used to create nodes. +

    +

    + Example: +

    +
    +podTemplate(...) {
    +    node(POD_LABEL) {
    +        // some steps
    +    }
    +}
    +
    diff --git a/src/main/webapp/help/annotations.html b/src/main/webapp/help/annotations.html deleted file mode 100644 index 48d793e01a..0000000000 --- a/src/main/webapp/help/annotations.html +++ /dev/null @@ -1,2 +0,0 @@ -Annotations to set on pod metadata
    -https://kubernetes.io/docs/user-guide/annotations/ \ No newline at end of file diff --git a/src/main/webapp/help/inheritFrom.html b/src/main/webapp/help/inheritFrom.html deleted file mode 100644 index fbfa970b56..0000000000 --- a/src/main/webapp/help/inheritFrom.html +++ /dev/null @@ -1,13 +0,0 @@ -A podTemplate may or may not inherit from an existing template. This means that the podTemplate will inherit node selector, service account, image pull secrets, containerTemplates and volumes from the template it inheritsFrom. - -Service account and Node selector when are overridden completely substitute any possible value found on the 'parent'. - -Container templates that are added to the podTemplate, that has a matching containerTemplate (a containerTemplate with the same name) in the 'parent' template, will inherit the configuration of the parent containerTemplate. -If no matching containerTemplate is found, the template is added as is. - -Volume inheritance works exactly as Container templates. - -Image Pull Secrets** are combined (all secrets defined both on 'parent' and 'current' template are used). - -By default, pod template inherits outer pod template definitions. -Inheritance can be stopped by using the empty string. diff --git a/src/main/webapp/help/serviceAccount.html b/src/main/webapp/help/serviceAccount.html deleted file mode 100755 index 5cdf207bab..0000000000 --- a/src/main/webapp/help/serviceAccount.html +++ /dev/null @@ -1 +0,0 @@ -The service account to use. \ No newline at end of file diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloudTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloudTest.java index 66b288e80b..6dd269c5ce 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloudTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloudTest.java @@ -221,6 +221,11 @@ public KubernetesClient connect() { Collection plannedNodes = cloud.provision(test, 200); assertEquals(200, plannedNodes.size()); + cloud.setContainerCapStr("0"); + podTemplate.setInstanceCap(20); + plannedNodes = cloud.provision(test, 200); + assertEquals(0, plannedNodes.size()); + cloud.setContainerCapStr("10"); podTemplate.setInstanceCap(20); plannedNodes = cloud.provision(test, 200); diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateJenkinsTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateJenkinsTest.java index a9b1d032f9..c064181389 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateJenkinsTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/PodTemplateJenkinsTest.java @@ -5,6 +5,9 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; +import java.util.Map; + +import static org.csanchez.jenkins.plugins.kubernetes.PodTemplate.LABEL_DIGEST_FUNCTION; import static org.junit.Assert.assertEquals; public class PodTemplateJenkinsTest { @@ -12,26 +15,32 @@ public class PodTemplateJenkinsTest { public JenkinsRule j = new JenkinsRule(); @Test - @Issue("JENKINS-60537") + @Issue({"JENKINS-59690", "JENKINS-60537"}) public void singleLabel() { PodTemplate podTemplate = new PodTemplate(); podTemplate.setLabel("foo"); - assertEquals("foo" , podTemplate.getLabelsMap().get("jenkins/label")); + Map labelsMap = podTemplate.getLabelsMap(); + assertEquals("foo" , labelsMap.get("jenkins/label")); + assertEquals(LABEL_DIGEST_FUNCTION.hashString("foo").toString(), labelsMap.get("jenkins/label-digest")); } @Test - @Issue("JENKINS-60537") + @Issue({"JENKINS-59690", "JENKINS-60537"}) public void multiLabel() { PodTemplate podTemplate = new PodTemplate(); podTemplate.setLabel("foo bar"); - assertEquals("foo_bar", podTemplate.getLabelsMap().get("jenkins/label")); + Map labelsMap = podTemplate.getLabelsMap(); + assertEquals("foo_bar", labelsMap.get("jenkins/label")); + assertEquals(LABEL_DIGEST_FUNCTION.hashString("foo bar").toString(), labelsMap.get("jenkins/label-digest")); } @Test - @Issue("JENKINS-60937") + @Issue({"JENKINS-59690", "JENKINS-60537"}) public void defaultLabel() { PodTemplate podTemplate = new PodTemplate(); podTemplate.setLabel(null); - assertEquals("slave-default", podTemplate.getLabelsMap().get("jenkins/label")); + Map labelsMap = podTemplate.getLabelsMap(); + assertEquals("slave-default", labelsMap.get("jenkins/label")); + assertEquals("0", labelsMap.get("jenkins/label-digest")); } } diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentTest.java index 4678eb80dc..2c2a36bfc0 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentTest.java @@ -61,6 +61,10 @@ public void declarative() throws Exception { FlowNode podTemplateNode = new DepthFirstScanner().findFirstMatch(b.getExecution(), Predicates.and(new NodeStepTypePredicate("podTemplate"), FlowScanningUtils.hasActionPredicate(ArgumentsAction.class))); assertNotNull("recorded arguments for podTemplate", podTemplateNode); Map arguments = podTemplateNode.getAction(ArgumentsAction.class).getArguments(); + FlowNode nodeNode = new DepthFirstScanner().findFirstMatch(b.getExecution(), Predicates.and(new NodeStepTypePredicate("node"), FlowScanningUtils.hasActionPredicate(ArgumentsAction.class))); + assertNotNull("recorded arguments for node", nodeNode); + Map nodeArguments = nodeNode.getAction(ArgumentsAction.class).getArguments(); + assertEquals("labels && multiple", nodeArguments.get("label")); @SuppressWarnings("unchecked") List containers = (List) arguments.get("containers"); assertNotNull(containers); diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentUnitTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentUnitTest.java new file mode 100644 index 0000000000..e63ed24f67 --- /dev/null +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentUnitTest.java @@ -0,0 +1,90 @@ +package org.csanchez.jenkins.plugins.kubernetes.pipeline; + +import org.csanchez.jenkins.plugins.kubernetes.pod.retention.Never; +import org.csanchez.jenkins.plugins.kubernetes.pod.yaml.Merge; +import org.csanchez.jenkins.plugins.kubernetes.volumes.workspace.DynamicPVCWorkspaceVolume; +import org.jenkinsci.plugins.pipeline.modeldefinition.generator.AgentDirective; +import org.jenkinsci.plugins.pipeline.modeldefinition.generator.DirectiveGeneratorTester; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import java.util.Collections; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.isA; + +public class KubernetesDeclarativeAgentUnitTest { + @ClassRule + public static JenkinsRule j = new JenkinsRule(); + + KubernetesDeclarativeAgent instance; + + DirectiveGeneratorTester dg; + AgentDirective directive; + + @Before + public void setUp() { + instance = new KubernetesDeclarativeAgent(); + directive = new AgentDirective(instance); + dg = new DirectiveGeneratorTester(j); + } + + @Test + public void serializationNull() { + Map args = instance.getAsArgs(); + assertThat(args, equalTo(Collections.emptyMap())); + } + + @Test + public void serialization() throws Exception { + instance.setCloud("cloud"); + instance.setLabel("label"); + instance.setYaml("yaml"); + instance.setYamlMergeStrategy(new Merge()); + DynamicPVCWorkspaceVolume workspaceVolume = new DynamicPVCWorkspaceVolume("sc", "1G", "ReadWrite"); + instance.setWorkspaceVolume(workspaceVolume); + instance.setIdleMinutes(1); + instance.setInheritFrom("inheritFrom"); + Map args = instance.getAsArgs(); + + assertThat(args.get("cloud"), equalTo("cloud")); + assertThat(args.get("label"), equalTo("label")); + assertThat(args.get("yaml"), equalTo("yaml")); + assertThat(args.get("yamlMergeStrategy"),isA(Merge.class)); + assertThat(args.get("workspaceVolume"),equalTo(workspaceVolume)); + assertThat(args.get("idleMinutes"), equalTo(1)); + assertThat(args.get("inheritFrom"), equalTo("inheritFrom")); + } + + @Test + public void simpleGenerator() throws Exception { + dg.assertGenerateDirective(directive, "agent {\n" + + " kubernetes true\n" + + "}"); + } + + @Test + public void complexGenerator() throws Exception { + instance.setCloud("cloud"); + instance.setYaml("yaml"); + instance.setYamlMergeStrategy(new Merge()); + DynamicPVCWorkspaceVolume workspaceVolume = new DynamicPVCWorkspaceVolume("sc", "1G", "ReadWrite"); + instance.setWorkspaceVolume(workspaceVolume); + instance.setPodRetention(new Never()); + instance.setInheritFrom("inheritFrom"); + dg.assertGenerateDirective(directive, "agent {\n" + + " kubernetes {\n" + + " cloud 'cloud'\n" + + " inheritFrom 'inheritFrom'\n" + + " podRetention never()\n" + + " workspaceVolume dynamicPVC(accessModes: 'ReadWrite', requestsSize: '1G', storageClassName: 'sc')\n" + + " yaml 'yaml'\n" + + " yamlMergeStrategy merge()\n" + + " }\n" + + "}"); + } +} diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java index 46748ce369..e381552a67 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java @@ -626,6 +626,13 @@ public void dynamicPVC() throws Exception { r.assertBuildStatusSuccess(r.waitForCompletion(b)); } + @Test + public void invalidPodGetsCancelled() throws Exception { + r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b)); + r.assertLogContains("ERROR: Unable to create pod", b); + r.assertLogContains("ERROR: Queue task was cancelled", b); + } + private R assertBuildStatus(R run, Result... status) throws Exception { for (Result s : status) { if (s == run.getResult()) { diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecutionTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecutionTest.java index 16555369b8..3c84167c62 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecutionTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecutionTest.java @@ -76,14 +76,4 @@ public void testBadNameYamlDetection() throws Exception { r.waitForMessage(Messages.RFC1123_error("badcontainername_!, badcontainername2_!"), b); } - @Test - public void testBadLabel() throws Exception { - WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "bad_label"); - p.setDefinition(new CpsFlowDefinition(loadPipelineScript("badlabel.groovy"), true)); - WorkflowRun b = p.scheduleBuild2(0).waitForStart(); - assertNotNull(b); - r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b)); - r.waitForMessage(Messages.label_error("mypod!123"), b); - } - } diff --git a/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/badlabel.groovy b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/badlabel.groovy deleted file mode 100644 index 3bb5e87031..0000000000 --- a/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/badlabel.groovy +++ /dev/null @@ -1,26 +0,0 @@ -podTemplate(label: 'mypod!123', yaml: """ -apiVersion: v1 -kind: Pod -metadata: - labels: - some-label: some-label-value -spec: - containers: - - name: badcontainername - image: busybox - command: - - cat - tty: true -""" -) { - - node(POD_LABEL) { - stage('Run') { - container('busybox') { - sh """ - will never run - """ - } - } - } -} diff --git a/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/declarative.groovy b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/declarative.groovy index 62d15eaf15..fe2d7eba0d 100644 --- a/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/declarative.groovy +++ b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/declarative.groovy @@ -1,6 +1,7 @@ pipeline { agent { kubernetes { + label 'multiple labels' containerTemplate { name 'maven' image 'maven:3.3.9-jdk-8-alpine' diff --git a/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/invalidPodGetsCancelled.groovy b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/invalidPodGetsCancelled.groovy new file mode 100644 index 0000000000..6f3d43ed06 --- /dev/null +++ b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/invalidPodGetsCancelled.groovy @@ -0,0 +1,9 @@ +podTemplate(yaml: ''' +spec: + containers: + - name: invalid-container +''') { + node(POD_LABEL) { + sh 'This will never run' + } +} diff --git a/src/test/resources/rules.xml b/src/test/resources/rules.xml deleted file mode 100644 index d561350364..0000000000 --- a/src/test/resources/rules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - .*-beta.* - .*-alpha.* - - diff --git a/test-in-k8s.sh b/test-in-k8s.sh old mode 100644 new mode 100755