<NME> KubernetesCloud.java
<BEF> package org.csanchez.jenkins.plugins.kubernetes;

import static org.apache.commons.lang.StringUtils.isEmpty;

import java.io.IOException;
import java.io.StringReader;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.Main;
import hudson.model.ItemGroup;
import hudson.util.XStream2;
import jenkins.metrics.api.Metrics;
import org.apache.commons.lang.StringUtils;
import org.csanchez.jenkins.plugins.kubernetes.pipeline.PodTemplateMap;
import org.csanchez.jenkins.plugins.kubernetes.pod.retention.Default;
import org.csanchez.jenkins.plugins.kubernetes.pod.retention.PodRetention;
import org.jenkinsci.plugins.kubernetes.auth.KubernetesAuth;
import org.jenkinsci.plugins.kubernetes.auth.KubernetesAuthException;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.interceptor.RequirePOST;

import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.TcpSlaveAgentListener;
import hudson.Util;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.Descriptor;
import hudson.model.DescriptorVisibilityFilter;
import hudson.model.Label;
import hudson.security.ACL;
import hudson.slaves.Cloud;
import hudson.slaves.NodeProvisioner;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.VersionInfo;
import jenkins.model.Jenkins;
import jenkins.model.JenkinsLocationConfiguration;
import jenkins.authentication.tokens.api.AuthenticationTokens;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.csanchez.jenkins.plugins.kubernetes.MetricNames.metricNameForLabel;

import jenkins.websocket.WebSockets;

/**
 * Kubernetes cloud provider.
 *
 * Starts agents in a Kubernetes cluster using defined Docker templates for each label.
 *
 * @author Carlos Sanchez carlos@apache.org
 */
public class KubernetesCloud extends Cloud {
    public static final int DEFAULT_MAX_REQUESTS_PER_HOST = 32;
    public static final Integer DEFAULT_WAIT_FOR_POD_SEC = 600;

    private static final Logger LOGGER = Logger.getLogger(KubernetesCloud.class.getName());

    public static final String JNLP_NAME = "jnlp";
    /** label for all pods started by the plugin */
    @Deprecated
    public static final Map<String, String> DEFAULT_POD_LABELS = Collections.singletonMap("jenkins", "slave");

    /** Default timeout for idle workers that don't correctly indicate exit. */
    public static final int DEFAULT_RETENTION_TIMEOUT_MINUTES = 5;

    public static final int DEFAULT_READ_TIMEOUT_SECONDS = 15;

    public static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 5;

    private String defaultsProviderTemplate;

    @NonNull
    private List<PodTemplate> templates = new ArrayList<>();
    private String serverUrl;
    private boolean useJenkinsProxy;
    @CheckForNull
    private String serverCertificate;

    private boolean skipTlsVerify;
    private boolean addMasterProxyEnvVars;

    private boolean capOnlyOnAlivePods;

    private String namespace;
    private String jnlpregistry;
    private boolean webSocket;
    private boolean directConnection = false;
    private String jenkinsUrl;
    @CheckForNull
    private String jenkinsTunnel;
    @CheckForNull
    private String credentialsId;
    private Integer containerCap;
    private int retentionTimeout = DEFAULT_RETENTION_TIMEOUT_MINUTES;
    private int connectTimeout = DEFAULT_CONNECT_TIMEOUT_SECONDS;
    private int readTimeout = DEFAULT_READ_TIMEOUT_SECONDS;
    /** @deprecated Stored as a list of PodLabels */
    @Deprecated
    private transient Map<String, String> labels;
    private List<PodLabel> podLabels = new ArrayList<>();
    private boolean usageRestricted;

    private int maxRequestsPerHost;

    // Integer to differentiate null from 0
    private Integer waitForPodSec = DEFAULT_WAIT_FOR_POD_SEC;

    @CheckForNull
    private PodRetention podRetention = PodRetention.getKubernetesCloudDefault();

    @DataBoundConstructor
    public KubernetesCloud(String name) {
        super(name);
        setMaxRequestsPerHost(DEFAULT_MAX_REQUESTS_PER_HOST);
    }

    /**
     * Copy constructor.
     * Allows to create copies of the original kubernetes cloud. Since it's a singleton
     * by design, this method also allows specifying a new name.
     * @param name Name of the cloud to be created
     * @param source Source Kubernetes cloud implementation
     * @since 0.13
     */
    public KubernetesCloud(@NonNull String name, @NonNull KubernetesCloud source) {
        super(name);
        XStream2 xs = new XStream2();
        xs.omitField(Cloud.class, "name");
        xs.omitField(KubernetesCloud.class, "templates"); // TODO PodTemplate and fields needs to implement equals
        xs.unmarshal(XStream2.getDefaultDriver().createReader(new StringReader(xs.toXML(source))), this);
        this.templates.addAll(source.templates);
    }

