Skip to content

Commit

Permalink
Add buildWithEiffel pipeline step
Browse files Browse the repository at this point in the history
This is a new pipeline step that extends BuildTriggerStep from
jenkinsci/pipeline-build-step-plugin#112 in order to add a new build
action EiffelActivityDataAction to the downstream build. This build 
action will supplement Eiffel activity data that will override
corresponding data fields in the EiffelActivityTriggeredEvent when
the downstream build enters the build queue.

Unfortunately, in order to add a new action to triggered builds,
the step execution of BuildTriggerStep had to be modified, which
meant that a number of project files had to be copied with slight
modifications to ensure that there is no interference when running
with both jenkinsci/eiffel-broadcaster-plugin and
jenkinsci/pipeline-build-step-plugin. Also, to ensure the underlying 
build trigger functionality inherited by the buildWithEiffel step,
a regression test suite has been added, containing all the
BuildTriggerStep tests. These tests have been updated to use
buildWithEiffel and the suite itself is excluded from normal testing
to reduce execution time.

Currently, buildWithEiffel step only supports activity name.
  • Loading branch information
CJohanH committed Mar 7, 2024
1 parent 31dc8fc commit ac48793
Show file tree
Hide file tree
Showing 30 changed files with 2,003 additions and 11 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ in each job. Duplicate entries will be eliminated.

## Pipeline steps

### buildWithEiffel

The buildWithEiffel pipeline step is an extension of [BuildTriggerStep](https://github.com/jenkinsci/pipeline-build-step-plugin/blob/491.v1fec530da_858/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep.java)
from [pipeline-build-step-plugin 491.v1fec530da_858](https://github.com/jenkinsci/pipeline-build-step-plugin/tree/491.v1fec530da_858) that can override event data in the EiffelActivityTriggeredEvent (ActT) that is sent when the triggered downstream build enters the
build queue. Currently, `data.name` can be overridden.

In addition to the parameters in the base step (see [Pipeline: Build step](https://plugins.jenkins.io/pipeline-build-step/releases/#version_491.v1fec530da_858))
it accepts the following parameters:

| Argument | Required | Description |
|--------------|----------|--------------------------------------------------|
| activityName | | The Eiffel activity name of the triggered build. |

Example:
```
buildWithEiffel job: "foo/bar", propagate: false, waitForStart: true, activityName: "activity_name"
```

### createPackageURL

The createPackageURL pipeline step accepts individual components of a
Expand Down
29 changes: 26 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,11 @@
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>matrix-project</artifactId>
<scope>test</scope>
<artifactId>pipeline-build-step</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>pipeline-build-step</artifactId>
<artifactId>matrix-project</artifactId>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -180,6 +179,30 @@
<artifactId>toxiproxy</artifactId>
<scope>test</scope>
</dependency>
<!--Dependencies for regression testing of BuildWithEiffelStep-->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<!--
workflow-cps:tests 3691.v28b_14c465a_b_b_ is used because
older versions are not compatible with parent pom.
-->
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<classifier>tests</classifier>
<version>3691.v28b_14c465a_b_b_</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
The MIT License
Copyright 2024 Axis Communications AB.
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.
*/

package com.axis.jenkins.plugins.eiffel.eiffelbroadcaster;

import com.axis.jenkins.plugins.eiffel.eiffelbroadcaster.eiffel.EiffelActivityTriggeredEvent;
import com.axis.jenkins.plugins.eiffel.eiffelbroadcaster.pipeline.build.BuildWithEiffelStep;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.Action;

/**
* An {@link Action} for storing the data fields of {@link EiffelActivityTriggeredEvent.Data} for Eiffel activity event
* {@link EiffelActivityTriggeredEvent} ActT, that enables the option of setting custom data when the event fires
* as a build enters a waiting state in the build queue. Currently, only name field is supported.
*
* This action is instantiated when a {@link BuildWithEiffelStep} step is executed and will always contain
* a name.
*/
public class EiffelActivityDataAction implements Action {

/** The activity name of the Eiffel activity event {@link EiffelActivityTriggeredEvent} ActT*/
private final String name;

public EiffelActivityDataAction(@NonNull String name) {
this.name=name;
}

@CheckForNull
public String getName() {
return name;
}

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

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

@CheckForNull
@Override
public String getUrlName() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ of this software and associated documentation files (the "Software"), to deal
import hudson.model.queue.QueueListener;
import hudson.triggers.SCMTrigger;
import hudson.triggers.TimerTrigger;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.TreeSet;
import java.util.stream.Collectors;

/**
* Receives notifications about when tasks are submitted to the
Expand Down Expand Up @@ -70,6 +70,16 @@ public void onEnterWaiting(Queue.WaitingItem wi) {
var event = new EiffelActivityTriggeredEvent(data);
EiffelJobTable.getInstance().setEventTrigger(wi.getId(), event.getMeta().getId());

// Override activity name if item has action EiffelActivityDataAction
wi.getAllActions().stream()
.filter(action -> action instanceof EiffelActivityDataAction)
.findFirst()
.ifPresent(action -> {
// This should be moved into a new method when more overrides are added to buildWithEiffelStep
var activityName = ((EiffelActivityDataAction) action).getName();
data.setName(activityName == null ? data.getName() : activityName);
});

// Populate activity categories
var categories = new TreeSet<String>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// The following code is copied from https://github.com/jenkinsci/pipeline-build-step-plugin

package com.axis.jenkins.plugins.eiffel.eiffelbroadcaster.pipeline.build;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.model.Action;
import hudson.model.Actionable;
import hudson.model.InvisibleAction;
import hudson.model.Queue;
import hudson.model.queue.FoldableAction;
import org.jenkinsci.plugins.workflow.steps.StepContext;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

@SuppressWarnings("SynchronizeOnNonFinalField")
class BuildWithEiffelAction extends InvisibleAction implements FoldableAction {

private static final Logger LOGGER = Logger.getLogger(BuildWithEiffelAction.class.getName());

@Deprecated
private StepContext context;

@Deprecated
private Boolean propagate;

/** Record of one upstream build step. */
static class Trigger {

final StepContext context;

final boolean propagate;
final boolean waitForStart;

/** Record of cancellation cause passed to {@link BuildWithEiffelStepExecution#stop}, if any. */
@CheckForNull
Throwable interruption;

Trigger(StepContext context, boolean propagate, boolean waitForStart) {
this.context = context;
this.propagate = propagate;
this.waitForStart = waitForStart;
}

}

private /* final */ List<BuildWithEiffelAction.Trigger> triggers;

BuildWithEiffelAction(StepContext context, boolean propagate, boolean waitForStart) {
triggers = new ArrayList<>();
triggers.add(new BuildWithEiffelAction.Trigger(context, propagate, waitForStart));
}

private Object readResolve() {
if (triggers == null) {
triggers = new ArrayList<>();
triggers.add(new BuildWithEiffelAction.Trigger(context, propagate != null ? propagate : /* old serialized record */ true, false));
context = null;
propagate = null;
}
return this;
}

static Iterable<BuildWithEiffelAction.Trigger> triggersFor(Actionable actionable) {
List<BuildWithEiffelAction.Trigger> triggers = new ArrayList<>();
for (BuildWithEiffelAction action : actionable.getActions(BuildWithEiffelAction.class)) {
synchronized (action.triggers) {
triggers.addAll(action.triggers);
}
}
return triggers;
}

@Override public void foldIntoExisting(Queue.Item item, Queue.Task owner, List<Action> otherActions) {
// there may be >1 upstream builds (or other unrelated causes) for a single downstream build
BuildWithEiffelAction existing = item.getAction(BuildWithEiffelAction.class);
if (existing == null) {
item.addAction(this);
} else {
synchronized (existing.triggers) {
existing.triggers.addAll(triggers);
}
}
LOGGER.log(Level.FINE, "coalescing actions for {0}", item);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* The MIT License
*
* Copyright 2019 CloudBees, Inc.
*
* 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.
*/

// The following code is copied from https://github.com/jenkinsci/pipeline-build-step-plugin

package com.axis.jenkins.plugins.eiffel.eiffelbroadcaster.pipeline.build;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.console.ModelHyperlinkNote;
import hudson.model.Run;
import hudson.model.TaskListener;
import jenkins.model.CauseOfInterruption;

/**
* Indicates that an upstream build failed because of a downstream build’s status.
*/
public final class BuildWithEiffelDownstreamFailureCause extends CauseOfInterruption {

private static final long serialVersionUID = 1;

private final String id;

BuildWithEiffelDownstreamFailureCause(Run<?, ?> downstream) {
id = downstream.getExternalizableId();
}

public @CheckForNull Run<?, ?> getDownstreamBuild() {
return Run.fromExternalizableId(id);
}

@Override public void print(TaskListener listener) {
String description;
Run<?, ?> downstream = getDownstreamBuild();
if (downstream != null) {
// encodeTo(Run) calls getDisplayName, which does not include the project name.
description = ModelHyperlinkNote.encodeTo("/" + downstream.getUrl(), downstream.getFullDisplayName()) + " completed with status " + downstream.getResult() + " (propagate: false to ignore)";
} else {
description = "Downstream build was not stable (propagate: false to ignore)";
}
listener.getLogger().println(description);
}

@Override public String getShortDescription() {
Run<?, ?> downstream = getDownstreamBuild();
if (downstream != null) {
return downstream.getFullDisplayName() + " completed with status " + downstream.getResult() + " (propagate: false to ignore)";
} else {
return "Downstream build was not stable (propagate: false to ignore)";
}
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// The following code is copied from https://github.com/jenkinsci/pipeline-build-step-plugin

package com.axis.jenkins.plugins.eiffel.eiffelbroadcaster.pipeline.build;

import hudson.AbortException;
import hudson.Extension;
import hudson.model.Queue;
import hudson.model.queue.QueueListener;

/**
* @author Vivek Pandey
*/
@Extension
public class BuildWithEiffelQueueListener extends QueueListener {
@Override
public void onLeft(Queue.LeftItem li) {
if(li.isCancelled()){
for (BuildWithEiffelAction.Trigger trigger : BuildWithEiffelAction.triggersFor(li)) {
trigger.context.onFailure(new AbortException("Build of " + li.task.getFullDisplayName() + " was cancelled"));
}
}
}


}
Loading

0 comments on commit ac48793

Please sign in to comment.