Skip to main content

Jenkins

Our Jenkins integration allows you to import jobs, builds, and users from your Jenkins environment into Port, according to your mapping and definitions.

Common use cases

  • Map jobs, builds, and users in your Jenkins environment.
  • Watch for object changes (create/update/delete) in real-time, and automatically apply the changes to your entities in Port.

Prerequisites

To install the integration, you need a Kubernetes cluster that the integration's container chart will be deployed to.

Please make sure that you have kubectl and helm installed on your machine, and that your kubectl CLI is connected to the Kubernetes cluster where you plan to install the integration.

Troubleshooting

If you are having trouble installing this integration, please refer to these troubleshooting steps.

To generate a token for authenticating the Jenkins API calls:

  1. In the Jenkins banner frame, click your user name to open the user menu.
  2. Navigate to Your Username > Configure > API Token.
  3. Click Add new Token.
  4. Click Generate.
  5. Copy the API token that is generated to use as the JENKINS_TOKEN.
Install the People View Plugin

Recent Jenkins versions (2.452 and above) no longer include the "People" view by default. This view is essential for providing the user information API that will be queried by the integration.

To install the plugin:

  • In Jenkins, navigate to Manage Jenkins -> Plugins.
  • Search for and install the "People View" plugin.

Installation

Choose one of the following installation methods:

Using this installation option means that the integration will be hosted by Port, with a customizable resync interval to ingest data into Port.

To install, follow the following steps:

  1. Go to the Data sources page of your portal.

  2. Click on the + Data source button in the top-right corner.

  3. Click on the relevant integration in the list.

  4. Under Select your installation method, choose Hosted by Port.

  5. Configure the integration settings and application settings as you wish (see below for details).

Application settings

Every integration hosted by Port has the following customizable application settings:

  • Resync interval: The frequency at which Port will ingest data from the integration. There are various options available, ranging from every 1 hour to once a day.

  • Send raw data examples: A boolean toggle (enabled by default). If enabled, raw data examples will be sent from the integration to Port. These examples are used when testing your mapping configuration, they allow you to run your jq expressions against real data and see the results.

Integration settings

Every integration has its own tool-specific settings, under the Integration settings section.
Each of these settings has an ⓘ icon next to it, which you can hover over to see a description of the setting.

Port secrets

Some integration settings require sensitive pieces of data, such as tokens.
For these settings, Port secrets will be used, ensuring that your sensitive data is encrypted and secure.

When changing such a setting, you will be prompted to choose an existing secret or create a new one:

Live event support

Currently, live events are not supported for integrations hosted by Port.
Resyncs will be performed periodically, based on the Resync interval you have configured, or manually triggered by you via Port's UI.

Therefore, real-time events such as GitOps pushes will not be ingested into Port immediately.
Live events are WIP and will be supported in the future.

Ingesting Jenkins objects

The Jenkins integration uses a YAML configuration to describe the process of loading data into the developer portal.

Here is an example snippet from the config which demonstrates the process for getting job data from Jenkins:

createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: job
selector:
query: "true"
port:
entity:
mappings:
identifier: .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("/"; "-") | .[:-1]
title: .fullName
blueprint: '"jenkinsJob"'
properties:
jobName: .name
url: .url
jobStatus: '{"notbuilt": "created", "blue": "passing", "red": "failing"}[.color]'
timestamp: .time

The integration makes use of the JQ JSON processor to select, modify, concatenate, transform and perform other operations on existing fields and values from Jenkins's API events.

Configuration structure

