[FIXED JENKINS-50148] Handle SimpleBuildWrappers in editor (#1786)

* [FIXED JENKINS-50148] Handle SimpleBuildWrappers in editor

* Add withSonarQubeEnv tests, update StepMetadata.json so tests actually
do things.
This commit is contained in:
Andrew Bayer 2018-08-09 18:32:53 -04:00 committed by Vivek Pandey
parent e4b71e88b0
commit 9cd5d47167
8 changed files with 235 additions and 3 deletions

View File

@ -62,5 +62,15 @@
<groupId>org.jenkinsci.plugins</groupId>
<artifactId>pipeline-model-definition</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>sonar</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ant</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -24,6 +24,7 @@
package io.blueocean.rest.pipeline.editor;
import jenkins.tasks.SimpleBuildWrapper;
import org.jenkinsci.plugins.structs.describable.DescribableModel;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
@ -44,4 +45,12 @@ public class ExportedPipelineFunction extends ExportedDescribableModel {
public String getFunctionName() {
return functionName;
}
/**
* Indicates this step wraps a block of other steps
*/
@Exported
public boolean getIsBlockContainer() {
return SimpleBuildWrapper.class.isAssignableFrom(model.getType());
}
}

View File

@ -46,6 +46,7 @@ public class ExportedPipelineStep extends ExportedPipelineFunction {
/**
* Indicates this step wraps a block of other steps
*/
@Override
@Exported
public boolean getIsBlockContainer() {
return descriptor.takesImplicitBlockArgument();

View File

@ -7,6 +7,7 @@ import hudson.Launcher;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.security.csrf.CrumbIssuer;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.tools.ToolDescriptor;
@ -15,6 +16,7 @@ import io.jenkins.blueocean.commons.stapler.TreeResponse;
import io.jenkins.blueocean.rest.ApiRoutable;
import jenkins.model.Jenkins;
import jenkins.tasks.SimpleBuildStep;
import jenkins.tasks.SimpleBuildWrapper;
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.DeclarativeAgent;
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.DeclarativeAgentDescriptor;
import org.jenkinsci.plugins.pipeline.modeldefinition.model.BuildCondition;
@ -164,6 +166,7 @@ public class PipelineMetadataService implements ApiRoutable {
List<Descriptor<?>> metaStepDescriptors = new ArrayList<Descriptor<?>>();
populateMetaSteps(metaStepDescriptors, Builder.class);
populateMetaSteps(metaStepDescriptors, Publisher.class);
populateMetaSteps(metaStepDescriptors, BuildWrapper.class);
for (Descriptor<?> d : metaStepDescriptors) {
ExportedPipelineFunction metaStep = getStepMetadata(d);
@ -199,6 +202,8 @@ public class PipelineMetadataService implements ApiRoutable {
for (Descriptor<?> d : j.getDescriptorList(c)) {
if (SimpleBuildStep.class.isAssignableFrom(d.clazz) && symbolForObject(d) != null) {
r.add(d);
} else if (SimpleBuildWrapper.class.isAssignableFrom(d.clazz) && symbolForObject(d) != null) {
r.add(d);
}
}
}

View File

@ -30,17 +30,19 @@ public class PipelineMetadataServiceTest {
@Test
public void testBasicStepsReturned() throws IOException {
JSONWebResponse rsp = j.getJSON("blue/rest/pipeline-metadata/pipelineStepMetadata");
assert(rsp != null) : "Should have results";
JSONObject node = null;
JSONObject withSonarQubeEnv = null;
for (Object o : JSONArray.fromObject(rsp.getContentAsString())) {
JSONObject meta = (JSONObject)o;
if("node".equals(meta.get("functionName"))) {
node = meta;
break;
} else if ("withSonarQubeEnv".equals(meta.get("functionName"))) {
withSonarQubeEnv = meta;
}
}
assert(node != null) : "PipelineStepMetadata node not found";
assert(withSonarQubeEnv != null) : "PipelineStepMetadata withSonarQubeEnv not found";
}
@Test
@ -131,6 +133,9 @@ public class PipelineMetadataServiceTest {
// Verify that we *do* have advanced steps that are explicitly whitelisted in.
assertThat(steps, hasItem(stepWithName("catchError")));
// Verify that we have a Symbol-provided SimpleBuildWrapper
assertThat(steps, hasItem(stepWithName("withSonarQubeEnv")));
}
private Matcher<? super ExportedPipelineStep> stepWithName(String stepName) {

View File

@ -1241,5 +1241,57 @@
"symbol": null,
"type": "hudson.tasks.Fingerprinter",
"functionName": "fingerprint"
},
{
"_class": "io.blueocean.rest.pipeline.editor.ExportedPipelineFunction",
"displayName": "With Ant",
"hasSingleRequiredParameter": false,
"help": "<div>\r\n Prepares an environment for Jenkins to run builds using Apache Ant.\r\n Annotates Ant-specific output to display executed targets.\r\n Optionally sets up an Ant and/or JDK installation.\r\n<\/div>\r\n",
"parameters": [
{
"capitalizedName": "Installation",
"collectionTypes": [],
"descriptorUrl": null,
"help": "<p>\r\n Name of an Ant installation to use so that <code>ant<\/code> will be in the path.\r\n<\/p>\r\n",
"isDeprecated": false,
"isRequired": false,
"name": "installation",
"type": "java.lang.String"
},
{
"capitalizedName": "Jdk",
"collectionTypes": [],
"descriptorUrl": null,
"help": "<p>\r\n Name of an Java installation to use when running Ant.\r\n<\/p>\r\n",
"isDeprecated": false,
"isRequired": false,
"name": "jdk",
"type": "java.lang.String"
}
],
"symbol": null,
"type": "hudson.tasks.AntWrapper",
"functionName": "withAnt",
"isBlockContainer": true
},
{
"_class": "io.blueocean.rest.pipeline.editor.ExportedPipelineFunction",
"displayName": "Prepare SonarQube Scanner environment",
"hasSingleRequiredParameter": true,
"help": null,
"parameters": [ {
"capitalizedName": "InstallationName",
"collectionTypes": [],
"descriptorUrl": null,
"help": null,
"isDeprecated": false,
"isRequired": true,
"name": "installationName",
"type": "java.lang.String"
}],
"symbol": null,
"type": "hudson.plugins.sonar.SonarBuildWrapper",
"functionName": "withSonarQubeEnv",
"isBlockContainer": true
}
]
]

View File

@ -354,4 +354,142 @@ describe('Pipeline Syntax Converter', () => {
assert(out.pipeline.stages[0].parallel[1].name == 'stage 2', "Bad parallel conversion");
});
it('converts from JSON: SimpleBuildWrapper with named parameter', () => {
const p = {"pipeline": {
"stages": [{"name": "with wrapper",
"branches": [{
"name": "default","steps": [{
"name": "withAnt","arguments": [
{"key": "installation","value": {"isLiteral": true,"value": "default"}}],
"children": [{"name": "echo","arguments":
{"isLiteral": true,"value": "hello"}}]}]}]}],
"agent": {"isLiteral": true,"value": "any"}}};
const internal = convertJsonToInternalModel(p);
const containerStep = internal.children[0].steps[0];
assert(containerStep.name == 'withAnt', "Incorrect step function");
// 'script' is the required parameter
assert(containerStep.children.length == 1, "No children for nested step");
});
it('converts from JSON: SimpleBuildWrapper with single required unnamed parameter', () => {
const p = {"pipeline": {
"stages": [{"name": "with wrapper",
"branches": [{
"name": "default","steps": [{
"name": "withSonarQubeEnv","arguments": [
{"isLiteral": true,"value": "default"}],
"children": [{"name": "echo","arguments":
{"isLiteral": true,"value": "hello"}}]}]}]}],
"agent": {"isLiteral": true,"value": "any"}}};
const internal = convertJsonToInternalModel(p);
const containerStep = internal.children[0].steps[0];
assert(containerStep.name == 'withSonarQubeEnv', "Incorrect step function");
assert(containerStep.data.installationName.value == 'default', "Incorrect arguments value");
// 'script' is the required parameter
assert(containerStep.children.length == 1, "No children for nested step");
});
it('converts from JSON: SimpleBuildWrapper with no required parameters', () => {
const p = {"pipeline": {
"stages": [{"name": "with wrapper",
"branches": [{
"name": "default","steps": [{
"name": "withAnt","arguments": [],
"children": [{"name": "echo","arguments":
{"isLiteral": true,"value": "hello"}}]}]}]}],
"agent": {"isLiteral": true,"value": "any"}}};
const internal = convertJsonToInternalModel(p);
const containerStep = internal.children[0].steps[0];
assert(containerStep.name == 'withAnt', "Incorrect step function");
// 'script' is the required parameter
assert(containerStep.children.length == 1, "No children for nested step");
});
it('converts to JSON: SimpleBuildWrapper', () => {
const internal: Pipeline = {
children: [
{
name: "with wrapper",
steps: [
{
name: 'withAnt',
data: {
installation: {
isLiteral: true,
value: 'default',
},
},
isContainer: true,
children: [{
name: "echo",
data: {
message: {
isLiteral: true,
value: "hello"
}
}
}]
}
]
},
]
};
const out = convertInternalModelToJson(internal);
assert(out.pipeline.
stages[0].
branches[0].
steps[0].
arguments[0].
key == 'installation', "Incorrect conversion to JSON: expected installation as key");
assert(out.pipeline.
stages[0].
branches[0].
steps[0].
children[0].
name == 'echo', "Incorrect conversion to JSON: expected echo as nested step");
});
it('converts to JSON: SimpleBuildWrapper with unnamed parameter', () => {
const internal: Pipeline = {
children: [
{
name: "with wrapper",
steps: [
{
name: 'withSonarQubeEnv',
data: {
installationName: {
isLiteral: true,
value: 'default',
},
},
isContainer: true,
children: [{
name: "echo",
data: {
message: {
isLiteral: true,
value: "hello"
}
}
}]
}
]
},
]
};
const out = convertInternalModelToJson(internal);
assert(out.pipeline.
stages[0].
branches[0].
steps[0].
arguments[0].
key == 'installationName', "Incorrect conversion to JSON: expected installationName as key");
assert(out.pipeline.
stages[0].
branches[0].
steps[0].
children[0].
name == 'echo', "Incorrect conversion to JSON: expected echo as nested step");
});
});

12
pom.xml
View File

@ -741,6 +741,18 @@
</exclusions>
</dependency>
<!-- used in blueocean-pipeline-editor test -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>sonar</artifactId>
<version>2.8</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ant</artifactId>
<version>1.8</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.hamcrest</groupId>