diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep.java new file mode 100755 index 0000000000..e9da1afad4 --- /dev/null +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 Original Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.csanchez.jenkins.plugins.kubernetes.pipeline; + +import com.google.common.collect.ImmutableSet; +import hudson.Extension; +import hudson.FilePath; +import hudson.model.Node; +import hudson.model.TaskListener; +import org.jenkinsci.plugins.workflow.steps.Step; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepDescriptor; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +import java.io.Serializable; +import java.util.Set; + +public class ContainerLogStep extends Step implements Serializable { + private static final long serialVersionUID = 5588861066775717487L; + + private final String name; + private boolean returnLog = false; + private int tailingLines = 0; + private int sinceSeconds = 0; + private int limitBytes = 0; + + @DataBoundConstructor + public ContainerLogStep(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @DataBoundSetter + public void setReturnLog(boolean returnLog) { + this.returnLog = returnLog; + } + + public boolean isReturnLog() { + return returnLog; + } + + @Override + public StepExecution start(StepContext context) throws Exception { + return new ContainerLogStepExecution(this, context); + } + + public int getTailingLines() { + return tailingLines; + } + + @DataBoundSetter + public void setTailingLines(int tailingLines) { + this.tailingLines = tailingLines; + } + + public int getSinceSeconds() { + return sinceSeconds; + } + + @DataBoundSetter + public void setSinceSeconds(int sinceSeconds) { + this.sinceSeconds = sinceSeconds; + } + + public int getLimitBytes() { + return limitBytes; + } + + @DataBoundSetter + public void setLimitBytes(int limitBytes) { + this.limitBytes = limitBytes; + } + + @Extension + public static class DescriptorImpl extends StepDescriptor { + + @Override + public String getFunctionName() { + return "containerLog"; + } + + @Override + public String getDisplayName() { + return "Get container log from Kubernetes"; + } + + @Override + public boolean takesImplicitBlockArgument() { + return false; + } + + @Override + public boolean isAdvanced() { + return false; + } + + @Override + public Set> getRequiredContext() { + return ImmutableSet.of(Node.class, FilePath.class, TaskListener.class); + } + } +} diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStepExecution.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStepExecution.java new file mode 100755 index 0000000000..96a1da8584 --- /dev/null +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStepExecution.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2017 Original Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.csanchez.jenkins.plugins.kubernetes.pipeline; + +import hudson.model.TaskListener; +import hudson.util.LogTaskListener; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.*; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; + +import java.io.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.csanchez.jenkins.plugins.kubernetes.pipeline.Resources.closeQuietly; + +public class ContainerLogStepExecution extends SynchronousNonBlockingStepExecution { + private static final long serialVersionUID = 5588861066775717487L; + private static final transient Logger LOGGER = Logger.getLogger(ContainerLogStepExecution.class.getName()); + + private final ContainerLogStep step; + private transient KubernetesClient client; + + ContainerLogStepExecution(ContainerLogStep step, StepContext context) { + super(context); + this.step = step; + } + + private PrintStream logger() { + TaskListener l = null; + StepContext context = getContext(); + try { + l = context.get(TaskListener.class); + } catch (Exception x) { + LOGGER.log(Level.WARNING, "Failed to find TaskListener in context"); + } finally { + if (l == null) { + l = new LogTaskListener(LOGGER, Level.FINE); + } + } + return l.getLogger(); + } + + @Override + protected String run() throws Exception { + boolean returnLog = step.isReturnLog(); + String containerName = step.getName(); + int tailingLines = step.getTailingLines(); + int sinceSeconds = step.getSinceSeconds(); + int limitBytes = step.getLimitBytes(); + + try { + LOGGER.log(Level.FINE, "Starting containerLog step."); + + KubernetesNodeContext nodeContext = new KubernetesNodeContext(getContext()); + client = nodeContext.connectToCloud(); + + String podName = nodeContext.getPodName(); + ContainerResource container = client.pods() + .inNamespace(nodeContext.getNamespace()) + .withName(podName) + .inContainer(containerName); + + TimeTailPrettyLoggable limited = limitBytes > 0 ? container.limitBytes(limitBytes) : container; + + TailPrettyLoggable since = sinceSeconds > 0 ? limited.sinceSeconds(sinceSeconds) : limited; + + PrettyLoggable tailed = tailingLines > 0 ? since.tailingLines(tailingLines) : since; + String log = tailed.getLog(); + + if (returnLog) { + return log; + } else { + logger().println("> start log of container '" + containerName + "' in pod '" + podName + "'"); + logger().print(log); + if (log.length() > 0 && log.charAt(log.length() - 1) != '\n') { + logger().println(); + } + logger().println("> end log of container '" + containerName + "' in pod '" + podName + "'"); + } + + return ""; + } catch (InterruptedException e) { + logger().println("Interrupted while getting logs of container"); + LOGGER.log(Level.FINE, "interrupted while getting logs of container {1}", containerName); + return ""; + } catch (Exception e) { + String message = "Failed to get logs for container"; + logger().println(message); + LOGGER.log(Level.WARNING, message, e); + return ""; + } finally { + closeQuietly(getContext(), client); + } + } + + @Override + public void stop(Throwable cause) throws Exception { + LOGGER.log(Level.FINE, "Stopping container log step."); + try { + super.stop(cause); + } finally { + closeQuietly(getContext(), client); + } + } +} diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStepExecution.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStepExecution.java index c661e938e0..eef55bba05 100755 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStepExecution.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStepExecution.java @@ -1,31 +1,26 @@ package org.csanchez.jenkins.plugins.kubernetes.pipeline; import java.io.Closeable; -import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; -import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; -import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave; import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl; import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; import org.jenkinsci.plugins.workflow.steps.BodyInvoker; import org.jenkinsci.plugins.workflow.steps.StepContext; -import hudson.AbortException; -import hudson.FilePath; import hudson.LauncherDecorator; -import hudson.model.Node; -import hudson.model.TaskListener; -import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; +import javax.annotation.Nonnull; + +import static org.csanchez.jenkins.plugins.kubernetes.pipeline.Resources.closeQuietly; + public class ContainerStepExecution extends AbstractStepExecutionImpl { private static final long serialVersionUID = 7634132798345235774L; private static final transient Logger LOGGER = Logger.getLogger(ContainerStepExecution.class.getName()); - private static final transient String HOSTNAME_FILE = "/etc/hostname"; private final ContainerStep step; @@ -40,24 +35,12 @@ public class ContainerStepExecution extends AbstractStepExecutionImpl { @Override public boolean start() throws Exception { LOGGER.log(Level.FINE, "Starting container step."); - FilePath workspace = getContext().get(FilePath.class); - String podName = workspace.child(HOSTNAME_FILE).readToString().trim(); - String namespace = workspace.child(Config.KUBERNETES_NAMESPACE_PATH).readToString().trim(); - String containerName = step.getName(); - Node node = getContext().get(Node.class); - if (! (node instanceof KubernetesSlave)) { - throw new AbortException(String.format("Node is not a Kubernetes node: %s", node.getNodeName())); - } - KubernetesSlave slave = (KubernetesSlave) node; - KubernetesCloud cloud = (KubernetesCloud) slave.getCloud(); - if (cloud == null) { - throw new AbortException(String.format("Cloud does not exist: %s", slave.getCloudName())); - } - client = cloud.connect(); + KubernetesNodeContext nodeContext = new KubernetesNodeContext(getContext()); + client = nodeContext.connectToCloud(); - decorator = new ContainerExecDecorator(client, podName, containerName, namespace); + decorator = new ContainerExecDecorator(client, nodeContext.getPodName(), containerName, nodeContext.getNamespace()); getContext().newBodyInvoker() .withContext(BodyInvoker .mergeLauncherDecorators(getContext().get(LauncherDecorator.class), decorator)) @@ -67,13 +50,9 @@ public boolean start() throws Exception { } @Override - public void stop(Throwable cause) throws Exception { + public void stop(@Nonnull Throwable cause) throws Exception { LOGGER.log(Level.FINE, "Stopping container step."); - closeQuietly(client, decorator); - } - - private void closeQuietly(Closeable... closeables) { - closeQuietly(getContext(), closeables); + closeQuietly(getContext(), client, decorator); } private static class ContainerExecCallback extends BodyExecutionCallback.TailCall { @@ -91,19 +70,4 @@ public void finished(StepContext context) { } } - private static void closeQuietly(StepContext context, Closeable... closeables) { - for (Closeable c : closeables) { - if (c != null) { - try { - c.close(); - } catch (IOException e) { - try { - context.get(TaskListener.class).error("Error while closing: [" + c + "]"); - } catch (IOException | InterruptedException e1) { - LOGGER.log(Level.WARNING, "Error writing to task listener", e); - } - } - } - } - } } diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesNodeContext.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesNodeContext.java new file mode 100644 index 0000000000..b2a8fc6347 --- /dev/null +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesNodeContext.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 Original Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.csanchez.jenkins.plugins.kubernetes.pipeline; + +import hudson.AbortException; +import hudson.FilePath; +import hudson.model.Node; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; +import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave; +import org.jenkinsci.plugins.workflow.steps.StepContext; + +/** + * helper class for steps running in a kubernetes `node` context + */ +class KubernetesNodeContext { + private static final transient String HOSTNAME_FILE = "/etc/hostname"; + private StepContext context; + private FilePath workspace; + + KubernetesNodeContext(StepContext context) throws Exception { + this.context = context; + workspace = context.get(FilePath.class); + } + + String getPodName() throws Exception { + return workspace.child(HOSTNAME_FILE).readToString().trim(); + } + + public String getNamespace() throws Exception { + return workspace.child(Config.KUBERNETES_NAMESPACE_PATH).readToString().trim(); + } + + KubernetesClient connectToCloud() throws Exception { + Node node = context.get(Node.class); + if (! (node instanceof KubernetesSlave)) { + throw new AbortException(String.format("Node is not a Kubernetes node: %s", node != null ? node.getNodeName() : null)); + } + KubernetesSlave slave = (KubernetesSlave) node; + KubernetesCloud cloud = (KubernetesCloud) slave.getCloud(); + if (cloud == null) { + throw new AbortException(String.format("Cloud does not exist: %s", slave.getCloudName())); + } + return cloud.connect(); + } +} diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/Resources.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/Resources.java new file mode 100644 index 0000000000..4ae7b3a0f2 --- /dev/null +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/Resources.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 Original Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.csanchez.jenkins.plugins.kubernetes.pipeline; + +import hudson.model.TaskListener; +import org.jenkinsci.plugins.workflow.steps.StepContext; + +import java.io.Closeable; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Resources { + private static final transient Logger LOGGER = Logger.getLogger(ContainerStepExecution.class.getName()); + static void closeQuietly(StepContext context, Closeable... closeables) { + for (Closeable c : closeables) { + if (c != null) { + try { + c.close(); + } catch (IOException e) { + try { + context.get(TaskListener.class).error("Error while closing: [" + c + "]"); + } catch (IOException | InterruptedException e1) { + LOGGER.log(Level.WARNING, "Error writing to task listener", e); + } + } + } + } + } +} diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/config.jelly b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/config.jelly new file mode 100755 index 0000000000..c277a6dd8e --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/config.jelly @@ -0,0 +1,26 @@ + + + + + +

+ Gets the log of a container running in the current pod, and prints it to the build log or returns it. + Only works inside a node that requests a Kubernetes slave. +

+
+ + + + + + + + + + + + + + + +
diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-limitBytes.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-limitBytes.html new file mode 100644 index 0000000000..0752507bc0 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-limitBytes.html @@ -0,0 +1,3 @@ +
+ If set, the number of bytes to read from the server before terminating the log output. This may not display a complete final line of logging, and may return slightly more or slightly less than the specified limit. +
\ No newline at end of file diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-name.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-name.html new file mode 100644 index 0000000000..082ef7ef8c --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-name.html @@ -0,0 +1,3 @@ +
+ Name of the container, as specified in containerTemplate. +
\ No newline at end of file diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-returnLog.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-returnLog.html new file mode 100644 index 0000000000..86ecd1d677 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-returnLog.html @@ -0,0 +1,3 @@ +
+ Return the container log. If not checked, the log will be printed to the build log. +
\ No newline at end of file diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-sinceSeconds.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-sinceSeconds.html new file mode 100644 index 0000000000..7c27805fa0 --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-sinceSeconds.html @@ -0,0 +1,3 @@ +
+ A relative time in seconds before the current time from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. +
\ No newline at end of file diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-tailingLines.html b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-tailingLines.html new file mode 100644 index 0000000000..e120ea7b8c --- /dev/null +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStep/help-tailingLines.html @@ -0,0 +1,3 @@ +
+ If set, the number of lines from the end of the log to show. If not specified, log is shown from the creation of the container or limited by "sinceSeconds". +
\ No newline at end of file diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/AbstractKubernetesPipelineTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/AbstractKubernetesPipelineTest.java new file mode 100644 index 0000000000..a7900d24e4 --- /dev/null +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/AbstractKubernetesPipelineTest.java @@ -0,0 +1,146 @@ +/* + * The MIT License + * + * Copyright (c) 2016, Carlos Sanchez + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.csanchez.jenkins.plugins.kubernetes.pipeline; + +import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.*; +import static org.junit.Assert.*; + +import com.google.common.collect.ImmutableMap; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import jenkins.model.JenkinsLocationConfiguration; +import org.apache.commons.compress.utils.IOUtils; +import org.csanchez.jenkins.plugins.kubernetes.*; +import org.csanchez.jenkins.plugins.kubernetes.model.KeyValueEnvVar; +import org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar; +import org.csanchez.jenkins.plugins.kubernetes.model.TemplateEnvVar; +import org.junit.*; +import org.jvnet.hudson.test.BuildWatcher; +import org.jvnet.hudson.test.JenkinsRuleNonLocalhost; +import org.jvnet.hudson.test.LoggerRule; + +import java.net.InetAddress; +import java.net.URL; +import java.util.Collections; +import java.util.logging.Level; + +import static java.util.Arrays.asList; +import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.setupCloud; + +public class AbstractKubernetesPipelineTest { + protected static final String CONTAINER_ENV_VAR_VALUE = "container-env-var-value"; + protected static final String POD_ENV_VAR_VALUE = "pod-env-var-value"; + protected static final String SECRET_KEY = "password"; + protected static final String CONTAINER_ENV_VAR_FROM_SECRET_VALUE = "container-pa55w0rd"; + protected static final String POD_ENV_VAR_FROM_SECRET_VALUE = "pod-pa55w0rd"; + + @ClassRule + public static BuildWatcher buildWatcher = new BuildWatcher(); + protected static KubernetesCloud cloud; + + @Rule + public JenkinsRuleNonLocalhost r = new JenkinsRuleNonLocalhost(); + @Rule + public LoggerRule logs = new LoggerRule().record(KubernetesCloud.class, Level.ALL); + + @BeforeClass + public static void configureCloud() throws Exception { + cloud = setupCloud(); + createSecret(cloud.connect()); + } + + @Before + public void configureTemplates() throws Exception { + cloud.getTemplates().clear(); + cloud.addTemplate(buildBusyboxTemplate("busybox")); + deletePods(cloud.connect(), Collections.emptyMap(), false); + } + + @After + public void cleanup() throws Exception { + assertFalse("There are pods leftover after test execution, see previous logs", + deletePods(cloud.connect(), KubernetesCloud.DEFAULT_POD_LABELS, true)); + } + + @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(); + + String hostAddress = System.getProperty("jenkins.host.address"); + if (hostAddress == null) { + hostAddress = InetAddress.getLocalHost().getHostAddress(); + } + URL nonLocalhostUrl = new URL(url.getProtocol(), hostAddress, url.getPort(), + url.getFile()); + JenkinsLocationConfiguration.get().setUrl(nonLocalhostUrl.toString()); + + r.jenkins.clouds.add(cloud); + } + + private PodTemplate buildBusyboxTemplate(String label) { + // Create a busybox template + PodTemplate podTemplate = new PodTemplate(); + podTemplate.setLabel(label); + + ContainerTemplate containerTemplate = new ContainerTemplate("busybox", "busybox", "cat", ""); + containerTemplate.setTtyEnabled(true); + podTemplate.getContainers().add(containerTemplate); + setEnvVariables(podTemplate); + return podTemplate; + } + + protected String loadPipelineScript(String name) { + try { + return new String(IOUtils.toByteArray(getClass().getResourceAsStream(name))); + } catch (Throwable t) { + throw new RuntimeException("Could not read resource:[" + name + "]."); + } + } + + private static void createSecret(KubernetesClient client) { + Secret secret = new SecretBuilder() + .withStringData(ImmutableMap.of(SECRET_KEY, CONTAINER_ENV_VAR_FROM_SECRET_VALUE)).withNewMetadata() + .withName("container-secret").endMetadata().build(); + client.secrets().createOrReplace(secret); + secret = new SecretBuilder().withStringData(ImmutableMap.of(SECRET_KEY, POD_ENV_VAR_FROM_SECRET_VALUE)) + .withNewMetadata().withName("pod-secret").endMetadata().build(); + client.secrets().createOrReplace(secret); + } + + private static void setEnvVariables(PodTemplate podTemplate) { + TemplateEnvVar podSecretEnvVar = new SecretEnvVar("POD_ENV_VAR_FROM_SECRET", "pod-secret", SECRET_KEY); + TemplateEnvVar podSimpleEnvVar = new KeyValueEnvVar("POD_ENV_VAR", POD_ENV_VAR_VALUE); + podTemplate.setEnvVars(asList(podSecretEnvVar, podSimpleEnvVar)); + TemplateEnvVar containerEnvVariable = new KeyValueEnvVar("CONTAINER_ENV_VAR", CONTAINER_ENV_VAR_VALUE); + TemplateEnvVar containerEnvVariableLegacy = new ContainerEnvVar("CONTAINER_ENV_VAR_LEGACY", + CONTAINER_ENV_VAR_VALUE); + TemplateEnvVar containerSecretEnvVariable = new SecretEnvVar("CONTAINER_ENV_VAR_FROM_SECRET", + "container-secret", SECRET_KEY); + podTemplate.getContainers().get(0) + .setEnvVars(asList(containerEnvVariable, containerEnvVariableLegacy, containerSecretEnvVariable)); + } +} diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStepTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStepTest.java new file mode 100644 index 0000000000..04697d3057 --- /dev/null +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerLogStepTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Original Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.csanchez.jenkins.plugins.kubernetes.pipeline; + +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; + +import static org.junit.Assert.assertNotNull; + +public class ContainerLogStepTest extends AbstractKubernetesPipelineTest { + @Issue("JENKINS-46085") + @Test + public void simple() throws Exception { + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "containerLog"); + p.setDefinition(new CpsFlowDefinition(loadPipelineScript("getContainerLog.groovy"), true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + assertNotNull(b); + r.assertBuildStatusSuccess(r.waitForCompletion(b)); + r.assertLogContains("INFO: Handshaking", b); + r.assertLogContains("INFO: Connected", b); + } +} diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java index 4855d2696d..389e494b4c 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java @@ -24,129 +24,40 @@ package org.csanchez.jenkins.plugins.kubernetes.pipeline; -import static java.util.Arrays.*; -import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.*; import static org.junit.Assert.*; -import java.net.InetAddress; -import java.net.URL; import java.util.Collections; -import java.util.logging.Level; -import org.apache.commons.compress.utils.IOUtils; -import org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar; -import org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate; import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud; -import org.csanchez.jenkins.plugins.kubernetes.PodTemplate; -import org.csanchez.jenkins.plugins.kubernetes.model.KeyValueEnvVar; -import org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar; -import org.csanchez.jenkins.plugins.kubernetes.model.TemplateEnvVar; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runners.model.Statement; -import org.jvnet.hudson.test.BuildWatcher; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRuleNonLocalhost; -import org.jvnet.hudson.test.LoggerRule; -import org.jvnet.hudson.test.RestartableJenkinsRule; - -import com.google.common.collect.ImmutableMap; import hudson.model.Node; import hudson.slaves.DumbSlave; import hudson.slaves.NodeProperty; import hudson.slaves.RetentionStrategy; import io.fabric8.kubernetes.api.model.NamespaceBuilder; -import io.fabric8.kubernetes.api.model.Secret; -import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.KubernetesClient; -import jenkins.model.JenkinsLocationConfiguration; +import org.jvnet.hudson.test.RestartableJenkinsRule; /** * @author Carlos Sanchez */ -public class KubernetesPipelineTest { - - private static final String SECRET_KEY = "password"; - private static final String CONTAINER_ENV_VAR_VALUE = "container-env-var-value"; - private static final String POD_ENV_VAR_VALUE = "pod-env-var-value"; - private static final String CONTAINER_ENV_VAR_FROM_SECRET_VALUE = "container-pa55w0rd"; - private static final String POD_ENV_VAR_FROM_SECRET_VALUE = "pod-pa55w0rd"; - - @ClassRule - public static BuildWatcher buildWatcher = new BuildWatcher(); +public class KubernetesPipelineTest extends AbstractKubernetesPipelineTest { @Rule public RestartableJenkinsRule story = new RestartableJenkinsRule(); - @Rule - public JenkinsRuleNonLocalhost r = new JenkinsRuleNonLocalhost(); - @Rule - public LoggerRule logs = new LoggerRule().record(KubernetesCloud.class, Level.ALL); - @Rule public TemporaryFolder tmp = new TemporaryFolder(); - private static KubernetesCloud cloud; - - @BeforeClass - public static void configureCloud() throws Exception { - cloud = setupCloud(); - createSecret(cloud.connect()); - } - - @Before - public void configureTemplates() throws Exception { - cloud.getTemplates().clear(); - cloud.addTemplate(buildBusyboxTemplate("busybox")); - deletePods(cloud.connect(), Collections.emptyMap(), false); - } - - @After - public void cleanup() throws Exception { - assertFalse("There are pods leftover after test execution, see previous logs", - deletePods(cloud.connect(), KubernetesCloud.DEFAULT_POD_LABELS, true)); - } - - /** - * Create a busybox template - */ - private PodTemplate buildBusyboxTemplate(String label) { - // Create a busybox template - PodTemplate podTemplate = new PodTemplate(); - podTemplate.setLabel(label); - - ContainerTemplate containerTemplate = new ContainerTemplate("busybox", "busybox", "cat", ""); - containerTemplate.setTtyEnabled(true); - podTemplate.getContainers().add(containerTemplate); - setEnvVariables(podTemplate); - return podTemplate; - } - - @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(); - - String hostAddress = System.getProperty("jenkins.host.address"); - if (hostAddress == null) { - hostAddress = InetAddress.getLocalHost().getHostAddress(); - } - URL nonLocalhostUrl = new URL(url.getProtocol(), hostAddress, url.getPort(), - url.getFile()); - JenkinsLocationConfiguration.get().setUrl(nonLocalhostUrl.toString()); - - r.jenkins.clouds.add(cloud); - } - @Test public void runInPod() throws Exception { WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); @@ -338,35 +249,4 @@ public void declarative() throws Exception { r.assertBuildStatusSuccess(r.waitForCompletion(b)); r.assertLogContains("Apache Maven 3.3.9", b); } - - private String loadPipelineScript(String name) { - try { - return new String(IOUtils.toByteArray(getClass().getResourceAsStream(name))); - } catch (Throwable t) { - throw new RuntimeException("Could not read resource:[" + name + "]."); - } - } - - private static void createSecret(KubernetesClient client) { - Secret secret = new SecretBuilder() - .withStringData(ImmutableMap.of(SECRET_KEY, CONTAINER_ENV_VAR_FROM_SECRET_VALUE)).withNewMetadata() - .withName("container-secret").endMetadata().build(); - client.secrets().createOrReplace(secret); - secret = new SecretBuilder().withStringData(ImmutableMap.of(SECRET_KEY, POD_ENV_VAR_FROM_SECRET_VALUE)) - .withNewMetadata().withName("pod-secret").endMetadata().build(); - client.secrets().createOrReplace(secret); - } - - private static void setEnvVariables(PodTemplate podTemplate) { - TemplateEnvVar podSecretEnvVar = new SecretEnvVar("POD_ENV_VAR_FROM_SECRET", "pod-secret", SECRET_KEY); - TemplateEnvVar podSimpleEnvVar = new KeyValueEnvVar("POD_ENV_VAR", POD_ENV_VAR_VALUE); - podTemplate.setEnvVars(asList(podSecretEnvVar, podSimpleEnvVar)); - TemplateEnvVar containerEnvVariable = new KeyValueEnvVar("CONTAINER_ENV_VAR", CONTAINER_ENV_VAR_VALUE); - TemplateEnvVar containerEnvVariableLegacy = new ContainerEnvVar("CONTAINER_ENV_VAR_LEGACY", - CONTAINER_ENV_VAR_VALUE); - TemplateEnvVar containerSecretEnvVariable = new SecretEnvVar("CONTAINER_ENV_VAR_FROM_SECRET", - "container-secret", SECRET_KEY); - podTemplate.getContainers().get(0) - .setEnvVars(asList(containerEnvVariable, containerEnvVariableLegacy, containerSecretEnvVariable)); - } } diff --git a/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/getContainerLog.groovy b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/getContainerLog.groovy new file mode 100644 index 0000000000..22e7a224d7 --- /dev/null +++ b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/getContainerLog.groovy @@ -0,0 +1,8 @@ +//noinspection GrPackage +podTemplate(cloud: 'kubernetes-plugin-test', label: 'mypod') { + node ('mypod') { + stage('container log') { + containerLog 'jnlp' + } + } +}