Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autoconfigure cloud if kubernetes url is not set #208

Merged
merged 13 commits into from
Sep 5, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ CHANGELOG
* Change containerCap and instanceCap 0 to mean do not use [JENKINS-45845](https://issues.jenkins-ci.org/browse/JENKINS-45845) [#199](https://github.com/jenkinsci/kubernetes-plugin/pull/199)
* Add environment variables to container from a secret [JENKINS-39867](https://issues.jenkins-ci.org/browse/JENKINS-39867) [#162](https://github.com/jenkinsci/kubernetes-plugin/pull/162)
* Deprecate `containerEnvVar` for `envVar` and added `secretEnvVar`
* Enable setting slaveConnectTimeout in podTemplate defined in pipeline [#213](https://github.com/jenkinsci/kubernetes-plugin/pull/213)
* Make `withEnv` work inside a container [JENKINS-46278](https://issues.jenkins-ci.org/browse/JENKINS-46278) [#204](https://github.com/jenkinsci/kubernetes-plugin/pull/204)
* Close resource leak, fix broken pipe error. Make number of concurrent requests to Kubernetes configurable [JENKINS-40825](https://issues.jenkins-ci.org/browse/JENKINS-40825) [#182](https://github.com/jenkinsci/kubernetes-plugin/pull/182)
* Delete pods in the cloud namespace when pod namespace is not defined [JENKINS-45910](https://issues.jenkins-ci.org/browse/JENKINS-45910) [#192](https://github.com/jenkinsci/kubernetes-plugin/pull/192)
* Use `Util.replaceMacro` instead of our custom replacement logic. Behavior change: when a var is not defined it is not replaced, ie. `${key1} or ${key2} or ${key3}` -> `value1 or value2 or ${key3}` [#198](https://github.com/jenkinsci/kubernetes-plugin/pull/198)
* Allow to create non-configurable instances programmatically [#191](https://github.com/jenkinsci/kubernetes-plugin/pull/191)
* Do not cache kubernetes connection to reflect config changes and credential expiration [JENKINS-39867](https://issues.jenkins-ci.org/browse/JENKINS-39867) [#189](https://github.com/jenkinsci/kubernetes-plugin/pull/189)
* Inherit podAnnotations when inheriting pod templates [#209](https://github.com/jenkinsci/kubernetes-plugin/pull/209)


0.12
Expand Down
2 changes: 1 addition & 1 deletion examples/declarative.groovy
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pipeline {
agent {
kubernetes {
//cloud 'kubernetes-plugin-test'
//cloud 'kubernetes'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that comment was changed by an automated IDE refactoring.
What was that comment for in the first place? Can we remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, it's just that now the cloud is the default kubernetes one

label 'mypod'
containerTemplate {
name 'maven'
Expand Down
3 changes: 0 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
</developers>

<properties>
<kubernetes.context>minikube</kubernetes.context>

<java.level>8</java.level>

<!-- dependency versions -->
Expand Down Expand Up @@ -202,7 +200,6 @@
<hudson.slaves.NodeProvisioner.initialDelay>0</hudson.slaves.NodeProvisioner.initialDelay>
<!-- listen in all interfaces from connections from kubernetes pods -->
<connectorHost>0.0.0.0</connectorHost>
<kubernetes.context>${kubernetes.context}</kubernetes.context>
</systemPropertyVariables>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,12 +472,8 @@ public FormValidation doTestConnection(@QueryParameter String name, @QueryParame
@QueryParameter int connectionTimeout,
@QueryParameter int readTimeout) throws Exception {

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

try {
KubernetesClient client = new KubernetesFactoryAdapter(serverUrl, namespace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.logging.Level;
import static java.util.logging.Level.*;
import java.util.logging.Logger;

import javax.annotation.CheckForNull;
Expand All @@ -29,6 +29,7 @@

import hudson.security.ACL;
import hudson.util.Secret;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
Expand Down Expand Up @@ -93,9 +94,21 @@ private StandardCredentials getCredentials(String credentials) {

public KubernetesClient createClient() throws NoSuchAlgorithmException, UnrecoverableKeyException,
KeyStoreException, IOException, CertificateEncodingException {
ConfigBuilder builder = new ConfigBuilder().withMasterUrl(serviceAddress)
.withRequestTimeout(readTimeout * 1000)
.withConnectionTimeout(connectTimeout * 1000);

ConfigBuilder builder;
// autoconfigure if url is not set
if (StringUtils.isBlank(serviceAddress)) {
LOGGER.log(FINE, "Autoconfiguring Kubernetes client");
builder = new ConfigBuilder(Config.autoConfigure());
} else {
// although this will still autoconfigure based on Config constructor notes
// In future releases (2.4.x) the public constructor will be empty.
// The current functionality will be provided by autoConfigure().
// This is a necessary change to allow us distinguish between auto configured values and builder values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit that I don't fully understand this comment.

I guess it means that by 2.4.x, this

new ConfigBuilder().withMasterUrl(serviceAddress)

Will behave differently, i.e. if we move the dependency to 2.4.x we will either introduce a breaking change, or we will have to change that line again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it comes from https://github.com/fabric8io/kubernetes-client/blob/master/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/Config.java#L161

  //In future releases (2.4.x) the public constructor will be empty.
  //The current functionality will be provided by autoConfigure().
  //This is a necessary change to allow us distinguish between auto configured values and builder values.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this will still cause a breaking change if somebody changes the client library to 2.4 and doesn't update this code here, right?

  • before 2.4.x, providing a master url means: do autoconfigure for everything but the master url
  • after 2.4.x, providing a master url will mean "do not autoconfigure at all".

Is that intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it may be a breaking change to document. Better than the current behavior because your jenkins settings may be ignored
btw client is in 2.6.x and code still there

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better than the current behavior because your jenkins settings may be ignored

Right, I remember we had that problem as well.

The only remaining downside that remains is that as of now, you can't have autoconfigure AND overwrite the kubernetes URL, like it used to work.

One solution for this would be to add a checkbox for autoconfigure to make it explicit...

builder = new ConfigBuilder().withMasterUrl(serviceAddress);
}

builder = builder.withRequestTimeout(readTimeout * 1000).withConnectionTimeout(connectTimeout * 1000);

if (!StringUtils.isBlank(namespace)) {
builder.withNamespace(namespace);
Expand Down Expand Up @@ -128,7 +141,7 @@ public KubernetesClient createClient() throws NoSuchAlgorithmException, Unrecove
}
builder.withMaxConcurrentRequestsPerHost(maxRequestsPerHost);

LOGGER.log(Level.FINE, "Creating Kubernetes client: {0}", this.toString());
LOGGER.log(FINE, "Creating Kubernetes client: {0}", this.toString());
return new DefaultKubernetesClient(builder.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</f:entry>

<f:entry title="${%Kubernetes URL}" field="serverUrl">
<f:textbox default="https://kubernetes.default.svc.cluster.local" clazz="required"/>
<f:textbox/>
</f:entry>

<f:entry title="${%Kubernetes server certificate key}" field="serverCertificate">
Expand All @@ -18,7 +18,7 @@
</f:entry>

<f:entry title="${%Kubernetes Namespace}" field="namespace">
<f:textbox default="default" clazz="required"/>
<f:textbox/>
</f:entry>

<f:entry title="${%Credentials}" field="credentialsId">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,84 +23,68 @@
*/
package org.csanchez.jenkins.plugins.kubernetes;

import static io.fabric8.kubernetes.client.Config.*;
import static java.util.logging.Level.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assume.*;

import java.io.File;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateEncodingException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import io.fabric8.kubernetes.api.model.Cluster;
import io.fabric8.kubernetes.api.model.Config;
import io.fabric8.kubernetes.api.model.NamedCluster;
import io.fabric8.kubernetes.api.model.NamedContext;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
import io.fabric8.kubernetes.client.internal.KubeConfigUtils;
import io.fabric8.kubernetes.client.utils.Utils;

public class KubernetesTestUtil {

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

public static final String TESTING_NAMESPACE = "kubernetes-plugin-test";
public static final String KUBERNETES_CONTEXT = System.getProperty("kubernetes.context", "minikube");

public static KubernetesCloud setupCloud() throws UnrecoverableKeyException, CertificateEncodingException,
NoSuchAlgorithmException, KeyStoreException, IOException {

KubernetesCloud cloud = new KubernetesCloud("kubernetes-plugin-test");

File kubeConfigFile = new File(Utils.getSystemPropertyOrEnvVar(KUBERNETES_KUBECONFIG_FILE,
new File(System.getProperty("user.home"), ".kube" + File.separator + "config").toString()));
assumeThat("Kubernetes config file exists: " + kubeConfigFile.getAbsolutePath(), kubeConfigFile.exists(),
is(true));

Config config = KubeConfigUtils.parseConfig(kubeConfigFile);
Optional<NamedContext> context = config.getContexts().stream()
.filter((c) -> c.getName().equals(KUBERNETES_CONTEXT)).findFirst();
assumeThat("Kubernetes context is configured: " + KUBERNETES_CONTEXT, context.isPresent(), is(true));

String clusterName = context.get().getContext().getCluster();
Optional<NamedCluster> clusterOptional = config.getClusters().stream()
.filter((c) -> c.getName().equals(clusterName)).findFirst();
assumeThat("Kubernetes cluster is configured: " + clusterName, clusterOptional.isPresent(), is(true));

Cluster cluster = clusterOptional.get().getCluster();
cloud.setServerUrl(cluster.getServer());

cloud.setNamespace(TESTING_NAMESPACE);
KubernetesCloud cloud = new KubernetesCloud("kubernetes");
KubernetesClient client = cloud.connect();

// Run in our own testing namespace
try {
// Run in our own testing namespace
client.namespaces().createOrReplace(
new NamespaceBuilder().withNewMetadata().withName(TESTING_NAMESPACE).endMetadata().build());
client.namespaces()
.create(new NamespaceBuilder().withNewMetadata().withName(TESTING_NAMESPACE).endMetadata().build());
} catch (KubernetesClientException e) {
assumeNoException("Kubernetes cluster is not accessible", e);
LOGGER.warning(e.getMessage());
}
cloud.setNamespace(TESTING_NAMESPACE);
client = cloud.connect();

return cloud;
}

public static void assumeKubernetes() throws Exception {
try (DefaultKubernetesClient client = new DefaultKubernetesClient(
new ConfigBuilder(Config.autoConfigure()).build())) {
client.pods().list();
} catch (Exception e) {
assumeNoException(e);
}
}

/**
* Delete pods with matching labels
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.net.InetAddress;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.compress.utils.IOUtils;
import org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar;
Expand Down Expand Up @@ -63,27 +64,26 @@ public class AbstractKubernetesPipelineTest {

@ClassRule
public static BuildWatcher buildWatcher = new BuildWatcher();
protected static KubernetesCloud cloud;
protected KubernetesCloud cloud;

@Rule
public JenkinsRuleNonLocalhost r = new JenkinsRuleNonLocalhost();
@Rule
public LoggerRule logs = new LoggerRule().record(KubernetesCloud.class, Level.ALL);
public LoggerRule logs = new LoggerRule().record(Logger.getLogger(KubernetesCloud.class.getPackage().getName()),
Level.ALL);

@BeforeClass
public static void configureCloud() throws Exception {
cloud = setupCloud();
createSecret(cloud.connect());
public static void isKubernetesConfigured() throws Exception {
assumeKubernetes();
}

@Before
public void configureTemplates() throws Exception {
public void configureCloud() throws Exception {
cloud = setupCloud();
createSecret(cloud.connect());
cloud.getTemplates().clear();
cloud.addTemplate(buildBusyboxTemplate("busybox"));
}

@Before
public void addCloudToJenkins() throws Exception {
// Slaves running in Kubernetes (minikube) need to connect to this server, so localhost does not work
URL url = r.getURL();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

import org.apache.commons.io.output.TeeOutputStream;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.AfterClass;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
Expand Down Expand Up @@ -65,18 +65,15 @@ public class ContainerExecDecoratorTest {
private ContainerExecDecorator decorator;

@BeforeClass
public static void configureCloud() throws Exception {
client = setupCloud().connect();
deletePods(client, labels, false);
public static void isKubernetesConfigured() throws Exception {
assumeKubernetes();
}

@AfterClass
public static void after() throws Exception {
@Before
public void configureCloud() throws Exception {
client = setupCloud().connect();
deletePods(client, labels, false);
}

@Before
public void containerExecDecorator() throws Exception {
String image = "busybox";
Container c = new ContainerBuilder().withName(image).withImagePullPolicy("IfNotPresent").withImage(image)
.withCommand("cat").withTty(true).build();
Expand All @@ -89,6 +86,11 @@ public void containerExecDecorator() throws Exception {
decorator = new ContainerExecDecorator(client, pod.getMetadata().getName(), image, client.getNamespace());
}

@After
public void after() throws Exception {
deletePods(client, labels, false);
}

@Test(timeout = 10000)
public void testCommandExecution() throws Exception {
Thread[] t = new Thread[10];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
pipeline {
agent {
kubernetes {
cloud 'kubernetes-plugin-test'
label 'mypod'
containerTemplate {
name 'maven'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//noinspection GrPackage
podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod') {
podTemplate(label: 'mypod') {
node ('mypod') {
stage('container log') {
containerLog 'jnlp'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod', containers: [
podTemplate(label: 'mypod', containers: [
containerTemplate(name: 'busybox', image: 'busybox', ttyEnabled: true, command: '/bin/cat'),
]) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod', containers: [
podTemplate(label: 'mypod', containers: [
containerTemplate(name: 'busybox', image: 'busybox', ttyEnabled: true, command: '/bin/cat'),
]) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod', volumes: [emptyDirVolume(mountPath: '/my-mount')], containers: [
podTemplate(label: 'mypod', volumes: [emptyDirVolume(mountPath: '/my-mount')], containers: [
containerTemplate(name: 'jnlp', image: 'jenkinsci/jnlp-slave:2.62-alpine', args: '${computer.jnlpmac} ${computer.name}'),
containerTemplate(name: 'busybox', image: 'busybox', ttyEnabled: true, command: 'cat', livenessProbe: containerLivenessProbe( execArgs: 'uname -a', initialDelaySeconds: 5, timeoutSeconds: 1, failureThreshold: 3, periodSeconds: 10, successThreshold: 1))
]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod', volumes: [emptyDirVolume(mountPath: '/my-mount')], containers: [
podTemplate(label: 'mypod', volumes: [emptyDirVolume(mountPath: '/my-mount')], containers: [
containerTemplate(name: 'jnlp', image: 'jenkinsci/jnlp-slave:2.62-alpine', args: '${computer.jnlpmac} ${computer.name}'),
containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'golang', image: 'golang:1.6.3-alpine', ttyEnabled: true, command: 'cat')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.csanchez.jenkins.plugins.kubernetes.pipeline

podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod', containers: [
podTemplate(label: 'mypod', containers: [
containerTemplate(name: 'busybox', image: 'busybox', ttyEnabled: true, command: '/bin/cat'),
]) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod', containers: [
podTemplate(label: 'mypod', containers: [
containerTemplate(name: 'busybox', image: 'busybox', ttyEnabled: true, command: '/bin/cat'),
]) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod',
podTemplate(label: 'mypod',
envVars: [
envVar(key: 'POD_ENV_VAR', value: 'pod-env-var-value'),
secretEnvVar(key: 'POD_ENV_VAR_FROM_SECRET', secretName: 'pod-secret', secretKey: 'password')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod',
podTemplate(label: 'mypod',
containers: [
containerTemplate(name: 'busybox', image: 'busybox', ttyEnabled: true, command: '/bin/cat'),
]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod', volumes: [emptyDirVolume(mountPath: '/my-mount')], containers: [
podTemplate(label: 'mypod', volumes: [emptyDirVolume(mountPath: '/my-mount')], containers: [
containerTemplate(name: 'jnlp', image: 'jenkinsci/jnlp-slave:2.62-alpine', args: '${computer.jnlpmac} ${computer.name}')
]) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Step namespace should have priority over anything else.
podTemplate(cloud: 'kubernetes-plugin-test',
podTemplate(
namespace: 'kubernetes-plugin-test-overridden-namespace2', label: 'mypod',
volumes: [emptyDirVolume(mountPath: '/my-mount')],
containers: [
Expand Down