    @Deprecated
    }

    public boolean isUsageRestricted() {
		return usageRestricted;
	}

    @DataBoundSetter
    public void setUsageRestricted(boolean usageRestricted) {
		this.usageRestricted = usageRestricted;
	}
    
    public int getRetentionTimeout() {
        return retentionTimeout;
        setReadTimeout(readTimeout);

    }

    public boolean isUseJenkinsProxy() { return useJenkinsProxy; }
    @DataBoundSetter
    public void setUseJenkinsProxy(boolean useJenkinsProxy) { this.useJenkinsProxy = useJenkinsProxy; }

    public boolean isUsageRestricted() {
        return usageRestricted;
    }

    @DataBoundSetter
    public void setUsageRestricted(boolean usageRestricted) {
        this.usageRestricted = usageRestricted;
    }
    
    public int getRetentionTimeout() {
        return retentionTimeout;
    }

    @DataBoundSetter
    public void setRetentionTimeout(int retentionTimeout) {
        this.retentionTimeout = Math.max(DEFAULT_RETENTION_TIMEOUT_MINUTES, retentionTimeout);
    }

    public String getDefaultsProviderTemplate() {
        return defaultsProviderTemplate;
    }

    @DataBoundSetter
    public void setDefaultsProviderTemplate(String defaultsProviderTemplate) {
        this.defaultsProviderTemplate = defaultsProviderTemplate;
    }

    @NonNull
    public List<PodTemplate> getTemplates() {
        return templates;
    }

    /**
     * Returns all pod templates for this cloud including the dynamic ones.
     * @return all pod templates for this cloud including the dynamic ones.
     */
    @NonNull
    public List<PodTemplate> getAllTemplates() {
        return PodTemplateSource.getAll(this);
    }

    @DataBoundSetter
    public void setTemplates(@NonNull List<PodTemplate> templates) {
        this.templates = new ArrayList<>(templates);
    }

    public String getServerUrl() {
        return serverUrl;
    }

    @DataBoundSetter
    public void setServerUrl(@NonNull String serverUrl) {
        this.serverUrl = serverUrl;
    }

    public String getServerCertificate() {
        return serverCertificate;
    }

    @DataBoundSetter
    public void setServerCertificate(String serverCertificate) {
        this.serverCertificate = Util.fixEmpty(serverCertificate);
    }

    public boolean isSkipTlsVerify() {
        return skipTlsVerify;
    }

    @DataBoundSetter
    public void setSkipTlsVerify(boolean skipTlsVerify) {
        this.skipTlsVerify = skipTlsVerify;
    }
    
    public boolean isAddMasterProxyEnvVars() {
    	return this.addMasterProxyEnvVars;
    }
    
    @DataBoundSetter
    public void setAddMasterProxyEnvVars(boolean addMasterProxyEnvVars) {
    	this.addMasterProxyEnvVars = addMasterProxyEnvVars;
    }

    public String getNamespace() {
        return namespace;
    }

    @DataBoundSetter
    public void setNamespace(String namespace) {
        this.namespace = Util.fixEmpty(namespace);
    }

    public String getJnlpregistry() {
        return jnlpregistry;
    }

    @DataBoundSetter
    public void setJnlpregistry(String jnlpregistry) {
        this.jnlpregistry = Util.fixEmpty(jnlpregistry);
    }

    @CheckForNull
    public String getJenkinsUrl() {
        return jenkinsUrl;
    }

    @DataBoundSetter
    @Deprecated
    public void setCapOnlyOnAlivePods(boolean capOnlyOnAlivePods) {
        this.capOnlyOnAlivePods = capOnlyOnAlivePods;
    }

    @Deprecated
    public boolean isCapOnlyOnAlivePods() {
        return capOnlyOnAlivePods;
    }

    /**
     * @return same as {@link #getJenkinsUrlOrNull}, if set
     * @throws IllegalStateException if no Jenkins URL could be computed.
     */
    @NonNull
    public String getJenkinsUrlOrDie() {
        String url = getJenkinsUrlOrNull();
        if (url == null) {
            throw new IllegalStateException("Jenkins URL for Kubernetes is null");
        }
        return url;
    }

    /**
     * Jenkins URL to be used by agents launched by this cloud.
     *
     * <p>Tries in order:<ol>
     * <li>an explicitly configured URL ({@link #getJenkinsUrl})
     * <li>the system property or environment variable {@code KUBERNETES_JENKINS_URL}, unless {@link #isWebSocket} mode and {@link #getCredentialsId} is defined
     * <li>{@link JenkinsLocationConfiguration#getUrl}
     * </ol>
     *
     * @return Jenkins URL to be used by agents launched by this cloud. Always ends with a trailing slash.
     *         Null if no Jenkins URL could be computed.
     */
    @CheckForNull
    public String getJenkinsUrlOrNull() {
        String url = getJenkinsUrl();
        if (url == null && (!isWebSocket() || getCredentialsId() == null)) {
            url = Util.fixEmpty(System.getProperty("KUBERNETES_JENKINS_URL", System.getenv("KUBERNETES_JENKINS_URL")));
        }
        if (url == null) {
            url = JenkinsLocationConfiguration.get().getUrl();
        }
        if (url == null) {
            return null;
        }
        url = url.endsWith("/") ? url : url + "/";
        return url;
    }

    public boolean isWebSocket() {
        return webSocket;
    }

    @DataBoundSetter
    public void setWebSocket(boolean webSocket) {
        this.webSocket = webSocket;
    }

    public boolean isDirectConnection() {
        return directConnection;
    }

    @DataBoundSetter
    public void setDirectConnection(boolean directConnection) {
        this.directConnection = directConnection;
    }

    @DataBoundSetter
    public void setJenkinsUrl(String jenkinsUrl) {
        this.jenkinsUrl = Util.fixEmptyAndTrim(jenkinsUrl);
    }

    public String getJenkinsTunnel() {
        return jenkinsTunnel;
    }

    @DataBoundSetter
    public void setJenkinsTunnel(String jenkinsTunnel) {
        this.jenkinsTunnel = Util.fixEmpty(jenkinsTunnel);
    }

    public String getCredentialsId() {
        return credentialsId;
    }

    @DataBoundSetter
    public void setCredentialsId(String credentialsId) {
        this.credentialsId = Util.fixEmpty(credentialsId);
    }

    public int getContainerCap() {
        return containerCap != null ? containerCap : Integer.MAX_VALUE;
    }

    @DataBoundSetter
    public void setContainerCapStr(String containerCapStr) {
        setContainerCap(containerCapStr.equals("") ? null : Integer.parseInt(containerCapStr));
    }

    public void setContainerCap(Integer containerCap) {
        this.containerCap = (containerCap != null && containerCap > 0) ? containerCap : null;
    }

    public String getContainerCapStr() {
        // null, serialized Integer.MAX_VALUE, or 0 means no limit
        return (containerCap == null || containerCap == Integer.MAX_VALUE || containerCap == 0) ? "" : String.valueOf(containerCap);
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    @DataBoundSetter
    public void setReadTimeout(int readTimeout) {
        this.readTimeout = Math.max(DEFAULT_READ_TIMEOUT_SECONDS, readTimeout);
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    /**
     * Labels for all pods started by the plugin
     * @return immutable map of pod labels
     * @deprecated use {@link #getPodLabels()}
     */
    @Deprecated
    public Map<String, String> getLabels() {
        return getPodLabelsMap();
    }

    /**
     * Set pod labels
     *
     * @param labels pod labels
     * @deprecated use {@link #setPodLabels(List)}
     */
    @Deprecated
    public void setLabels(Map<String, String> labels) {
        setPodLabels(labels != null ? PodLabel.fromMap(labels) : Collections.emptyList());
    }

    /**
     * Labels for all pods started by the plugin
     */
    @NonNull
    public List<PodLabel> getPodLabels() {
        return podLabels == null || podLabels.isEmpty() ? PodLabel.fromMap(DEFAULT_POD_LABELS) : podLabels;
    }

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

    /**
     * Map of labels to add to all pods started by the plugin
     * @return immutable map of pod labels
     */
    Map<String, String> getPodLabelsMap() {
        return PodLabel.toMap(getPodLabels());
    }

    @DataBoundSetter
    public void setMaxRequestsPerHostStr(String maxRequestsPerHostStr) {
        try  {
            setMaxRequestsPerHost(Integer.parseInt(maxRequestsPerHostStr));
        } catch (NumberFormatException e) {
            setMaxRequestsPerHost(DEFAULT_MAX_REQUESTS_PER_HOST);
        }
    }

    @DataBoundSetter
    public void setMaxRequestsPerHost(int maxRequestsPerHost) {
        if (maxRequestsPerHost < 0) {
            this.maxRequestsPerHost = DEFAULT_MAX_REQUESTS_PER_HOST;
        } else {
            this.maxRequestsPerHost = maxRequestsPerHost;
        }
    }

    public String getMaxRequestsPerHostStr() {
        return String.valueOf(maxRequestsPerHost);
    }

    public int getMaxRequestsPerHost() {
        return maxRequestsPerHost;
    }

    @DataBoundSetter
    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = Math.max(DEFAULT_CONNECT_TIMEOUT_SECONDS, connectTimeout);
    }

    /**
     * Gets the global pod retention policy for the plugin.
     */
    public PodRetention getPodRetention() {
        return this.podRetention;
    }
    
    /**
     * Set the global pod retention policy for the plugin.
     * 
     * @param podRetention the pod retention policy for the plugin.
     */
    @DataBoundSetter
    public void setPodRetention(PodRetention podRetention) {
        if (podRetention == null || podRetention instanceof Default) {
            podRetention = PodRetention.getKubernetesCloudDefault();
        }
        this.podRetention = podRetention;
    }

    /**
     * Connects to Kubernetes.
     *
     * @return Kubernetes client.
     */
    @SuppressFBWarnings({ "IS2_INCONSISTENT_SYNC", "DC_DOUBLECHECK" })
    public KubernetesClient connect() throws KubernetesAuthException, IOException {

        LOGGER.log(Level.FINEST, "Building connection to Kubernetes {0} URL {1} namespace {2}",
                new String[] { getDisplayName(), serverUrl, namespace });
        KubernetesClient client = KubernetesClientProvider.createClient(this);

        LOGGER.log(Level.FINE, "Connected to Kubernetes {0} URL {1} namespace {2}", new String[] { getDisplayName(), client.getMasterUrl().toString(), namespace });
        return client;
    }

    @Override
    public Collection<NodeProvisioner.PlannedNode> provision(@NonNull final Cloud.CloudState state, final int excessWorkload) {
        try {
            Metrics.metricRegistry().meter(metricNameForLabel(state.getLabel())).mark(excessWorkload);
            Label label = state.getLabel();
            int plannedCapacity = state.getAdditionalPlannedCapacity(); // Planned nodes, will be launched on the next round of NodeProvisioner
            Set<String> allInProvisioning = InProvisioning.getAllInProvisioning(label); // Nodes being launched
            LOGGER.log(Level.FINE, () -> "In provisioning : " + allInProvisioning);
            int toBeProvisioned = Math.max(0, excessWorkload - allInProvisioning.size());
            List<NodeProvisioner.PlannedNode> plannedNodes = new ArrayList<>();
            LOGGER.log(Level.FINE, "Label \"{0}\" excess workload: {1}, executors: {2}",
                    new Object[] {label, toBeProvisioned, plannedCapacity});

            for (PodTemplate podTemplate : getTemplatesFor(label)) {
                LOGGER.log(Level.FINE, "Template for label \"{0}\": {1}", new Object[]{label, podTemplate.getName()});
                // check overall concurrency limit using the default label(s) on all templates
                int numExecutors = 1;
                PodTemplate unwrappedTemplate = getUnwrappedTemplate(podTemplate);
                while (toBeProvisioned > 0 && KubernetesProvisioningLimits.get().register(this, podTemplate, numExecutors)) {
                    plannedNodes.add(PlannedNodeBuilderFactory.createInstance().cloud(this).template(unwrappedTemplate).label(label).numExecutors(1).build());
                    toBeProvisioned--;
                }
                if (!plannedNodes.isEmpty()) {
                    // Return early when a matching template was found and nodes were planned
                    LOGGER.log(Level.FINEST, "Planned {0} Kubernetes agents with template \"{1}\"", new Object[]{plannedNodes.size(), podTemplate.getName()});
                    Metrics.metricRegistry().counter(MetricNames.PROVISION_NODES).inc(plannedNodes.size());
                    return plannedNodes;
                }
            }
            Metrics.metricRegistry().counter(MetricNames.PROVISION_NODES).inc(plannedNodes.size());
            return plannedNodes;
        } catch (KubernetesClientException e) {
            Metrics.metricRegistry().counter(MetricNames.PROVISION_FAILED).inc();
            Throwable cause = e.getCause();
            if (cause instanceof SocketTimeoutException || cause instanceof ConnectException || cause instanceof UnknownHostException) {
                LOGGER.log(Level.WARNING, "Failed to connect to Kubernetes at {0}: {1}",
                        new String[] { serverUrl, cause.getMessage() });
            } else {
                LOGGER.log(Level.WARNING, "Failed to count the # of live instances on Kubernetes",
                        cause != null ? cause : e);
            }
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, "Failed to count the # of live instances on Kubernetes", e);
        }
        return Collections.emptyList();
    }

    @Override
    public boolean canProvision(@NonNull Cloud.CloudState state) {
        return getTemplate(state.getLabel()) != null;
    }

    /**
     * Gets {@link PodTemplate} that has the matching {@link Label}.
     * @param label label to look for in templates
     * @return the template
     */
    @CheckForNull
    public PodTemplate getTemplate(@CheckForNull Label label) {
        return PodTemplateUtils.getTemplateByLabel(label, getAllTemplates());
    }

    @CheckForNull
    public PodTemplate getTemplateById(@NonNull String id) {
        return getAllTemplates().stream().filter(t -> id.equals(t.getId())).findFirst().orElse(null);
    }

    /**
     * Unwraps the given pod template.
     * @param podTemplate the pod template to unwrap.
     * @return the unwrapped pod template
     */
    public PodTemplate getUnwrappedTemplate(PodTemplate podTemplate) {
        return PodTemplateUtils.unwrap(podTemplate, getDefaultsProviderTemplate(), getAllTemplates());
    }

    /**
     * Gets all PodTemplates that have the matching {@link Label}.
     * @param label label to look for in templates
     * @return list of matching templates
     * @deprecated Use {@link #getTemplatesFor(Label)} instead.
     */
    @Deprecated
    public ArrayList<PodTemplate> getMatchingTemplates(@CheckForNull Label label) {
        return new ArrayList<>(getTemplatesFor(label));
    }

    /**
     * Gets all PodTemplates that have the matching {@link Label}.
     * @param label label to look for in templates
     * @return list of matching templates
     */
    public List<PodTemplate> getTemplatesFor(@CheckForNull Label label) {
        return PodTemplateFilter.applyAll(this, getAllTemplates(), label);
    }

    /**
     * Add a new template to the cloud
     * @param t docker template
     */
    public void addTemplate(PodTemplate t) {
        this.templates.add(t);
        // t.parent = this;
    }

    /**
     * Remove a
     *
     * @param t docker template
     */
    public void removeTemplate(PodTemplate t) {
        this.templates.remove(t);
    }

    /**
     * Add a dynamic pod template. Won't be displayed in UI, and persisted separately from the cloud instance.
     * @param t the template to add
     */
    public void addDynamicTemplate(PodTemplate t) {
        PodTemplateMap.get().addTemplate(this, t);
    }

    /**
     * Remove a dynamic pod template.
     * @param t the template to remove
     */
    public void removeDynamicTemplate(PodTemplate t) {
        PodTemplateMap.get().removeTemplate(this, t);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        KubernetesCloud that = (KubernetesCloud) o;
        return skipTlsVerify == that.skipTlsVerify &&
                addMasterProxyEnvVars == that.addMasterProxyEnvVars &&
                capOnlyOnAlivePods == that.capOnlyOnAlivePods &&
                Objects.equals(containerCap, that.containerCap) &&
                retentionTimeout == that.retentionTimeout &&
                connectTimeout == that.connectTimeout &&
                readTimeout == that.readTimeout &&
                usageRestricted == that.usageRestricted &&
                maxRequestsPerHost == that.maxRequestsPerHost &&
                Objects.equals(defaultsProviderTemplate, that.defaultsProviderTemplate) &&
                templates.equals(that.templates) &&
                Objects.equals(serverUrl, that.serverUrl) &&
                Objects.equals(serverCertificate, that.serverCertificate) &&
                Objects.equals(namespace, that.namespace) &&
                Objects.equals(jnlpregistry, that.jnlpregistry)&&
                Objects.equals(jenkinsUrl, that.jenkinsUrl) &&
                Objects.equals(jenkinsTunnel, that.jenkinsTunnel) &&
                Objects.equals(credentialsId, that.credentialsId) &&
                Objects.equals(podLabels, that.podLabels) &&
                Objects.equals(podRetention, that.podRetention) &&
                Objects.equals(waitForPodSec, that.waitForPodSec) &&
                useJenkinsProxy==that.useJenkinsProxy;
    }

    @Override
    public int hashCode() {
        return Objects.hash(defaultsProviderTemplate, templates, serverUrl, serverCertificate, skipTlsVerify,
                addMasterProxyEnvVars, capOnlyOnAlivePods, namespace, jnlpregistry, jenkinsUrl, jenkinsTunnel, credentialsId,
                containerCap, retentionTimeout, connectTimeout, readTimeout, podLabels, usageRestricted,
                maxRequestsPerHost, podRetention, useJenkinsProxy);
    }

    public Integer getWaitForPodSec() {
        return waitForPodSec;
    }

    @DataBoundSetter
    public void setWaitForPodSec(Integer waitForPodSec) {
        this.waitForPodSec = waitForPodSec;
    }

    @Extension
    public static class DescriptorImpl extends Descriptor<Cloud> {
        @Override
        public String getDisplayName() {
            return "Kubernetes";
        }

        @Initializer(before = InitMilestone.PLUGINS_STARTED)
        public static void addAliases() {
            Jenkins.XSTREAM2.addCompatibilityAlias(
                    "org.csanchez.jenkins.plugins.kubernetes.OpenShiftBearerTokenCredentialImpl",
                    org.jenkinsci.plugins.kubernetes.credentials.OpenShiftBearerTokenCredentialImpl.class);
            Jenkins.XSTREAM2.addCompatibilityAlias(
                    "org.csanchez.jenkins.plugins.kubernetes.OpenShiftTokenCredentialImpl",
                    StringCredentialsImpl.class);
            Jenkins.XSTREAM2.addCompatibilityAlias("org.csanchez.jenkins.plugins.kubernetes.ServiceAccountCredential",
                    org.jenkinsci.plugins.kubernetes.credentials.FileSystemServiceAccountCredential.class);
        }

        @RequirePOST
        @SuppressWarnings("unused") // used by jelly
        public FormValidation doTestConnection(@QueryParameter String name,
                                               @QueryParameter String serverUrl,
                                               @QueryParameter String credentialsId,
                                               @QueryParameter String serverCertificate,
                                               @QueryParameter boolean skipTlsVerify,
                                               @QueryParameter String namespace,
                                               @QueryParameter int connectionTimeout,
                                               @QueryParameter int readTimeout,
                                               @QueryParameter boolean useJenkinsProxy) throws Exception {
            Jenkins.get().checkPermission(Jenkins.ADMINISTER);

            if (StringUtils.isBlank(name))
                return FormValidation.error("name is required");

            try (KubernetesClient client = new KubernetesFactoryAdapter(serverUrl, namespace,
                        Util.fixEmpty(serverCertificate), Util.fixEmpty(credentialsId), skipTlsVerify,
                        connectionTimeout, readTimeout, DEFAULT_MAX_REQUESTS_PER_HOST, useJenkinsProxy).createClient()) {
                    // test listing pods
                    client.pods().list();
                VersionInfo version = client.getVersion();
                return FormValidation.ok("Connected to Kubernetes " + version.getGitVersion());
            } catch (KubernetesClientException e) {
                LOGGER.log(Level.FINE, String.format("Error testing connection %s", serverUrl), e);
                return FormValidation.error("Error testing connection %s: %s", serverUrl, e.getCause() == null
                        ? e.getMessage()
                        : String.format("%s: %s", e.getCause().getClass().getName(), e.getCause().getMessage()));
            } catch (Exception e) {
                LOGGER.log(Level.FINE, String.format("Error testing connection %s", serverUrl), e);
                return FormValidation.error("Error testing connection %s: %s", serverUrl, e.getMessage());
            }
        }

        @RequirePOST
        @SuppressWarnings("unused") // used by jelly
        public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context, @QueryParameter String serverUrl) {
            Jenkins.get().checkPermission(Jenkins.ADMINISTER);
            StandardListBoxModel result = new StandardListBoxModel();
            result.includeEmptyValue();
            result.includeMatchingAs(
                ACL.SYSTEM,
                context,
                StandardCredentials.class,
                serverUrl != null ? URIRequirementBuilder.fromUri(serverUrl).build()
                            : Collections.EMPTY_LIST,
                CredentialsMatchers.anyOf(
                    AuthenticationTokens.matcher(KubernetesAuth.class)
                )
            );
            return result;
        }

        @RequirePOST
        @SuppressWarnings("unused") // used by jelly
        public FormValidation doCheckMaxRequestsPerHostStr(@QueryParameter String value) throws IOException, ServletException {
            return FormValidation.validatePositiveInteger(value);
        }

        @RequirePOST
        @SuppressWarnings("unused") // used by jelly
        public FormValidation doCheckConnectTimeout(@QueryParameter String value) {
            return FormValidation.validateIntegerInRange(value, DEFAULT_CONNECT_TIMEOUT_SECONDS, Integer.MAX_VALUE);
        }

        @RequirePOST
        @SuppressWarnings("unused") // used by jelly
        public FormValidation doCheckReadTimeout(@QueryParameter String value) {
            return FormValidation.validateIntegerInRange(value, DEFAULT_READ_TIMEOUT_SECONDS, Integer.MAX_VALUE);
        }

        @RequirePOST
        @SuppressWarnings("unused") // used by jelly
        public FormValidation doCheckRetentionTimeout(@QueryParameter String value) {
            return FormValidation.validateIntegerInRange(value, DEFAULT_RETENTION_TIMEOUT_MINUTES, Integer.MAX_VALUE);
        }

        @SuppressWarnings("unused") // used by jelly
        public FormValidation doCheckDirectConnection(@QueryParameter boolean value, @QueryParameter String jenkinsUrl, @QueryParameter boolean webSocket) throws IOException, ServletException {
            if (!webSocket) {
                TcpSlaveAgentListener tcpSlaveAgentListener = Jenkins.get().getTcpSlaveAgentListener();
                if (tcpSlaveAgentListener == null) {
                    return FormValidation.warning("'TCP port for inbound agents' is disabled in Global Security settings. Connecting Kubernetes agents will not work without this or WebSocket mode!");
                }
                if (tcpSlaveAgentListener.getIdentityPublicKey() == null) {
                    return FormValidation.error("You must install the instance-identity plugin to use inbound agents in TCP mode");
                }
            }

            if(value) {
                if (webSocket) {
                    return FormValidation.error("Direct connection and WebSocket mode are mutually exclusive");
                }
                if(!isEmpty(jenkinsUrl)) return FormValidation.warning("No need to configure Jenkins URL when direct connection is enabled");

                if(Jenkins.get().getSlaveAgentPort() == 0) return FormValidation.warning(
                        "A random 'TCP port for inbound agents' is configured in Global Security settings. In 'direct connection' mode agents will not be able to reconnect to a restarted controller with random port!");
            } else {
                if (isEmpty(jenkinsUrl)) {
                    String url = StringUtils.defaultIfBlank(System.getProperty("KUBERNETES_JENKINS_URL", System.getenv("KUBERNETES_JENKINS_URL")), JenkinsLocationConfiguration.get().getUrl());
                    if (url != null) {
                        return FormValidation.ok("Will connect using " + url);
                    } else {
                        return FormValidation.warning("Configure either Direct Connection or Jenkins URL");
                    }
                }
            }
            return FormValidation.ok();
        }

        @SuppressWarnings("unused") // used by jelly
        public FormValidation doCheckJenkinsUrl(@QueryParameter String value, @QueryParameter boolean directConnection) throws IOException, ServletException {
            try {
                if(!isEmpty(value)) new URL(value);
            } catch (MalformedURLException e) {
                return FormValidation.error(e, "Invalid Jenkins URL");
            }
            return FormValidation.ok();
        }

        public FormValidation doCheckWebSocket(@QueryParameter boolean webSocket, @QueryParameter boolean directConnection, @QueryParameter String jenkinsTunnel) {
            if (webSocket) {
                if (!WebSockets.isSupported()) {
                    return FormValidation.error("WebSocket support is not enabled in this Jenkins installation");
                }
                if (Util.fixEmpty(jenkinsTunnel) != null) {
                    return FormValidation.error("Tunneling is not currently supported in WebSocket mode");
                }
            }
            return FormValidation.ok();
        }

        @SuppressWarnings("unused") // used by jelly
        public List<Descriptor<PodRetention>> getAllowedPodRetentions() {
            Jenkins jenkins = Jenkins.getInstanceOrNull();
            if (jenkins == null) {
                return new ArrayList<>(0);
            }
            return DescriptorVisibilityFilter.apply(this, jenkins.getDescriptorList(PodRetention.class));
        }

        @SuppressWarnings({"rawtypes", "unused"}) // used by jelly
        public Descriptor getDefaultPodRetention() {
            Jenkins jenkins = Jenkins.getInstanceOrNull();
            if (jenkins == null) {
                return null;
            }
            return jenkins.getDescriptor(PodRetention.getKubernetesCloudDefault().getClass());
        }

        @SuppressWarnings("unused") // used by jelly
        public int getDefaultReadTimeout() {
            return DEFAULT_READ_TIMEOUT_SECONDS;
        }

        @SuppressWarnings("unused") // used by jelly
        public int getDefaultConnectTimeout() {
            return DEFAULT_CONNECT_TIMEOUT_SECONDS;
        }

        @SuppressWarnings("unused") // used by jelly
        public int getDefaultRetentionTimeout() {
            return DEFAULT_RETENTION_TIMEOUT_MINUTES;
        }

        public int getDefaultWaitForPod() {
            return DEFAULT_WAIT_FOR_POD_SEC;
        }

    }

    @Override
    public String toString() {
        return "KubernetesCloud{name=" + name +
                ", defaultsProviderTemplate='" + defaultsProviderTemplate + '\'' +
                ", serverUrl='" + serverUrl + '\'' +
                ", serverCertificate='" + serverCertificate + '\'' +
                ", skipTlsVerify=" + skipTlsVerify +
                ", addMasterProxyEnvVars=" + addMasterProxyEnvVars +
                ", capOnlyOnAlivePods=" + capOnlyOnAlivePods +
                ", namespace='" + namespace + '\'' +
                ", jnlpregistry='" + jnlpregistry + '\'' +
                ", jenkinsUrl='" + jenkinsUrl + '\'' +
                ", jenkinsTunnel='" + jenkinsTunnel + '\'' +
                ", credentialsId='" + credentialsId + '\'' +
                ", webSocket=" + webSocket +
                ", containerCap=" + containerCap +
                ", retentionTimeout=" + retentionTimeout +
                ", connectTimeout=" + connectTimeout +
                ", readTimeout=" + readTimeout +
                ", labels=" + labels +
                ", podLabels=" + podLabels +
                ", usageRestricted=" + usageRestricted +
                ", maxRequestsPerHost=" + maxRequestsPerHost +
                ", waitForPodSec=" + waitForPodSec +
                ", podRetention=" + podRetention +
                ", useJenkinsProxy=" + useJenkinsProxy +
                ", templates=" + templates +
                '}';
    }

    private Object readResolve() {
        if ((serverCertificate != null) && !serverCertificate.trim().startsWith("-----BEGIN CERTIFICATE-----")) {
            serverCertificate = new String(Base64.getDecoder().decode(serverCertificate.getBytes(UTF_8)), UTF_8);
            LOGGER.log(Level.INFO, "Upgraded Kubernetes server certificate key: {0}",
                    serverCertificate.substring(0, 80));
        }

        if (maxRequestsPerHost == 0) {
            maxRequestsPerHost = DEFAULT_MAX_REQUESTS_PER_HOST;
        }
        if (podRetention == null) {
            podRetention = PodRetention.getKubernetesCloudDefault();
        }
        setConnectTimeout(connectTimeout);
        setReadTimeout(readTimeout);
        setRetentionTimeout(retentionTimeout);
        if (waitForPodSec == null) {
            waitForPodSec = DEFAULT_WAIT_FOR_POD_SEC;
        }
        if (podLabels == null && labels != null) {
            setPodLabels(PodLabel.fromMap(labels));
        }
        if (containerCap != null && containerCap == 0) {
            containerCap = null;
        }
        return this;
    }

    @Extension
    public static class PodTemplateSourceImpl extends PodTemplateSource {
        @NonNull
        @Override
        public List<PodTemplate> 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);
            }
        }
    }
}

