Jenkins pipeline: How to get the hostname of the slave - jenkins

I have two different Linux servers (prod and dev) with different $HOSTNAME and different certificates, which are by default named after the hostname.
Now I want to determine within the Jenkins-Pipeline on which host I am and thus use the right certificate.
To do so I wrote the following test script:
def labels = []
labels.add('jenkins_<slavehost>_x86')
def builders = [:]
for (x in labels) {
def label = x
builders[label] = {
ansiColor('xterm') {
node (label) {
stage('cleanup') {
deleteDir()
}
stage('test') {
sh """
echo $HOSTNAME
"""
}
}
}
}
}
parallel builders
Which does not work since the $HOSTNAME is not defined.
groovy.lang.MissingPropertyException: No such property: HOSTNAME for class: groovy.lang.Binding
How can I get the hostname of the jenkins-slave within a sh in a pipeline?
Since you can name the node in any way you like, you can't just use the NODE_NAME, it does not have to be the same as the $HOSTNAME you would get from echo $HOSTNAME on a bash on the slave machine.

def getJenkinsMaster() {
return env.BUILD_URL.split('/')[2].split(':')[0]
}
You can get the hostname from the url

sh "echo ${env.NODE_NAME}"
You can add this shell command and get the hostname from the environment variable.

not sh step, but you can use groovy:
import java.security.MessageDigest
import org.jenkinsci.plugins.workflow.cps.CpsThread
import hudson.FilePath
#NonCPS
def get_current_pipeline_node() {
def thread = CpsThread.current()
def cv = thread.contextVariables
def fp
try {
fp = cv.get(FilePath, null, null)
} catch(MissingMethodException) {
fp = cv.get(FilePath)
}
return fp?.toComputer()?.node
}
node = get_current_pipeline_node()
print(node.nodeName)

Related

Using hostname to find Nodename or label to connect to Node

I have hostname that is found in a xml file but would need to use that hostname to get the node name in Jenkins to be able to connect to that node and do the scripting.
The hostname is example.co.uk and would like to use script to find the hostname in jenkins and get the node name of the Jenkins and use that as find-hostname. Is this possible?
////
stage('use_nodename_found') {
node("$find-hostname") {
unstash 'db-process'
sh """
sudo su - postgres -c 'echo -e "-------------------------------System Information of current node running --------------------"'
""".stripIndent()
You can use the following script to retrieve the Node name passing the Host. If you want to get the labels instead of node.getNodeName() call node.getLabelString(). Read more here.
def getNodeName(def host) {
for(def node : Jenkins.instance.getNodes()) {
if(node.getLauncher().getHost() == host){
return node.getNodeName()
}
}
return null
}
Note: This is a basic script, make sure you d proper error handling.
Update: Print all Hosts
def getNodeName(def host) {
for(def node : Jenkins.instance.getNodes()) {
println node.getNodeName()
println node.getLauncher().getHost()
}
return null
}
Following is a full scripted Pipeline.
node {
stage('Test') {
echo getNodeName("172.17.0.3")
}
}
def getNodeName(def host) {
for(def node : Jenkins.instance.getNodes()) {
if(node.getLauncher().getHost() == host){
return node.getNodeName()
}
}
return null
}

How to pass parameters and variables from a file to jenkinsfile?

I'm trying to convert my jenkins pipeline to a shared library since it can be reusable on most of the application. As part of that i have created groovy file in vars folder and kept pipeline in jenkins file in github and able to call that in jenkins successfully
As part of improving this i want to pass params, variables, node labels through a file so that we should not touch jenkins pipeline and if we want to modify any vars, params, we have to do that in git repo itself
pipeline {
agent
{
node
{
label 'jks_deployment'
}
}
environment{
ENV_CONFIG_ID = 'jenkins-prod'
ENV_CONFIG_FILE = 'test.groovy'
ENV_PLAYBOOK_NAME = 'test.tar.gz'
}
parameters {
string (
defaultValue: 'test.x86_64',
description: 'Enter app version',
name: 'app_version'
)
choice (
choices: ['10.0.0.1','10.0.0.2','10.0.0.3'],
description: 'Select a host to be delpoyed',
name: 'host'
)
}
stages {
stage("reading properties from properties file") {
steps {
// Use a script block to do custom scripting
script {
def props = readProperties file: 'extravars.properties'
env.var1 = props.var1
env.var2 = props.var2
}
echo "The variable 1 value is $var1"
echo "The variable 2 value is $var2"
}
In above code,i used pipeline utility steps plugin and able to read variables from extravars.properties file. Is it same way we can do for jenkins parameters also? Or do we have any suitable method to take care of passing this parameters via a file from git repo?
Also is it possible to pass variable for node label also?
=====================================================================
Below are the improvements which i have made in this project
Used node label plugin to pass the node name as variable
Below is my vars/sayHello.groovy file content
def call(body) {
// evaluate the body block, and collect configuration into the object
def pipelineParams= [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = pipelineParams
body()
pipeline {
agent
{
node
{
label "${pipelineParams.slaveName}"
}
}
stages {
stage("reading properties from properties file") {
steps {
// Use a script block to do custom scripting
script {
// def props = readProperties file: 'extravars.properties'
// script {
readProperties(file: 'extravars.properties').each {key, value -> env[key] = value }
//}
// env.var1 = props.var1
// env.var2 = props.var2
}
echo "The variable 1 value is $var1"
echo "The variable 2 value is $var2"
}
}
stage ('stage2') {
steps {
sh "echo ${var1}"
sh "echo ${var2}"
sh "echo ${pipelineParams.appVersion}"
sh "echo ${pipelineParams.hostIp}"
}
}
}
}
}
Below is my vars/params.groovy file
properties( [
parameters([
choice(choices: ['10.80.66.171','10.80.67.6','10.80.67.200'], description: 'Select a host to be delpoyed', name: 'host')
,string(defaultValue: 'fxxxxx.x86_64', description: 'Enter app version', name: 'app_version')
])
] )
Below is my jenkinsfile
def _hostIp = params.host
def _appVersion = params.app_version
sayHello {
slaveName = 'master'
hostIp = _hostIp
appVersion = _appVersion
}
Now Is it till we can improve this?Any suggestions let me know.

Jenkinsfile Declarative Pipeline defining dynamic env vars

I'm new to Jenkins pipeline; I'm defining a declarative syntax pipeline and I don't know if I can solve my problem, because I didn't find a solution.
In this example, I need to pass a variable to ansible plugin (in old version I use an ENV_VAR or injecting it from file with inject plugin) that variable comes from a script.
This is my perfect scenario (but it doesn't work because environment{}):
pipeline {
agent { node { label 'jenkins-node'}}
stages {
stage('Deploy') {
environment {
ANSIBLE_CONFIG = '${WORKSPACE}/chimera-ci/ansible/ansible.cfg'
VERSION = sh("python3.5 docker/get_version.py")
}
steps {
ansiblePlaybook credentialsId: 'example-credential', extras: '-e version=${VERSION}', inventory: 'development', playbook: 'deploy.yml'
}
}
}
}
I tried other ways to test how env vars work in other post, example:
pipeline {
agent { node { label 'jenkins-node'}}
stages {
stage('PREPARE VARS') {
steps {
script {
env['VERSION'] = sh(script: "python3.5 get_version.py")
}
echo env.VERSION
}
}
}
}
but "echo env.VERSION" return null.
Also tried the same example with:
- VERSION=python3.5 get_version.py
- VERSION=python3.5 get_version.py > props.file (and try to inject it, but didnt found how)
If this is not possible I will do it in the ansible role.
UPDATE
There is another "issue" in Ansible Plugin, to use vars in extra vars it must have double quotes instead of single.
ansiblePlaybook credentialsId: 'example-credential', extras: "-e version=${VERSION}", inventory: 'development', playbook: 'deploy.yml'
You can create variables before the pipeline block starts. You can have sh return stdout to assign to these variables. You don't have the same flexibility to assign to environment variables in the environment stanza. So substitute in python3.5 get_version.py where I have echo 0.0.1 in the script here (and make sure your python script just returns the version to stdout):
def awesomeVersion = 'UNKNOWN'
pipeline {
agent { label 'docker' }
stages {
stage('build') {
steps {
script {
awesomeVersion = sh(returnStdout: true, script: 'echo 0.0.1').trim()
}
}
}
stage('output_version') {
steps {
echo "awesomeVersion: ${awesomeVersion}"
}
}
}
}
The output of the above pipeline is:
awesomeVersion: 0.0.1
In Jenkins 2.76 I was able to simplify the solution from #burnettk to:
pipeline {
agent { label 'docker' }
environment {
awesomeVersion = sh(returnStdout: true, script: 'echo 0.0.1')
}
stages {
stage('output_version') {
steps {
echo "awesomeVersion: ${awesomeVersion}"
}
}
}
}
Using the "pipeline utility steps" plugin, you can define general vars available to all stages from a properties file. For example, let props.txt as:
version=1.0
fix=alfa
and mix script and declarative Jenkins pipeline as:
def props
def VERSION
def FIX
def RELEASE
node {
props = readProperties file:'props.txt'
VERSION = props['version']
FIX = props['fix']
RELEASE = VERSION + "_" + FIX
}
pipeline {
stages {
stage('Build') {
echo ${RELEASE}
}
}
}
A possible variation of the main answer is to provide variable using another pipeline instead of a sh script.
example (set the variable pipeline) : my-set-env-variables pipeline
script
{
env.my_dev_version = "0.0.4-SNAPSHOT"
env.my_qa_version = "0.0.4-SNAPSHOT"
env.my_pp_version = "0.0.2"
env.my_prd_version = "0.0.2"
echo " My versions [DEV:${env.my_dev_version}] [QA:${env.my_qa_version}] [PP:${env.my_pp_version}] [PRD:${env.my_prd_version}]"
}
(use these variables) in a another pipeline my-set-env-variables-test
script
{
env.dev_version = "NOT DEFINED DEV"
env.qa_version = "NOT DEFINED QA"
env.pp_version = "NOT DEFINED PP"
env.prd_version = "NOT DEFINED PRD"
}
stage('inject variables') {
echo "PRE DEV version = ${env.dev_version}"
script
{
// call set variable job
def variables = build job: 'my-set-env-variables'
def vars = variables.getBuildVariables()
//println "found variables" + vars
env.dev_version = vars.my_dev_version
env.qa_version = vars.my_qa_version
env.pp_version = vars.my_pp_version
env.prd_version = vars.my_prd_version
}
}
stage('next job') {
echo "NEXT JOB DEV version = ${env.dev_version}"
echo "NEXT JOB QA version = ${env.qa_version}"
echo "NEXT JOB PP version = ${env.pp_version}"
echo "NEXT JOB PRD version = ${env.prd_version}"
}
For those who wants the environment's key to be dynamic, the following code can be used:
stage('Prepare Environment') {
steps {
script {
def data = [
"k1": "v1",
"k2": "v2",
]
data.each { key ,value ->
env."$key" = value
// env[key] = value // Deprecated, this can be used as well, but need approval in sandbox ScriptApproval page
}
}
}
}
You can also dump all your vars into a file, and then use the '-e #file' syntax. This is very useful if you have many vars to populate.
steps {
echo "hello World!!"
sh """
var1: ${params.var1}
var2: ${params.var2}
" > vars
"""
ansiblePlaybook inventory: _inventory, playbook: 'test-playbook.yml', sudoUser: null, extras: '-e #vars'
}
You can do use library functions in the environments section, like so:
#Library('mylibrary') _ // contains functions.groovy with several functions.
pipeline {
environment {
ENV_VAR = functions.myfunc()
}
…
}

Jenkins Declarative Pipeline: How to inject properties

I have Jenkins 2.19.4 with Pipeline: Declarative Agent API 1.0.1. How does one use readProperties if you cannot define a variable to assign properties read to?
For example, to capture SVN revision number, I currently capture it with following in Script style:
```
echo "SVN_REVISION=\$(svn info ${svnUrl}/projects | \
grep Revision | \
sed 's/Revision: //g')" > svnrev.txt
```
def svnProp = readProperties file: 'svnrev.txt'
Then I can access using:
${svnProp['SVN_REVISION']}
Since it is not legal to def svnProp in Declarative style, how is readProperties used?
You can use the script step inside the steps tag to run arbitrary pipeline code.
So something in the lines of:
pipeline {
agent any
stages {
stage('A') {
steps {
writeFile file: 'props.txt', text: 'foo=bar'
script {
def props = readProperties file:'props.txt';
env['foo'] = props['foo'];
}
}
}
stage('B') {
steps {
echo env.foo
}
}
}
}
Here I'm using env to propagate the values between stages, but it might be possible to do other solutions.
The Jon S answer requires granting script approval because it is setting environment variables. This is not needed when running in same stage.
pipeline {
agent any
stages {
stage('A') {
steps {
writeFile file: 'props.txt', text: 'foo=bar'
script {
def props = readProperties file:'props.txt';
}
sh "echo $props['foo']"
}
}
}
}
To define general vars available to all stages, define values for example in props.txt as:
version=1.0
fix=alfa
and mix script and declarative Jenkins pipeline as:
def props
def VERSION
def FIX
def RELEASE
node {
props = readProperties file:'props.txt'
VERSION = props['version']
FIX = props['fix']
RELEASE = VERSION + "_" + FIX
}
pipeline {
stages {
stage('Build') {
echo ${RELEASE}
}
}
}

Get username logged in Jenkins from Jenkins Workflow (Pipeline) Plugin

I am using the Pipeline plugin in Jenkins by Clouldbees (the name was Workflow plugin before), I am trying to get the user name in the Groovy script but I am not able to achieve it.
stage 'checkout svn'
node('master') {
// Get the user name logged in Jenkins
}
Did you try installing the Build User Vars plugin? If so, you should be able to run
node {
wrap([$class: 'BuildUser']) {
def user = env.BUILD_USER_ID
}
}
or similar.
To make it work with Jenkins Pipeline:
Install user build vars plugin
Then run the following:
pipeline {
agent any
stages {
stage('build user') {
steps {
wrap([$class: 'BuildUser']) {
sh 'echo "${BUILD_USER}"'
}
}
}
}
}
Here's a slightly shorter version that doesn't require the use of environment variables:
#NonCPS
def getBuildUser() {
return currentBuild.rawBuild.getCause(Cause.UserIdCause).getUserId()
}
The use of rawBuild requires that it be in a #NonCPS block.
It is possible to do this without a plugin (assuming JOB_BASE_NAME and BUILD_ID are in the environment):
def job = Jenkins.getInstance().getItemByFullName(env.JOB_BASE_NAME, Job.class)
def build = job.getBuildByNumber(env.BUILD_ID as int)
def userId = build.getCause(Cause.UserIdCause).getUserId()
There is also a getUserName, which returns the full name of the user.
This works for me without the Build User plugin:
// get first entry of JSONArray
def buildCause = currentBuild.getBuildCauses()[0]
def buildPrincipal = [type:"unknown", name:""]
if (buildCause._class ==~ /.+BranchEventCause/) {
def branchCause = currentBuild.getRawBuild().getCause(jenkins.branch.BranchEventCause)
buildPrincipal = [type:"branch",name:buildCause.shortDescription]
} else
if (buildCause._class ==~ /.+TimerTriggerCause/) {
def timerCause = currentBuild.getRawBuild().getCause(hudson.triggers.TimerTrigger.TimerTriggerCause)
buildPrincipal = [type:"timer", name:"Timer event"]
} else
if (buildCause._class ==~ /.+UserIdCause/) {
def buildUserCause = currentBuild.getRawBuild().getCause(hudson.model.Cause.UserIdCause)
buildPrincipal = [type:"user", name:buildCause.userId]
} else
// ... other causes
def jobUserId, jobUserName
//then somewhere
wrap([$class: 'BuildUser']) {
jobUserId = "${BUILD_USER_ID}"
jobUserName = "${BUILD_USER}"
}
//then
println("Started By: ${jobUserName}")
We were using this plugin : Build User Vars Plugin. More variables are available.
//Below is a generic groovy function to get the XML metadata for a Jenkins build.
//curl the env.BUILD_URL/api/xml parse it with grep and return the string
//I did an or true on curl, but possibly there is a better way
//echo -e "some_string \c" will always return some_string without \n char
//use the readFile() and return the string
def GetUserId(){
sh """
/usr/bin/curl -k -s -u \
\$USERNAME:\$PASSWORD -o \
/tmp/api.xml \
\$BUILD_URL/api/xml || true
THE_USERID=`cat /tmp/api.xml | grep -oP '(?<=<userId>).*?(?=</userId>)'`
echo -e "\$THE_USERID \\c" > /tmp/user_id.txt
"""
def some_userid = readFile("/tmp/user_id.txt")
some_userid
}
I modified #shawn derik response to get it to work in my pipeline:
stage("preserve build user") {
wrap([$class: 'BuildUser']) {
GET_BUILD_USER = sh ( script: 'echo "${BUILD_USER}"', returnStdout: true).trim()
}
}
Then I can reference that variable later on by passing it or in the same scope as ${GET_BUILD_USER} . I installed the same plugin referenced.
Edit: I re-read the question - the below only gets you the user running the build (which technically is often more interesting), not the one triggering the build in the frontend (be it REST-API or WebUI).
If you have Jenkins impersonation enabled, then I believe the result should be equivalent, otherwise this will only get you the user who owns the jenkins agent on the build machine.
Original answer:
Another way would be to
sh 'export jenkins_user=$(whoami)'
Downside: Linux-dependent, difficult to port across multiple agents in a single build (but then, the auth context may be different on each slave)
Upside: No need to install plugins (which on shared/large Jenkins instances can be tricky)
The Build User Vars Plugin is useful when you are executing the stage on an agent.
The alternative is to use the current build clause (see https://code-maven.com/jenkins-get-current-user), which also works when your stage is set with agent none.
The following code is inspired by Juergen's solution but I added more possible trigger reason and display them in a formatted manner:
String getTriggerReason() {
def buildCause = currentBuild.getBuildCauses()[0]
if (buildCause._class ==~ /.+(BranchEventCause|BranchIndexingCause)/) {
if (env.JOB_BASE_NAME == 'master') {
return 'Triggered by master commit'
} else {
return "Triggered by ${buildCause.shortDescription}"
}
}
if (buildCause._class ==~ /.+TimerTriggerCause/) {
return 'Triggered by timer'
}
if (buildCause._class ==~ /.+BuildUpstreamCause/) {
return "Triggered by build #${buildCause.upstreamBuild}"
}
if (buildCause._class ==~ /.+UserIdCause/) {
def userName = buildCause.userName.replaceFirst(/\s?\(.*/, '')
return "Triggered by user ${userName}"
}
return 'Unknown trigger'
}

Resources