Skip to content

Commit

Permalink
Merge pull request #4 from jenkinsci/support_context_name
Browse files Browse the repository at this point in the history
Add support for contextName
  • Loading branch information
maxlaverse authored Jul 17, 2018
2 parents 11b5ae9 + 91f7f03 commit 86ebc57
Show file tree
Hide file tree
Showing 14 changed files with 294 additions and 35 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,5 @@ install:
- chmod +x ./kubectl
- sudo mv ./kubectl /usr/local/bin/kubectl


after_success:
- mvn clean test jacoco:report coveralls:report
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class KubectlBuildStep extends Step {
@DataBoundSetter
public String caCertificate;

@DataBoundSetter
public String contextName;

@DataBoundConstructor
public KubectlBuildStep() {
}
Expand Down Expand Up @@ -74,6 +77,7 @@ public boolean start() throws Exception {
step.serverUrl,
step.credentialsId,
step.caCertificate,
step.contextName,
getContext());

// Write config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public class KubectlBuildWrapper extends SimpleBuildWrapper {
@DataBoundSetter
public String caCertificate;

@DataBoundSetter
public String contextName;

@DataBoundConstructor
public KubectlBuildWrapper() {
}
Expand All @@ -59,6 +62,7 @@ public void setUp(Context context, Run<?, ?> build,
this.serverUrl,
this.credentialsId,
this.caCertificate,
this.contextName,
workspace,
launcher,
build);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Set;

Expand All @@ -32,24 +33,26 @@ public class KubeConfigWriter {

private static final String KUBECTL_BINARY = "kubectl";
private static final String USERNAME = "cluster-admin";
private static final String CONTEXTNAME = "k8s";
private static final String DEFAULT_CONTEXTNAME = "k8s";
private static final String CLUSTERNAME = "k8s";

private final String serverUrl;
private final String credentialsId;
private final String caCertificate;
private final String contextName;
private final FilePath workspace;
private final Launcher launcher;
private final Run<?, ?> build;

public KubeConfigWriter(@Nonnull String serverUrl, @Nonnull String credentialsId,
@Nonnull String caCertificate, FilePath workspace, Launcher launcher, Run<?, ?> build) {
@Nonnull String caCertificate, @Nonnull String contextName, FilePath workspace, Launcher launcher, Run<?, ?> build) {
this.serverUrl = serverUrl;
this.credentialsId = credentialsId;
this.caCertificate = caCertificate;
this.workspace = workspace;
this.launcher = launcher;
this.build = build;
this.contextName = contextName;
}

/**
Expand All @@ -68,14 +71,24 @@ public String writeKubeConfig() throws IOException, InterruptedException {
FilePath configFile = workspace.createTempFile(".kube", "config");

final StandardCredentials credentials = getCredentials(build);
if (credentials instanceof FileCredentials) {
if (credentials == null) {
throw new AbortException("No credentials defined to setup Kubernetes CLI");
} else if (credentials instanceof FileCredentials) {
setRawKubeConfig(configFile, (FileCredentials) credentials);
if (wasContextProvided()) {
useContext(configFile.getRemote(), this.contextName);
}
if (this.wasServerUrlProvided()) {
launcher.getListener().getLogger().println("the serverUrl will be ignored as a raw kubeconfig file was provided");
}
} else {
setCluster(configFile.getRemote());
setCredentials(configFile.getRemote(), credentials);
setContext(configFile.getRemote());
setContext(configFile.getRemote(), this.getContextNameOrDefault());
useContext(configFile.getRemote(), this.getContextNameOrDefault());
}


return configFile.getRemote();
}

Expand All @@ -86,7 +99,9 @@ public String writeKubeConfig() throws IOException, InterruptedException {
* @throws InterruptedException on file operations
*/
private void setRawKubeConfig(FilePath configFile, FileCredentials credentials) throws IOException, InterruptedException {
IOUtils.copy(credentials.getContent(), configFile.write());
try (OutputStream output = configFile.write()) {
IOUtils.copy(credentials.getContent(), output);
}
}

/**
Expand Down Expand Up @@ -139,9 +154,7 @@ private void setCredentials(String configFile, StandardCredentials credentials)

String credentialsArgs;
int sensitiveFieldsCount = 1;
if (credentials == null) {
throw new AbortException("No credentials defined to setup Kubernetes CLI");
} else if (credentials instanceof TokenProducer) {
if (credentials instanceof TokenProducer) {
credentialsArgs = "--token=\"" + ((TokenProducer) credentials).getToken(serverUrl, null, true) + "\"";
} else if (credentials instanceof StringCredentials) {
credentialsArgs = "--token=\"" + ((StringCredentials) credentials).getSecret() + "\"";
Expand Down Expand Up @@ -186,25 +199,32 @@ private void setCredentials(String configFile, StandardCredentials credentials)
* @throws IOException on file operations
* @throws InterruptedException on file operations
*/
private void setContext(String configFile) throws IOException, InterruptedException {
private void setContext(String configFile, String contextName) throws IOException, InterruptedException {
// Add the context
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,
contextName,
CLUSTERNAME,
USERNAME))
.stdout(launcher.getListener())
.join();
if (status != 0) throw new IOException("Failed to add kubectl context (exit code " + status + ")");
}

// Set the default context
status = launcher.launch()
/**
* Set the current context of the kube configuration file.
*
* @throws IOException on file operations
* @throws InterruptedException on file operations
*/
private void useContext(String configFile, String contextName) throws IOException, InterruptedException {
int status = launcher.launch()
.envs(String.format("KUBECONFIG=%s", configFile))
.cmdAsSingleString(String.format("%s config use-context %s",
KUBECTL_BINARY,
CONTEXTNAME))
contextName))
.stdout(launcher.getListener())
.join();

