4

How do you capture a JSON object as a prettified string using a Jenkins declarative-syntax pipeline?

pipeline {
  agent any

  stages {

    stage( "Set up" ) {
      steps {
        script {
          hostname    = "bld-machine"
          reply_email = "jenkins@${hostname}.company.com"
          actor_email = "user@company.com"
          status_json = initStatusJson()
        }
      }
    }

    /** Try figure out the difference between "global" and "env." variables. */
    stage( "Capture variables" ) {
      steps {
        script {
          status_json.env["var"]  = "${env.var}" as String
          status_json.env["var2"] = "${var}" as String
        }
      }
    }
  }

  post {
    always {
      script {
        def pretty_json = writeJSON( returnText: true, json: status_json )
      }
      emailext( subject: "CI/CD | ${currentBuild.currentResult}",
                from: "${reply_email}",
                to: "${actor_email}",
                mimeType: "text/plain",
                body: "${pretty_json}" )
    }
  }
}

def initStatusJson() {
  def json_obj = readJSON text: '{}'
  json_obj.job = readJSON text: '{}'
  json_obj.env = [:]
  json_obj.job.name = "${JOB_BASE_NAME}" as String
  json_obj.job.number = "${BUILD_ID}" as String
  json_obj.job.server = "${JENKINS_URL}" as String
  json_obj.job.visualization = "${JENKINS_URL}/blue/organizations/jenkins/${JOB_BASE_NAME}/detail/${JOB_BASE_NAME}/${BUILD_ID}/pipeline" as String

  return json_obj
}

The def pretty_json =... statement in the above Jenkinsfile triggers the following error:

WARNING: Unknown parameter(s) found for class type WARNING: Unknown parameter(s) found for class type 'org.jenkinsci.plugins.pipeline.utility.steps.json.WriteJSONStep': returnText
[Pipeline] }
[Pipeline] // script
Error when executing always post condition:
java.lang.IllegalArgumentException: You have to provided a file for writeJSON.
    at org.jenkinsci.plugins.pipeline.utility.steps.json.WriteJSONStepExecution.run(WriteJSONStepExecution.java:61)
    at org.jenkinsci.plugins.pipeline.utility.steps.json.WriteJSONStepExecution.run(WriteJSONStepExecution.java:43)
    at org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:47)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

What I have tried:

  1. The def pretty_json = writeJSON( returnText: true, json: status_json ) statement is inspired by these resources:
    Jenkinsfile pipeline construct JSON object and write to file
    https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#writejson-write-json-to-a-file-in-the-workspace

  2. I also tried def pretty_json = writeJSON returnText: true, json: status_json which resulted in an identical error.

  3. status_json.toString() returns a valid, but non-prettified JSON string.

  4. I tried def pretty_json = JsonOutput.toJson(status_json) based on Create JSON strings from Groovy variables in Jenkins Pipeline, and it generates this error:

Error when executing always post condition:
groovy.lang.MissingPropertyException: No such property: JsonOutput for class: groovy.lang.Binding
  1. Tried def pretty_json = groovy.json.JsonOutput.prettyPrint(status_json) based on https://gist.github.com/osima/1161966, and it generated this error:
Error when executing always post condition:
groovy.lang.MissingMethodException: No signature of method: java.lang.Class.prettyPrint() is applicable for argument types: (net.sf.json.JSONObject)

Update: Attempted @daggett's solution as follows:

  post {
    always {
      script {
        def pretty_json = status_json.toString(2)
      }
      emailext( subject: "CI/CD | ${currentBuild.currentResult}",
                from: "${reply_email}",
                to: "${actor_email}",
                mimeType: "text/plain",
                body: "${pretty_json}" )
    }
  }

...and also tried some variations like pretty_json = ... (instead of def pretty_json = ...) and also moving the pretty_json assignment outside the script{...} scope...but none worked.

Inside the script{...} context, the .toString(2) generated this error:

Scripts not permitted to use method net.sf.json.JSON toString int.

Outside the script{...} context, it generated what I interpret to be a "syntax error":

WorkflowScript: 79: Expected a step @ line 79, column 7.
         pretty_json = status_json.toString(2)
StoneThrow
  • 5,314
  • 4
  • 44
  • 86

2 Answers2

4

According to last error message

groovy.lang.MissingMethodException: 
No signature of method: java.lang.Class.prettyPrint() 
is applicable for argument types: (net.sf.json.JSONObject)

You have net.sf.json.JSONObject in status_json variable.

that's really strange - seems you are getting status_json not in a standard way for jenkins

however according to documentation of this class

http://json-lib.sourceforge.net/apidocs/jdk15/net/sf/json/JSONObject.html#toString(int)

just do following to make pretty json:

def pretty_json = status_json.toString(2)

If you have Scripts not permitted to use method XYZ exception:

for security reasons a lot of non-standard methods are disabled in jenkins.

refer this answer to resolve this kind of issue: https://stackoverflow.com/a/39412951/1276664


and finally - almost every case from your question should work:

  1. writeJSON( returnText: true, json: status_json ) : update pipeline-utility-steps jenkins plugin to the latest version to support returnText parameter

  2. the same as above

...

  1. JsonOutput.toJson(status_json) : JsonOutput class located in groovy.json package. you could import this package at t he beginning of the script import groovy.json or call it like this: groovy.json.JsonOutput.toJson(status_json). note that this method returns non-formatted json.

  2. groovy.json.JsonOutput.prettyPrint(status_json) : check the documentation for JsonOutput.prettyPrint - it could be called for string and not for object. so this could work: groovy.json.JsonOutput.prettyPrint(status_json.toString()) but only in case when status_json.toString() returns a valid json and JsonOutput.prettyPrint allowed to be called in jenkins admin.

daggett
  • 26,404
  • 3
  • 40
  • 56
  • Looked promising, but didn't work -- I updated my post with the result of trying this answer -- maybe the errors are meaningful to you, but I'm not experienced enough with the language to know how to fix the errors. – StoneThrow Jun 04 '21 at 01:22
  • Script not permitted - just search for this error. This requires to grant jenkins to call this method in admin. – daggett Jun 04 '21 at 05:33
0

I just did a test and it gave results :
def pretty_json = writeJSON( returnText: true, json: status_json , pretty: 4)
Note : Ensure you have the plugin Pipeline Utility Steps installed. Or reinstall it again.
Below is the script example:

#!groovy
import hudson.model.Result
import groovy.json.*
pipeline 
{
    agent any 
    stages 
    {
        stage ('Set up')
        {
            steps
            {
                script
                {
                    hostname    = "bld-machine"
                    reply_email = "jenkins@${hostname}.company.com"
                    actor_email = "user@company.com"
                    status_json = initStatusJson()
                    println (status_json)
                }
            }
        }
        stage ('Capture variables')
        {
            steps
            {
                script
                {
                    // Added just for test
                    status_json.env["var"]  = "Alt" as String
                    status_json.env["var2"]  = "su" as String
                    println (status_json)
                }
            }
        }
    }
    
    post {
    always {
      script {
        def pretty_json = writeJSON( returnText: true, json: status_json , pretty: 4)
        println (pretty_json)
         emailext( subject: "CI/CD | ${currentBuild.currentResult}",
                from: "${reply_email}",
                to: "${actor_email}",
                mimeType: "text/plain",
                body: "${pretty_json}" )
       }
     }
    }
} 
def initStatusJson() {
  def json_obj = readJSON text: '{}'
  json_obj.job = readJSON text: '{}'
  json_obj.env = [:]
  json_obj.job.name = "${JOB_BASE_NAME}" as String
  json_obj.job.number = "${BUILD_ID}" as String
  json_obj.job.server = "${JENKINS_URL}" as String
  json_obj.job.visualization = "${JENKINS_URL}/blue/organizations/jenkins/${JOB_BASE_NAME}/detail/${JOB_BASE_NAME}/${BUILD_ID}/pipeline" as String

  return json_obj
}

Output log : enter image description here

Altaf
  • 2,838
  • 1
  • 16
  • 8