Home > database >  cps mismatch when sending a slack message in a jenkins pipeline
cps mismatch when sending a slack message in a jenkins pipeline

Time:12-18

I am calling this function from a declarative pipeline in the post always stage:

import groovy.json.JsonSlurper
import com.cloudbees.groovy.cps.NonCPS
@NonCPS
def call(){
    String path = "C:\\Users\\tests"
    String jsonFileToRead = new File(path).listFiles().findAll { it.name.endsWith(".json") }
        .sort { -it.lastModified() }?.head()
    JsonSlurper jsonSlurper = new JsonSlurper()
    Map data = jsonSlurper.parse(new File(jsonFileToRead))
    Map testResults = [:]
    int totalTests = data.results.size()
    int totalFailures = 0
    int totalSuccess = 0
    def results = []

    //Map test names and results from json file
    for (int i = 0; i < data.results.size(); i  ) {
        testResults.put(i, [data['results'][i]['TestName'], data['results'][i]['result']])
    }

    //Iterate through the map and send to slack test name and results and then a summary of the total tests, failures and success
    for (test in testResults.values()){
        String testName =  test[0]
        String testResult = test[1]
        String msg = "Test: "   testName   " Result: "   testResult
        if(testResult == "fail")
        {
            totalFailures   
            println msg
            try {
                slackSend color : "danger", message: "${msg}", channel: '#somechannel'
            } catch (Throwable e) {
                error "Caught ${e.toString()}" 
            }   
        }
        else if(testResult == "pass")
        {
            totalSuccess  
            println msg
            try {
                slackSend color : "good", message: "${msg}", channel: '#somechannel'
            } catch (Throwable e) {
                error "Caught ${e.toString()}" 
            }            
        }
        else
        {
            println "Unknown test result: "   testResult
        }
    }
    def resultsSummary = "Total Tests: "   totalTests   " Total Failures: "   totalFailures   " Total Success: "   totalSuccess
    slackSend color : "good", message: "${resultsSummary}", channel: '#somechannel'
    println resultsSummary

but I keep getting this error, even though I am using the @NonCPS. I am bit confused as if I comment out the 3 lines where I call the slackSend functionality everything goes fine and I get in the console output the right messages and the pipeline finishes succesfully, but if I run it like it is I get this error even if I just try to send the summary and comment out the other 2 slackSend:

13:53:23  [Pipeline] echo
13:53:23  Test: triggers_attackMove Result: pass
13:53:23  [Pipeline] slackSend
13:53:23  Slack Send Pipeline step running, values are - baseUrl: <empty>, teamDomain: forgottenempires, channel: #test_harness_logs_phoenix, color: good, botUser: true, tokenCredentialId: Slack_bot_test, notifyCommitters: false, iconEmoji: :goose, username: Jenkins Wizard, timestamp: <empty>
13:53:23  [Pipeline] error
13:53:24  [Pipeline] }
13:53:24  [Pipeline] // script
13:53:24  Error when executing always post condition:
13:53:24  hudson.AbortException: Caught CpsCallableInvocation{methodName=slackSend, call=com.cloudbees.groovy.cps.impl.CpsFunction@149776fc, receiver=null, arguments=[org.jenkinsci.plugins.workflow.cps.DSL$ThreadTaskImpl@25def8]}
13:53:24    at org.jenkinsci.plugins.workflow.steps.ErrorStep$Execution.run(ErrorStep.java:64)
13:53:24    at org.jenkinsci.plugins.workflow.steps.ErrorStep$Execution.run(ErrorStep.java:51)
13:53:24    at org.jenkinsci.plugins.workflow.steps.SynchronousStepExecution.start(SynchronousStepExecution.java:37)
13:53:24    at org.jenkinsci.plugins.workflow.cps.DSL.invokeStep(DSL.java:322)
13:53:24    at org.jenkinsci.plugins.workflow.cps.DSL.invokeMethod(DSL.java:196)
enter code here
....

I tried to take the slackSend functionality to another function with the @NonCPS decorator and call it from within the function but I keep getting the same error, and honestly I am quiet lost a this point

CodePudding user response:

slackSend is a pipeline step and as such can't be called from @NonCPS context. There are only a few exceptions like echo, that work from @NonCPS context. See Use of Pipeline steps from @NonCPS for details.

My approach in such cases is to remove @NonCPS from the function that calls the step(s) and move only the code that actually requires @NonCPS into separate @NonCPS functions. In your case that's propably just the code that uses the File and JsonSlurper classes.

Also, we need to make sure that any data returned by the @NonCPS functions is serializable. JsonSlurper returns a LazyMap that isn't. As OP commented, the easiest solution is to use JsonSlurperClassic instead, which returns a regular, serializable Map (or a List in case the root is an array).

@NonCPS
Map readData(){
    String path = "C:\\Users\\tests"
    String jsonFileToRead = new File(path).listFiles().findAll { it.name.endsWith(".json") }
        .sort { -it.lastModified() }?.head()
    def jsonSlurper = new JsonSlurperClassic()
    return jsonSlurper.parse(new File(jsonFileToRead))
}

// Note: Must not be @NonCPS because pipeline steps are called!
def call(){
    Map data = readData()
    Map testResults = [:]
    int totalTests = data.results.size()
    int totalFailures = 0
    int totalSuccess = 0
    def results = []

    //Map test names and results from json file
    for (int i = 0; i < data.results.size(); i  ) {
        testResults.put(i, [data['results'][i]['TestName'], data['results'][i]['result']])
    }

    //Iterate through the map and send to slack test name and results and then a summary of the total tests, failures and success
    for (test in testResults.values()){
        String testName =  test[0]
        String testResult = test[1]
        String msg = "Test: "   testName   " Result: "   testResult
        if(testResult == "fail")
        {
            totalFailures   
            println msg
            try {
                slackSend color : "danger", message: "${msg}", channel: '#somechannel'
            } catch (Throwable e) {
                error "Caught ${e.toString()}" 
            }   
        }
        else if(testResult == "pass")
        {
            totalSuccess  
            println msg
            try {
                slackSend color : "good", message: "${msg}", channel: '#somechannel'
            } catch (Throwable e) {
                error "Caught ${e.toString()}" 
            }            
        }
        else
        {
            println "Unknown test result: "   testResult
        }
    }
    def resultsSummary = "Total Tests: "   totalTests   " Total Failures: "   totalFailures   " Total Success: "   totalSuccess
    slackSend color : "good", message: "${resultsSummary}", channel: '#somechannel'
    println resultsSummary
}
  • Related