diff --git a/README.md b/README.md index 7c76c2e..a746d80 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The parameters have a slightly different effect depending if a plain KubeConfig | --------------- | --------- | ------------- | | `credentialsId` | yes | The Jenkins ID of the credentials. | | `serverUrl` | yes | URL of the API server's. | -| `caCertificate` | no | Cluster Certificate Authority used to validate the API server's certificate. Validation skipped if the parameter is not provided. | +| `caCertificate` | no | Cluster Certificate Authority used to validate the API server's certificate. The validation is skipped if the parameter is not provided. | | `clusterName` | no | Name of the generated Cluster configuration. (default: `k8s`) | | `namespace` | no | Namespace for the Context. | | `contextName` | no | Name of the generated Context configuration. (default: `k8s`) | @@ -41,11 +41,11 @@ The plugin writes the plain KubeConfig file and doesn't change any other field i | Name | Mandatory | Description | | --------------- | --------- | ------------- | | `credentialsId` | yes | The Jenkins ID of the plain KubeConfig file. | -| `serverUrl` | no | URL of the API server's. | -| `caCertificate` | no | Cluster Certificate Authority used to validate the API server's certificate. Validation skipped if the parameter is not provided. | -| `clusterName` | no | Name of the generated Cluster configuration if a `clusterName` was provided. | -| `namespace` | no | Namespace for the Context. | -| `contextName` | no | Name of the Context to use. | +| `serverUrl` | no | URL of the API server's. This will create a new `cluster` block and modify the current Context to use it. | +| `caCertificate` | no | Cluster Certificate Authority used to validate the API server's certificate if a `serverUrl` was provided. The validation is skipped if the parameter is not provided. | +| `clusterName` | no | Modifies the Cluster of the current Context. Also used for the generated `cluster` block if a `serverUrl` was provided. | +| `namespace` | no | Modifies the Namespace of the current Context. | +| `contextName` | no | Switch the current Context to this name. The Context must already exist in the KubeConfig file. | ### Pipeline usage diff --git a/src/main/java/org/jenkinsci/plugins/kubernetes/cli/kubeconfig/KubeConfigWriter.java b/src/main/java/org/jenkinsci/plugins/kubernetes/cli/kubeconfig/KubeConfigWriter.java index 99a84ff..66d0bfa 100644 --- a/src/main/java/org/jenkinsci/plugins/kubernetes/cli/kubeconfig/KubeConfigWriter.java +++ b/src/main/java/org/jenkinsci/plugins/kubernetes/cli/kubeconfig/KubeConfigWriter.java @@ -1,33 +1,30 @@ package org.jenkinsci.plugins.kubernetes.cli.kubeconfig; -import static com.google.common.collect.Sets.newHashSet; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Collections; -import java.util.Set; - -import javax.annotation.Nonnull; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; -import org.jenkinsci.plugins.kubernetes.credentials.TokenProducer; -import org.jenkinsci.plugins.plaincredentials.FileCredentials; -import org.jenkinsci.plugins.plaincredentials.StringCredentials; - import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials; import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; import com.cloudbees.plugins.credentials.domains.DomainRequirement; - import hudson.AbortException; import hudson.FilePath; import hudson.Launcher; import hudson.model.Run; import hudson.util.QuotedStringTokenizer; import hudson.util.Secret; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.jenkinsci.plugins.kubernetes.credentials.TokenProducer; +import org.jenkinsci.plugins.plaincredentials.FileCredentials; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; + +import javax.annotation.Nonnull; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.Set; + +import static com.google.common.collect.Sets.newHashSet; /** * @author Max Laverse @@ -88,25 +85,30 @@ public String writeKubeConfig() throws IOException, InterruptedException { useContext(configFile.getRemote(), this.contextName); } - if (this.wasServerUrlProvided()) { - launcher.getListener().getLogger().println("the serverUrl will be ignored as a raw kubeconfig file was provided"); + if (wasServerUrlProvided()) { + setCluster(configFile.getRemote()); } - + if (wasClusterProvided()) { - setCluster(configFile.getRemote(), this.clusterName); + setContextCluster(configFile.getRemote(), this.clusterName); + } else if (wasServerUrlProvided()) { + setContextCluster(configFile.getRemote(), getClusterNameOrDefault()); + } + + if (wasNamespaceProvided()) { + setContextNamespace(configFile.getRemote(), namespace); } } else { - setCluster(configFile.getRemote(), getClusterNameOrDefault()); + setCluster(configFile.getRemote()); setCredentials(configFile.getRemote(), credentials); - setContext(configFile.getRemote(), getContextNameOrDefault(), getClusterNameOrDefault()); + if (wasNamespaceProvided()) { + setFullContext(configFile.getRemote(), namespace); + } else { + setFullContext(configFile.getRemote()); + } useContext(configFile.getRemote(), getContextNameOrDefault()); } - if (wasNamespaceProvided()){ - setNamespace(configFile.getRemote(),namespace); - } - - return configFile.getRemote(); } @@ -130,7 +132,7 @@ private void setRawKubeConfig(FilePath configFile, FileCredentials credentials) * @throws IOException on file operations * @throws InterruptedException on file operations */ - private void setCluster(String configFile, String clusterName) throws IOException, InterruptedException { + private void setCluster(String configFile) throws IOException, InterruptedException { String tlsConfigArgs; Set filesToBeRemoved = newHashSet(); @@ -150,7 +152,7 @@ private void setCluster(String configFile, String clusterName) throws IOExceptio .envs(String.format("KUBECONFIG=%s", configFile)) .cmdAsSingleString(String.format("%s config set-cluster %s --server=%s %s", KUBECTL_BINARY, - clusterName, + getClusterNameOrDefault(), serverUrl, tlsConfigArgs)) .stdout(launcher.getListener()) @@ -219,26 +221,41 @@ private void setCredentials(String configFile, StandardCredentials credentials) * @throws IOException on file operations * @throws InterruptedException on file operations */ - private void setContext(String configFile, String contextName, String clusterName) throws IOException, InterruptedException { + private void setFullContext(String configFile) throws IOException, InterruptedException { int status = launcher.launch() .envs(String.format("KUBECONFIG=%s", configFile)) .cmdAsSingleString(String.format("%s config set-context %s --cluster=%s --user=%s", KUBECTL_BINARY, - contextName, - clusterName, + getContextNameOrDefault(), + getClusterNameOrDefault(), USERNAME)) .stdout(launcher.getListener()) .join(); if (status != 0) throw new IOException("Failed to add kubectl context (exit code " + status + ")"); } + private void setFullContext(String configFile, String namespace) throws IOException, InterruptedException { + int status = launcher.launch() + .envs(String.format("KUBECONFIG=%s", configFile)) + .cmdAsSingleString(String.format("%s config set-context %s --cluster=%s --user=%s --namespace=%s", + KUBECTL_BINARY, + getContextNameOrDefault(), + getClusterNameOrDefault(), + USERNAME, + namespace)) + .stdout(launcher.getListener()) + .join(); + if (status != 0) + throw new IOException("Failed to add kubectl context with namespace (exit code " + status + ")"); + } + /** * Set the namespace of the context section in the kube configuration file. * * @throws IOException on file operations * @throws InterruptedException on file operations */ - private void setNamespace(String configFile, String namespace) throws IOException, InterruptedException { + private void setContextNamespace(String configFile, String namespace) throws IOException, InterruptedException { // Starting kubectl 1.12, we can use --current instead of having to determine the context we are in. // To be done once we drop support for <1.12 int status = launcher.launch() @@ -246,11 +263,30 @@ private void setNamespace(String configFile, String namespace) throws IOExceptio .cmdAsSingleString(String.format("%s config set-context %s --namespace=%s", KUBECTL_BINARY, getCurrentContext(configFile), - namespace, - USERNAME)) + namespace)) .stdout(launcher.getListener()) .join(); - if (status != 0) throw new IOException("Failed to set kubectl namespace (exit code " + status + ")"); + if (status != 0) throw new IOException("Failed to set kubectl context namespace (exit code " + status + ")"); + } + + /** + * Set the cluster of the context section in the kube configuration file. + * + * @throws IOException on file operations + * @throws InterruptedException on file operations + */ + private void setContextCluster(String configFile, String clusterName) throws IOException, InterruptedException { + // Starting kubectl 1.12, we can use --current instead of having to determine the context we are in. + // To be done once we drop support for <1.12 + int status = launcher.launch() + .envs(String.format("KUBECONFIG=%s", configFile)) + .cmdAsSingleString(String.format("%s config set-context %s --cluster=%s", + KUBECTL_BINARY, + getCurrentContext(configFile), + clusterName)) + .stdout(launcher.getListener()) + .join(); + if (status != 0) throw new IOException("Failed to set kubectl context cluster (exit code " + status + ")"); } /** @@ -331,7 +367,7 @@ private StandardCredentials getCredentials(Run build) throws AbortExceptio private boolean wasContextProvided() { return this.contextName != null && !this.contextName.isEmpty(); } - + /** * Return whether or not a clusterName was provided * @@ -355,7 +391,9 @@ private boolean wasServerUrlProvided() { * * @return true if a namespace was provided to the plugin. */ - private boolean wasNamespaceProvided() { return this.namespace != null && !this.namespace.isEmpty(); } + private boolean wasNamespaceProvided() { + return this.namespace != null && !this.namespace.isEmpty(); + } /** * Returns a contextName diff --git a/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlBuildStepTest.java b/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlBuildStepTest.java index 61b158d..22239ff 100644 --- a/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlBuildStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlBuildStepTest.java @@ -37,7 +37,7 @@ public void testScopedCredentials() throws Exception { CredentialsProvider.lookupStores(folder).iterator().next().addCredentials(Domain.global(), usernamePasswordCredential(CREDENTIAL_ID)); WorkflowJob p = folder.createProject(WorkflowJob.class, "testScopedCredentials"); - p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectl.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMocked.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); waitForResult(b, Result.SUCCESS); @@ -49,7 +49,7 @@ public void testMissingScopedCredentials() throws Exception { CredentialsProvider.lookupStores(folder).iterator().next().addCredentials(Domain.global(), usernamePasswordCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testMissingScopedCredentials"); - p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectl.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMocked.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); waitForResult(b, Result.FAILURE); @@ -61,7 +61,7 @@ public void testSecretWithSpace() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), secretCredentialWithSpace(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testSecretWithSpace"); - p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectl.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMocked.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); waitForResult(b, Result.SUCCESS); @@ -74,7 +74,7 @@ public void testUsernamePasswordWithSpace() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), usernamePasswordCredentialWithSpace(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testUsernamePasswordWithSpace"); - p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectl.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMocked.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); waitForResult(b, Result.SUCCESS); @@ -87,7 +87,7 @@ public void testKubeConfigDisposed() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), usernamePasswordCredentialWithSpace(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testCleanupOnFailure"); - p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectlFailure.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMockedFailure.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); waitForResult(b, Result.FAILURE); @@ -97,7 +97,7 @@ public void testKubeConfigDisposed() throws Exception { @Test public void testCredentialNotProvided() throws Exception { WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testWithEmptyCredentials"); - p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectlWithEmptyCredential.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMockedWithEmptyCredential.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); waitForResult(b, Result.FAILURE); @@ -109,7 +109,7 @@ public void testUnsupportedCredential() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), unsupportedCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testWithUnsupportedCredentials"); - p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectl.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMocked.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); waitForResult(b, Result.FAILURE); @@ -137,7 +137,7 @@ public void testInvalidCertificate() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), brokenCertificateCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testWithBrokenCertificate"); - p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectl.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMocked.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); waitForResult(b, Result.FAILURE); @@ -145,15 +145,27 @@ public void testInvalidCertificate() throws Exception { } @Test - public void testServerProvidedWithFileCredential() throws Exception { + public void testPlainKubeConfig() throws Exception { + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); + + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testWithFileCertificateAndClusterName"); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMockedWithCluster.groovy"), true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + assertNotNull(b); + waitForResult(b, Result.SUCCESS); + r.assertLogNotContains("kubectl, config, set-cluster", b); + } + + @Test + public void testPlainKubeConfigWithServerUrl() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testWithFileCertificateAndServer"); - p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectl.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMocked.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); waitForResult(b, Result.SUCCESS); - r.assertLogContains("the serverUrl will be ignored as a raw kubeconfig file was provided", b); + r.assertLogContains("kubectl, config, set-cluster", b); } private void waitForResult(WorkflowRun b, Result result) throws Exception { diff --git a/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlIntegrationTest.java b/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlIntegrationTest.java index 10b3225..d76e61e 100644 --- a/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlIntegrationTest.java +++ b/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlIntegrationTest.java @@ -3,6 +3,7 @@ import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.domains.Domain; import hudson.FilePath; +import hudson.util.IOUtils; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; @@ -11,7 +12,12 @@ import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.nio.file.Files; import java.util.Base64; +import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; @@ -37,7 +43,7 @@ public void testBasicWithCa() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), usernamePasswordCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testBasicWithCa"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlWithCa.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithServerAndCa.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -49,13 +55,13 @@ public void testBasicWithCa() throws Exception { assertThat(configDumpContent, containsString("certificate-authority-data: " + encodedCertificate)); assertThat(configDumpContent, containsString("server: " + SERVER_URL)); } - + @Test public void testBasicWithCluster() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), usernamePasswordCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testBasicWithCluster"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlRawWithCluster.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithCluster.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -72,7 +78,7 @@ public void testBasicWithoutCa() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), usernamePasswordCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testBasicWithoutCa"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlWithoutCa.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithServer.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -90,7 +96,7 @@ public void testBasicWithNamespace() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), usernamePasswordCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testBasicWithNamespace"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlRawWithNamespace.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithNamespace.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -107,7 +113,7 @@ public void testUsernamePasswordCredentials() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), usernamePasswordCredentialWithSpace(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testUsernamePasswordCredentials"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlWithoutCa.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithServer.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -125,7 +131,7 @@ public void testNonFileCredentialsWithContext() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), usernamePasswordCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testUsernamePasswordCredentials"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlWithoutCaWithContext.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithServerAndContext.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -138,97 +144,12 @@ public void testNonFileCredentialsWithContext() throws Exception { assertThat(configDumpContent, containsString("current-context: test-sample")); } - @Test - public void testFileCredentials() throws Exception { - CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); - - WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "fileCredential"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlMinimal.groovy"), true)); - WorkflowRun b = p.scheduleBuild2(0).waitForStart(); - assertNotNull(b); - r.assertBuildStatusSuccess(r.waitForCompletion(b)); - - FilePath configDump = r.jenkins.getWorkspaceFor(p).child("configDump"); - assertTrue(configDump.exists()); - String configDumpContent = configDump.readToString().trim(); - assertEquals("---\n" + - "apiVersion: v1\n" + - "contexts:\n" + - "- context:\n" + - " name: test-sample\n" + - "- context:\n" + - " name: k8s\n" + - "current-context: minikube", configDumpContent); - } - - @Test - public void testFileCredentialsWithContext() throws Exception { - CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); - - WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "fileCredential"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlRawWithContext.groovy"), true)); - WorkflowRun b = p.scheduleBuild2(0).waitForStart(); - assertNotNull(b); - r.assertBuildStatusSuccess(r.waitForCompletion(b)); - - FilePath configDump = r.jenkins.getWorkspaceFor(p).child("configDump"); - assertTrue(configDump.exists()); - String configDumpContent = configDump.readToString().trim(); - assertEquals("apiVersion: v1\n" + - "clusters: []\n" + - "contexts:\n" + - "- context:\n" + - " cluster: \"\"\n" + - " user: \"\"\n" + - " name: k8s\n" + - "- context:\n" + - " cluster: \"\"\n" + - " user: \"\"\n" + - " name: test-sample\n" + - "current-context: test-sample\n" + - "kind: Config\n" + - "preferences: {}\n" + - "users: []", configDumpContent); - } - - @Test - public void testFileCredentialsWithCluster() throws Exception { - CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); - - WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "fileCredential"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlRawWithCluster.groovy"), true)); - WorkflowRun b = p.scheduleBuild2(0).waitForStart(); - assertNotNull(b); - r.assertBuildStatusSuccess(r.waitForCompletion(b)); - - FilePath configDump = r.jenkins.getWorkspaceFor(p).child("configDump"); - assertTrue(configDump.exists()); - String configDumpContent = configDump.readToString().trim(); - assertThat(configDumpContent, containsString("name: " + CLUSTER_NAME)); - } - - @Test - public void testFileCredentialsWithNamespace() throws Exception { - CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); - - WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "fileCredential"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlRawWithNamespace.groovy"), true)); - WorkflowRun b = p.scheduleBuild2(0).waitForStart(); - assertNotNull(b); - r.assertBuildStatusSuccess(r.waitForCompletion(b)); - - FilePath configDump = r.jenkins.getWorkspaceFor(p).child("configDump"); - assertTrue(configDump.exists()); - String configDumpContent = configDump.readToString().trim(); - assertThat(configDumpContent, containsString("namespace: test-ns")); - } - @Test public void testSecretCredentials() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), secretCredentialWithSpace(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testSecretCredentials"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlWithoutCa.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithServer.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -245,7 +166,7 @@ public void testCertificateCredentials() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), certificateCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testCertificateCredentials"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlWithoutCa.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithServer.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -305,7 +226,7 @@ public void testKubeConfigPathWithSpace() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), usernamePasswordCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "kubectl path with spaces"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpKubeConfigPath.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlEchoKubeConfigPath.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -319,7 +240,7 @@ public void testTokenProducer() throws Exception { CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), tokenCredential(CREDENTIAL_ID)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "tokenProducerCredential"); - p.setDefinition(new CpsFlowDefinition(loadResource("kubectlWithoutCa.groovy"), true)); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithServer.groovy"), true)); WorkflowRun b = p.scheduleBuild2(0).waitForStart(); assertNotNull(b); r.assertBuildStatusSuccess(r.waitForCompletion(b)); @@ -329,4 +250,159 @@ public void testTokenProducer() throws Exception { String configDumpContent = configDump.readToString().trim(); assertThat(configDumpContent, containsString("token: faketoken:bob:s3cr3t")); } + + @Test + public void testPlainKubeConfig() throws Exception { + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); + + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "fileCredential"); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpMinimal.groovy"), true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + assertNotNull(b); + r.assertBuildStatusSuccess(r.waitForCompletion(b)); + + FilePath configDump = r.jenkins.getWorkspaceFor(p).child("configDump"); + assertTrue(configDump.exists()); + String configDumpContent = configDump.readToString().trim(); + + String originalContent = new BufferedReader(new InputStreamReader(fileCredential(CREDENTIAL_ID).getContent())).lines().collect(Collectors.joining("\n")); + assertEquals(originalContent, configDumpContent); + } + + @Test + public void testPlainKubeConfigWithContext() throws Exception { + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); + + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "fileCredential"); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithContext.groovy"), true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + assertNotNull(b); + r.assertBuildStatusSuccess(r.waitForCompletion(b)); + + FilePath configDump = r.jenkins.getWorkspaceFor(p).child("configDump"); + assertTrue(configDump.exists()); + String configDumpContent = configDump.readToString().trim(); + assertEquals("apiVersion: v1\n" + + "clusters:\n" + + "- cluster:\n" + + " server: \"\"\n" + + " name: test-sample\n" + + "contexts:\n" + + "- context:\n" + + " cluster: \"\"\n" + + " user: \"\"\n" + + " name: minikube\n" + + "- context:\n" + + " cluster: test-sample\n" + + " user: \"\"\n" + + " name: test-sample\n" + + "current-context: minikube\n" + + "kind: Config\n" + + "preferences: {}\n" + + "users: []", configDumpContent); + } + + @Test + public void testPlainKubeConfigWithCluster() throws Exception { + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); + + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "fileCredential"); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithCluster.groovy"), true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + assertNotNull(b); + r.assertBuildStatusSuccess(r.waitForCompletion(b)); + + FilePath configDump = r.jenkins.getWorkspaceFor(p).child("configDump"); + assertTrue(configDump.exists()); + String configDumpContent = configDump.readToString().trim(); + assertEquals("apiVersion: v1\n" + + "clusters:\n" + + "- cluster:\n" + + " server: \"\"\n" + + " name: test-sample\n" + + "contexts:\n" + + "- context:\n" + + " cluster: \"\"\n" + + " user: \"\"\n" + + " name: minikube\n" + + "- context:\n" + + " cluster: test-cluster\n" + + " user: \"\"\n" + + " name: test-sample\n" + + "current-context: test-sample\n" + + "kind: Config\n" + + "preferences: {}\n" + + "users: []", configDumpContent); + } + + @Test + public void testPlainKubeConfigWithServer() throws Exception { + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); + + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "fileCredential"); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithServer.groovy"), true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + assertNotNull(b); + r.assertBuildStatusSuccess(r.waitForCompletion(b)); + + FilePath configDump = r.jenkins.getWorkspaceFor(p).child("configDump"); + assertTrue(configDump.exists()); + String configDumpContent = configDump.readToString().trim(); + assertEquals("apiVersion: v1\n" + + "clusters:\n" + + "- cluster:\n" + + " insecure-skip-tls-verify: true\n" + + " server: https://localhost:6443\n" + + " name: k8s\n" + + "- cluster:\n" + + " server: \"\"\n" + + " name: test-sample\n" + + "contexts:\n" + + "- context:\n" + + " cluster: \"\"\n" + + " user: \"\"\n" + + " name: minikube\n" + + "- context:\n" + + " cluster: k8s\n" + + " user: \"\"\n" + + " name: test-sample\n" + + "current-context: test-sample\n" + + "kind: Config\n" + + "preferences: {}\n" + + "users: []", configDumpContent); + } + + @Test + public void testPlainKubeConfigWithNamespace() throws Exception { + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), fileCredential(CREDENTIAL_ID)); + + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "fileCredential"); + p.setDefinition(new CpsFlowDefinition(loadResource("kubectlDumpWithNamespace.groovy"), true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + assertNotNull(b); + r.assertBuildStatusSuccess(r.waitForCompletion(b)); + + FilePath configDump = r.jenkins.getWorkspaceFor(p).child("configDump"); + assertTrue(configDump.exists()); + String configDumpContent = configDump.readToString().trim(); + assertEquals("apiVersion: v1\n" + + "clusters:\n" + + "- cluster:\n" + + " server: \"\"\n" + + " name: test-sample\n" + + "contexts:\n" + + "- context:\n" + + " cluster: \"\"\n" + + " user: \"\"\n" + + " name: minikube\n" + + "- context:\n" + + " cluster: test-sample\n" + + " namespace: test-ns\n" + + " user: \"\"\n" + + " name: test-sample\n" + + "current-context: test-sample\n" + + "kind: Config\n" + + "preferences: {}\n" + + "users: []", configDumpContent); + } } diff --git a/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlTestBase.java b/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlTestBase.java index d33ca30..57909ec 100644 --- a/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlTestBase.java +++ b/src/test/java/org/jenkinsci/plugins/kubernetes/cli/KubectlTestBase.java @@ -123,12 +123,16 @@ protected FileCredentials fileCredential(String credentialId) throws Unsupported "file-name", SecretBytes.fromBytes(("---\n" + "apiVersion: v1\n" + + "clusters:\n" + + "- cluster:\n" + + " name: test-sample\n" + "contexts:\n" + "- context:\n" + + " cluster: test-sample\n" + " name: test-sample\n" + "- context:\n" + - " name: k8s\n" + - "current-context: minikube\n").getBytes("UTF-8"))); + " name: minikube\n" + + "current-context: test-sample\n").getBytes("UTF-8"))); } protected FakeBearerTokenCredentialImpl tokenCredential(String credentialId) { diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMinimal.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpMinimal.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMinimal.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpMinimal.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlRawWithCluster.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithCluster.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlRawWithCluster.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithCluster.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithContext.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithContext.groovy new file mode 100644 index 0000000..d427bc2 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithContext.groovy @@ -0,0 +1,7 @@ +node{ + stage('Run') { + withKubeConfig([credentialsId: 'cred1234', contextName: 'minikube']) { + sh 'cat "$KUBECONFIG" > configDump' + } + } +} diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlRawWithNamespace.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithNamespace.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlRawWithNamespace.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithNamespace.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlWithoutCa.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithServer.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlWithoutCa.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithServer.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlWithCa.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithServerAndCa.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlWithCa.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithServerAndCa.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlWithoutCaWithContext.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithServerAndContext.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlWithoutCaWithContext.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpWithServerAndContext.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpKubeConfigPath.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlEchoKubeConfigPath.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlDumpKubeConfigPath.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlEchoKubeConfigPath.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/mockedKubectl.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMocked.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/mockedKubectl.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMocked.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/mockedKubectlFailure.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMockedFailure.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/mockedKubectlFailure.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMockedFailure.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMockedWithCluster.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMockedWithCluster.groovy new file mode 100644 index 0000000..cd8eeea --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMockedWithCluster.groovy @@ -0,0 +1,8 @@ +node{ + label "mocked-kubectl" + stage('Run') { + withKubeConfig([credentialsId: 'cred1234', clusterName: 'name']) { + echo "File has been configured '${env.KUBECONFIG}'" + } + } +} diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/mockedKubectlWithEmptyCredential.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMockedWithEmptyCredential.groovy similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/kubernetes/cli/mockedKubectlWithEmptyCredential.groovy rename to src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlMockedWithEmptyCredential.groovy diff --git a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlRawWithContext.groovy b/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlRawWithContext.groovy deleted file mode 100644 index 108abc5..0000000 --- a/src/test/resources/org/jenkinsci/plugins/kubernetes/cli/kubectlRawWithContext.groovy +++ /dev/null @@ -1,7 +0,0 @@ -node{ - stage('Run') { - withKubeConfig([credentialsId: 'cred1234', contextName: 'test-sample']) { - sh 'cat "$KUBECONFIG" > configDump' - } - } -}