The integration configuration determines which resources will be queried from Jenkins, and which entities and properties will be created in Port.

  • The root key of the integration configuration is the resources key:

    resources:
    - kind: job
    selector:
    ...
  • The kind key is a specifier for a Jenkins object:

      resources:
    - kind: job
    selector:
    ...
  • The selector and the query keys allow you to filter which objects of the specified kind will be ingested into your software catalog:

    resources:
    - kind: job
    selector:
    query: "true" # JQ boolean expression. If evaluated to false - this object will be skipped.
    port:
  • The port, entity and the mappings keys are used to map the Jenkins object fields to Port entities. To create multiple mappings of the same kind, you can add another item in the resources array;

    resources:
    - kind: job
    selector:
    query: "true"
    port:
    entity:
    mappings: # Mappings between one Jenkins object to a Port entity. Each value is a JQ query.
    identifier: .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("/"; "-") | .[:-1]
    title: .fullName
    blueprint: '"jenkinsJob"'
    properties:
    jobName: .name
    url: .url
    jobStatus: '{"notbuilt": "created", "blue": "passing", "red": "failing"}[.color]'
    timestamp: .time
    - kind: job # In this instance job is mapped again with a different filter
    selector:
    query: '.name == "MyJobName"'
    port:
    entity:
    mappings: ...
    Blueprint key

    Note the value of the blueprint key - if you want to use a hardcoded string, you need to encapsulate it in 2 sets of quotes, for example use a pair of single-quotes (') and then another pair of double-quotes (")

Ingest data into Port

To ingest Jenkins objects using the integration configuration, you can follow the steps below:

  1. Go to the DevPortal Builder page.
  2. Select a blueprint you want to ingest using Jenkins.
  3. Choose the Ingest Data option from the menu.
  4. Select Jenkins under the CI/CD category.
  5. Modify the configuration according to your needs.
  6. Click Resync.

Examples

Examples of blueprints and the relevant integration configurations:

Job

Job blueprint
{
"identifier": "jenkinsJob",
"description": "This blueprint represents a job in Jenkins",
"title": "Jenkins Job",
"icon": "Jenkins",
"schema": {
"properties": {
"jobName": {
"type": "string",
"title": "Job Name"
},
"jobStatus": {
"type": "string",
"title": "Job Status",
"enum": [
"created",
"unknown",
"passing",
"failing"
],
"enumColors": {
"passing": "green",
"created": "darkGray",
"failing": "red",
"unknown": "orange"
}
},
"timestamp": {
"type": "string",
"format": "date-time",
"title": "Timestamp",
"description": "Last updated timestamp of the job"
},
"url": {
"type": "string",
"title": "Project URL"
},
"parentJob": {
"type": "object",
"title": "Parent Job"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
Integration configuration
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: job
selector:
query: "true"
port:
entity:
mappings:
identifier: .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("/"; "-") | .[:-1]
title: .fullName
blueprint: '"jenkinsJob"'
properties:
jobName: .name
url: .url
jobStatus: '{"notbuilt": "created", "blue": "passing", "red": "failing"}[.color]'
timestamp: .time
parentJob: .__parentJob

Build

Build blueprint
{
"identifier": "jenkinsBuild",
"description": "This blueprint represents a build event from Jenkins",
"title": "Jenkins Build",
"icon": "Jenkins",
"schema": {
"properties": {
"buildStatus": {
"type": "string",
"title": "Build Status",
"enum": [
"SUCCESS",
"FAILURE",
"UNSTABLE"
],
"enumColors": {
"SUCCESS": "green",
"FAILURE": "red",
"UNSTABLE": "yellow"
}
},
"buildUrl": {
"type": "string",
"title": "Build URL",
"description": "URL to the build"
},
"timestamp": {
"type": "string",
"format": "date-time",
"title": "Timestamp",
"description": "Last updated timestamp of the build"
},
"buildDuration": {
"type": "number",
"title": "Build Duration",
"description": "Duration of the build"
}
},
"required": []
},
"mirrorProperties": {
"previousBuildStatus": {
"title": "Previous Build Status",
"path": "previousBuild.buildStatus"
}
},
"calculationProperties": {},
"relations": {
"parentJob": {
"title": "Jenkins Job",
"target": "jenkinsJob",
"required": false,
"many": false
},
"previousBuild": {
"title": "Previous Build",
"target": "jenkinsBuild",
"required": false,
"many": false
}
}
}
Integration configuration
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: build
selector:
query: "true"
port:
entity:
mappings:
identifier: .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("/"; "-") | .[:-1]
title: .displayName
blueprint: '"jenkinsBuild"'
properties:
buildStatus: .result
buildUrl: .url
buildDuration: .duration
timestamp: '.timestamp / 1000 | todate'
relations:
parentJob: .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("/"; "-") | .[:-1] | gsub("-[0-9]+$"; "")
previousBuild: .previousBuild.url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("/"; "-") | .[:-1]

User

User blueprint
{
"identifier": "jenkinsUser",
"description": "This blueprint represents a jenkins user",
"title": "Jenkins User",
"icon": "Jenkins",
"schema": {
"properties": {
"url": {
"type": "string",
"title": "URL",
"format": "url"
},
"lastUpdateTime": {
"type": "string",
"format": "date-time",
"title": "Last Update",
"description": "Last updated timestamp of the user"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
Integration configuration
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: user
selector:
query: "true"
port:
entity:
mappings:
identifier: .user.id
title: .user.fullName
blueprint: '"jenkinsUser"'
properties:
url: .user.absoluteUrl
lastUpdateTime: if .lastChange then (.lastChange/1000) else now end | strftime("%Y-%m-%dT%H:%M:%SZ")

Let's Test It

This section includes a sample response data from Jenkins. In addition, it includes the entity created from the resync event based on the Ocean configuration provided in the previous section.

Payload

Here is an example of the payload structure from Jenkins:

Job response data
{
"_class" : "hudson.model.FreeStyleProject",
"displayName" : "Hello Job",
"fullName" : "Hello Job",
"name" : "Hello Job",
"url" : "http://localhost:8080/job/Hello%20Job/",
"buildable" : true,
"builds" : [
{
"_class" : "hudson.model.FreeStyleBuild",
"displayName" : "#2",
"duration" : 221,
"fullDisplayName" : "Hello Job #2",
"id" : "2",
"number" : 2,
"result" : "SUCCESS",
"timestamp" : 1700569094576,
"url" : "http://localhost:8080/job/Hello%20Job/2/"
},
{
"_class" : "hudson.model.FreeStyleBuild",
"displayName" : "#1",
"duration" : 2214,
"fullDisplayName" : "Hello Job #1",
"id" : "1",
"number" : 1,
"result" : "SUCCESS",
"timestamp" : 1700567994163,
"url" : "http://localhost:8080/job/Hello%20Job/1/"
}
],
"color" : "blue"
}
Build response data
{
"_class" : "hudson.model.FreeStyleBuild",
"displayName" : "#2",
"duration" : 221,
"fullDisplayName" : "Hello Job #2",
"id" : "2",
"number" : 2,
"result" : "SUCCESS",
"timestamp" : 1700569094576,
"url" : "http://localhost:8080/job/Hello%20Job/2/"
}
User response data
{
"user" : {
"absoluteUrl" : "http://localhost:8080/user/admin",
"fullName" : "admin",
"description" : "System Administrator",
"id" : "admin"
},
"lastChange" : 1700569094576
}

Mapping Result

The combination of the sample payload and the Ocean configuration generates the following Port entity:

Job entity
{
"identifier": "hello-job",
"title": "Hello Job",
"blueprint": "jenkinsJob",
"properties": {
"jobName": "Hello Job",
"url": "http://localhost:8080/job/Hello%20Job/",
"jobStatus": "passing",
"timestamp": "2023-09-08T14:58:14Z"
},
"relations": {},
"createdAt": "2023-12-18T08:37:21.637Z",
"createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW",
"updatedAt": "2023-12-18T08:37:21.637Z",
"updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW"
}
Build entity
{
"identifier": "hello-job-2",
"title": "Hello Job #2",
"blueprint": "jenkinsBuild",
"properties": {
"buildStatus": "SUCCESS",
"buildUrl": "http://localhost:8080/job/Hello%20Job/2/",
"buildDuration": 221,
"timestamp": "2023-09-08T14:58:14Z"
},
"relations": {
"parentJob": "hello-job"
},
"createdAt": "2023-12-18T08:37:21.637Z",
"createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW",
"updatedAt": "2023-12-18T08:37:21.637Z",
"updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW"
}
User entity
{
"identifier": "admin",
"title": "admin",
"blueprint": "jenkinsUser",
"properties": {
"url": "http://localhost:8080/user/admin",
"lastUpdateTime": "2023-09-08T14:58:14Z"
},
"relations": {},
"createdAt": "2023-12-18T08:37:21.637Z",
"createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW",
"updatedAt": "2023-12-18T08:37:21.637Z",
"updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW"
}