<MSG> tabs replaced with blanks
<DFF> @@ -164,13 +164,13 @@ public class KubernetesCloud extends Cloud {
     }
 
     public boolean isUsageRestricted() {
-		return usageRestricted;
-	}
+        return usageRestricted;
+    }
 
     @DataBoundSetter
     public void setUsageRestricted(boolean usageRestricted) {
-		this.usageRestricted = usageRestricted;
-	}
+        this.usageRestricted = usageRestricted;
+    }
     
     public int getRetentionTimeout() {
         return retentionTimeout;

{'commit': 'diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java\nindex 153c99db..6a3ab6c4 100644\n--- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java\n+++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java\n@@ -164,13 +164,13 @@ public class KubernetesCloud extends Cloud {\n     }\n \n     public boolean isUsageRestricted() {\n-\t\treturn usageRestricted;\n-\t}\n+        return usageRestricted;\n+    }\n \n     @DataBoundSetter\n     public void setUsageRestricted(boolean usageRestricted) {\n-\t\tthis.usageRestricted = usageRestricted;\n-\t}\n+        this.usageRestricted = usageRestricted;\n+    }\n     \n     public int getRetentionTimeout() {\n         return retentionTimeout;\n', 'commit_subject': 'tabs replaced with blanks', 'repo_name': 'jenkinsci/kubernetes-plugin', 'committer': {'name': 'sixtyeight', 'email': '684a79caca6e0537d90938ed044d5e98be20aa9d@gmx.at', 'time_sec': '1528028243', 'tz_offset': '120', 'date': {'seconds': '1528028243'}}, 'commit_tree': 'e1247f73e10a8e993dfa9fc3d9f3926233994058', 'commit_message': 'tabs replaced with blanks', 'language_name': 'Shell', 'license': 'apache-2.0', 'hunks': '@@ -164,13 +164,13 @@ public class KubernetesCloud extends Cloud {\n     }\n \n     public boolean isUsageRestricted() {\n-\t\treturn usageRestricted;\n-\t}\n+        return usageRestricted;\n+    }\n \n     @DataBoundSetter\n     public void setUsageRestricted(boolean usageRestricted) {\n-\t\tthis.usageRestricted = usageRestricted;\n-\t}\n+        this.usageRestricted = usageRestricted;\n+    }\n     \n     public int getRetentionTimeout() {\n         return retentionTimeout;\n', 'addition_count': 4, 'deletion_count': 4, 'src_file': 'a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java', 'tgt_file': 'b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/KubernetesCloud.java', 'file_extension': '.java', 'before_file': 'package org.csanchez.jenkins.plugins.kubernetes;\n\nimport static org.apache.commons.lang.StringUtils.isEmpty;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.net.ConnectException;\nimport java.net.MalformedURLException;\nimport java.net.SocketTimeoutException;\nimport java.net.URL;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport javax.servlet.ServletException;\n\nimport edu.umd.cs.findbugs.annotations.CheckForNull;\nimport hudson.Main;\nimport hudson.model.ItemGroup;\nimport hudson.util.XStream2;\nimport jenkins.metrics.api.Metrics;\nimport org.apache.commons.lang.StringUtils;\nimport org.csanchez.jenkins.plugins.kubernetes.pipeline.PodTemplateMap;\nimport org.csanchez.jenkins.plugins.kubernetes.pod.retention.Default;\nimport org.csanchez.jenkins.plugins.kubernetes.pod.retention.PodRetention;\nimport org.jenkinsci.plugins.kubernetes.auth.KubernetesAuth;\nimport org.jenkinsci.plugins.kubernetes.auth.KubernetesAuthException;\nimport org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;\nimport org.kohsuke.stapler.AncestorInPath;\nimport org.kohsuke.stapler.DataBoundConstructor;\nimport org.kohsuke.stapler.DataBoundSetter;\nimport org.kohsuke.stapler.QueryParameter;\nimport org.kohsuke.stapler.interceptor.RequirePOST;\n\nimport com.cloudbees.plugins.credentials.CredentialsMatchers;\nimport com.cloudbees.plugins.credentials.common.StandardCredentials;\nimport com.cloudbees.plugins.credentials.common.StandardListBoxModel;\nimport com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;\n\nimport edu.umd.cs.findbugs.annotations.NonNull;\nimport edu.umd.cs.findbugs.annotations.SuppressFBWarnings;\nimport hudson.Extension;\nimport hudson.TcpSlaveAgentListener;\nimport hudson.Util;\nimport hudson.init.InitMilestone;\nimport hudson.init.Initializer;\nimport hudson.model.Descriptor;\nimport hudson.model.DescriptorVisibilityFilter;\nimport hudson.model.Label;\nimport hudson.security.ACL;\nimport hudson.slaves.Cloud;\nimport hudson.slaves.NodeProvisioner;\nimport hudson.util.FormValidation;\nimport hudson.util.ListBoxModel;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientException;\nimport io.fabric8.kubernetes.client.VersionInfo;\nimport jenkins.model.Jenkins;\nimport jenkins.model.JenkinsLocationConfiguration;\nimport jenkins.authentication.tokens.api.AuthenticationTokens;\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.csanchez.jenkins.plugins.kubernetes.MetricNames.metricNameForLabel;\n\nimport jenkins.websocket.WebSockets;\n\n/**\n * Kubernetes cloud provider.\n *\n * Starts agents in a Kubernetes cluster using defined Docker templates for each label.\n *\n * @author Carlos Sanchez carlos@apache.org\n */\npublic class KubernetesCloud extends Cloud {\n    public static final int DEFAULT_MAX_REQUESTS_PER_HOST = 32;\n    public static final Integer DEFAULT_WAIT_FOR_POD_SEC = 600;\n\n    private static final Logger LOGGER = Logger.getLogger(KubernetesCloud.class.getName());\n\n    public static final String JNLP_NAME = "jnlp";\n    /** label for all pods started by the plugin */\n    @Deprecated\n    public static final Map<String, String> DEFAULT_POD_LABELS = Collections.singletonMap("jenkins", "slave");\n\n    /** Default timeout for idle workers that don\'t correctly indicate exit. */\n    public static final int DEFAULT_RETENTION_TIMEOUT_MINUTES = 5;\n\n    public static final int DEFAULT_READ_TIMEOUT_SECONDS = 15;\n\n    public static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 5;\n\n    private String defaultsProviderTemplate;\n\n    @NonNull\n    private List<PodTemplate> templates = new ArrayList<>();\n    private String serverUrl;\n    private boolean useJenkinsProxy;\n    @CheckForNull\n    private String serverCertificate;\n\n    private boolean skipTlsVerify;\n    private boolean addMasterProxyEnvVars;\n\n    private boolean capOnlyOnAlivePods;\n\n    private String namespace;\n    private String jnlpregistry;\n    private boolean webSocket;\n    private boolean directConnection = false;\n    private String jenkinsUrl;\n    @CheckForNull\n    private String jenkinsTunnel;\n    @CheckForNull\n    private String credentialsId;\n    private Integer containerCap;\n    private int retentionTimeout = DEFAULT_RETENTION_TIMEOUT_MINUTES;\n    private int connectTimeout = DEFAULT_CONNECT_TIMEOUT_SECONDS;\n    private int readTimeout = DEFAULT_READ_TIMEOUT_SECONDS;\n    /** @deprecated Stored as a list of PodLabels */\n    @Deprecated\n    private transient Map<String, String> labels;\n    private List<PodLabel> podLabels = new ArrayList<>();\n    private boolean usageRestricted;\n\n    private int maxRequestsPerHost;\n\n    // Integer to differentiate null from 0\n    private Integer waitForPodSec = DEFAULT_WAIT_FOR_POD_SEC;\n\n    @CheckForNull\n    private PodRetention podRetention = PodRetention.getKubernetesCloudDefault();\n\n    @DataBoundConstructor\n    public KubernetesCloud(String name) {\n        super(name);\n        setMaxRequestsPerHost(DEFAULT_MAX_REQUESTS_PER_HOST);\n    }\n\n    /**\n     * Copy constructor.\n     * Allows to create copies of the original kubernetes cloud. Since it\'s a singleton\n     * by design, this method also allows specifying a new name.\n     * @param name Name of the cloud to be created\n     * @param source Source Kubernetes cloud implementation\n     * @since 0.13\n     */\n    public KubernetesCloud(@NonNull String name, @NonNull KubernetesCloud source) {\n        super(name);\n        XStream2 xs = new XStream2();\n        xs.omitField(Cloud.class, "name");\n        xs.omitField(KubernetesCloud.class, "templates"); // TODO PodTemplate and fields needs to implement equals\n        xs.unmarshal(XStream2.getDefaultDriver().createReader(new StringReader(xs.toXML(source))), this);\n        this.templates.addAll(source.templates);\n    }\n\n    @Deprecated\n    }\n\n    public boolean isUsageRestricted() {\n\t\treturn usageRestricted;\n\t}\n\n    @DataBoundSetter\n    public void setUsageRestricted(boolean usageRestricted) {\n\t\tthis.usageRestricted = usageRestricted;\n\t}\n    \n    public int getRetentionTimeout() {\n        return retentionTimeout;\n        setReadTimeout(readTimeout);\n\n    }\n\n    public boolean isUseJenkinsProxy() { return useJenkinsProxy; }\n    @DataBoundSetter\n    public void setUseJenkinsProxy(boolean useJenkinsProxy) { this.useJenkinsProxy = useJenkinsProxy; }\n\n    public boolean isUsageRestricted() {\n        return usageRestricted;\n    }\n\n    @DataBoundSetter\n    public void setUsageRestricted(boolean usageRestricted) {\n        this.usageRestricted = usageRestricted;\n    }\n    \n    public int getRetentionTimeout() {\n        return retentionTimeout;\n    }\n\n    @DataBoundSetter\n    public void setRetentionTimeout(int retentionTimeout) {\n        this.retentionTimeout = Math.max(DEFAULT_RETENTION_TIMEOUT_MINUTES, retentionTimeout);\n    }\n\n    public String getDefaultsProviderTemplate() {\n        return defaultsProviderTemplate;\n    }\n\n    @DataBoundSetter\n    public void setDefaultsProviderTemplate(String defaultsProviderTemplate) {\n        this.defaultsProviderTemplate = defaultsProviderTemplate;\n    }\n\n    @NonNull\n    public List<PodTemplate> getTemplates() {\n        return templates;\n    }\n\n    /**\n     * Returns all pod templates for this cloud including the dynamic ones.\n     * @return all pod templates for this cloud including the dynamic ones.\n     */\n    @NonNull\n    public List<PodTemplate> getAllTemplates() {\n        return PodTemplateSource.getAll(this);\n    }\n\n    @DataBoundSetter\n    public void setTemplates(@NonNull List<PodTemplate> templates) {\n        this.templates = new ArrayList<>(templates);\n    }\n\n    public String getServerUrl() {\n        return serverUrl;\n    }\n\n    @DataBoundSetter\n    public void setServerUrl(@NonNull String serverUrl) {\n        this.serverUrl = serverUrl;\n    }\n\n    public String getServerCertificate() {\n        return serverCertificate;\n    }\n\n    @DataBoundSetter\n    public void setServerCertificate(String serverCertificate) {\n        this.serverCertificate = Util.fixEmpty(serverCertificate);\n    }\n\n    public boolean isSkipTlsVerify() {\n        return skipTlsVerify;\n    }\n\n    @DataBoundSetter\n    public void setSkipTlsVerify(boolean skipTlsVerify) {\n        this.skipTlsVerify = skipTlsVerify;\n    }\n    \n    public boolean isAddMasterProxyEnvVars() {\n    \treturn this.addMasterProxyEnvVars;\n    }\n    \n    @DataBoundSetter\n    public void setAddMasterProxyEnvVars(boolean addMasterProxyEnvVars) {\n    \tthis.addMasterProxyEnvVars = addMasterProxyEnvVars;\n    }\n\n    public String getNamespace() {\n        return namespace;\n    }\n\n    @DataBoundSetter\n    public void setNamespace(String namespace) {\n        this.namespace = Util.fixEmpty(namespace);\n    }\n\n    public String getJnlpregistry() {\n        return jnlpregistry;\n    }\n\n    @DataBoundSetter\n    public void setJnlpregistry(String jnlpregistry) {\n        this.jnlpregistry = Util.fixEmpty(jnlpregistry);\n    }\n\n    @CheckForNull\n    public String getJenkinsUrl() {\n        return jenkinsUrl;\n    }\n\n    @DataBoundSetter\n    @Deprecated\n    public void setCapOnlyOnAlivePods(boolean capOnlyOnAlivePods) {\n        this.capOnlyOnAlivePods = capOnlyOnAlivePods;\n    }\n\n    @Deprecated\n    public boolean isCapOnlyOnAlivePods() {\n        return capOnlyOnAlivePods;\n    }\n\n    /**\n     * @return same as {@link #getJenkinsUrlOrNull}, if set\n     * @throws IllegalStateException if no Jenkins URL could be computed.\n     */\n    @NonNull\n    public String getJenkinsUrlOrDie() {\n        String url = getJenkinsUrlOrNull();\n        if (url == null) {\n            throw new IllegalStateException("Jenkins URL for Kubernetes is null");\n        }\n        return url;\n    }\n\n    /**\n     * Jenkins URL to be used by agents launched by this cloud.\n     *\n     * <p>Tries in order:<ol>\n     * <li>an explicitly configured URL ({@link #getJenkinsUrl})\n     * <li>the system property or environment variable {@code KUBERNETES_JENKINS_URL}, unless {@link #isWebSocket} mode and {@link #getCredentialsId} is defined\n     * <li>{@link JenkinsLocationConfiguration#getUrl}\n     * </ol>\n     *\n     * @return Jenkins URL to be used by agents launched by this cloud. Always ends with a trailing slash.\n     *         Null if no Jenkins URL could be computed.\n     */\n    @CheckForNull\n    public String getJenkinsUrlOrNull() {\n        String url = getJenkinsUrl();\n        if (url == null && (!isWebSocket() || getCredentialsId() == null)) {\n            url = Util.fixEmpty(System.getProperty("KUBERNETES_JENKINS_URL", System.getenv("KUBERNETES_JENKINS_URL")));\n        }\n        if (url == null) {\n            url = JenkinsLocationConfiguration.get().getUrl();\n        }\n        if (url == null) {\n            return null;\n        }\n        url = url.endsWith("/") ? url : url + "/";\n        return url;\n    }\n\n    public boolean isWebSocket() {\n        return webSocket;\n    }\n\n    @DataBoundSetter\n    public void setWebSocket(boolean webSocket) {\n        this.webSocket = webSocket;\n    }\n\n    public boolean isDirectConnection() {\n        return directConnection;\n    }\n\n    @DataBoundSetter\n    public void setDirectConnection(boolean directConnection) {\n        this.directConnection = directConnection;\n    }\n\n    @DataBoundSetter\n    public void setJenkinsUrl(String jenkinsUrl) {\n        this.jenkinsUrl = Util.fixEmptyAndTrim(jenkinsUrl);\n    }\n\n    public String getJenkinsTunnel() {\n        return jenkinsTunnel;\n    }\n\n    @DataBoundSetter\n    public void setJenkinsTunnel(String jenkinsTunnel) {\n        this.jenkinsTunnel = Util.fixEmpty(jenkinsTunnel);\n    }\n\n    public String getCredentialsId() {\n        return credentialsId;\n    }\n\n    @DataBoundSetter\n    public void setCredentialsId(String credentialsId) {\n        this.credentialsId = Util.fixEmpty(credentialsId);\n    }\n\n    public int getContainerCap() {\n        return containerCap != null ? containerCap : Integer.MAX_VALUE;\n    }\n\n    @DataBoundSetter\n    public void setContainerCapStr(String containerCapStr) {\n        setContainerCap(containerCapStr.equals("") ? null : Integer.parseInt(containerCapStr));\n    }\n\n    public void setContainerCap(Integer containerCap) {\n        this.containerCap = (containerCap != null && containerCap > 0) ? containerCap : null;\n    }\n\n    public String getContainerCapStr() {\n        // null, serialized Integer.MAX_VALUE, or 0 means no limit\n        return (containerCap == null || containerCap == Integer.MAX_VALUE || containerCap == 0) ? "" : String.valueOf(containerCap);\n    }\n\n    public int getReadTimeout() {\n        return readTimeout;\n    }\n\n    @DataBoundSetter\n    public void setReadTimeout(int readTimeout) {\n        this.readTimeout = Math.max(DEFAULT_READ_TIMEOUT_SECONDS, readTimeout);\n    }\n\n    public int getConnectTimeout() {\n        return connectTimeout;\n    }\n\n    /**\n     * Labels for all pods started by the plugin\n     * @return immutable map of pod labels\n     * @deprecated use {@link #getPodLabels()}\n     */\n    @Deprecated\n    public Map<String, String> getLabels() {\n        return getPodLabelsMap();\n    }\n\n    /**\n     * Set pod labels\n     *\n     * @param labels pod labels\n     * @deprecated use {@link #setPodLabels(List)}\n     */\n    @Deprecated\n    public void setLabels(Map<String, String> labels) {\n        setPodLabels(labels != null ? PodLabel.fromMap(labels) : Collections.emptyList());\n    }\n\n    /**\n     * Labels for all pods started by the plugin\n     */\n    @NonNull\n    public List<PodLabel> getPodLabels() {\n        return podLabels == null || podLabels.isEmpty() ? PodLabel.fromMap(DEFAULT_POD_LABELS) : podLabels;\n    }\n\n    /**\n     * Set Pod labels  for all pods started by the plugin.\n     */\n    @DataBoundSetter\n    public void setPodLabels(@CheckForNull List<PodLabel> labels) {\n        this.podLabels = new ArrayList<>();\n        if (labels != null) {\n            this.podLabels.addAll(labels);\n        }\n    }\n\n    /**\n     * Map of labels to add to all pods started by the plugin\n     * @return immutable map of pod labels\n     */\n    Map<String, String> getPodLabelsMap() {\n        return PodLabel.toMap(getPodLabels());\n    }\n\n    @DataBoundSetter\n    public void setMaxRequestsPerHostStr(String maxRequestsPerHostStr) {\n        try  {\n            setMaxRequestsPerHost(Integer.parseInt(maxRequestsPerHostStr));\n        } catch (NumberFormatException e) {\n            setMaxRequestsPerHost(DEFAULT_MAX_REQUESTS_PER_HOST);\n        }\n    }\n\n    @DataBoundSetter\n    public void setMaxRequestsPerHost(int maxRequestsPerHost) {\n        if (maxRequestsPerHost < 0) {\n            this.maxRequestsPerHost = DEFAULT_MAX_REQUESTS_PER_HOST;\n        } else {\n            this.maxRequestsPerHost = maxRequestsPerHost;\n        }\n    }\n\n    public String getMaxRequestsPerHostStr() {\n        return String.valueOf(maxRequestsPerHost);\n    }\n\n    public int getMaxRequestsPerHost() {\n        return maxRequestsPerHost;\n    }\n\n    @DataBoundSetter\n    public void setConnectTimeout(int connectTimeout) {\n        this.connectTimeout = Math.max(DEFAULT_CONNECT_TIMEOUT_SECONDS, connectTimeout);\n    }\n\n    /**\n     * Gets the global pod retention policy for the plugin.\n     */\n    public PodRetention getPodRetention() {\n        return this.podRetention;\n    }\n    \n    /**\n     * Set the global pod retention policy for the plugin.\n     * \n     * @param podRetention the pod retention policy for the plugin.\n     */\n    @DataBoundSetter\n    public void setPodRetention(PodRetention podRetention) {\n        if (podRetention == null || podRetention instanceof Default) {\n            podRetention = PodRetention.getKubernetesCloudDefault();\n        }\n        this.podRetention = podRetention;\n    }\n\n    /**\n     * Connects to Kubernetes.\n     *\n     * @return Kubernetes client.\n     */\n    @SuppressFBWarnings({ "IS2_INCONSISTENT_SYNC", "DC_DOUBLECHECK" })\n    public KubernetesClient connect() throws KubernetesAuthException, IOException {\n\n        LOGGER.log(Level.FINEST, "Building connection to Kubernetes {0} URL {1} namespace {2}",\n                new String[] { getDisplayName(), serverUrl, namespace });\n        KubernetesClient client = KubernetesClientProvider.createClient(this);\n\n        LOGGER.log(Level.FINE, "Connected to Kubernetes {0} URL {1} namespace {2}", new String[] { getDisplayName(), client.getMasterUrl().toString(), namespace });\n        return client;\n    }\n\n    @Override\n    public Collection<NodeProvisioner.PlannedNode> provision(@NonNull final Cloud.CloudState state, final int excessWorkload) {\n        try {\n            Metrics.metricRegistry().meter(metricNameForLabel(state.getLabel())).mark(excessWorkload);\n            Label label = state.getLabel();\n            int plannedCapacity = state.getAdditionalPlannedCapacity(); // Planned nodes, will be launched on the next round of NodeProvisioner\n            Set<String> allInProvisioning = InProvisioning.getAllInProvisioning(label); // Nodes being launched\n            LOGGER.log(Level.FINE, () -> "In provisioning : " + allInProvisioning);\n            int toBeProvisioned = Math.max(0, excessWorkload - allInProvisioning.size());\n            List<NodeProvisioner.PlannedNode> plannedNodes = new ArrayList<>();\n            LOGGER.log(Level.FINE, "Label \\"{0}\\" excess workload: {1}, executors: {2}",\n                    new Object[] {label, toBeProvisioned, plannedCapacity});\n\n            for (PodTemplate podTemplate : getTemplatesFor(label)) {\n                LOGGER.log(Level.FINE, "Template for label \\"{0}\\": {1}", new Object[]{label, podTemplate.getName()});\n                // check overall concurrency limit using the default label(s) on all templates\n                int numExecutors = 1;\n                PodTemplate unwrappedTemplate = getUnwrappedTemplate(podTemplate);\n                while (toBeProvisioned > 0 && KubernetesProvisioningLimits.get().register(this, podTemplate, numExecutors)) {\n                    plannedNodes.add(PlannedNodeBuilderFactory.createInstance().cloud(this).template(unwrappedTemplate).label(label).numExecutors(1).build());\n                    toBeProvisioned--;\n                }\n                if (!plannedNodes.isEmpty()) {\n                    // Return early when a matching template was found and nodes were planned\n                    LOGGER.log(Level.FINEST, "Planned {0} Kubernetes agents with template \\"{1}\\"", new Object[]{plannedNodes.size(), podTemplate.getName()});\n                    Metrics.metricRegistry().counter(MetricNames.PROVISION_NODES).inc(plannedNodes.size());\n                    return plannedNodes;\n                }\n            }\n            Metrics.metricRegistry().counter(MetricNames.PROVISION_NODES).inc(plannedNodes.size());\n            return plannedNodes;\n        } catch (KubernetesClientException e) {\n            Metrics.metricRegistry().counter(MetricNames.PROVISION_FAILED).inc();\n            Throwable cause = e.getCause();\n            if (cause instanceof SocketTimeoutException || cause instanceof ConnectException || cause instanceof UnknownHostException) {\n                LOGGER.log(Level.WARNING, "Failed to connect to Kubernetes at {0}: {1}",\n                        new String[] { serverUrl, cause.getMessage() });\n            } else {\n                LOGGER.log(Level.WARNING, "Failed to count the # of live instances on Kubernetes",\n                        cause != null ? cause : e);\n            }\n        } catch (Exception e) {\n            LOGGER.log(Level.WARNING, "Failed to count the # of live instances on Kubernetes", e);\n        }\n        return Collections.emptyList();\n    }\n\n    @Override\n    public boolean canProvision(@NonNull Cloud.CloudState state) {\n        return getTemplate(state.getLabel()) != null;\n    }\n\n    /**\n     * Gets {@link PodTemplate} that has the matching {@link Label}.\n     * @param label label to look for in templates\n     * @return the template\n     */\n    @CheckForNull\n    public PodTemplate getTemplate(@CheckForNull Label label) {\n        return PodTemplateUtils.getTemplateByLabel(label, getAllTemplates());\n    }\n\n    @CheckForNull\n    public PodTemplate getTemplateById(@NonNull String id) {\n        return getAllTemplates().stream().filter(t -> id.equals(t.getId())).findFirst().orElse(null);\n    }\n\n    /**\n     * Unwraps the given pod template.\n     * @param podTemplate the pod template to unwrap.\n     * @return the unwrapped pod template\n     */\n    public PodTemplate getUnwrappedTemplate(PodTemplate podTemplate) {\n        return PodTemplateUtils.unwrap(podTemplate, getDefaultsProviderTemplate(), getAllTemplates());\n    }\n\n    /**\n     * Gets all PodTemplates that have the matching {@link Label}.\n     * @param label label to look for in templates\n     * @return list of matching templates\n     * @deprecated Use {@link #getTemplatesFor(Label)} instead.\n     */\n    @Deprecated\n    public ArrayList<PodTemplate> getMatchingTemplates(@CheckForNull Label label) {\n        return new ArrayList<>(getTemplatesFor(label));\n    }\n\n    /**\n     * Gets all PodTemplates that have the matching {@link Label}.\n     * @param label label to look for in templates\n     * @return list of matching templates\n     */\n    public List<PodTemplate> getTemplatesFor(@CheckForNull Label label) {\n        return PodTemplateFilter.applyAll(this, getAllTemplates(), label);\n    }\n\n    /**\n     * Add a new template to the cloud\n     * @param t docker template\n     */\n    public void addTemplate(PodTemplate t) {\n        this.templates.add(t);\n        // t.parent = this;\n    }\n\n    /**\n     * Remove a\n     *\n     * @param t docker template\n     */\n    public void removeTemplate(PodTemplate t) {\n        this.templates.remove(t);\n    }\n\n    /**\n     * Add a dynamic pod template. Won\'t be displayed in UI, and persisted separately from the cloud instance.\n     * @param t the template to add\n     */\n    public void addDynamicTemplate(PodTemplate t) {\n        PodTemplateMap.get().addTemplate(this, t);\n    }\n\n    /**\n     * Remove a dynamic pod template.\n     * @param t the template to remove\n     */\n    public void removeDynamicTemplate(PodTemplate t) {\n        PodTemplateMap.get().removeTemplate(this, t);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        KubernetesCloud that = (KubernetesCloud) o;\n        return skipTlsVerify == that.skipTlsVerify &&\n                addMasterProxyEnvVars == that.addMasterProxyEnvVars &&\n                capOnlyOnAlivePods == that.capOnlyOnAlivePods &&\n                Objects.equals(containerCap, that.containerCap) &&\n                retentionTimeout == that.retentionTimeout &&\n                connectTimeout == that.connectTimeout &&\n                readTimeout == that.readTimeout &&\n                usageRestricted == that.usageRestricted &&\n                maxRequestsPerHost == that.maxRequestsPerHost &&\n                Objects.equals(defaultsProviderTemplate, that.defaultsProviderTemplate) &&\n                templates.equals(that.templates) &&\n                Objects.equals(serverUrl, that.serverUrl) &&\n                Objects.equals(serverCertificate, that.serverCertificate) &&\n                Objects.equals(namespace, that.namespace) &&\n                Objects.equals(jnlpregistry, that.jnlpregistry)&&\n                Objects.equals(jenkinsUrl, that.jenkinsUrl) &&\n                Objects.equals(jenkinsTunnel, that.jenkinsTunnel) &&\n                Objects.equals(credentialsId, that.credentialsId) &&\n                Objects.equals(podLabels, that.podLabels) &&\n                Objects.equals(podRetention, that.podRetention) &&\n                Objects.equals(waitForPodSec, that.waitForPodSec) &&\n                useJenkinsProxy==that.useJenkinsProxy;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(defaultsProviderTemplate, templates, serverUrl, serverCertificate, skipTlsVerify,\n                addMasterProxyEnvVars, capOnlyOnAlivePods, namespace, jnlpregistry, jenkinsUrl, jenkinsTunnel, credentialsId,\n                containerCap, retentionTimeout, connectTimeout, readTimeout, podLabels, usageRestricted,\n                maxRequestsPerHost, podRetention, useJenkinsProxy);\n    }\n\n    public Integer getWaitForPodSec() {\n        return waitForPodSec;\n    }\n\n    @DataBoundSetter\n    public void setWaitForPodSec(Integer waitForPodSec) {\n        this.waitForPodSec = waitForPodSec;\n    }\n\n    @Extension\n    public static class DescriptorImpl extends Descriptor<Cloud> {\n        @Override\n        public String getDisplayName() {\n            return "Kubernetes";\n        }\n\n        @Initializer(before = InitMilestone.PLUGINS_STARTED)\n        public static void addAliases() {\n            Jenkins.XSTREAM2.addCompatibilityAlias(\n                    "org.csanchez.jenkins.plugins.kubernetes.OpenShiftBearerTokenCredentialImpl",\n                    org.jenkinsci.plugins.kubernetes.credentials.OpenShiftBearerTokenCredentialImpl.class);\n            Jenkins.XSTREAM2.addCompatibilityAlias(\n                    "org.csanchez.jenkins.plugins.kubernetes.OpenShiftTokenCredentialImpl",\n                    StringCredentialsImpl.class);\n            Jenkins.XSTREAM2.addCompatibilityAlias("org.csanchez.jenkins.plugins.kubernetes.ServiceAccountCredential",\n                    org.jenkinsci.plugins.kubernetes.credentials.FileSystemServiceAccountCredential.class);\n        }\n\n        @RequirePOST\n        @SuppressWarnings("unused") // used by jelly\n        public FormValidation doTestConnection(@QueryParameter String name,\n                                               @QueryParameter String serverUrl,\n                                               @QueryParameter String credentialsId,\n                                               @QueryParameter String serverCertificate,\n                                               @QueryParameter boolean skipTlsVerify,\n                                               @QueryParameter String namespace,\n                                               @QueryParameter int connectionTimeout,\n                                               @QueryParameter int readTimeout,\n                                               @QueryParameter boolean useJenkinsProxy) throws Exception {\n            Jenkins.get().checkPermission(Jenkins.ADMINISTER);\n\n            if (StringUtils.isBlank(name))\n                return FormValidation.error("name is required");\n\n            try (KubernetesClient client = new KubernetesFactoryAdapter(serverUrl, namespace,\n                        Util.fixEmpty(serverCertificate), Util.fixEmpty(credentialsId), skipTlsVerify,\n                        connectionTimeout, readTimeout, DEFAULT_MAX_REQUESTS_PER_HOST, useJenkinsProxy).createClient()) {\n                    // test listing pods\n                    client.pods().list();\n                VersionInfo version = client.getVersion();\n                return FormValidation.ok("Connected to Kubernetes " + version.getGitVersion());\n            } catch (KubernetesClientException e) {\n                LOGGER.log(Level.FINE, String.format("Error testing connection %s", serverUrl), e);\n                return FormValidation.error("Error testing connection %s: %s", serverUrl, e.getCause() == null\n                        ? e.getMessage()\n                        : String.format("%s: %s", e.getCause().getClass().getName(), e.getCause().getMessage()));\n            } catch (Exception e) {\n                LOGGER.log(Level.FINE, String.format("Error testing connection %s", serverUrl), e);\n                return FormValidation.error("Error testing connection %s: %s", serverUrl, e.getMessage());\n            }\n        }\n\n        @RequirePOST\n        @SuppressWarnings("unused") // used by jelly\n        public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context, @QueryParameter String serverUrl) {\n            Jenkins.get().checkPermission(Jenkins.ADMINISTER);\n            StandardListBoxModel result = new StandardListBoxModel();\n            result.includeEmptyValue();\n            result.includeMatchingAs(\n                ACL.SYSTEM,\n                context,\n                StandardCredentials.class,\n                serverUrl != null ? URIRequirementBuilder.fromUri(serverUrl).build()\n                            : Collections.EMPTY_LIST,\n                CredentialsMatchers.anyOf(\n                    AuthenticationTokens.matcher(KubernetesAuth.class)\n                )\n            );\n            return result;\n        }\n\n        @RequirePOST\n        @SuppressWarnings("unused") // used by jelly\n        public FormValidation doCheckMaxRequestsPerHostStr(@QueryParameter String value) throws IOException, ServletException {\n            return FormValidation.validatePositiveInteger(value);\n        }\n\n        @RequirePOST\n        @SuppressWarnings("unused") // used by jelly\n        public FormValidation doCheckConnectTimeout(@QueryParameter String value) {\n            return FormValidation.validateIntegerInRange(value, DEFAULT_CONNECT_TIMEOUT_SECONDS, Integer.MAX_VALUE);\n        }\n\n        @RequirePOST\n        @SuppressWarnings("unused") // used by jelly\n        public FormValidation doCheckReadTimeout(@QueryParameter String value) {\n            return FormValidation.validateIntegerInRange(value, DEFAULT_READ_TIMEOUT_SECONDS, Integer.MAX_VALUE);\n        }\n\n        @RequirePOST\n        @SuppressWarnings("unused") // used by jelly\n        public FormValidation doCheckRetentionTimeout(@QueryParameter String value) {\n            return FormValidation.validateIntegerInRange(value, DEFAULT_RETENTION_TIMEOUT_MINUTES, Integer.MAX_VALUE);\n        }\n\n        @SuppressWarnings("unused") // used by jelly\n        public FormValidation doCheckDirectConnection(@QueryParameter boolean value, @QueryParameter String jenkinsUrl, @QueryParameter boolean webSocket) throws IOException, ServletException {\n            if (!webSocket) {\n                TcpSlaveAgentListener tcpSlaveAgentListener = Jenkins.get().getTcpSlaveAgentListener();\n                if (tcpSlaveAgentListener == null) {\n                    return FormValidation.warning("\'TCP port for inbound agents\' is disabled in Global Security settings. Connecting Kubernetes agents will not work without this or WebSocket mode!");\n                }\n                if (tcpSlaveAgentListener.getIdentityPublicKey() == null) {\n                    return FormValidation.error("You must install the instance-identity plugin to use inbound agents in TCP mode");\n                }\n            }\n\n            if(value) {\n                if (webSocket) {\n                    return FormValidation.error("Direct connection and WebSocket mode are mutually exclusive");\n                }\n                if(!isEmpty(jenkinsUrl)) return FormValidation.warning("No need to configure Jenkins URL when direct connection is enabled");\n\n                if(Jenkins.get().getSlaveAgentPort() == 0) return FormValidation.warning(\n                        "A random \'TCP port for inbound agents\' is configured in Global Security settings. In \'direct connection\' mode agents will not be able to reconnect to a restarted controller with random port!");\n            } else {\n                if (isEmpty(jenkinsUrl)) {\n                    String url = StringUtils.defaultIfBlank(System.getProperty("KUBERNETES_JENKINS_URL", System.getenv("KUBERNETES_JENKINS_URL")), JenkinsLocationConfiguration.get().getUrl());\n                    if (url != null) {\n                        return FormValidation.ok("Will connect using " + url);\n                    } else {\n                        return FormValidation.warning("Configure either Direct Connection or Jenkins URL");\n                    }\n                }\n            }\n            return FormValidation.ok();\n        }\n\n        @SuppressWarnings("unused") // used by jelly\n        public FormValidation doCheckJenkinsUrl(@QueryParameter String value, @QueryParameter boolean directConnection) throws IOException, ServletException {\n            try {\n                if(!isEmpty(value)) new URL(value);\n            } catch (MalformedURLException e) {\n                return FormValidation.error(e, "Invalid Jenkins URL");\n            }\n            return FormValidation.ok();\n        }\n\n        public FormValidation doCheckWebSocket(@QueryParameter boolean webSocket, @QueryParameter boolean directConnection, @QueryParameter String jenkinsTunnel) {\n            if (webSocket) {\n                if (!WebSockets.isSupported()) {\n                    return FormValidation.error("WebSocket support is not enabled in this Jenkins installation");\n                }\n                if (Util.fixEmpty(jenkinsTunnel) != null) {\n                    return FormValidation.error("Tunneling is not currently supported in WebSocket mode");\n                }\n            }\n            return FormValidation.ok();\n        }\n\n        @SuppressWarnings("unused") // used by jelly\n        public List<Descriptor<PodRetention>> getAllowedPodRetentions() {\n            Jenkins jenkins = Jenkins.getInstanceOrNull();\n            if (jenkins == null) {\n                return new ArrayList<>(0);\n            }\n            return DescriptorVisibilityFilter.apply(this, jenkins.getDescriptorList(PodRetention.class));\n        }\n\n        @SuppressWarnings({"rawtypes", "unused"}) // used by jelly\n        public Descriptor getDefaultPodRetention() {\n            Jenkins jenkins = Jenkins.getInstanceOrNull();\n            if (jenkins == null) {\n                return null;\n            }\n            return jenkins.getDescriptor(PodRetention.getKubernetesCloudDefault().getClass());\n        }\n\n        @SuppressWarnings("unused") // used by jelly\n        public int getDefaultReadTimeout() {\n            return DEFAULT_READ_TIMEOUT_SECONDS;\n        }\n\n        @SuppressWarnings("unused") // used by jelly\n        public int getDefaultConnectTimeout() {\n            return DEFAULT_CONNECT_TIMEOUT_SECONDS;\n        }\n\n        @SuppressWarnings("unused") // used by jelly\n        public int getDefaultRetentionTimeout() {\n            return DEFAULT_RETENTION_TIMEOUT_MINUTES;\n        }\n\n        public int getDefaultWaitForPod() {\n            return DEFAULT_WAIT_FOR_POD_SEC;\n        }\n\n    }\n\n    @Override\n    public String toString() {\n        return "KubernetesCloud{name=" + name +\n                ", defaultsProviderTemplate=\'" + defaultsProviderTemplate + \'\\\'\' +\n                ", serverUrl=\'" + serverUrl + \'\\\'\' +\n                ", serverCertificate=\'" + serverCertificate + \'\\\'\' +\n                ", skipTlsVerify=" + skipTlsVerify +\n                ", addMasterProxyEnvVars=" + addMasterProxyEnvVars +\n                ", capOnlyOnAlivePods=" + capOnlyOnAlivePods +\n                ", namespace=\'" + namespace + \'\\\'\' +\n                ", jnlpregistry=\'" + jnlpregistry + \'\\\'\' +\n                ", jenkinsUrl=\'" + jenkinsUrl + \'\\\'\' +\n                ", jenkinsTunnel=\'" + jenkinsTunnel + \'\\\'\' +\n                ", credentialsId=\'" + credentialsId + \'\\\'\' +\n                ", webSocket=" + webSocket +\n                ", containerCap=" + containerCap +\n                ", retentionTimeout=" + retentionTimeout +\n                ", connectTimeout=" + connectTimeout +\n                ", readTimeout=" + readTimeout +\n                ", labels=" + labels +\n                ", podLabels=" + podLabels +\n                ", usageRestricted=" + usageRestricted +\n                ", maxRequestsPerHost=" + maxRequestsPerHost +\n                ", waitForPodSec=" + waitForPodSec +\n                ", podRetention=" + podRetention +\n                ", useJenkinsProxy=" + useJenkinsProxy +\n                ", templates=" + templates +\n                \'}\';\n    }\n\n    private Object readResolve() {\n        if ((serverCertificate != null) && !serverCertificate.trim().startsWith("-----BEGIN CERTIFICATE-----")) {\n            serverCertificate = new String(Base64.getDecoder().decode(serverCertificate.getBytes(UTF_8)), UTF_8);\n            LOGGER.log(Level.INFO, "Upgraded Kubernetes server certificate key: {0}",\n                    serverCertificate.substring(0, 80));\n        }\n\n        if (maxRequestsPerHost == 0) {\n            maxRequestsPerHost = DEFAULT_MAX_REQUESTS_PER_HOST;\n        }\n        if (podRetention == null) {\n            podRetention = PodRetention.getKubernetesCloudDefault();\n        }\n        setConnectTimeout(connectTimeout);\n        setReadTimeout(readTimeout);\n        setRetentionTimeout(retentionTimeout);\n        if (waitForPodSec == null) {\n            waitForPodSec = DEFAULT_WAIT_FOR_POD_SEC;\n        }\n        if (podLabels == null && labels != null) {\n            setPodLabels(PodLabel.fromMap(labels));\n        }\n        if (containerCap != null && containerCap == 0) {\n            containerCap = null;\n        }\n        return this;\n    }\n\n    @Extension\n    public static class PodTemplateSourceImpl extends PodTemplateSource {\n        @NonNull\n        @Override\n        public List<PodTemplate> getList(@NonNull KubernetesCloud cloud) {\n            return cloud.getTemplates();\n        }\n    }\n\n    @Initializer(after = InitMilestone.SYSTEM_CONFIG_LOADED)\n    public static void hpiRunInit() {\n        if (Main.isDevelopmentMode) {\n            Jenkins jenkins = Jenkins.get();\n            String hostAddress = System.getProperty("jenkins.host.address");\n            if (hostAddress != null && jenkins.clouds.getAll(KubernetesCloud.class).isEmpty()) {\n                KubernetesCloud cloud = new KubernetesCloud("kubernetes");\n                cloud.setJenkinsUrl("http://" + hostAddress + ":8080/jenkins/");\n                jenkins.clouds.add(cloud);\n            }\n        }\n    }\n}\n', 'message': 'tabs replaced with blanks', 'author_name': 'sixtyeight', 'lang': 'java'}