Skip to content

Commit

Permalink
Merge pull request #2 from abayer/JENKINS-38585-step-editor
Browse files Browse the repository at this point in the history
Add old-school SimpleBuildSteps via Symbols.
  • Loading branch information
kzantow committed Dec 2, 2016
2 parents a80f6fb + 9d753f8 commit 7349001
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 39 deletions.
26 changes: 20 additions & 6 deletions pom.xml
Expand Up @@ -23,6 +23,8 @@
<jenkins.version>2.7.1</jenkins.version>
<node.version>5.8.0</node.version>
<npm.version>3.7.3</npm.version>
<blueocean.version>1.0.0-b13-SNAPSHOT</blueocean.version>
<declarative.version>0.7-SNAPSHOT</declarative.version>
</properties>

<repositories>
Expand All @@ -42,14 +44,13 @@
<dependencies>
<dependency>
<groupId>io.jenkins.blueocean</groupId>
<artifactId>blueocean</artifactId>
<version>1.0.0-b13-SNAPSHOT</version>
<scope>test</scope>
<artifactId>blueocean-rest</artifactId>
<version>${blueocean.version}</version>
</dependency>
<dependency>
<groupId>io.jenkins.blueocean</groupId>
<artifactId>blueocean-rest</artifactId>
<version>1.0.0-b13-SNAPSHOT</version>
<groupId>org.jenkinsci.plugins</groupId>
<artifactId>pipeline-model-api</artifactId>
<version>${declarative.version}</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
Expand All @@ -61,6 +62,19 @@
<artifactId>workflow-cps</artifactId>
<version>2.23</version>
</dependency>

