Home > Blockchain >  Use pipeline method in jenkins class
Use pipeline method in jenkins class

Time:02-26

I would like to make a resilient slack notification class for my Jenkins instance. Currently I just call the slackSend function from the Jenkins slack plugin. However if the call to slack fails, so does my build. This means I have a very hard dependency on Slack that I am not ok with.

I have managed to get the code to work but it is rather ugly and I would like to know if there is a better way to do this.

Firstly I have class defined in src/something/slack/slackHandler.groovy The code looks a little like this:

package something.slack

public class slackHandler {
  private String threadId = ""
  private String channelName = ""
  private Boolean silent = false
  private Closure sender
  private Logger logger

  public silence(Boolean trigger) {
    this.silent = trigger
  }

  public sendMessage(String msg) {

    if (threadId == "") {
      def (slackResponse, ok) = this.sendMessageTo(this.channelName, msg)
      if (!ok) {
        // We have tried to send a on channel. But the send failed so we can not determine the threadId.
        // We should at this point store the messages and try send it again on the next call to slack.
        return
      }

      this.setThreadId(slackResponse.threadId)

    } else {
      def (slackResponse, ok) = this.sendMessageTo(this.threadId, msg)
      if (!ok){
        // We tried to send on a threadId, it failed. We have the threadId so we can leave the slackResponse alone.
        // We should at this point store the messages and try send it again on the next call to slack.
        return
      }
    }
  }

  public sendMessageTo(String channel, String msg) {
    return this.trySend(channel, msg)
  }

  private trySend(String to, String msg) {
    if (this.silent) {
      return [[threadId: "nothing"], true]
    }

    try {
      return [sender(to, msg), true]
    } catch (e) {
      // These do not work :(
      println("There wasn an error sending the slack message. Error $e")
      println("Message being sent: $msg")
      return [null, false]
    }
  }
}

This is the part I'm not happy about. To use the above code I need to create and pass a closure to send the messages to slack because the slackSend function is not available inside my class. I can not find out how to give it the ability to use this function or access the slack class. The println calls are also not showing up in the Jenkins console which is a problem for this class as the fallback is to log to the console. This makes me think that I am missing some sort of context that I need to give my class.

This is how I currently use the class:

def slackSender = new slackHandler(
  [
    channelName:"rd-bots",
    sender: {to, msg -> return slackSend(channel: to, message: msg)}
  ]
)
slackSender.sendMessage("Hello :wave:")

Can someone please tell me if the is a way to pass on the context or if what I have done is the only way? Also why don't the println calls appear in the Jenkins log?

CodePudding user response:

You can include a try-catch in your pipeline code around your call to your slack handler.

Here's the documentation for catchError: https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/#catcherror-catch-error-and-set-build-result-to-failure

I think it should look something like this in a pipeline:

@Library('shared-lib@main')_
pipeline{
    agent any
    options {
        timestamps()
    }
    environment {
        Custom_Variables = 'stuff'
    }
    stages{
        stage ('blah') {
            steps {
                catchError{
                    def slackSender = new slackHandler(
                        [
                            channelName:"rd-bots",
                            sender: {to, msg -> return slackSend(channel: to, message: msg)}
                        ]
                        )
                    slackSender.sendMessage("Hello :wave:")
                }
            }
        }
    }
}

CodePudding user response:

After a bit of digging and looking at different questions for other things where context is needed I managed to find an answer.

When running in Jenkins the this value will give the context of your running environment. You can pass that on to something else.

Code updated to look like this:

public class slackHandler {
  private String threadId = ""
  private String channelName = ""
  private Boolean silent = false
  private ctx

  public silence(Boolean trigger) {
    this.silent = trigger
  }

  public setThreadId(String id) {
    this.threadId = id
  }

  public sendMessage(String msg) {
    if (threadId == "") {
      def (slackResponse, ok) = this.sendMessageTo(this.channelName, msg)
      if (!ok) {
        // We have tried to send a on channel. But the send failed so we can not determine the threadId.
        // We should at this point store the messages and try send it again on the next call to slack.
        return
      }
      this.setThreadId(slackResponse.threadId)

    } else {
      def (slackResponse, ok) = this.sendMessageTo(this.threadId, msg)
      if (!ok){
        // We tried to send on a threadId, it failed. We have the threadId so we can leave the slackResponse alone.
        // We should at this point store the messages and try send it again on the next call to slack.
        return
      }
    }
  }

  public sendMessageTo(String channel, String msg) {
    return this.trySend(channel, msg)
  }

  private trySend(String to, String msg) {
    if (this.silent) {
      return [[threadId: "nothing"], true]
    }

      def slackResponse = ctx.slackSend(channel: to, message: msg)
      if (slackResponse != null) {
        return [slackResponse, true]
      } else {
        ctx.echo("There was an error sending slack message sent: $msg")
        return [null, false]
      }
  }
}

Used like this:

import com.proquoai.slack.slackHandler

def slackSender = new slackHandler(
    [
        channelName:"trashroom10120123",
        ctx: this
    ]
)
node ('docker') {
    stage('Send Slack Messages') {
        slackSender.sendMessage("Hello :wave:")
        slackSender.sendMessage("It's running :go_dance:")
    }
    stage('Send out of band messages') {
        slackSender.sendMessageTo("rd-bots", ":ship-it:")
    }
}

As a side note, the slackSend function appears to swallow the error and simply doesn't return a slackResponse. Therefore using a try/catch block didn't actually help in determining if slack sending failed.

  • Related