Expand Down Expand Up @@ -243,4 +263,35 @@ private StandardCredentials getCredentials(Run<?, ?> build) throws AbortExceptio
}
return result;
}

/**
* Return whether or not a contextName was provided
*
* @return true if a contextName was provided to the plugin.
*/
private boolean wasContextProvided() {
return this.contextName != null && !this.contextName.isEmpty();
}

/**
* Return whether or not a serverUrl was provided
*
* @return true if a serverUrl was provided to the plugin.
*/
private boolean wasServerUrlProvided() {
return this.serverUrl != null && !this.serverUrl.isEmpty();
}

/**
* Returns a contextName
*
* @return contextName if provided, else the default value.
*/
private String getContextNameOrDefault() {
if (!wasContextProvided()) {
return DEFAULT_CONTEXTNAME;
}
return this.contextName;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
*/
public abstract class KubeConfigWriterFactory {
public static KubeConfigWriter get(@Nonnull String serverUrl, @Nonnull String credentialsId,
@Nonnull String caCertificate, FilePath workspace, Launcher launcher, Run<?, ?> build) {
return new KubeConfigWriter(serverUrl, credentialsId, caCertificate, workspace, launcher, build);
@Nonnull String caCertificate, @Nonnull String contextName, FilePath workspace, Launcher launcher, Run<?, ?> build) {
return new KubeConfigWriter(serverUrl, credentialsId, caCertificate, contextName, workspace, launcher, build);
}

public static KubeConfigWriter get(@Nonnull String serverUrl, @Nonnull String credentialsId,
@Nonnull String caCertificate, StepContext context) throws IOException, InterruptedException {
@Nonnull String caCertificate, @Nonnull String contextName, StepContext context) throws IOException, InterruptedException {
Run<?, ?> run = context.get(Run.class);
FilePath workspace = context.get(FilePath.class);
Launcher launcher = context.get(Launcher.class);
return new KubeConfigWriter(serverUrl, credentialsId, caCertificate, workspace, launcher, run);
return new KubeConfigWriter(serverUrl, credentialsId, caCertificate, contextName, workspace, launcher, run);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,36 @@ public void testUsernamePasswordWithSpace() throws Exception {
public void testKubeConfigDisposed() 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));
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testCleanupOnFailure");
p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectlFailure.groovy"), true));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
assertNotNull(b);
r.assertBuildStatusSuccess(r.waitForCompletion(b));
r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b));
r.assertLogContains("kubectl configuration cleaned up", b);
}

@Test
public void testCredentialNotProvided() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testWithEmptyCredentials");
p.setDefinition(new CpsFlowDefinition(loadResource("mockedKubectlWithEmptyCredential.groovy"), true));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
assertNotNull(b);
r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b));
r.assertLogContains("ERROR: No credentials defined to setup Kubernetes CLI", b);
}

@Test
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));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
assertNotNull(b);
r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b));
r.assertLogContains("ERROR: Unsupported Credentials type org.jenkinsci.plugins.kubernetes.cli.utils.UnsupportedCredential", b);
}

@Test
public void testListedCredentials() throws Exception {
CredentialsStore store = CredentialsProvider.lookupStores(r.jenkins).iterator().next();
Expand All @@ -109,4 +131,28 @@ public void testListedCredentials() throws Exception {
ListBoxModel s = d.doFillCredentialsIdItems(p.asItem(), "");
assertEquals(6, s.size());
}

@Test
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));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
assertNotNull(b);
r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b));
r.assertLogContains("ERROR: Uninitialized keystore", b);
}

@Test
public void testServerProvidedWithFileCredential() 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));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
assertNotNull(b);
r.assertBuildStatus(Result.SUCCESS, r.waitForCompletion(b));
r.assertLogContains("the serverUrl will be ignored as a raw kubeconfig file was provided", b);
}
}
Loading

0 comments on commit 86ebc57

Please sign in to comment.