Repeat defined pipeline for other environment - jenkins

For below Pipeline, if selected environment is 'dev' then after completing pipeline it should promt and deploy to other environments i.e. 'qa' or 'staging' as well.
How do we achieve this in JenkinsFile? Simply putting if conditions OR any other plugins such as 'build promotion' ?
The purpose of this is to replicate any changes to all environments.
{
properties([
parameters([choice(choices: "dev\nqa\nstg", description: 'Environment?', name: 'environment')])
])
pipeline {
agent any
stages {
stage('Build') {
steps {
echo "Building..${params.environment}"
}
}
stage('Test') {
steps {
echo "Testing..${params.environment}"
}
}
stage('Deploy') {
steps {
echo "Deploying..${params.environment}"
}
}
}
}
}

Related

Running script (stash) prior to parallel stages being invoked

I have a parallel stage setup, and would like to know if it's possible to run a script prior to the nested stages, so something like this:
stage('E2E-PR-CYPRESS') {
when {
allOf {
expression {
return fileExists("cypress.json")
}
branch "PR-*"
}
}
steps {
script {
stash name: 'cypress-dir', includes: 'cypress/**/*'
}
}
parallel {
stage('Cypress Tests 1') {
agent { label 'aws_micro_slave_e2e' }
options { skipDefaultCheckout() }
steps {
runE2eTests()
}
}
stage('Cypress Tests 2') {
agent { label 'aws_micro_slave_e2e' }
options { skipDefaultCheckout() }
steps {
runE2eTests()
}
}
}
post {
always {
e2eAfterCypressRun(this, true)
}
}
}
I know the above is wrong, I get the error Only one of "matrix", "parallel", "stages", or "steps" allowed for stage "E2E-PR-CYPRESS"
I already have the stash script in a setup stage at the beginning of my pipeline, but I'd like to be able to restart from this stage above on Jenkins, and so need the stash part in this stage as the parallel stages need to unstash the contents.
Updated Answer:
After playing a bit with the Restart from a Stage option there is seems to be a nice feature designed exactly for your needs called Preserving stashes for Use with Restarted Stages:
Normally, when you run the stash step in your Pipeline, the resulting
stash of artifacts is cleared when the Pipeline completes, regardless
of the result of the Pipeline. Since stash artifacts aren’t accessible
outside of the Pipeline run that created them, this has not created
any limitations on usage. But with Declarative stage restarting, you
may want to be able to unstash artifacts from a stage which ran before
the stage you’re restarting from.
To enable this, there is a job property that allows you to configure a
maximum number of completed runs whose stash artifacts should be
preserved for reuse in a restarted run. You can specify anywhere from
1 to 50 as the number of runs to preserve.
This job property can be configured in your Declarative Pipeline’s options section, as below:
options {
preserveStashes()
// or
preserveStashes(buildCount: 5)
}
This built in feature is exactly what you need to solve your issue without any special modifications to your code, as it will allow you to rerun the pipeline from any stage and still use the existing file that were previously stashed.
Original Answer:
You can actually achieve this quite simply using the scripted syntax for the parallel command, and it will also allow you to avoid the duplicate code in the parallel stages.
parallel: Execute in parallel
Takes a map from branch names to closures and an optional argument failFast which will terminate all branches upon a failure in any other branch:
parallel firstBranch: {
// do something
}, secondBranch: {
// do something else
},
failFast: true|false
In your case it can look like:
stage('E2E-PR-CYPRESS') {
when {
allOf {
expression {
return fileExists("cypress.json")
}
branch "PR-*"
}
}
steps {
script {
stash name: 'cypress-dir', includes: 'cypress/**/*'
// Define the parallel execution stages
def stages = ['Cypress Tests 1', 'Cypress Tests 2']
// Create the parallel executions and run them
parallel stages.collectEntries {
["Running ${it}": {
node('aws_micro_slave_e2e') {
skipDefaultCheckout()
runE2eTests()
}
}]
}
}
}
post {
always {
e2eAfterCypressRun(this, true)
}
}
}
This way you can easily add more parallel steps by updating the stages list, or even receive it as an input parameter. In addition you can create the parallel executions by different labels or tests suits, instead of the stage name.
You can add a Prepare stage at the top like this:
stages{
stage('Preperation'){
when {
allOf {
expression {
return fileExists("cypress.json")
}
branch "PR-*"
}
}
steps {
script {
stash name: 'cypress-dir', includes: 'cypress/**/*'
}
}
}
stage('E2E-PR-CYPRESS') {
parallel {
stage('Cypress Tests 1') {
agent { label 'aws_micro_slave_e2e' }
options { skipDefaultCheckout() }
steps {
runE2eTests()
}
}
stage('Cypress Tests 2') {
agent { label 'aws_micro_slave_e2e' }
options { skipDefaultCheckout() }
steps {
runE2eTests()
}
}
}
}
}
post {
always {
e2eAfterCypressRun(this, true)
}
}
An out of the box concept
Propose splitting the job into 2 parts taking the following into consideration:
Currently use an EC2 plugin, as the current agents are EC2
Running the parallel stages with the same stashed content ready to unstash
Create jenkins pipeline job 1:
This job will checkout the workspace with any type of agent
Create a packer json to create a customised AMI for the EC2
The customised AMI will stash the contents and move to a directory that will appear on the EC2 when the agent is built
Output the AMI ID, run a groovy job to update the EC2 plugin AMI ID with the customised AMI ID to temporarily set the AMI in memory on Jenkins
pipeline {
agent {
docker {
test-container
}
}
options {
buildDiscarder(
logRotator(
numToKeepStr: '10',
artifactNumToKeepStr: '10'
)
)
ansiColor('xterm')
gitConnection("git")
}
stages {
stage('Run Stash Cypress Functional Test') {
steps {
dir('functional-test') {
// develop branch is canary build, all other branches are stable builds
script {
sh """
# script to stash cypress tests
"""
}
}
}
}
stage('Functional Test AMI Build') {
steps {
dir('functional-test/packer') {
withAWS(role: 'PackerBuild', roleAccount: '123456789012', roleSessionName: 'Jenkins-Workflow-FunctionalTest-Packer') {
script {
sh """
# packer json script will require to copy contents from workspace, run the script to stash content
# packer json script will require to capture new AMI ID
# https://discuss.devopscube.com/t/how-to-get-the-ami-id-after-a-packer-build/36
# https://www.packer.io/docs/post-processors/manifest
packer validate FunctionalTestPacker.json
packer build -debug FunctionalTestPacker.json
# grab AMI ID and export as jenkins env variable
"""
}
}
}
}
}
stage('run groovy script to update AMI ID on EC2 plugin') {
steps {
dir(groovy job dir) {
script {
sh """
# run groovy job to update AMI on Jenkins EC2 plugin
# https://gist.github.com/vrivellino/97954495938e38421ba4504049fd44ea
"""
}
}
}
}
stage('Kickoff Functional Test Deploy') {
// pipeline checkbox parameter, when ticked it will automatically kick off the functional test pipeline
when {
expression {params.RUN_TESTS.toBoolean()}
}
steps {
script{
env.branch = params.BRANCH
sh """
echo "Branch is ${branch}"
"""
}
build job: 'workflow/CypressFunctionaTestDeployAndRun',
parameters: [
string(name: 'BRANCH', value: env.branch)
],
wait : false
}
}
}
post {
always {
cleanWs()
}
}
}
Create jenkins pipeline job 2:
This job will create the EC2 agents via the plugin from the customised AMI from pipeline job 1
This means your agents will have the same workspace ready to unstash - so you can execute a parallel run
Also you could move a lot of your user data script that is in the EC2 plugin as part of the customised AMI build, thus cut down the time for each EC2 agent to get ready to carry out execution
pipeline {
stages {
stage('E2E-PR-CYPRESS') {
when {
allOf {
expression {
return fileExists("cypress.json")
}
branch "PR-*"
}
}
}
parallel {
stage('Cypress Tests 1') {
agent { label 'aws_micro_slave_e2e' }
options { skipDefaultCheckout() }
steps {
runE2eTests()
}
}
stage('Cypress Tests 2') {
agent { label 'aws_micro_slave_e2e' }
options { skipDefaultCheckout() }
steps {
runE2eTests()
}
}
}
}
post {
always {
e2eAfterCypressRun(this, true)
}
}
}

Jenkins Environment Variables Conditional set

So I had to do a lot of different renditions of this with no success unless the environment was set before the stages. I am trying to define the environment for aws creds depending on the branch im in. qa then use qa creds for the env BUT it does not set when its inside the stage phase
agent {
docker {
image '/terraform-npm:latest'
registryCredentialsId 'dockerhubPW'
}
}
stages {
stage('Initialize Dev Environment') {
when {
branch 'dev'
}
environment {
TF_VAR_aws_access_key = credentials('dev-aws-access-key-id')
TF_VAR_aws_secret_key = credentials('dev-aws-secret-access-key')
AWS_ACCESS_KEY_ID = credentials('dev-aws-access-key-id')
AWS_SECRET_ACCESS_KEY = credentials('dev-aws-secret-access-key')
AWS_REGION = "us-west-2"
}
steps {
sh 'terraform init -backend-config="bucket=${GIT_BRANCH}-terraform-state" -backend-config="dynamodb_table=${GIT_BRANCH}-terraform-state-locking" -backend-config="region=$AWS_REGION" -backend-config="key=${GIT_BRANCH}-terraform-state/terraform.tfstate"'
}
}
IF i obviously set it before the stage phase in the pipeline of course it works.
agent {
docker {
image '/terraform-npm:latest'
registryCredentialsId 'dockerhubPW'
}
}
environment {
TF_VAR_aws_access_key = credentials('dev-aws-access-key-id')
TF_VAR_aws_secret_key = credentials('dev-aws-secret-access-key')
AWS_ACCESS_KEY_ID = credentials('dev-aws-access-key-id')
AWS_SECRET_ACCESS_KEY = credentials('dev-aws-secret-access-key')
AWS_REGION = "us-west-2"
}
stages {
stage('Initialize Dev Environment') {
when {
branch 'dev'
}
steps {
sh 'terraform init -backend-config="bucket=${GIT_BRANCH}-terraform-state" -backend-config="dynamodb_table=${GIT_BRANCH}-terraform-state-locking" -backend-config="region=$AWS_REGION" -backend-config="key=${GIT_BRANCH}-terraform-state/terraform.tfstate"'
}
}
My question is , is there a way to set the environment variables before the stages phase BUT conditionally depending on the branch?
Well, yes, there is.
First option: you can run a combination of scripted and declarative pipeline (please note that I haven't checked it works, this is just to send you down a right path):
// scripted pipeline
node('master') {
stage("Init variables") {
if (env.GIT_BRANCH == 'dev') {
env.AWS_REGION = "us-west-2"
}
else {
// ...
}
}
}
// declarative pipeline
pipeline {
agent {
docker {
image '/terraform-npm:latest'
registryCredentialsId 'dockerhubPW'
}
}
stages {
stage('Use variables') {
steps {
sh 'echo $AWS_REGION'
}
}
}
Another option is to use withEnv directive inside steps:
stage('Initialize Dev Environment') {
when {
branch 'dev'
}
steps {
withEnv(['AWS_REGION=us-west-2']) {
sh 'echo $AWS_REGION'
}
}
Thanks you MaratC for guiding me in the right path, it def helped. here is what i used
steps {
withCredentials([string(credentialsId: 'qa-aws-access-key-id', variable: 'TF_VAR_aws_access_key'),string(credentialsId: 'qa-aws-secret-access-key', variable: 'TF_VAR_aws_secret_key'),string(credentialsId: 'qa-aws-access-key-id', variable: 'AWS_ACCESS_KEY_ID'),string(credentialsId: 'qa-aws-secret-access-key', variable: 'AWS_SECRET_ACCESS_KEY')])
{
sh 'terraform plan -var-file=${GIT_BRANCH}.tfvars -out=${GIT_BRANCH}-output.plan'
}
}

Jenkins Scripted Pipeline use global timestamps options

In my scripted pipeline I would like to set global timestamps and ansicolor option.
Below scripted pipeline not working. How can we add these two options in scripted pipeline?
Declarative Pipeline
pipeline {
agent none
options {
timestamps()
ansiColor('xterm')
}
stages {
stage('Checkout') {
agent { label 'linux' }
steps{
echo "test"
}
}
}
}
Scripted Pipeline
node('linux') {
options {
timestamps()
ansiColor('xterm')
}
stage('Pre Build Setup') {
task('Display env') {
echo "test"
}
}
}
In case of a scripted pipeline, all you have to do is to wrap your script with timestamps and ansiColor('xterm') steps as shown in the example down below:
node {
timestamps {
ansiColor("xterm") {
stage("A") {
echo 'This is stage A'
sh 'printf "\\e[31mHello World\\e[0m\\n"'
sh "sleep 3s"
}
stage("B") {
echo "This is stage B"
}
}
}
}

Jenkins Multibranch job with declarative pipeline cloning repo for every stage

Trying to create a workflow in Jenkins using Declarative Pipeline to do something like this:
Checkout the code on 'master'
Build solution on 'master' (I know this is not a secure way to do it, but Jenkins is in the intranet so it should be fine for us)
Stash artifacts (.dll, .exe, .pdb, etc) => 1st stage
Unstash artifacts on nodes depending on what it's needed (Unit tests on a slave, Integration tests on another one and Selenium tests on a another one) => 2nd stage
Run tests depending on the slave => 3rd stage running in parallel
The problem that I'm facing is that the git checkout (GitSCM) is executed for every stage.
My pipeline looks like this:
pipeline {
agent {
label {
label "master"
customWorkspace "C:\\Jenkins\\workspace\\CustomWorkspace"
}
}
options {
timestamps()
}
stages {
stage("Build") {
agent {
label {
label "master"
customWorkspace "C:\\Jenkins\\workspace\\CustomWorkspace"
}
}
steps {
/*
steps to build the solution here
*/
//Sleep because stashing fails otherwise
script {
sleep(1)
}
dir("${env.WORKSPACE}\\UnitTests\\bin\\Release") {
stash name: 'unit-tests'
}
dir("${env.WORKSPACE}\\WebUnitTests\\bin\\x64\\Release") {
stash name: 'web-unit-tests'
}
}
stage('Export artefacts') {
agent {
label {
label "UnitTest"
customWorkspace "C:\\Jenkins\\workspace\\CustomWorkspace"
}
}
steps {
echo "Copying dlls from master to ${env.NODE_NAME}"
dir("${env.WORKSPACE}\\UnitTests\\bin\\Release") {
unstash 'unit-tests'
}
}
}
stage('Run tests') {
parallel {
stage("Run tests #1") {
agent {
label {
label "UnitTest"
customWorkspace "C:\\Jenkins\\workspace\\CustomWorkspace"
}
}
steps {
/*
run tests here
*/
}
post {
//post results here
}
}
//other parallel stages
}
}
}
}
So, as mentioned earlier, the GitSCM (code checkout) is a part of and performed for every stage:
Build stage
Export stage
A couple simple changes should solve this. You need to tell the pipeline script not to checkout by default every time a node is allocated. Then you need to tell it to do the checkout where you need it:
pipeline {
agent {
label {
label "master"
customWorkspace "C:\\Jenkins\\workspace\\CustomWorkspace"
}
}
options {
timestamps()
skipDefaultCheckout() // Don't checkout automatically
}
stages {
stage("Build") {
agent {
label {
label "master"
customWorkspace "C:\\Jenkins\\workspace\\CustomWorkspace"
}
}
steps {
checkout scm //this will checkout the appropriate commit in this stage
/*
steps to build the solution here
*/
//Sleep because stashing fails otherwise
script {
sleep(1)
}
dir("${env.WORKSPACE}\\UnitTests\\bin\\Release") {
stash name: 'unit-tests'
}
dir("${env.WORKSPACE}\\WebUnitTests\\bin\\x64\\Release") {
stash name: 'web-unit-tests'
}
}
stage('Export artefacts') {
agent {
label {
label "UnitTest"
customWorkspace "C:\\Jenkins\\workspace\\CustomWorkspace"
}
}
steps {
echo "Copying dlls from master to ${env.NODE_NAME}"
dir("${env.WORKSPACE}\\UnitTests\\bin\\Release") {
unstash 'unit-tests'
}
}
}
stage('Run tests') {
parallel {
stage("Run tests #1") {
agent {
label {
label "UnitTest"
customWorkspace "C:\\Jenkins\\workspace\\CustomWorkspace"
}
}
steps {
/*
run tests here
*/
}
post {
//post results here
}
}
//other parallel stages
}
}
}
I have added 2 lines there. One in the options section (skipDefaultCheckout()), and a checkout scm in the first stage.

Use a lightweight executor for a declarative pipeline stage (agent none)

I'm using Jenkins Pipeline with the declarative syntax, currently with the following stages:
Prepare
Build (two parallel sets of steps)
Test (also two parallel sets of steps)
Ask if/where to deploy
Deploy
For steps 1, 2, 3, and 5 I need and agent (an executor) because they do actual work on the workspace. For step 4, I don't need one, and I would like to not block my available executors while waiting for user input. This seem to be referred to as either a "flyweight" or "lightweight" executor for the classic, scripted syntax, but I cannot find any information on how to achieve this with the declarative syntax.
So far I've tried:
Setting an agent directly in the pipeline options, and then setting agent none on the stage. This has no effect, and the pipeline runs as normalt, blocking the executor while waiting for input. It is also mentioned in the documentation that it will have no effect, but I thought I'd give it a shot anyway.
Setting agent none in the pipeline options, and then setting an agent for each stage except #4. Unfortunately, but expectedly, this allocates a new workspace for every stage, which in turn requires me to stash and unstash. This is both messy and gives me further problems in the parallel stages (2 and 3) because I cannot have code outside the parallel construct. I assume the parallel steps run in the same workspace, so stashing/unstashing in both would have unfortunate results.
Here is an outline of my Jenkinsfile:
pipeline {
agent {
label 'build-slave'
}
stages {
stage("Prepare build") {
steps {
// ...
}
}
stage("Build") {
steps {
parallel(
frontend: {
// ...
},
backend: {
// ...
}
)
}
}
stage("Test") {
steps {
parallel(
jslint: {
// ...
},
phpcs: {
// ...
},
)
}
post {
// ...
}
}
stage("Select deploy target") {
steps {
script {
// ... code that determines choiceParameterDefinition based on branch name ...
try {
timeout(time: 5, unit: 'MINUTES') {
deployEnvironment = input message: 'Deploy target', parameters: [choiceParameterDefinition]
}
} catch(ex) {
deployEnvironment = null
}
}
}
}
stage("Deploy") {
when {
expression {
return binding.variables.get("deployEnvironment")
}
}
steps {
// ...
}
}
}
post {
// ...
}
}
Am I missing something here, or is it just not possible in the current version?
Setting agent none at the top level, then agent { label 'foo' } on every stage, with agent none again on the input stage seems to work as expected for me.
i.e. Every stage that does some work runs on the same agent, while the input stage does not consume an executor on any agent.
pipeline {
agent none
stages {
stage("Prepare build") {
agent { label 'some-agent' }
steps {
echo "prepare: ${pwd()}"
}
}
stage("Build") {
agent { label 'some-agent' }
steps {
parallel(
frontend: {
echo "frontend: ${pwd()}"
},
backend: {
echo "backend: ${pwd()}"
}
)
}
}
stage("Test") {
agent { label 'some-agent' }
steps {
parallel(
jslint: {
echo "jslint: ${pwd()}"
},
phpcs: {
echo "phpcs: ${pwd()}"
},
)
}
}
stage("Select deploy target") {
agent none
steps {
input message: 'Deploy?'
}
}
stage("Deploy") {
agent { label 'some-agent' }
steps {
echo "deploy: ${pwd()}"
}
}
}
}
However, there are no guarantee that using the same agent label within a Pipeline will always end up using the same workspace, e.g. as another build of the same job while the first build is waiting on the input.
You would have to use stash after the build steps. As you note, this cannot be done normally with parallel at the moment, so you'd have to additionally use a script block, in order to write a snippet of Scripted Pipeline for the stashing/unstashing after/before the parallel steps.
There is a workaround to use the same build slave in the other stages.
You can set a variable with the node name and use it in the others.
ie:
pipeline {
agent none
stages {
stage('First Stage Gets Agent Dynamically') {
agent {
node {
label "some-agent"
}
}
steps {
echo "first stage running on ${NODE_NAME}"
script {
BUILD_AGENT = NODE_NAME
}
}
}
stage('Second Stage Setting Node by Name') {
agent {
node {
label "${BUILD_AGENT}"
}
}
steps {
echo "Second stage using ${NODE_NAME}"
}
}
}
}
As of today (2021), you can use nested stages (https://www.jenkins.io/doc/book/pipeline/syntax/#sequential-stages) to group all the stages that must run in the same workspace before the input step, and all the stages that must be run in the same workspace after the input step. Of course, you need to stash or to store artifacts in some external repository before the input step, because the second workspace may not be the same than the first one:
pipeline {
agent none
stages {
stage('Deployment to Preproduction') {
agent any
stages {
stage('Stage PRE.1') {
steps {
echo "StagePRE.1"
sleep(10)
}
}
stage('Stage PRE.2') {
steps {
echo "Stage PRE.2"
sleep(10)
}
}
}
}
stage('Stage Ask Deploy') {
steps {
input message: 'Deploy to production?'
}
}
stage('Deployment to Production') {
agent any
stages {
stage('Stage PRO.1') {
steps {
echo "Stage PRO.1"
sleep(10)
}
}
stage('Stage PRO.2') {
steps {
echo "Stage PRO.2"
sleep(10)
}
}
}
}
}
}

Resources