Jenkins and JFrog Pipelines: CI/CD Working Together to Release Your Software
As a software producer, you need to keep releases moving, even as you need to move your technology ahead. Transitioning your Jenkins continuous integration (CI) pipelines to a newer, optimized system can’t be a roadblock, and your enterprise can’t afford the work stoppage a rip-and-replace rework would require.
We understood that deeply when we built our CI/CD solution, JFrog Pipelines. That’s why we made it very easy to connect your current Jenkins pipelines to ones in JFrog Pipelines, so that you can extend your existing toolchain, not disrupt it.
Like many organizations, you’ve already invested hundreds of developer hours building many Jenkins pipelines that perform and have become integral to your software build process. Those Jenkins workhorses can continue to drive essential parts of your CI, but hand off to new workflows in JFrog Pipelines.
Let’s take a look at how that’s done, to enable Jenkins and Pipelines to work together.
Pipelines and the Platform Difference
Pipelines is the CI/CD component of the JFrog DevOps Platform end-to-end set of solutions for “one-stop DevOps.” Powered by Artifactory, the JFrog Platform provides everything you need to manage your organization’s software delivery, from artifact repositories, distribution of binaries, security scanning and CI/CD automation.
Chances are good that your Jenkins pipelines are already pushing artifacts and builds to Artifactory repositories for things like Go, Docker, and Helm. That’s because Artifactory’s universal repository management enables connection with the DevOps tools you choose, including the most popular CI servers.That’s helped push Artifactory to be accepted as the industry standard for binary repository management.
JFrog Pipelines is the automation glue that helps unify all the tools in the JFrog DevOps Platform, Like Jenkins, it can move your software through each stage from code to build to binaries and all the way to distribution. But as part of the JFrog Platform, it’s naturally integrated with Artifactory, Xray, and Distribution, and can be administered through a unified permissions model for fine-grained access control.
Pipelines also operates at enterprise scale, able to support hundreds of CI/CD pipelines through a single, central platform for all administrators and teams.
Even with these compelling reasons to migrate your CI from Jenkins to Pipelines, it might not be practical to do so all at once.
From Jenkins to Pipelines
For this example, we have built a Go REST application that Jenkins will build, run unit tests and then push the application to a staging Docker repository. Next, JFrog Pipelines will deploy the Docker Go application from the staging repository to a Kubernetes cluster. We will use Google Kubernetes Engine (GKE). Additionally, we will use Artifactory as our Docker registry. This makes it easy to promote the build to release without pushing the same build to another release registry.
The code repository for this example contains our Go REST application, the Jenkins pipeline Jenkinsfile and the JFrog Pipeline YAML file. Per best practices, the pipeline infrastructure is defined in these files, too.
Jenkins Pipeline
Our Jenkins pipeline performs the initial build and testing of our application, pushing a Docker container to a repository with build information.
Let’s take a look at our Jenkins pipeline. The sections of the following Jenkinsfile that are important are the Publish Build Info and post stages. After Jenkins builds and tests our Go application image, we publish the build info to Artifactory.
stages {
stage('Build') {
steps {
container('golang'){
sh 'go build'
}
}
}
stage('Unit Tests') {
steps {
container('golang'){
sh 'go test ./... -run Unit'
}
}
}
stage('Docker Build') {
steps {
container('docker'){
sh "docker build -t partnership-public-images.jfrog.io/goci-example:latest ."
}
}
}
stage('Docker Push to Repo') {
steps {
container('docker'){
script {
docker.withRegistry( 'https://partnership-public-images.jfrog.io', 'gociexamplerepo' ) {
sh "docker push partnership-public-images.jfrog.io/goci-example:latest"
}
}
}
}
}
stage('Publish Build Info') {
environment {
JFROG_CLI_OFFER_CONFIG = false
}
steps {
container('jfrog-cli-go'){
withCredentials([usernamePassword(credentialsId: 'gociexamplerepo', passwordVariable: 'APIKEY', usernameVariable: 'USER')]) {
sh "jfrog rt bce $JOB_NAME $BUILD_NUMBER"
sh "jfrog rt bag $JOB_NAME $BUILD_NUMBER"
sh "jfrog rt bad $JOB_NAME $BUILD_NUMBER \"go.*\""
sh "jfrog rt bp --build-url=https://jenkins.openshiftk8s.com/ --url=https://partnership.jfrog.io/artifactory --user=$USER --apikey=$APIKEY $JOB_NAME $BUILD_NUMBER"
}
}
}
}
}
Then in the post stage, we trigger JFrog Pipelines by referencing that build info in a special webhook call to the Pipelines REST API. We will talk about how this webhook is set up in JFrog Pipelines next.
post {
success {
script {
sh "curl -XPOST -H \"Authorization: Basic amVmabcdefM25rMW5z=\" \"https://partnership-pipelines-api.jfrog.io/v1/projectIntegrations/17/hook\" -d '{\"buildName\":\"$JOB_NAME\",\"buildNumber\":\"$BUILD_NUMBER\",\"buildInfoResourceName\":\"jenkinsBuildInfo\"}' -H \"Content-Type: application/json\""
}
}
}
JFrog Pipelines
Our JFrog Pipeline will trigger through the build info pushed by the Jenkins pipeline, and perform the remaining deployment and staging actions to release.
To connect Jenkins to JFrog Pipelines, we must first create a Jenkins integration in our Pipelines deployment, here called jenkins_openshiftk8s_com
. This UI provides our curl webhook command above, and enables our Jenkins pipeline to trigger our JFrog pipeline.
JFrog Pipelines defines its pipeline steps in YAML. The first section of this file is our resources. These are sources and destinations of data that are used by the pipeline. In our case, we are defining our GitHub repo, a BuildInfo resource connected to the jenkins_openshiftk8s_com
Jenkins integration, and a final BuildInfo resource to promote our release. The BuildInfo resource is used to store metadata for our build.
resources:
- name: gociexampleGithubRepo
type: GitRepo
configuration:
gitProvider: myGithub
path: myaccount/goci-example
- name: jenkinsBuildInfo
type: BuildInfo
configuration:
sourceArtifactory: MyArtifactory
buildName: goci-example/master
buildNumber: 1
externalCI: jenkins_openshiftk8s_com
- name: releaseBuildInfo
type: BuildInfo
configuration:
sourceArtifactory: MyArtifactory
buildName: goci-example/master
buildNumber: 1
Our first step is a Bash step that receives our Jenkins trigger through jenkinsBuildInfo
.
- name: start_from_jenkins
type: Bash
configuration:
inputResources:
- name: jenkinsBuildInfo
execution:
onExecute:
- echo 'Jenkins job triggered Pipelines'
If all goes well, we then deploy our Go REST application to our staging environment. In this case, we have a GKE cluster for this. We reference this cluster through a Kubernetes integration named gociexampleClusterCreds . We can integrate with any Kubernetes cluster by providing our kubeconfig data as an Integration object.
We use the HelmDeploy step to deploy our application using a HelmChart directory in our repo.
- name: deploy_staging
type: HelmDeploy
configuration:
inputSteps:
- name: start_from_jenkins
inputResources:
- name: gociexampleGithubRepo
trigger: false
integrations:
- name: gociexampleClusterCreds
releaseName: goci-example
chartPath: chart/goci-example/
Then we have a Bash step that waits for the Go REST application to become available.
- name: wait_for_server
type: Bash
configuration:
inputSteps:
- name: deploy_staging
execution:
onExecute:
- timeout 60 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' https://goci-example.35.238.177.209.xip.io)" != "200" ]]; do sleep 5; done' || true
Once our Go REST application comes up in our staging environment, we execute our staging tests.
- name: staging_test
type: Bash
configuration:
inputSteps:
- name: wait_for_server
inputResources:
- name: gociexampleGithubRepo
trigger: false
runtime:
type: image
image:
auto:
language: go
versions:
- "1.13"
environmentVariables:
STAGING_URL: "https://goci-example.35.238.177.209.xip.io"
execution:
onExecute:
- cd ../dependencyState/resources/gociexampleGithubRepo
- go mod download
- go test ./test -run Staging
Finally, if our staging tests pass, we promote the build to release.
- name: promote_release
type: PromoteBuild
configuration:
targetRepository: partnership-public-images.jfrog.io
status: Released
comment: Passed staging tests.
inputResources:
- name: jenkinsBuildInfo
outputResources:
- name: releaseBuildInfo
Our JFrog Pipeline can be further extended to provide continuous delivery (CD) operations using JFrog Distribution to publish your software to end systems and JFrog Edge systems. But we will leave that to a future blog post.
Keep DevOps Moving
As you can see, it’s a straightforward process to connect your Jenkins pipeline to one in JFrog Pipelines. If needed, you can also trigger a Jenkins pipeline from JFrog Pipelines.
Developing your software delivery toolchain from a patchwork of tools can be a time consuming and frustrating task. Using a unified toolchain like the JFrog Platform allows you to focus on your software and not the tools. But if you have existing tools, you can easily plug these into the JFrog platform and still take advantage of the JFrog Platform features.