Skip to content

Commit

Permalink
Merge pull request #26 from jenkinsci/JENKINS-38564
Browse files Browse the repository at this point in the history
[JENKINS-38564] API to convert json step blob to step syntax (and back) - one step at a time
  • Loading branch information
abayer committed Oct 9, 2016
2 parents 366510a + 5354f88 commit 8d84b1f
Show file tree
Hide file tree
Showing 8 changed files with 486 additions and 53 deletions.
Expand Up @@ -37,6 +37,7 @@ import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.SourceUnit
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTPipelineDef
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStep
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution
import org.jenkinsci.plugins.workflow.cps.CpsScript

Expand Down Expand Up @@ -149,6 +150,31 @@ public class Converter {
return model[0];
}

public static List<ModelASTStep> scriptToPlainSteps(String script) {
CompilationUnit cu = new CompilationUnit(
CompilerConfiguration.DEFAULT,
new CodeSource(new URL("file", "", DEFAULT_CODE_BASE), (Certificate[]) null),
new GroovyClassLoader())
cu.addSource(PIPELINE_SCRIPT_NAME, script)

return compilationUnitToPlainSteps(cu)
}

private static List<ModelASTStep> compilationUnitToPlainSteps(CompilationUnit cu) {
final List<ModelASTStep>[] model = new List<ModelASTStep>[1];

cu.addPhaseOperation(new CompilationUnit.SourceUnitOperation() {
@Override
public void call(SourceUnit source) throws CompilationFailedException {
model[0] = new ModelParser(source).parsePlainSteps(source.AST);
}
}, CANONICALIZATION);

cu.compile(CANONICALIZATION);

return model[0];
}

