Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-44930

Allow sh to return exit status, stdout and stderr all at once

    XMLWordPrintable

    Details

    • Similar Issues:

      Description

      Like many of the commenters on -JENKINS-26133- I'd like to be able to capture the exit status and text written to standard out at the same time.

      My current use case is calling git merge --no-edit $branches and if there was an error sending a slack notification with the output. 

      The current workaround is:

      def status = sh(returnStatus: true, script: "git merge --no-edit $branches > merge_output.txt")
      if (status != 0) {
        currentBuild.result = 'FAILED'
        def output = readFile('merge_output.txt').trim()
        slackSend channel: SLACK_CHANNEL, message: "<${env.JOB_URL}|${env.JOB_NAME}> ran into an error merging the PR branches into the ${TARGET_BRANCH} branch:\n```\n${output}\n```\n<${env.BUILD_URL}/console|See the full output>", color: 'warning', tokenCredentialId: 'slack-token'
        error 'Merge conflict'
      }
      sh 'rm merge_output.txt'

      Which works but isn't a great developer experience... It would be great if I could request an object that contained: status, stdout, and stderr.

        Attachments

          Issue Links

            Activity

            Hide
            anentropic Anentropic added a comment - - edited

            I have exactly the same case as the OP

            Sad to see a sane implementation of this feature is still nowhere 3.5 years later

            And the docs for `sh` have disappeared https://www.jenkins.io/doc/pipeline/steps/workflow-durable-task-step

            :chef-kiss:

            Show
            anentropic Anentropic added a comment - - edited I have exactly the same case as the OP Sad to see a sane implementation of this feature is still nowhere 3.5 years later And the docs for `sh` have disappeared https://www.jenkins.io/doc/pipeline/steps/workflow-durable-task-step :chef-kiss:
            Hide
            shadycuz Levi Blaney added a comment -

            I also have wanted to be able to have the exitCode and the stdOut at the same time. About a year ago I started what I call the Jenkins Standard Library to help make building pipelines faster and easier. One of the first things I solved was these issues with `sh()`.

            @Library('jenkins-std-lib')
            import org.dsty.bash.BashClient
            import org.dsty.bash.ScriptError
            
            node() {    
            
                String msg = 'TestMessage'
            
                def bash = new BashClient(this)
            
                def result = bash("echo '${msg}'")
            
                if (result.stdOut != msg ) {
                    error('Did not contain correct output.')
                }
            
                if ( result.stdErr ) {
                    error('Should not have output anything.')
                }    
            
                if ( result.exitCode != 0 ) {
                    error('Exited with wrong code.')
                }    
            
                def exception = null
            
                try {
                    bash('fakecommand')
                } catch (ScriptError e) {
                    exception = e
                }    
            
                if ( !exception.stdErr.contains('fakecommand: command not found') ) {
                    error('Command was found.')
                }   
              
                if (exception.stdOut) {
                    error('Should not have stdOut.')
                }    
            
                if ( exception.exitCode != 127) {
                    error('Exited with wrong code.')
                }
            }
            
            

            Also not documented here is `result.output` which contains both the stdErr and stdOut but the order can't be guaranteed because bash is async. I also made the exception the same as the result because why wouldn't you want to include things like stdErr, stdOut and exitCode in your failure notification. Also `bash.silent()` which will not output ANYTHING to the build console. That way you can keep your build logs short and concise. Finally `bash.ignoreErrors()` which will return the result object even if the script errors. It also supports not sending output to the build console. 

            You can also browse the full documentation.

            I stopped working on the project when I couldn't find a way to test the code completely but about 2 weeks ago I found a new experimental way to test my library and now that I'm able to completely test the codebase I'm back to adding more features. 

            Right now the library only has the BashClient and LogClient but I think next I will add an easy way to POST and GET instead of dropping down to curl all the time. I think after that maybe github status checks or maybe a better slack/teams notification.

            It would be awesome if you would use my library and open github issues for features/feedback. If not you can always look at the BashClient code and see how I made it work and use it in your own shared libraries. The code is completely OpenSource and free. I will also work on a contributors guide if anyone is interested. 

            andrew morton Berend Dekens Marcel 'childNo͡.de' Trautwein Mark Pettigrew juan perez too many to tag so I will stop here.

            Show
            shadycuz Levi Blaney added a comment - I also have wanted to be able to have the exitCode and the stdOut at the same time. About a year ago I started what I call the Jenkins Standard Library  to help make building pipelines faster and easier. One of the first things I solved was these issues with `sh()`. @Library( 'jenkins-std-lib' ) import org.dsty.bash.BashClient import org.dsty.bash.ScriptError node() { String msg = 'TestMessage' def bash = new BashClient( this ) def result = bash( "echo '${msg}' " ) if (result.stdOut != msg ) { error( 'Did not contain correct output.' ) } if ( result.stdErr ) { error( 'Should not have output anything.' ) } if ( result.exitCode != 0 ) { error( 'Exited with wrong code.' ) } def exception = null try { bash( 'fakecommand' ) } catch (ScriptError e) { exception = e } if ( !exception.stdErr.contains( 'fakecommand: command not found' ) ) { error( 'Command was found.' ) } if (exception.stdOut) { error( 'Should not have stdOut.' ) } if ( exception.exitCode != 127) { error( 'Exited with wrong code.' ) } } Also not documented here is `result.output` which contains both the stdErr and stdOut but the order can't be guaranteed because bash is async. I also made the exception the same as the result because why wouldn't you want to include things like stdErr, stdOut and exitCode in your failure notification. Also `bash.silent()` which will not output ANYTHING to the build console. That way you can keep your build logs short and concise. Finally `bash.ignoreErrors()` which will return the result object even if the script errors. It also supports not sending output to the build console.  You can also browse the full documentation . I stopped working on the project when I couldn't find a way to test the code completely but about 2 weeks ago I found a new experimental way to test my library and now that I'm able to completely test the codebase I'm back to adding more features.  Right now the library only has the BashClient and LogClient but I think next I will add an easy way to POST and GET instead of dropping down to curl all the time. I think after that maybe github status checks or maybe a better slack/teams notification. It would be awesome if you would use my library and open github issues for features/feedback. If not you can always look at the BashClient code and see how I made it work and use it in your own shared libraries. The code is completely OpenSource and free. I will also work on a contributors guide if anyone is interested.  andrew morton Berend Dekens Marcel 'childNo͡.de' Trautwein Mark Pettigrew juan perez  too many to tag so I will stop here.
            Hide
            dindurthy Dharma Indurthy added a comment -

            Wow, open since 2017, amazing. This is not still controversial, right? I believe there's a lot of tech debt that makes this hard, but I can't imagine why this is controversial.

            Show
            dindurthy Dharma Indurthy added a comment - Wow, open since 2017, amazing. This is not still controversial, right? I believe there's a lot of tech debt that makes this hard, but I can't imagine why this is controversial.
            Hide
            evilezh Haralds added a comment - - edited

             

            Hi everyone, I've idea how to improve on this. Here is exact plan:

            https://github.com/jenkinsci/workflow-durable-task-step-plugin/blob/2a88fdd885caa0d1bcf3336efc00b579b75ced82/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/DurableTaskStep.java#L648

             

            if ((returnStatus && originalCause == null) || exitCode == 0) {  
            

             

            change to something like (in case when both stdout and status code are enabled): 

             

            if ((returnStatus && originalCause == null) || exitCode == 0 || (returnStatus && originalCause == null && returnStdout ) ) {
            

             

            Then in case if both are enabled then we will need extra if  like:

             

            if (returnStdOut && returnStatus) {
              getContext().onSuccess(["status":exitCode, "output": new String(output.produce(), StandardCharsets.UTF_8)]);
            else {
               getContext().onSuccess(returnStatus ? exitCode : returnStdout ? new String(output.produce(), StandardCharsets.UTF_8) : null);
            }  

             

            That would maintain compatibility with previous code. Also would need to update (or remove) https://github.com/jenkinsci/workflow-durable-task-step-plugin/blob/2a88fdd885caa0d1bcf3336efc00b579b75ced82/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/DurableTaskStep.java#L168 to allow both parameters in same time.

             

            Show
            evilezh Haralds added a comment - - edited   Hi everyone, I've idea how to improve on this. Here is exact plan: https://github.com/jenkinsci/workflow-durable-task-step-plugin/blob/2a88fdd885caa0d1bcf3336efc00b579b75ced82/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/DurableTaskStep.java#L648   if ((returnStatus && originalCause == null ) || exitCode == 0) {     change to something like (in case when both stdout and status code are enabled):    if ((returnStatus && originalCause == null ) || exitCode == 0 || (returnStatus && originalCause == null && returnStdout ) ) {   Then in case if both are enabled then we will need extra if  like:   if (returnStdOut && returnStatus) { getContext().onSuccess([ "status" :exitCode, "output" : new String (output.produce(), StandardCharsets.UTF_8)]); else { getContext().onSuccess(returnStatus ? exitCode : returnStdout ? new String (output.produce(), StandardCharsets.UTF_8) : null ); }   That would maintain compatibility with previous code. Also would need to update (or remove)  https://github.com/jenkinsci/workflow-durable-task-step-plugin/blob/2a88fdd885caa0d1bcf3336efc00b579b75ced82/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/DurableTaskStep.java#L168  to allow both parameters in same time.  
            Hide
            mcwarg m added a comment -

            I have a similar issue, except it's with bat calls. Is there a similar ticket for this or should it be handled here too?

            Show
            mcwarg m added a comment - I have a similar issue, except it's with bat calls. Is there a similar ticket for this or should it be handled here too?

              People

              Assignee:
              Unassigned Unassigned
              Reporter:
              drewish andrew morton
              Votes:
              148 Vote for this issue
              Watchers:
              130 Start watching this issue

                Dates

                Created:
                Updated: