Jenkinsfile with email in loop fails on first iteration - groovy

Latest Jenkins used.
edit: pastebin of full Java exception - https://pastebin.com/zZDNj18E
Goal: loop through all nodes, check for offline, email alert for each offline node.
(tried emailext alerts as well, could not use "offline")
Failed: My jenkinsfile runs perfectly with no email.
With email in the for loop or separately defined in a function, the job crashes after the first email is sent.
[Pipeline] End of Pipeline an exception which occurred: in field hudson.model.Slave.launcher in object hudson.slaves.DumbSlave#ae938e61 .... and many more
My jenkinsfile:
pipeline {
agent{
label 'master'
}
options {
// Enable timestamps in log
timestamps()
skipDefaultCheckout()
timeout(time: 4, unit: 'MINUTES')
}
stages {
stage('Monitor') {
steps{
script{
def offlineSlaves = []
for (aSlave in hudson.model.Hudson.instance.slaves) {
def thisSlave = aSlave.name
echo 'Name: ' + thisSlave + ' is being checked.'
if ( aSlave.getComputer().isOffline().toString() == 'true') {
slaveState = 'OFFLINE'
echo 'Name: ' + thisSlave + ' is ' + slaveState + ' !'
emailext (
mimeType: 'text/html',
body: "${env.JOB_NAME} found an OFFLINE node: ${name} ",
subject: "Jenkins ERROR: Build Node ${name} is OFFLINE " ,
to: 'jfisher#xxx')
}
}
}
}
}
}
post {
failure {
emailext (
body: 'Monitor Nodes Jenkins Job failed !',
presendScript: '$DEFAULT_PRESEND_SCRIPT',
recipientProviders: [requestor(),culprits()],
subject: 'Monitor Nodes Jenkins Failed',
to: 'jfisher#intouchhealth.com')
}
}
}

The problem with this code is the getComputer() part. In the pipeline you should only use Serializable and the SlaveComputer returned from getComputer() isn't.
https://javadoc.jenkins.io/hudson/slaves/SlaveComputer.html
What you should do is move this part to a function annotated with NonCPS
#NonCPS
def shallTrigger() {
for (aSlave in hudson.model.Hudson.instance.slaves) {
def thisSlave = aSlave.name
echo 'Name: ' + thisSlave + ' is being checked.'
if ( aSlave.getComputer().isOffline().toString() == 'true') {
slaveState = 'OFFLINE'
echo 'Name: ' + thisSlave + ' is ' + slaveState + ' !'
emailext (
mimeType: 'text/html',
body: "${env.JOB_NAME} found an OFFLINE node: ${name} ",
subject: "Jenkins ERROR: Build Node ${name} is OFFLINE " ,
to: 'jfisher#xxx')
}
}
}

Related

Test API toMatchObject() jest expect function

I'm trying to check if a response from the body matches partially an object of data "partialLaunchData" . I'm surprised to get this error:
● Test POST /launches › It should respond with 201 success created
expect(received).toMatchObject(expected)
- Expected - 3
+ Received + 13
Object {
- "mission": "Kepler_155",
- "rocket": "Explorer IS1",
- "target": "Kepler-186 f",
+ "launch": Object {
+ "customer": Array [
+ "SAFTA",
+ "NASA",
+ ],
+ "flightNumber": 101,
+ "launchDate": "2022-10-13T21:24:59.189Z",
+ "mission": "Kepler Exploration x",
+ "rocket": " Explorer IS1",
+ "success": true,
+ "target": "Kepler-442",
+ "upcoming": true,
+ },
}
42 | const responseDate= Date(response.body.launchDate).valueOf();
43 | expect(responseDate).toBe(requestDate);
> 44 | expect(response.body).toMatchObject(partialLaunchData);
| ^
45 | })
46 | //tests on the propreties sent
47 | test('It should catch missing required propreties', ()=>{});
at Object.toMatchObject (src/routes/launches/launches.test.js:44:31)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 3 passed, 4 total
Snapshots: 0 total
Time: 3.452 s
Ran all test suites.
This is my Code in the launches.test.js file describing a post Test on launches path "/launches" .
describe( 'Test POST /launches ', ()=> {
// test the response
const completeLaunchData={
// flightNumber: 100,
mission: 'Kepler Exploration x',
rocket: ' Explorer IS1',
launchDate: new Date(),
target: 'Kepler-442',
// customer: ['SpaceX, NASA'],
// upcoming: true,
}
const partialLaunchData={
// flightNumber: 100,
mission: 'Kepler_155',
rocket: 'Explorer IS1',
target: 'Kepler-186 f',
// upcoming: true,
}
test('It should respond with 201 success created', async()=>{
const response = await request(app).post('/launches')
// comparing two dates and joining them in one format : we will compare the value of the two dates
.send(
completeLaunchData
)
.expect('Content-Type',/json/)
.expect(201);
const requestDate = Date(completeLaunchData.launchDate).valueOf();
const responseDate= Date(response.body.launchDate).valueOf();
expect(responseDate).toBe(requestDate);
expect(response.body).toMatchObject(partialLaunchData);
})
Who has an idea about the issue ? I need help please.

Receiving error within Smartthings Device Handler

I am receiving the below error when trying to compile the device_handler code for the Sylvania Smart+ Plug. The code comes from https://images-na.ssl-images-amazon.com/images/I/71PrgM-PamL.pdf
The error:
Org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: script_dth_metadata_0631e407_ffd8_4ceb_b49a_877fd47635df: 94: expecting ''', found '\r' # line 94, column 55. nalResult.value == "on" ? '{{ ^ 1 error
Line 94:
def descriptionText = finalResult.value == "on" ? '{{
metadata {
definition (name: "SYLVANIA Smart Plug", namespace: "ledvanceDH", author:
"Ledvance") {
capability "Actuator"
capability "Switch"
capability "Power Meter"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Health Check"
fingerprint profileId: "C05E", inClusters:
"1000,0000,0003,0004,0005,0006,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM",
model: "Plug 01", deviceJoinName: "SYLVANIA Smart Plug"
fingerprint profileId: "0104", inClusters:
"0000,0003,0004,0005,0006,0B05,FC01,FC08", outClusters: "0003,0019", manufacturer:
"LEDVACE", model: "PLUG", deviceJoinName: "SYLVANIA Smart Plug"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.devicegse.smartthings.com/Outlet/US/OutletUS1.jpg",
"http://cdn.devicegse.smartthings.com/Outlet/US/OutletUS2.jpg"
])
}
}
// UI tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4,
canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label: 'On', action: "switch.off",
icon: "st.Appliances.appliances17", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "off", label: 'Off', action: "switch.on",
icon: "st.Appliances.appliances17", backgroundColor: "#565C51", nextState: "turningOn"
attributeState "turningOn", label: 'Turning On', action:
"switch.off", icon: "st.Appliances.appliances17", backgroundColor: "#60903A", nextState:
"turningOff"
attributeState "turningOff", label: 'Turning Off', action:
"switch.on", icon: "st.Appliances.appliances17", backgroundColor: "#CACACA", nextState:
"turningOn"
}
tileAttribute ("power", key: "SECONDARY_CONTROL") {
attributeState "power", label:'${currentValue} W'
}
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration:
"flat", width: 2, height: 2) {
state "default", label:'', action:"refresh.refresh",
icon:"st.secondary.refresh"
}
main "switch"
details(["switch","refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = zigbee.getKnownDescription(description)
def event = [:]
//TODO: Remove this after getKnownDescription can parse it automatically
if (!finalResult && description!="updated")
finalResult =
getPowerDescription(zigbee.parseDescriptionAsMap(description))
if (finalResult) {
log.info "final result = $finalResult"
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
event = null
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
event = createEvent(name: "power", value: powerValue,
descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable:
true)
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for
the device. AttrId: 0302 and 0300. Simplifying to 10
power level is an integer. The exact power level with
correct units needs to be handled in the device type
to account for the different Divisor value (AttrId: 0302)
and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
*/
}
else {
def descriptionText = finalResult.value == "on" ? '{{
device.displayName }} is On' : '{{ device.displayName }} is Off'
event = createEvent(name: finalResult.type, value:
finalResult.value, descriptionText: descriptionText, translatable: true)
}
}
else {
def cluster = zigbee.parse(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07){
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
event = createEvent(name: "checkInterval", value: 60 * 12,
displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error
code:${cluster.data[0]}"
event = null
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
}
return event
}
def off() {
zigbee.off()
}
def on() {
zigbee.on()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.onOffRefresh()
}
def refresh() {
zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh()
}
def configure() {
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false,
data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no
activity
refresh() + zigbee.onOffConfig(0, 300) + powerConfig()
}
//power config for devices with min reporting interval as 1 seconds and reporting
interval if no activity as 10min (600s)
//min change in value is 01
def powerConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04
{${device.zigbeeId}} {}", "delay 2000",
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}",
//The send-me-a-report is custom to the attribute type for CentraLite
"delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
]
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
//TODO: Remove this after getKnownDescription can parse it automatically
def getPowerDescription(descMap) {
def powerValue = "undefined"
if (descMap.cluster == "0B04") {
if (descMap.attrId == "050b") {
if(descMap.value!="ffff")
powerValue = zigbee.convertHexToInt(descMap.value)
}
}
else if (descMap.clusterId == "0B04") {
if(descMap.command=="07"){
return [type: "update", value : "power (0B04) capability configured
successfully"]
}
}
if (powerValue != "undefined"){
return [type: "power", value : powerValue]
}
else {
return [:]
}
}
I should have thought of it sooner... The problem is with the copy/paste. The long lines were broken by a "return". I removed the returns and the script compiled.

groovy:: compound if statement not evaluating to true

hi I am trying to understand issue where given if (version_exists != '' && env.force_build == true) is evaluating to true even if env.force_build is set to false
pipeline {
agent { label 'master' }
parameters {
string(defaultValue: 'DEV', description: '', name: 'ENV', trim: true)
string(defaultValue: 'sys', description: '', name: 'platform_type', trim: true)
string(defaultValue: 'yyy', description: '', name: 'dev_app_host', trim: true)
string(defaultValue: 'xxx.dv.local', description: '', name: 'dev_xbar_host', trim: true)
string(defaultValue: '1.0.0.23', description: '', name: 'VERSION', trim: true)
booleanParam(defaultValue: false, description: 'force build if possible', name: 'force_build')
}
environment {
git_credential_id = '1234'
app_name = 'application'
app_module_name = 'dvmt_event_processor'
app_git_url = 'ssh://git#oooo:7999/data/application.git'
dna_common_git_url = 'ssh://git#oooo:7999/data/dna-common.git'
ansible_git_url = 'ssh://git#oooo:7999/data/operations-ansible.git'
pip_port = '9700'
}
stages {
stage('checkout from SCM') {
.....
}
stage('build') {
steps {
script {
// version_exists == dvmt_event_processor-1.0.0.23-py2.py3-none-any.whl
// force_build == false
def version_exists = ''
version_exists = sh(script: "ssh -o StrictHostKeyChecking=no ansible#pypi server ls /var/pypi/packages/dev/ | grep ${env.app_module_name} | grep ${env.VERSION}" , returnStdout: true)
if (version_exists != '' && env.force_build == true) {
......
// evaluating to true no matter what
}
}
else if (version_exists != '' && env.force_build == false ) {
echo "version already exists, just deploying"
......
}
else {
echo "starting full build"
.........
}
}
}
}
stage('Test') {
steps {
echo 'Testing...'
.............
}
}
}
}
jenkins console o/p
dvmt_event_processor-1.0.0.23-py2.py3-none-any.whl
[Pipeline] echo
false
[Pipeline] echo
starting full build
env.force_build prints like a boolean, but it is a string.
"false" == true
// → false
"false" == false
// → false
Always debug problems like this with the proper tools. Use .inspect()
or even .dump() to get insights, what kind of data you have at hand:
"false".inspect()
// → 'false'
"false".dump()
// → <java.lang.String#5cb1923 value=[102, 97, 108, 115, 101] coder=0 hash=97196323>
Note the quotes around false with the .inspect() example.
To convert a string to boolean with groovy, you can use
String.toBoolean():
"false".toBoolean() == true
// → false
"false".toBoolean() == false
// → true

Groovy: Seems like a scope issue but cannot find where, can you?

I wrote some functions to help me writing Jenkins pipelines.
The following functions, are responsible to returning a shell command output:
def gen_uuid(){
randomUUID() as String
}
def sh_out(cmd){
String uuid = gen_uuid()
sh """( ${cmd} )> ${uuid}"""
String out = readFile(uuid).trim()
sh "set +x ; rm ${uuid}"
return out
}
That shown, here's another function:
Map get_started_by(){
withCredentials([ // Use Jenkins credentials ID of artifactory
[$class: 'UsernamePasswordMultiBinding', credentialsId: '0b8d591a-f4ce-XXXX-XXXX-faecb504d3d0', usernameVariable: 'J_USER', passwordVariable: 'J_PASS'],
]){
List startedBy = sh_out("""
set +x; curl -u ${J_USER}:${J_PASS} '${env.BUILD_URL}api/json' 2>/dev/null | \
python -mjson.tool | \
awk -F'"' '/(userId|userName)/{print \$4}'
""").split(/(\n)/)
return [
userId: startedBy[0],
userName: startedBy[1]
]
}
}
Which returns the userId and userName of the user who issue the job run.
Then, my problem is in this function:
def run_in_stage_func(String stage_name, Closure command, String sendTo){
String started_by = get_started_by()
String ulink = "<#${started_by['userId']}>"
String jlink = "(<${env.BUILD_URL}|Open>)"
println "============================================================"
stage (stage_name) {
try {
command()
if (currentBuild.result == 'FAILURE') {
error "Build failed, see log for further details."
}
println "============================================================"
} catch (Exception ex) {
def except = "${ex}"
slackSend channel: channel, color: 'danger', teamDomain: null, token: null,
message: " :${ulink} *Failed to build ${env.JOB_NAME}*! :x: ${jlink} (<!here|here>)"
echo "Pipeline failed at stage: ${stage_name}"
throw ex
}
}
}
When I run the job, I get the following error:
groovy.lang.MissingPropertyException: No such property: userId for class: java.lang.String
What could be the reason that the line ' String ulink = "<#${started_by['userId']}>" ' - is not working as intended?
You cast the result of get_started_by() to String explicitly. To fix it, change your code to
def started_by = get_started_by()
or
Map started_by = get_started_by()

How to use if else condition in Gradle

Can someone tell me how could I write the if else condition in the gradle script
I mean i have two different types of zip files one is LiceseGenerator-4.0.0.58 and other one is CLI-4.0.0.60.My deployment script is working fine but I am using the shell script to do this and I want everything in gradle instead of doing it in the shell script.I want when I am deploying the LicenseGenerator it should deploy in differnet way and if it is CLI then it should deploy in other way.Currently deployall task is doing everyting.If I put if else condition how could I call the task.Please let me know if need any other information
Below is my script
// ------ Tell the script to get dependencies from artifactory ------
buildscript {
repositories {
maven {
url "http://ct.ts.th.com:8/artifactory/libs-snapshot"
}
}
// ------ Tell the script to get dependencies from artifactory ------
dependencies {
classpath ([ "com.trn.cm:cmplugin:1.1.118" ])
}
}
apply plugin: 'com.trn.cm.cmgplugin'
/**
* The folloing -D parameters are required to run this task
* - deployLayer = one of acceptance, latest, production, test
*/
//------------------------------------------------------------------------------------------
// Read the properties file and take the value as per the enviornment.
//
//------------------------------------------------------------------------------------------
if(!System.properties.deployLayer) throw new Exception ("deployLayer must be set")
def thePropFile = file("config/${System.properties.deployLayer}.properties")
if(!thePropFile.exists()) throw new Exception("Cannot load the specified environment properties from ${thePropFile}")
println "Deploying ${System.properties.jobName}.${System.properties.buildNumber} to ${System.properties.deployLayer}"
// load the deploy properties from the file
def deployProperties = new Properties()
thePropFile.withInputStream {
stream -> deployProperties.load(stream)
}
// set them in the build environment
project.ext {
deployProps = deployProperties
deployRoot = deployProperties["${System.properties.jobName}.deployroot"]
deployFolder = deployProperties["${System.properties.jobName}.foldername"]
deployPostInstallSteps = deployProperties["${System.properties.jobName}.postInstallSteps"]
}
task deleteGraphicsAssets(type: Delete, dependsOn: deploy) {
def dirName = "${deployRoot}"
delete dirName
doLast {
file(dirName).mkdirs()
}
}
task myCustomTask(dependsOn: deleteGraphicsAssets) << {
copy {
from 'deploymentfiles'
into "${deployRoot}"
}
}
task cleanTempDir(type: Delete, dependsOn: myCustomTask) {
delete fileTree(dir: "build/artifacts", exclude: "*.zip")
}
task unzipArtifact(dependsOn: cleanTempDir) << {
file("${buildDir}/artifacts").eachFile() {
println "Deploying ${it}"
// ant.mkdir(dir: "${deployRoot}/${deployFolder}")
ant.unzip(src: it, dest: "${deployRoot}")
}
}
task setPerms( type: Exec, dependsOn: unzipArtifact) {
workingDir "${deployRoot}"
executable "bash"
args "-c", "dos2unix analyticsEngine.sh"
args "-c", "chmod u+x analyticsEngine.sh && ./analyticsEngine.sh"
}
task deployAll(dependsOn: setPerms){}
I used in below way it is working fine
// ------ Tell the script to get dependencies from artifactory ------
buildscript {
repositories {
maven {
url "http://c.t.th.com:8/artifactory/libs-snapshot"
}
}
// ------ Tell the script to get dependencies from artifactory ------
dependencies {
classpath ([ "c.t.c:cmgin:1.1.118" ])
}
}
apply plugin: 'com.t.c.cmlugin'
/**
* The folloing -D parameters are required to run this task
* - deployLayer = one of acceptance, latest, production, test
*/
//------------------------------------------------------------------------------------------
// Read the properties file and take the value as per the enviornment.
//
//------------------------------------------------------------------------------------------
if(!System.properties.deployLayer) throw new Exception ("deployLayer must be set")
def thePropFile = file("config/${System.properties.deployLayer}.properties")
if(!thePropFile.exists()) throw new Exception("Cannot load the specified environment properties from ${thePropFile}")
println "Deploying ${System.properties.jobName}.${System.properties.buildNumber} to ${System.properties.deployLayer}"
// load the deploy properties from the file
def deployProperties = new Properties()
thePropFile.withInputStream {
stream -> deployProperties.load(stream)
}
// set them in the build environment
project.ext {
deployProps = deployProperties
deployRoot = deployProperties["${System.properties.jobName}.deployroot"]
deploydir = deployProperties["${System.properties.jobName}.deploydir"]
deployFolder = deployProperties["${System.properties.jobName}.foldername"]
deployPostInstallSteps = deployProperties["${System.properties.jobName}.postInstallSteps"]
}
task deleteGraphicsAssets(type: Delete, dependsOn: deploy) {
def dirName = "${deployRoot}"
delete dirName
doLast {
file(dirName).mkdirs()
}
}
task copyartifactZip << {
copy {
from "${deployRoot}"
into "${deploydir}/"
}
}
task copyLicenseZip << {
copy {
from "${deployRoot}"
into "${deploydir}/${deployFolder}"
}
}
task myCustomTask(dependsOn: deleteGraphicsAssets) << {
copy {
from 'deploymentfiles'
into "${deployRoot}"
}
}
task unzipArtifact(dependsOn: myCustomTask) << {
def theZip = file("${buildDir}/artifacts").listFiles().find { it.name.endsWith('.zip') }
println "Unzipping ${theZip} the artifact to: ${deployRoot}"
ant.unzip(src: theZip, dest: "${deployRoot}", overwrite: true)
}
task setPerms(type:Exec, dependsOn: unzipArtifact) {
workingDir "${deployRoot}"
executable "bash"
args "-c", "chmod -fR 755 *"
}
def dirName = "${deploydir}/${deployFolder}"
task zipDeployment(type: GradleBuild, dependsOn: setPerms) { GradleBuild gBuild ->
def env = System.getenv()
def jobName=env['jobName']
if (jobName.equals("LicenseGenerator")) {
delete dirName
file(dirName).mkdirs()
gBuild.tasks = ['copyLicenseZip']
} else {
gBuild.tasks = ['copyartifactZip']
}
}
task deployAll(dependsOn: zipDeployment){}
It's usually a bad practice to have if/else logic in the build script because it adds complexity and sometimes causes surprising and unexpected results. Since you have very different artifacts, it's advisable to have two different tasks for that, instead of one-for-all deployAll. And you should call corresponding task when you are in different environments.

Resources