/**
* Converts the raw script from a {@link CpsScript} into {@link ModelASTPipelineDef}
*
Expand Down
Expand Up @@ -95,6 +95,12 @@ class ModelParser {
return parse(sourceUnit.AST);
}

public @CheckForNull List<ModelASTStep> parsePlainSteps(ModuleNode src) {
src.statementBlock.statements.collect() {
return parseStep(it);
}
}

/**
* Given a Groovy AST that represents a parsed source code, parses
* that into {@link ModelASTPipelineDef}
Expand Down
Expand Up @@ -28,20 +28,26 @@
import hudson.util.HttpResponses;
import hudson.util.TimeUnit2;
import jenkins.model.Jenkins;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTPipelineDef;
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStep;
import org.jenkinsci.plugins.pipeline.modeldefinition.parser.Converter;
import org.jenkinsci.plugins.pipeline.modeldefinition.parser.JSONParser;
import org.jenkinsci.plugins.pipeline.modeldefinition.validator.ErrorCollector;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;

import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static hudson.security.Permission.READ;

Expand Down Expand Up @@ -85,24 +91,12 @@ public HttpResponse doToJenkinsfile(StaplerRequest req) {

ModelASTPipelineDef pipelineDef = parser.parse();

if (parser.getErrorCollector().getErrorCount() > 0) {
result.accumulate("result", "failure");

JSONArray errors = new JSONArray();
for (String jsonError : parser.getErrorCollector().errorsAsStrings()) {
errors.add(jsonError);
}

result.accumulate("errors", errors);
} else {
if (!collectErrors(result, parser.getErrorCollector())) {
result.accumulate("result", "success");
result.accumulate("jenkinsfile", pipelineDef.toPrettyGroovy());
}
} catch (Exception je) {
result.accumulate("result", "failure");
JSONArray errors = new JSONArray();
errors.add(je.getMessage());
result.accumulate("errors", errors);
reportFailure(result, je);
}

return HttpResponses.okJSON(result);
Expand All @@ -121,20 +115,84 @@ public HttpResponse doToJson(StaplerRequest req) {
ModelASTPipelineDef pipelineDef = Converter.scriptToPipelineDef(groovyAsString);
result.accumulate("result", "success");
result.accumulate("json", pipelineDef.toJSON());
} catch (MultipleCompilationErrorsException e) {
result.accumulate("result", "failure");
JSONArray errors = new JSONArray();
for (Object o : e.getErrorCollector().getErrors()) {
if (o instanceof SyntaxErrorMessage) {
errors.add(((SyntaxErrorMessage) o).getCause().getMessage());
}
} catch (Exception e) {
reportFailure(result, e);
}

return HttpResponses.okJSON(result);
}

@SuppressWarnings("unused")
@RequirePOST
public HttpResponse doStepsToJson(StaplerRequest req) {
Jenkins.getInstance().checkPermission(READ);

JSONObject result = new JSONObject();

String groovyAsString = req.getParameter("jenkinsfile");

try {
List<ModelASTStep> steps = Converter.scriptToPlainSteps(groovyAsString);
JSONArray array = new JSONArray();
for (ModelASTStep step : steps) {
array.add(step.toJSON());
}
result.accumulate("errors", errors);
result.accumulate("result", "success");
result.accumulate("json", array);
} catch (Exception e) {
result.accumulate("result", "failure");
JSONArray errors = new JSONArray();
errors.add(e.getMessage());
result.accumulate("errors", errors);
reportFailure(result, e);
}

return HttpResponses.okJSON(result);
}

@SuppressWarnings("unused")
@RequirePOST
public HttpResponse doStepsToJenkinsfile(StaplerRequest req) {
Jenkins.getInstance().checkPermission(READ);

JSONObject result = new JSONObject();
try {
String jsonAsString = req.getParameter("json");
JSON json = JSONSerializer.toJSON(jsonAsString);

JSONArray jsonSteps;
if (json.isArray()) {
jsonSteps = (JSONArray)json;
} else {
jsonSteps = new JSONArray();
jsonSteps.add(json);
}
JSONParser parser = new JSONParser(null);
List<ModelASTStep> astSteps = new ArrayList<>(jsonSteps.size());
for (Object jsonStep : jsonSteps) {
if (!(jsonStep instanceof JSONObject)) {
continue;
}
ModelASTStep astStep = parser.parseStep((JSONObject)jsonStep);
if (astStep != null) {
astStep.validate(parser.getValidator());
astSteps.add(astStep);
}
}

boolean collectedSomeErrors = collectErrors(result, parser.getErrorCollector());

if (!collectedSomeErrors && astSteps.isEmpty()) {
reportFailure(result, "No result.");
} else if (!collectedSomeErrors){
result.accumulate("result", "success");
StringBuilder jenkinsFile = new StringBuilder();
for (ModelASTStep step : astSteps) {
if (jenkinsFile.length() > 0) {
jenkinsFile.append('\n');
}
jenkinsFile.append(step.toGroovy());
}
result.accumulate("jenkinsfile", jenkinsFile.toString());
}
} catch (Exception je) {
reportFailure(result, je);
}

return HttpResponses.okJSON(result);
Expand All @@ -152,20 +210,8 @@ public HttpResponse doValidateJenkinsfile(StaplerRequest req) {
try {
ModelASTPipelineDef pipelineDef = Converter.scriptToPipelineDef(groovyAsString);
result.accumulate("result", "success");
} catch (MultipleCompilationErrorsException e) {
result.accumulate("result", "failure");
JSONArray errors = new JSONArray();
for (Object o : e.getErrorCollector().getErrors()) {
if (o instanceof SyntaxErrorMessage) {
errors.add(((SyntaxErrorMessage) o).getCause().getMessage());
}
}
result.accumulate("errors", errors);
} catch (Exception e) {
result.accumulate("result", "failure");
JSONArray errors = new JSONArray();
errors.add(e.getMessage());
result.accumulate("errors", errors);
reportFailure(result, e);
}

return HttpResponses.okJSON(result);
Expand All @@ -187,23 +233,11 @@ public HttpResponse doValidateJson(StaplerRequest req) {

parser.parse();

if (parser.getErrorCollector().getErrorCount() > 0) {
result.accumulate("result", "failure");

JSONArray errors = new JSONArray();
for (String jsonError : parser.getErrorCollector().errorsAsStrings()) {
errors.add(jsonError);
}

result.accumulate("errors", errors);
} else {
if (!collectErrors(result, parser.getErrorCollector())) {
result.accumulate("result", "success");
}
} catch (Exception je) {
result.accumulate("result", "failure");
JSONArray errors = new JSONArray();
errors.add(je.getMessage());
result.accumulate("errors", errors);
reportFailure(result, je);
}

return HttpResponses.okJSON(result);
Expand All @@ -214,4 +248,66 @@ public HttpResponse doValidateJson(StaplerRequest req) {
public void doSchema(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
rsp.serveFile(req, getClass().getResource("/ast-schema.json"), TimeUnit2.DAYS.toMillis(1));
}

/**
* Checks the error collector for errors, and if there are any set the result as failure
* @param result the result to mutate if so
* @param errorCollector the collector of errors
* @return {@code true} if any errors where collected.
*/
private boolean collectErrors(JSONObject result, ErrorCollector errorCollector) {
if (errorCollector.getErrorCount() > 0) {
JSONArray errors = new JSONArray();
for (String jsonError : errorCollector.errorsAsStrings()) {
errors.add(jsonError);
}
reportFailure(result, errors);
return true;
}
return false;
}

/**
* Report result to be a failure message due to the given exception.
*
* @param result the result to mutate
* @param e the exception to report
*/
private void reportFailure(JSONObject result, Exception e) {
JSONArray errors = new JSONArray();
if (e instanceof MultipleCompilationErrorsException) {
MultipleCompilationErrorsException ce = (MultipleCompilationErrorsException)e;
for (Object o : ce.getErrorCollector().getErrors()) {
if (o instanceof SyntaxErrorMessage) {
errors.add(((SyntaxErrorMessage)o).getCause().getMessage());
}
}
} else {
errors.add(e.getMessage());
}
reportFailure(result, errors);
}

/**
* Report result to be a failure message due to the given error message.
*
* @param result the result to mutate
* @param message the error
*/
private void reportFailure(JSONObject result, String message) {
JSONArray errors = new JSONArray();
errors.add(message);
reportFailure(result, errors);
}

/**
* Report result to be a failure message due to the given error messages.
*
* @param result the result to mutate
* @param errors the errors
*/
private void reportFailure(JSONObject result, JSONArray errors) {
result.accumulate("result", "failure");
result.accumulate("errors", errors);
}
}

0 comments on commit 8d84b1f

Please sign in to comment.