Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #5 from fbelzunc/JENKINS-28632-clean-example
[FIXED JENKINS-28632] Workflow step for awaiting for a deployment
  • Loading branch information
fbelzunc committed Dec 17, 2015
2 parents 24c9e56 + 257f038 commit cc6becf
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 1 deletion.
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -36,6 +36,11 @@
<version>2.15</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-api</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

<scm>
Expand Down
Expand Up @@ -36,14 +36,18 @@ public int getThreshold() {
return threshold;
}


@Override
public RangeSet calcMatchingBuildNumberOf(Job upstream, DeploymentFacet<?> facet) {
Fingerprint f = facet.getFingerprint();
RangeSet r;

if (upstream==null) return new RangeSet();
RangeSet r = f.getRangeSet(upstream);

r = f.getRangeSet(upstream);
if (r.isEmpty()) return new RangeSet();


// at this point, we verified that the fingerprint touches the project we care about

// count the deployment
Expand Down
@@ -0,0 +1,68 @@
package org.jenkinsci.plugins.deployment.workflowsteps;

import hudson.model.Run;
import jenkins.model.RunAction2;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
* AwaitDeployment action
*/
public class AwaitDeploymentAction implements RunAction2 {

private final String message;
private final List<AwaitDeploymentStepExecution> executions = new ArrayList<AwaitDeploymentStepExecution>();

@DataBoundConstructor
public AwaitDeploymentAction(String message) {
this.message = message;
}

private transient Run<?,?> run;
@Override
public void onAttached(Run<?, ?> run) {
this.run = run;
}

@Override
public void onLoad(Run<?, ?> run) {
this.run = run;
assert executions != null && !executions.contains(null) : executions;
for (AwaitDeploymentStepExecution step : executions) {
step.run = run;
}
}

@Override
public String getIconFileName() {
return null;
}

@Override
public String getDisplayName() {
return null;
}

@Override
public String getUrlName() {
return null;
}

/**
* Called when {@link AwaitDeploymentAction} is completed to remove it from the active input list.
*/
public synchronized void remove(AwaitDeploymentStepExecution exec) throws IOException {
executions.remove(exec);
run.save();
}

public synchronized void add(@Nonnull AwaitDeploymentStepExecution step) throws IOException {
this.executions.add(step);
run.save();
}

}
@@ -0,0 +1,55 @@
package org.jenkinsci.plugins.deployment.workflowsteps;

import hudson.Extension;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.Serializable;

/**
* Workflow Step for awaiting for a deployment
*/
public class AwaitDeploymentStep extends AbstractStepImpl implements Serializable {

private final int threshold;
private final String env;

@DataBoundConstructor
public AwaitDeploymentStep(int threshold, String env) {
this.threshold = threshold;
this.env = env;
}

public int getThreshold() {
return threshold;
}

public String getEnv() {
return env;
}

@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl)super.getDescriptor();
}

@Extension
public static class DescriptorImpl extends AbstractStepDescriptorImpl {

public DescriptorImpl() {
super(AwaitDeploymentStepExecution.class);
}

@Override
public String getFunctionName() {
return "awaitDeployment";
}

@Override
public String getDisplayName() {
return "Awaiting for deployment";
}
}

}
@@ -0,0 +1,58 @@
package org.jenkinsci.plugins.deployment.workflowsteps;

import com.google.inject.Inject;
import hudson.model.Fingerprint;
import hudson.model.Job;
import hudson.model.Run;
import org.jenkinsci.plugins.deployment.DeploymentFacet;
import org.jenkinsci.plugins.deployment.HostRecord;
import org.jenkinsci.plugins.deployment.conditions.ThresholdCondition;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;

import java.util.concurrent.ScheduledFuture;

/**
* The Execution for AwaitDeploymentStep
*/
public class AwaitDeploymentStepExecution extends AbstractStepExecutionImpl {

@Inject(optional=true) private transient AwaitDeploymentStep awaitDeploymentStep;
@StepContextParameter private transient FlowNode node;
@StepContextParameter transient Run run;
@StepContextParameter private transient Run build;
@StepContextParameter private transient Job job;
private transient volatile ScheduledFuture<?> task;

@Override
public boolean start() throws Exception {
node.addAction(new AwaitDeploymentAction("Await for deployment"));

return false;
}

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

public void proceed(DeploymentFacet<?> facet, HostRecord newRecord) {
ThresholdCondition thresholdCondition = new ThresholdCondition(awaitDeploymentStep.getEnv(), awaitDeploymentStep.getThreshold());
Fingerprint.RangeSet r = thresholdCondition.calcMatchingBuildNumberOf(job, facet.getFingerprint());

if (!r.isEmpty()) {
for (Integer n : r.listNumbers()) {
Run b = job.getBuildByNumber(n);
if (b!=null) {
if (b.getId().equals(build.getId())) {
getContext().onSuccess(null);
}
}
}
}
}

}
@@ -0,0 +1,43 @@
package org.jenkinsci.plugins.deployment.workflowsteps;

import com.google.common.base.Function;
import hudson.Extension;
import org.jenkinsci.plugins.deployment.DeploymentFacet;
import org.jenkinsci.plugins.deployment.DeploymentFacetListener;
import org.jenkinsci.plugins.deployment.DeploymentTrigger;
import org.jenkinsci.plugins.deployment.HostRecord;

import javax.annotation.Nonnull;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Deployment Listener for Workflow jobs
*/
@Extension
public class WorkflowListenerImpl extends DeploymentFacetListener {

@Override
public void onChange(final DeploymentFacet facet, final HostRecord newRecord) {
LOGGER.log(Level.FINE, "Deployment triggered");
POOL.submit(new Runnable() {
public void run() {
AwaitDeploymentStepExecution.applyAll(AwaitDeploymentStepExecution.class, new Function<AwaitDeploymentStepExecution, Void>() {
@Override public Void apply(@Nonnull AwaitDeploymentStepExecution awaitDeploymentStepExecution) {
awaitDeploymentStepExecution.proceed(facet, newRecord);
return null;
}
});
}
});
}

public final ExecutorService POOL = new ThreadPoolExecutor(0, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
private static final Logger LOGGER = Logger.getLogger(DeploymentTrigger.class.getName());

}
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright 2015 Felix Belzunce Arcos.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:entry field="threshold" title="${%# of Servers}">
<f:number clazz="positive-number"/>
</f:entry>
<f:entry field="env" title="${%Environment}">
<f:textbox/>
</f:entry>
</j:jelly>
@@ -0,0 +1,5 @@
<div>
Pauses a Workflow job until artifacts are deployed to servers via configuration management tools
like Chef/Puppet/etc. For this feature to work, you must also install a plugin that's specific to the configuration
management tool you are using, such as the puppet plugin.
</div>

0 comments on commit cc6becf

Please sign in to comment.