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
            pfuntner John Pfuntner added a comment -

            My case for doing this is to have a fail or retry only under certain conditions. I have scripts that do a lot of cloud work (aws, gcp, etc) so there are transient errors sometimes where a retry might be called for. However there are also failure scenarios that are not transient and a retry would not help. By examining the exit status and output, I could determine whether or not an operation was successful, failed, or could use a retry.

            Show
            pfuntner John Pfuntner added a comment - My case for doing this is to have a fail or retry only under certain conditions. I have scripts that do a lot of cloud work (aws, gcp, etc) so there are transient errors sometimes where a retry might be called for. However there are also failure scenarios that are not transient and a retry would not help. By examining the exit status and output, I could determine whether or not an operation was successful, failed, or could use a retry.
            Hide
            monger39 David Riemens added a comment -

            My use case is an SVN operation to see if a TAG exists; here the command may return on STDOUT a revision nr if the tag exists, a warning in STDERR if the tag does not exist yet, or an error on STDERR if something went wrong. Rather than a 5 line call to sh/bat, I now have a 40 line pipeline script with retry/try/catch, storing the stdout/stderr in indvidual files, and reading them back....sigh

            Show
            monger39 David Riemens added a comment - My use case is an SVN operation to see if a TAG exists; here the command may return on STDOUT a revision nr if the tag exists, a warning in STDERR if the tag does not exist yet, or an error on STDERR if something went wrong. Rather than a 5 line call to sh/bat, I now have a 40 line pipeline script with retry/try/catch, storing the stdout/stderr in indvidual files, and reading them back....sigh
            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.

              People

              Assignee:
              Unassigned Unassigned
              Reporter:
              drewish andrew morton
              Votes:
              141 Vote for this issue
              Watchers:
              124 Start watching this issue

                Dates

                Created:
                Updated: