This Jenkins plugin sends Eiffel events to a Message Queue. For now, only RabbitMQ is supported. The plugin can be extended with more events and or more data but the required data is there. A detailed list of event representations can be found in the table below.
Read more about the Eiffel protocol at https://eiffel-community.github.io/. The plugin and its features were presented at the 2021 Eiffel Summit; the presentation slides and a video are available.
A number of Jenkins events automatically result in Eiffel events. Additionally, builds have various options of emitting events of their own.
Jenkins Event | Eiffel Event |
---|---|
Job Queued | EiffelActivityTriggeredEvent |
Job Dequeued (canceled) | EiffelActivityCanceledEvent |
Job Starts | EiffelActivityStartedEvent |
Job Finishes | EiffelActivityFinishedEvent |
Job Successful | EiffelActivityFinishedEvent |
Job Unstable | EiffelActivityFinishedEvent |
Job Failed | EiffelActivityFinishedEvent |
Job Aborted | EiffelActivityFinishedEvent |
The plugin conforms to the Paris edition of the Eiffel protocol for the events it emits. Users of the provided sendEiffelEvent pipeline step may choose to emit events from any Eiffel edition up to and including the Orizaba edition. See the documentation of each event for details of the corresponding event version used.
If a build needs to emit Eiffel events of its own they should probably have a CONTEXT or CAUSE link to the build's EiffelActivityTriggeredEvent. The plugins injects the following environment variables with the contents of the previously sent events:
EIFFEL_ACTIVITY_TRIGGERED
: The build's EiffelActivityTriggeredEvent event.EIFFEL_ACTIVITY_STARTED
: The build's EiffelActivityStartedEvent event.
The contents of the data.categories
member in EiffelActivityTriggeredEvent is configurable in two ways:
- A global plugin setting.
- A setting in each job, configured via the UI or the
eiffelActivity
pipeline property:
properties([
eiffelActivity(categories: ['jenkins', 'maven'])
])
Categories can e.g. be used to distinguish Eiffel activities that represent Jenkins builds from other kinds of activities, or distinguish between different kinds of Jenkins builds.
Globally configured categories will be merged with the categories specified in each job. Duplicate entries will be eliminated.
The buildWithEiffel pipeline step is a modified copy of BuildTriggerStep from pipeline-build-step-plugin 540.vb_e8849e1a_b_d8 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) 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"
The createPackageURL pipeline step accepts individual components of a Package URL and returns a properly formatted and quoted string that e.g. can be used when piecing together an EiffelArtifactedCreatedEvent. See the Package URL specification for details on what the components mean.
Argument | Required | Description |
---|---|---|
type | ✔ | The package "type" or package "protocol" such as maven, npm, nuget, gem, pypi, etc. |
namespace | A name prefix such as a Maven groupid, a Docker image owner, a GitHub user or organization. | |
name | ✔ | The name of the package. |
version | The version of the package. | |
qualifiers | A Groovy map with extra qualifying data for a package such as an OS, architecture, a distro, etc. | |
subpath | Extra subpath within a package, relative to the package root. |
Example:
def purl = createPackageURL type: 'generic', namespace: 'name/space',
name: 'pkgname', version: '1.0', subpath: 'some/path', qualifiers: [a: 'b']
echo "Here's the resulting purl: ${purl}"
The publishEiffelArtifacts pipeline step sends an EiffelArtifactPublishedEvent for each EiffelArtifactCreatedEvent that has been recorded in the build using a sendEiffelEvent step with the publishArtifact
argument enabled and for each artifact recorded in the specified JSON files in the workspace.
This requires that each EiffelArtifactCreatedEvent has at least one file defined in its data.fileInformation
array and that each relative file path in data.fileInformation.name
matches a Jenkins artifact in the build. Because of the latter requirement it's normally used after an archiveArtifacts step.
The EiffelArtifactPublishedEvent will have two links; one ARTIFACT link to the EiffelArtifactCreatedEvent and one CONTEXT link to the parent build's EiffelActivityTriggeredEvent.
This step returns immediately as soon as the event has been validated and put in the internal outbound queue. The actual delivery of the event to the broker might not have happened at the time of the return. The step's return value is a (possibly empty) list of enqueued events.
Argument | Required | Description |
---|---|---|
artifactEventFiles | An Ant-style glob expression that selects files containing JSON representations (one per line) of EiffelArtifactCreatedEvent to publish. |
Example of publishing artifacts connected to the build:
def event = [
'meta': [
'type': 'EiffelArtifactCreatedEvent',
'version': '3.0.0',
],
'data': [
'identity': 'pkg:generic/myprogram@1.0',
'fileInformation': [
[
'name': 'myprogram-1.0.tar.gz',
],
],
],
]
sendEiffelEvent event: event, publishArtifact: true
archiveArtifacts artifacts: 'myprogram-1.0.tar.gz'
publishEiffelArtifacts()
Example of publishing artifacts from a file:
def event = [
'meta': [
'type': 'EiffelArtifactCreatedEvent',
'version': '3.0.0',
],
'data': [
'identity': 'pkg:generic/myprogram@1.0',
'fileInformation': [
[
'name': 'myprogram-1.0.tar.gz',
],
],
],
]
writeJSON file: 'events.json', json: event
archiveArtifacts artifacts: 'myprogram-1.0.tar.gz'
publishEiffelArtifacts artifactEventFiles: '*.json'
The sendEiffelEvent pipeline step sends an Eiffel event from that's built in the Groovy code or read into a Groovy map from another location. It accepts the following parameters:
Argument | Required | Description |
---|---|---|
event | ✔ | A map with the event payload. The meta.id and meta.time members will be populated automatically. |
linkToActivity | If true (default) the event sent will automatically include link to the current build's EiffelActivityTriggeredEvent. | |
activityLinkType | The link type to use when linking to the EiffelActivityTriggeredEvent. Defaults to CONTEXT but can be set to CAUSE. | |
publishArtifact | If true and the event being sent is EiffelArtifactCreatedEvent it will be recorded for possible later use by the publishEiffelArtifacts step. | |
signatureCredentialsId | The id of the credentials containing the private key to use when signing. Set to an empty string (default) if the event shouldn't be signed. | |
signatureHashAlgorithm | If signatureCredentialsId is set | The name of the hash algorithm to use when signing. Must match the key algorithm; see the table in §3.1 of RFC 7518. Valid values are "SHA-256", "SHA-384", and "SHA-512". |
Example:
def event = [
"meta": [
"type": "EiffelCompositionDefinedEvent",
"version": "3.0.0",
],
"data": [
"name": "my-composition",
],
]
def sent = sendEiffelEvent event: event
echo "This event was sent: ${sent}"
echo "The event had the id ${sent.meta.id}"
// Make the activity link a CAUSE link
sendEiffelEvent event: event, activityLinkType: "CAUSE"
// Skip the activity link altogether
sendEiffelEvent event: event, linkToActivity: false
// Sign the event using the "event-signing" credentials, containing an ECDSA P-521 key.
sendEiffelEvent event: event, linkToActivity: false, signatureCredentialsId: "event-signing", signatureHashAlgorithm: "SHA-512"
This step returns immediately as soon as the event has been validated and put in the internal outbound queue. The actual delivery of the event to the broker might not have happened at the time of the return. The step's return value is the event that was enqueued, expressed as a map (see example above).
The validation supports all events and event versions up to and including the Orizaba edition.
The plugin will do its best to populate the emitted EiffelActivityTriggeredEvent with information taken from the causes of the build, but in many cases you'll want to inject additional Eiffel links that the plugin can't figure out on its own. This can be done with the $JOB_URL/eiffel/build API endpoint which works nearly identially to the standard $JOB_URL/build endpoint except that it requires an additional eiffellinks
form parameter that contains the Eiffel links to include in the EiffelActivityTriggeredEvent. eiffellinks
follows the same schema as the links
value in Eiffel events. Example (payload lacking URL encoding to improve readability):
POST $JOB_URL/eiffel/build
Content-Type: application/x-www-form-urlencoded
json={"eiffellinks": [{"target": "662b3813-bef4-4588-bf75-ffaead24a6d5", "type": "CAUSE"}], "parameter": [{"name": "PARAM_NAME", "value": "param value"}]}
The eiffellinks
key is mandatory for this endpoint but parameter
is optional. If the latter is omitted and the job is parameterized the default values for all parameters will be used.
If at least one CAUSE link is included in eiffellinks
, an EIFFEL_EVENT
trigger will be included in the EiffelActivityTriggeredEvent.
The plugin supports signing of event payloads according to the Eiffel protocol's signing specification. Events are signed with the private key from certificates stored in PKCS #12 files, uploaded to certificate credentials in Jenkins. It's fine if the certificate is self-signed; the certificate chain isn't verified and only the subject name is used (for the meta.security.authorIdentity
member).
For signing purposes, events come in two forms:
- System events, i.e. events sent by the plugin itself. This includes activity events and the EiffelArtifactPublishedEvent sent as a result of the publishEiffelArtifacts pipeline step. These events are signed with a private key stored in a system certificate credential that's only accessible to administrators. After creating the credential, select it in the Certificate to use for signing of system events dropdown in the plugin configuration (visible when Enable signing of system events is checked).
- User events, i.e. events whose contents are materially influenced by a user. This currently applies to events sent by the sendEiffelEvent step. These events are signed with the private key from a credential added to a folder accessible to the build.
Here's an example of how to create an ECDSA P-521 keypair and convert it to the PKCS #12 format expected by the Credentials plugin:
openssl ecparam -name secp521r1 -genkey -noout -out system-signing.priv.pem
openssl req -new -x509 -key system-signing.priv.pem -out system-signing.cert.pem -subj /CN=some-identifier -days 365
openssl x509 -noout -text -in system-signing.cert.pem
openssl pkcs12 -export -inkey system-signing.priv.pem -in system-signing.cert.pem -out system-signing.pfx
The -subj
flag to openssl req
specifies the subject of the certificate and can be any distinguished name that makes sense to you. If you omit that flag you'll be prompted for the values that can make up a distinguished name. The subject is what's going to be displayed in the Jenkins interface when certificate credentials are listed.
The openssl pkcs12
command will prompt you for a password. You can choose any password you like, just make sure that you'll be able to input the same password when the resulting file is uploaded to Jenkins.
In the EiffelBroadcaster root folder, use maven to compile.
$ mvn compile
In the EiffelBroadcaster root folder, use maven to build the .hpi file.
$ mvn hpi:hpi
- In the Jenkins web interface go to: Manage Jenkins -> Plugin Manager -> Advanced
- At "Upload Plugin", Browse to the .hpi file located in the EiffelBroadcaster/target directory and press "Upload"
The plugin should install without the need to reboot Jenkins.
- Isac Holm <isac.holm@axis.com>
- Magnus Bäck <magnus.back@axis.com>
The MIT License
Copyright 2018-2021 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.