diff --git a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/AbstractRunImpl.java b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/AbstractRunImpl.java index 149ea1bb..ad5fdae3 100644 --- a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/AbstractRunImpl.java +++ b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/AbstractRunImpl.java @@ -16,9 +16,7 @@ import io.jenkins.blueocean.rest.model.GenericResource; import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.export.Exported; -import org.kohsuke.stapler.export.ExportedBean; -import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -176,15 +174,8 @@ public class AbstractRunImpl extends BlueRun { return null; } - public Collection getActions() { - List actionProxies = new ArrayList<>(); - for(Action action:run.getAllActions()){ - if(action == null || !action.getClass().isAnnotationPresent(ExportedBean.class)){ - continue; - } - actionProxies.add(new ActionProxiesImpl(action, this)); - } - return actionProxies; + public Collection getActions() { + return PipelineImpl.getActionProxies(run.getAllActions(), this); } protected static BlueRun getBlueRun(Run r, Link parent){ diff --git a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/MultiBranchPipelineImpl.java b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/MultiBranchPipelineImpl.java index b8f74f97..47da0905 100644 --- a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/MultiBranchPipelineImpl.java +++ b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/MultiBranchPipelineImpl.java @@ -1,14 +1,19 @@ package io.jenkins.blueocean.service.embedded.rest; +import hudson.Extension; +import hudson.model.Item; import hudson.model.Job; import hudson.model.Result; import hudson.model.Run; import io.jenkins.blueocean.commons.ServiceException; import io.jenkins.blueocean.rest.Navigable; +import io.jenkins.blueocean.rest.Reachable; import io.jenkins.blueocean.rest.hal.Link; +import io.jenkins.blueocean.rest.model.BlueActionProxy; import io.jenkins.blueocean.rest.model.BlueMultiBranchPipeline; import io.jenkins.blueocean.rest.model.BluePipeline; import io.jenkins.blueocean.rest.model.BluePipelineContainer; +import io.jenkins.blueocean.rest.model.BluePipelineFactory; import io.jenkins.blueocean.rest.model.BlueQueueItem; import io.jenkins.blueocean.rest.model.BlueRun; import io.jenkins.blueocean.rest.model.BlueRunContainer; @@ -252,8 +257,25 @@ public class MultiBranchPipelineImpl extends BlueMultiBranchPipeline { }; } + @Override + public Collection getActions() { + return PipelineImpl.getActionProxies(mbp.getAllActions(), this); + } + @Override public Link getLink() { return self; } + + @Extension(ordinal = 2) + public static class PipelineFactoryImpl extends BluePipelineFactory{ + + @Override + public BluePipeline getPipeline(Item item, Reachable parent) { + if (item instanceof MultiBranchProject) { + return new MultiBranchPipelineImpl((MultiBranchProject) item, parent.getLink()); + } + return null; + } + } } diff --git a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineContainerImpl.java b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineContainerImpl.java index 47aad867..0d65129d 100644 --- a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineContainerImpl.java +++ b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineContainerImpl.java @@ -8,6 +8,7 @@ import io.jenkins.blueocean.commons.ServiceException; import io.jenkins.blueocean.rest.hal.Link; import io.jenkins.blueocean.rest.model.BluePipeline; import io.jenkins.blueocean.rest.model.BluePipelineContainer; +import io.jenkins.blueocean.rest.model.BluePipelineFactory; import jenkins.branch.MultiBranchProject; import jenkins.model.Jenkins; import org.jenkinsci.plugins.workflow.job.WorkflowJob; @@ -49,16 +50,12 @@ public class PipelineContainerImpl extends BluePipelineContainer { } public BluePipeline get(Item item){ - if (item instanceof BuildableItem) { - if (item instanceof MultiBranchProject) { - return new MultiBranchPipelineImpl((MultiBranchProject) item, getLink()); - } else if (item instanceof Job) { - return new PipelineImpl((Job) item, getLink()); + for(BluePipelineFactory factory:BluePipelineFactory.all()){ + BluePipeline pipeline = factory.getPipeline(item, this); + if( pipeline!= null){ + return pipeline; } - } else if (item instanceof ItemGroup) { - return new PipelineFolderImpl((ItemGroup) item, getLink()); } - // TODO: I'm going to turn this into a decorator annotation throw new ServiceException.NotFoundException(String.format("Pipeline %s not found", item.getName())); } diff --git a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineFolderImpl.java b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineFolderImpl.java index 5a94e254..a42b1123 100644 --- a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineFolderImpl.java +++ b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineFolderImpl.java @@ -1,14 +1,22 @@ package io.jenkins.blueocean.service.embedded.rest; +import hudson.Extension; +import hudson.model.Item; import hudson.model.ItemGroup; import io.jenkins.blueocean.commons.ServiceException; +import io.jenkins.blueocean.rest.Reachable; import io.jenkins.blueocean.rest.hal.Link; +import io.jenkins.blueocean.rest.model.BlueActionProxy; import io.jenkins.blueocean.rest.model.BluePipeline; import io.jenkins.blueocean.rest.model.BluePipelineContainer; +import io.jenkins.blueocean.rest.model.BluePipelineFactory; import io.jenkins.blueocean.rest.model.BluePipelineFolder; import io.jenkins.blueocean.service.embedded.util.FavoriteUtil; import org.kohsuke.stapler.json.JsonBody; +import java.util.Collection; +import java.util.Collections; + import static io.jenkins.blueocean.service.embedded.rest.PipelineImpl.getRecursivePathFromFullName; /** @@ -44,6 +52,11 @@ public class PipelineFolderImpl extends BluePipelineFolder { return folder.getFullName(); } + @Override + public Collection getActions() { + return Collections.emptyList(); + } + @Override public BluePipelineContainer getPipelines() { return new PipelineContainerImpl(folder); @@ -86,4 +99,15 @@ public class PipelineFolderImpl extends BluePipelineFolder { return OrganizationImpl.INSTANCE.getLink().rel("pipelines").rel(getRecursivePathFromFullName(this)); } + @Extension(ordinal = 0) + public static class PipelineFactoryImpl extends BluePipelineFactory{ + + @Override + public BluePipeline getPipeline(Item item, Reachable parent) { + if (item instanceof ItemGroup) { + return new PipelineFolderImpl((ItemGroup) item, parent.getLink()); + } + return null; + } + } } diff --git a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineImpl.java b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineImpl.java index 8303faa5..29466d5d 100644 --- a/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineImpl.java +++ b/blueocean-rest-impl/src/main/java/io/jenkins/blueocean/service/embedded/rest/PipelineImpl.java @@ -1,13 +1,18 @@ package io.jenkins.blueocean.service.embedded.rest; +import hudson.Extension; +import hudson.model.Action; import hudson.model.BuildableItem; import hudson.model.Item; import hudson.model.ItemGroup; import hudson.model.Job; import io.jenkins.blueocean.commons.ServiceException; import io.jenkins.blueocean.rest.Navigable; +import io.jenkins.blueocean.rest.Reachable; import io.jenkins.blueocean.rest.hal.Link; +import io.jenkins.blueocean.rest.model.BlueActionProxy; import io.jenkins.blueocean.rest.model.BluePipeline; +import io.jenkins.blueocean.rest.model.BluePipelineFactory; import io.jenkins.blueocean.rest.model.BlueQueueContainer; import io.jenkins.blueocean.rest.model.BlueRun; import io.jenkins.blueocean.rest.model.BlueRunContainer; @@ -15,16 +20,21 @@ import io.jenkins.blueocean.service.embedded.util.FavoriteUtil; import jenkins.branch.MultiBranchProject; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.WebMethod; +import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.json.JsonBody; import org.kohsuke.stapler.verb.DELETE; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import static io.jenkins.blueocean.service.embedded.rest.PipelineContainerImpl.isMultiBranchProjectJob; /** * @author Kohsuke Kawaguchi */ + public class PipelineImpl extends BluePipeline { /*package*/ final Job job; @@ -93,6 +103,11 @@ public class PipelineImpl extends BluePipeline { return new RunContainerImpl(this, job); } + @Override + public Collection getActions() { + return getActionProxies(job.getAllActions(), this); + } + @Override @Navigable public BlueQueueContainer getQueue() { @@ -140,8 +155,6 @@ public class PipelineImpl extends BluePipeline { @Override public Link getLink() { -// Link parentLink = (parent == null) ? OrganizationImpl.INSTANCE.getLink().rel("pipelines") : parent; - return OrganizationImpl.INSTANCE.getLink().rel("pipelines").rel(getRecursivePathFromFullName(this)); } @@ -164,4 +177,28 @@ public class PipelineImpl extends BluePipeline { return pipelinePath.toString(); } + @Extension(ordinal = 1) + public static class PipelineFactoryImpl extends BluePipelineFactory { + + @Override + public BluePipeline getPipeline(Item item, Reachable parent) { + if (item instanceof Job) { + return new PipelineImpl((Job) item, parent.getLink()); + } + return null; + } + } + + public static Collection getActionProxies(List actions, Reachable parent){ + List actionProxies = new ArrayList<>(); + for(Action action:actions){ + if(action == null || !action.getClass().isAnnotationPresent(ExportedBean.class)){ + continue; + } + actionProxies.add(new ActionProxiesImpl(action, parent)); + } + return actionProxies; + + } + } diff --git a/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/PipelineApiTest.java b/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/PipelineApiTest.java index 633bb816..95cdcec6 100644 --- a/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/PipelineApiTest.java +++ b/blueocean-rest-impl/src/test/java/io/jenkins/blueocean/service/embedded/PipelineApiTest.java @@ -2,6 +2,7 @@ package io.jenkins.blueocean.service.embedded; import com.google.common.collect.ImmutableMap; import com.mashape.unirest.http.HttpResponse; +import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.model.AbstractBuild; @@ -10,6 +11,9 @@ import hudson.model.Cause; import hudson.model.CauseAction; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; +import hudson.model.Item; +import hudson.model.ItemGroup; +import hudson.model.Job; import hudson.model.ParametersAction; import hudson.model.ParametersDefinitionProperty; import hudson.model.Project; @@ -21,6 +25,11 @@ import hudson.tasks.ArtifactArchiver; import hudson.tasks.Shell; import hudson.tasks.junit.JUnitResultArchiver; import hudson.tasks.junit.TestResultAction; +import io.jenkins.blueocean.rest.Reachable; +import io.jenkins.blueocean.rest.hal.Link; +import io.jenkins.blueocean.rest.model.BluePipeline; +import io.jenkins.blueocean.rest.model.BluePipelineFactory; +import io.jenkins.blueocean.service.embedded.rest.PipelineImpl; import jenkins.model.Jenkins; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; @@ -30,6 +39,7 @@ import org.junit.Test; import org.jvnet.hudson.test.MockFolder; import org.jvnet.hudson.test.TestBuilder; import org.kohsuke.stapler.AcceptHeader; +import org.kohsuke.stapler.export.Exported; import java.io.IOException; import java.util.List; @@ -532,4 +542,40 @@ public class PipelineApiTest extends BaseTest { Assert.assertNotNull(p3.getQueueItem()); Assert.assertEquals(Long.toString(p3.getQueueItem().getId()), r.get("id")); } + + @Test + public void getPipelinesExtensionTest() throws Exception { + + Project p = j.createFreeStyleProject("pipeline1"); + + Map response = get("/organizations/jenkins/pipelines/pipeline1"); + validatePipeline(p, response); + + Assert.assertEquals("hello world!", response.get("hello")); + } + + @Extension(ordinal = 3) + public static class PipelineFactoryTestImpl extends BluePipelineFactory { + + @Override + public BluePipeline getPipeline(Item item, Reachable parent) { + if(item instanceof Job){ + return new TestPipelineImpl(null, (Job)item, parent.getLink()); + } + return null; + } + } + + public static class TestPipelineImpl extends PipelineImpl { + + public TestPipelineImpl(ItemGroup folder, Job job, Link parent) { + super(folder, job, parent); + } + + @Exported(name = "hello") + public String getHello(){ + return "hello world!"; + } + } + } diff --git a/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BluePipeline.java b/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BluePipeline.java index 704cf0de..a16b1cc5 100644 --- a/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BluePipeline.java +++ b/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BluePipeline.java @@ -6,6 +6,8 @@ import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.json.JsonBody; import org.kohsuke.stapler.verb.PUT; +import java.util.Collection; + /** * Defines pipeline state and its routing * @@ -20,6 +22,7 @@ public abstract class BluePipeline extends Resource { public static final String LATEST_RUN = "latestRun"; public static final String ESTIMATED_DURATION = "estimatedDurationInMillis"; public static final String LAST_SUCCESSFUL_RUN = "lastSuccessfulRun"; + public static final String ACTIONS = "actions"; /** * @return name of the organization @@ -74,6 +77,15 @@ public abstract class BluePipeline extends Resource { @Navigable public abstract BlueRunContainer getRuns(); + + /** + * + * @return Gives Actions associated with this Run + */ + @Navigable + @Exported(name = ACTIONS, inline = true) + public abstract Collection getActions(); + /** * @return Gives {@link BlueQueueContainer} */ diff --git a/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BluePipelineFactory.java b/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BluePipelineFactory.java new file mode 100644 index 00000000..a01d2e7b --- /dev/null +++ b/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BluePipelineFactory.java @@ -0,0 +1,23 @@ +package io.jenkins.blueocean.rest.model; + +import hudson.ExtensionList; +import hudson.ExtensionPoint; +import hudson.model.Item; +import io.jenkins.blueocean.rest.Reachable; + +/** + * Factory that gives instance of {@link BluePipeline} + * + * It's useful for example in cases where a plugin that has custom project and they want to serve + * extra meta-data thru BluePipeline, would provide implementation of their BluePipeline and and implementation + * of BluePipelineFactory. + * + * @author Vivek Pandey + */ +public abstract class BluePipelineFactory implements ExtensionPoint { + public abstract BluePipeline getPipeline(Item item, Reachable parent); + + public static ExtensionList all(){ + return ExtensionList.lookup(BluePipelineFactory.class); + } +} diff --git a/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BlueRun.java b/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BlueRun.java index 753124cb..4ba4acbe 100644 --- a/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BlueRun.java +++ b/blueocean-rest/src/main/java/io/jenkins/blueocean/rest/model/BlueRun.java @@ -162,13 +162,19 @@ public abstract class BlueRun extends Resource { @Navigable public abstract BluePipelineNodeContainer getNodes(); + /** + * + * @return Gives Actions associated with this Run + */ + @Navigable @Exported(name = ACTIONS, inline = true) - public abstract Collection getActions(); + public abstract Collection getActions(); /** * @return Gives steps from pipeline. The list of steps must not include stages, this is because stage could be * interpreted as step as its StepAtomNode and implementation of this API must ensure not to include it. */ + @Navigable @Exported(name = STEPS) public abstract BluePipelineStepContainer getSteps(); @@ -182,6 +188,7 @@ public abstract class BlueRun extends Resource { * X-TEXT-SIZE header value with *start* query parameter. * */ + @Navigable public abstract Object getLog(); public enum BlueRunState {