Skip to content

Commit

Permalink
Stepping (#95)
Browse files Browse the repository at this point in the history
* initial step support
* make saltStep calls happen in thread
* save output as variable in pipeline
* Support resume with jid
  • Loading branch information
mchugh19 authored Jun 10, 2017
1 parent 2fef0da commit 981c1bd
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 101 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-basic-steps</artifactId>
<version>1.2</version>
<artifactId>workflow-step-api</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
Expand Down
20 changes: 12 additions & 8 deletions src/main/java/com/waytta/Builds.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.waytta;

import hudson.model.Run;
import hudson.model.Result;
import hudson.model.TaskListener;

import java.io.IOException;
Expand Down Expand Up @@ -125,29 +124,34 @@ public static JSONArray returnData(JSONObject saltReturn, String netapi) {
return returnArray;
}

public static JSONArray runBlockingBuild(Launcher launcher, Run build, String myservername,
String token, JSONObject saltFunc, TaskListener listener, int pollTime, int minionTimeout, String netapi)
public static String getBlockingBuildJid(Launcher launcher, String myservername,
String token, JSONObject saltFunc, TaskListener listener)
throws IOException, InterruptedException, SaltException {
JSONArray returnArray = new JSONArray();
JSONObject httpResponse = new JSONObject();
String jid = "";
String jid = null;
// Send request to /minion url. This will give back a jid which we
// will need to poll and lookup for completion
httpResponse = launcher.getChannel().call(new HttpCallable(myservername + "/minions", saltFunc, token));
returnArray = httpResponse.getJSONArray("return");
JSONObject httpResponse = launcher.getChannel().call(new HttpCallable(myservername + "/minions", saltFunc, token));
JSONArray returnArray = httpResponse.getJSONArray("return");
for (Object o : returnArray) {
JSONObject line = (JSONObject) o;
jid = line.getString("jid");
}
// Print out success
listener.getLogger().println("Running jid: " + jid);
return jid;
}

public static JSONArray checkBlockingBuild(Launcher launcher, String myservername, String token,
JSONObject saltFunc, TaskListener listener, int pollTime, int minionTimeout, String netapi, String jid)
throws IOException, InterruptedException, SaltException {
// Request successfully sent. Now use jid to check if job complete
int numMinions = 0;
int numMinionsDone = 0;
JSONArray minionsArray = new JSONArray();
JSONObject resultObject = new JSONObject();
JSONArray httpArray = new JSONArray();
JSONArray returnArray = new JSONArray();
JSONObject httpResponse = new JSONObject();
httpResponse = launcher.getChannel().call(new HttpCallable(myservername + "/jobs/" + jid, null, token));
httpArray = returnData(httpResponse, netapi);
for (Object o : httpArray) {
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/com/waytta/SaltAPIBuilder.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.waytta;

import java.io.IOException;
import java.io.Serializable;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -61,7 +62,9 @@
import net.sf.json.JSONObject;
import net.sf.json.util.JSONUtils;

public class SaltAPIBuilder extends Builder implements SimpleBuildStep {
public class SaltAPIBuilder extends Builder implements SimpleBuildStep, Serializable {
private static final long serialVersionUID = 1L;

private static final Logger LOGGER = Logger.getLogger("com.waytta.saltstack");

private String servername;
Expand Down Expand Up @@ -171,7 +174,6 @@ public String getTag() {
return clientInterface.getTag();
}


@Override
public void perform(Run<?, ?> build, FilePath workspace, Launcher launcher, TaskListener listener)
throws InterruptedException, IOException {
Expand Down Expand Up @@ -259,6 +261,13 @@ public void perform(Run<?, ?> build, FilePath workspace, Launcher launcher, Task
}
}

public String getJID(Launcher launcher, String serverName, String token, JSONObject saltFunc, TaskListener listener) throws IOException, InterruptedException, SaltException {
if (saltFunc.get("client").equals("local_async")) {
return Builds.getBlockingBuildJid(launcher, serverName, token, saltFunc, listener);
}
return null;
}

public JSONArray performRequest(Launcher launcher, Run build, String token, String serverName, JSONObject saltFunc, TaskListener listener, String netapi)
throws InterruptedException, IOException, SaltException {
JSONArray returnArray = new JSONArray();
Expand All @@ -277,7 +286,8 @@ public JSONArray performRequest(Launcher launcher, Run build, String token, Stri
int jobPollTime = getJobPollTime();
int minionTimeout = getMinionTimeout();
// poll /minion for response
returnArray = Builds.runBlockingBuild(launcher, build, serverName, token, saltFunc, listener, jobPollTime, minionTimeout, netapi);
String jid = getJID(launcher, serverName, token, saltFunc, listener);
returnArray = Builds.checkBlockingBuild(launcher, serverName, token, saltFunc, listener, jobPollTime, minionTimeout, netapi, jid);
} else {
// Just send a salt request to /. Don't wait for reply
httpResponse = launcher.getChannel().call(new HttpCallable(serverName, saltFunc, token));
Expand Down
183 changes: 145 additions & 38 deletions src/main/java/com/waytta/SaltAPIStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@

import com.waytta.SaltException;

import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.IOException;
import java.io.Serializable;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import javax.inject.Inject;

import hudson.model.Run;
import hudson.model.TaskListener;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
import org.jenkinsci.plugins.workflow.steps.*;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
Expand All @@ -24,22 +23,18 @@
import hudson.Launcher;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Result;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.waytta.clientinterface.BasicClient;

public class SaltAPIStep extends AbstractStepImpl {
import com.google.common.collect.ImmutableSet;

public class SaltAPIStep extends Step implements Serializable {
private static final Logger LOGGER = Logger.getLogger("com.waytta.saltstack");

private String servername;
Expand All @@ -48,6 +43,9 @@ public class SaltAPIStep extends AbstractStepImpl {
private boolean saveEnvVar = false;
private final String credentialsId;
private boolean saveFile = false;
private static String token = null;
private static String netapi = null;
private static JSONObject saltFunc = null;

@DataBoundConstructor
public SaltAPIStep(String servername, String authtype, BasicClient clientInterface, String credentialsId) {
Expand Down Expand Up @@ -149,10 +147,7 @@ public DescriptorImpl getDescriptor() {
}

@Extension
public static final class DescriptorImpl extends AbstractStepDescriptorImpl {
public DescriptorImpl() {
super(SaltAPIStepExecution.class);
}
public static final class DescriptorImpl extends StepDescriptor {

@Override
public String getFunctionName() {
Expand Down Expand Up @@ -186,27 +181,111 @@ public FormValidation doTestConnection(
@AncestorInPath Item project) {
return SaltAPIBuilder.DescriptorImpl.doTestConnection(servername, credentialsId, authtype, project);
}

@Override
public Set<Class<?>> getRequiredContext() {
return ImmutableSet.of(Run.class, FilePath.class, TaskListener.class, Launcher.class);
}
}

public static class SaltAPIStepExecution extends AbstractSynchronousStepExecution<String> {
@Override public StepExecution start(StepContext context) throws Exception {
return new Execution(this, context);
}

public class Execution extends AbstractStepExecutionImpl {
private static final long serialVersionUID = 1L;

private String jid;

@Inject
private transient SaltAPIStep saltStep;
private SaltAPIStep saltStep;

@StepContextParameter
private transient Run<?, ?> run;
private transient volatile ScheduledFuture<?> task;

@StepContextParameter
private transient FilePath workspace;
SaltAPIBuilder saltBuilder;

@StepContextParameter
private transient TaskListener listener;
Execution(SaltAPIStep step, StepContext context) {
super(context);
this.saltStep = step;
}

@StepContextParameter
private transient Launcher launcher;
@Override
public void stop(Throwable cause) throws Exception {
if (task != null) {
task.cancel(false);
}
getContext().onFailure(cause);
}

@Override
protected String run() throws Exception, SaltException {
SaltAPIBuilder saltBuilder = new SaltAPIBuilder(saltStep.servername, saltStep.authtype, saltStep.clientInterface, saltStep.credentialsId);
public boolean start() throws Exception {
Launcher launcher = getContext().get(Launcher.class);
TaskListener listener = getContext().get(TaskListener.class);

prepareRun();
jid = saltBuilder.getJID(launcher, saltBuilder.getServername(), token, saltFunc, listener);

new Thread("saltAPI") {
@Override
public void run() {
try {
saltPerform(token, saltFunc, netapi);
}
catch (Exception e) {
Execution.this.getContext().onFailure(e);
}
}
}.start();

return false;
}

@Override public void onResume() {
TaskListener listener = null;
Launcher launcher = null;
FilePath workspace = null;
try {
listener = getContext().get(TaskListener.class);
launcher = getContext().get(Launcher.class);
workspace = getContext().get(FilePath.class);
} catch (Exception e) {
Execution.this.getContext().onFailure(e);
}

// Fail out if missing jid
if (jid == null || jid.equals("")) {
throw new RuntimeException("Unable to resume. Missing JID.");
}

listener.getLogger().println("Resuming jid: " + jid);

// Auth to salt-api
try {
prepareRun();
} catch (Exception e) {
Execution.this.getContext().onFailure(e);
}

// Poll for completion
int jobPollTime = saltBuilder.getJobPollTime();
int minionTimeout = saltBuilder.getMinionTimeout();
JSONArray returnArray = null;
try {
returnArray = Builds.checkBlockingBuild(launcher, saltBuilder.getServername(), token, saltFunc, listener, jobPollTime, minionTimeout, netapi, jid);
} catch (Exception e) {
Execution.this.getContext().onFailure(e);
}

// Verify and return result
postRun(returnArray);
}

private void prepareRun() throws InterruptedException, IOException{
Run<?, ?>run = getContext().get(Run.class);
TaskListener listener = getContext().get(TaskListener.class);
Launcher launcher = getContext().get(Launcher.class);

saltBuilder = new SaltAPIBuilder(saltStep.servername, saltStep.authtype, saltStep.clientInterface, saltStep.credentialsId);

StandardUsernamePasswordCredentials credential = CredentialsProvider.findCredentialById(
saltBuilder.getCredentialsId(), StandardUsernamePasswordCredentials.class, run);
Expand All @@ -219,32 +298,60 @@ protected String run() throws Exception, SaltException {

// Get an auth token
ServerToken serverToken = Utils.getToken(launcher, saltBuilder.getServername(), auth);
String token = serverToken.getToken();
String netapi = serverToken.getServer();
token = serverToken.getToken();
netapi = serverToken.getServer();
LOGGER.log(Level.FINE, "Discovered netapi: " + netapi);

// If we got this far, auth must have been good and we've got a token
JSONObject saltFunc = saltBuilder.prepareSaltFunction(run, listener, saltBuilder.getClientInterface().getDescriptor().getDisplayName(), saltBuilder.getTarget(), saltBuilder.getFunction(), saltBuilder.getArguments());
saltFunc = saltBuilder.prepareSaltFunction(run, listener, saltBuilder.getClientInterface().getDescriptor().getDisplayName(), saltBuilder.getTarget(), saltBuilder.getFunction(), saltBuilder.getArguments());
LOGGER.log(Level.FINE, "Sending JSON: " + saltFunc.toString());
}

private void saltPerform(String token, JSONObject saltFunc, String netapi) throws Exception, SaltException {
Run<?, ?>run = getContext().get(Run.class);
FilePath workspace = getContext().get(FilePath.class);
TaskListener listener = getContext().get(TaskListener.class);
Launcher launcher = getContext().get(Launcher.class);

JSONArray returnArray = null;
if (jid != null && !jid.equals("")) {
returnArray = Builds.checkBlockingBuild(launcher, saltBuilder.getServername(), token, saltFunc, listener, saltBuilder.getJobPollTime(), saltBuilder.getMinionTimeout(), netapi, jid);
} else {
returnArray = saltBuilder.performRequest(launcher, run, token, saltBuilder.getServername(), saltFunc, listener, netapi);
}
postRun(returnArray);
}

private void postRun(JSONArray returnArray) {
TaskListener listener = null;
FilePath workspace = null;
try {
listener = getContext().get(TaskListener.class);
workspace = getContext().get(FilePath.class);
} catch (Exception e) {
Execution.this.getContext().onFailure(e);
}

JSONArray returnArray = saltBuilder.performRequest(launcher, run, token, saltBuilder.getServername(), saltFunc, listener, netapi);
LOGGER.log(Level.FINE, "Received response: " + returnArray);

// Check for error and print out results
boolean validFunctionExecution = Utils.validateFunctionCall(returnArray);
if (!validFunctionExecution) {
listener.error("One or more minion did not return code 0\n");
throw new SaltException(returnArray.toString());
Execution.this.getContext().onFailure(new SaltException(returnArray.toString()));
}

if (saltStep.saveFile) {
Utils.writeFile(returnArray.toString(), workspace);
try {
Utils.writeFile(returnArray.toString(), workspace);
} catch (Exception e) {
Execution.this.getContext().onFailure(e);
}
}

return returnArray.toString();
// Return results
getContext().onSuccess(returnArray.toString());
}

private static final long serialVersionUID = 1L;
}

}
2 changes: 0 additions & 2 deletions src/main/java/com/waytta/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
import hudson.Launcher;
import hudson.model.Run;
import hudson.model.TaskListener;
import jenkins.model.Jenkins;

import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
Expand Down
Loading

0 comments on commit 981c1bd

Please sign in to comment.