<dependency>
<groupId>io.jenkins.blueocean</groupId>
<artifactId>blueocean</artifactId>
<version>${blueocean.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkinsci.plugins</groupId>
<artifactId>pipeline-model-definition</artifactId>
<version>${declarative.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Expand Up @@ -3,8 +3,17 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import java.util.Set;

import hudson.model.Describable;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import jenkins.tasks.SimpleBuildStep;
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStep;
import org.jenkinsci.plugins.structs.SymbolLookup;
import org.jenkinsci.plugins.structs.describable.DescribableModel;
import org.jenkinsci.plugins.structs.describable.DescribableParameter;
import org.jenkinsci.plugins.workflow.cps.Snippetizer;
Expand All @@ -25,6 +34,8 @@
import io.jenkins.blueocean.rest.ApiRoutable;
import jenkins.model.Jenkins;

import javax.annotation.CheckForNull;

/**
* This provides and Blueocean REST API endpoint to obtain pipeline step metadata.
*
Expand All @@ -33,6 +44,9 @@
*/
@Extension
public class PipelineStepMetadataService implements ApiRoutable {

final static List<String> INCLUDED_ADVANCED_STEPS = Collections.unmodifiableList(Arrays.asList("catchError"));

ParameterNameDiscoverer nameFinder = new LocalVariableTableParameterNameDiscoverer();

@Override
Expand Down Expand Up @@ -194,57 +208,132 @@ public PipelineStepMetadata[] getPipelineStepMetadata() {
List<PipelineStepMetadata> pd = new ArrayList<PipelineStepMetadata>();
// POST to this with parameter names
// e.g. json:{"time": "1", "unit": "NANOSECONDS", "stapler-class": "org.jenkinsci.plugins.workflow.steps.TimeoutStep", "$class": "org.jenkinsci.plugins.workflow.steps.TimeoutStep"}
String snippetizerUrl = Stapler.getCurrentRequest().getContextPath() + "/" + snippetizer.getUrlName() + "/generateSnippet";
String snippetizerUrl = null;
if (Stapler.getCurrentRequest() != null) {
snippetizerUrl = Stapler.getCurrentRequest().getContextPath() + "/" + snippetizer.getUrlName() + "/generateSnippet";
}

for (StepDescriptor d : StepDescriptor.all()) {
PipelineStepMetadata step = getStepMetadata(d, snippetizerUrl);
if (step != null) {
pd.add(step);
if (includeStep(d)) {
PipelineStepMetadata step = getStepMetadata(d, snippetizerUrl);
if (step != null) {
pd.add(step);
}
}
}

List<Descriptor<?>> metaStepDescriptors = new ArrayList<Descriptor<?>>();
populateMetaSteps(metaStepDescriptors, Builder.class);
populateMetaSteps(metaStepDescriptors, Publisher.class);

for (Descriptor<?> d : metaStepDescriptors) {
PipelineStepMetadata metaStep = getStepMetadata(d);
if (metaStep != null) {
pd.add(metaStep);
}
}

return pd.toArray(new PipelineStepMetadata[pd.size()]);
}

private PipelineStepMetadata getStepMetadata(StepDescriptor d, String snippetizerUrl) {
private boolean includeStep(StepDescriptor d) {
boolean include = true;
if (ModelASTStep.getBlockedSteps().containsKey(d.getFunctionName())) {
include = false;
} else if (d.isAdvanced()
&& !INCLUDED_ADVANCED_STEPS.contains(d.getFunctionName())) {
include = false;
}

return include;
}

private <T extends Describable<T>,D extends Descriptor<T>> void populateMetaSteps(List<Descriptor<?>> r, Class<T> c) {
Jenkins j = Jenkins.getInstance();
for (Descriptor<?> d : j.getDescriptorList(c)) {
if (SimpleBuildStep.class.isAssignableFrom(d.clazz) && symbolForDescriptor(d) != null) {
r.add(d);
}
}
}

private BasicPipelineStepPropertyMetadata paramFromDescribable(DescribableParameter descParam) {
BasicPipelineStepPropertyMetadata param = new BasicPipelineStepPropertyMetadata();

param.type = descParam.getErasedType();
Type typ = descParam.getType().getActualType();
if (typ instanceof ParameterizedType) {
Type[] typeArgs = ((ParameterizedType) typ).getActualTypeArguments();
for (Type ptyp : typeArgs) {
if (ptyp instanceof Class<?>) {
param.collectionTypes.add((Class<?>) ptyp);
}
}
}
param.name = descParam.getName();
param.displayName = descParam.getCapitalizedName();
param.isRequired = descParam.isRequired();

Descriptor<?> pd = Descriptor.findByDescribableClassName(ExtensionList.lookup(Descriptor.class),
param.type.getName());

if (pd != null) {
param.descriptorUrl = pd.getDescriptorFullUrl();
}

return param;
}

private @CheckForNull String symbolForDescriptor(Descriptor<?> d) {
Set<String> symbols = SymbolLookup.getSymbolValue(d);
if (!symbols.isEmpty()) {
return symbols.iterator().next();
} else {
return null;
}
}

private @CheckForNull PipelineStepMetadata getStepMetadata(Descriptor<?> d) {
String symbol = symbolForDescriptor(d);

if (symbol != null) {
BasicPipelineStepMetadata step = new BasicPipelineStepMetadata(symbol, d.clazz, d.getDisplayName());
DescribableModel<?> m = new DescribableModel<>(d.clazz);
step.descriptorUrl = d.getDescriptorUrl();
step.hasSingleRequiredParameter = m.hasSingleRequiredParameter();

for (DescribableParameter descParam : m.getParameters()) {
step.props.add(paramFromDescribable(descParam));
}

return step;
} else {
return null;
}
}

private @CheckForNull PipelineStepMetadata getStepMetadata(StepDescriptor d, @CheckForNull String snippetizerUrl) {
BasicPipelineStepMetadata step = new BasicPipelineStepMetadata(d.getFunctionName(), d.clazz, d.getDisplayName());

try {
DescribableModel<?> model = new DescribableModel<>(step.type);

step.snippetizerUrl = snippetizerUrl + "?$class=" + d.clazz.getName(); // this isn't really accurate
if (snippetizerUrl != null) {
step.snippetizerUrl = snippetizerUrl + "?$class=" + d.clazz.getName(); // this isn't really accurate
}

step.isWrapper = d.takesImplicitBlockArgument();
step.requiredContext.addAll(d.getRequiredContext());
step.providedContext.addAll(d.getProvidedContext());
step.descriptorUrl = d.getDescriptorFullUrl();
if (Stapler.getCurrentRequest() != null) {
step.descriptorUrl = d.getDescriptorFullUrl();
} else {
step.descriptorUrl = d.getDescriptorUrl();
}
step.hasSingleRequiredParameter = model.hasSingleRequiredParameter();

for (DescribableParameter descParam : model.getParameters()) {
BasicPipelineStepPropertyMetadata param = new BasicPipelineStepPropertyMetadata();

param.type = descParam.getErasedType();
Type typ = descParam.getType().getActualType();
if (typ instanceof ParameterizedType) {
Type[] typeArgs = ((ParameterizedType)typ).getActualTypeArguments();
for (Type ptyp : typeArgs) {
if (ptyp instanceof Class<?>) {
param.collectionTypes.add((Class<?>)ptyp);
}
}
}
param.name = descParam.getName();
param.displayName = descParam.getCapitalizedName();
param.isRequired = descParam.isRequired();

Descriptor<?> pd = Descriptor.findByDescribableClassName(ExtensionList.lookup(Descriptor.class),
param.type.getName());

if (pd != null) {
param.descriptorUrl = pd.getDescriptorFullUrl();
}

step.props.add(param);
step.props.add(paramFromDescribable(descParam));
}

// Let any decorators adjust the step properties
Expand Down
@@ -1,16 +1,26 @@
package io.blueocean.rest.pipeline.editor;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.JSONWebResponse;
import org.jvnet.hudson.test.recipes.WithPlugin;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;


/**
* Basic tests for {@link PipelineStepMetadataService}
Expand All @@ -35,4 +45,31 @@ public void testBasicStepsReturned() throws IOException {
}
assert(node != null) : "PipelineStepMetadata node not found";
}

@Test
public void verifyFunctionNames() throws Exception {
PipelineStepMetadataService svc = new PipelineStepMetadataService();

List<PipelineStepMetadata> steps = new ArrayList<>();

steps.addAll(Arrays.asList(svc.getPipelineStepMetadata()));

assertFalse(steps.isEmpty());

// Verify we have a Symbol-provided Builder or Publisher
assertThat(steps, hasItem(stepWithName("archiveArtifacts")));

// Verify that we don't have steps blacklisted by Declarative
assertThat(steps, not(hasItem(stepWithName("properties"))));

// Verify that we don't have advanced steps
assertThat(steps, not(hasItem(stepWithName("archive"))));

// Verify that we *do* have advanced steps that are explicitly whitelisted in.
assertThat(steps, hasItem(stepWithName("catchError")));
}

private Matcher<? super PipelineStepMetadata> stepWithName(String stepName) {
return hasProperty("functionName", is(stepName));
}
}

0 comments on commit 7349001

Please sign in to comment.