GitLab WebHook with Slack

So you have a website which is totally controlled by a gitlab server?

Imagine if you could make alterations on the website repository and when you do the commit, gitlab send a "signal" to a webhook daemon, which in turn will pull the alterations to your website immediately.

Because some things can go wrong on the proccess, you may want to see what is happening on the server, so instead of connecting on the server, you could receive information about the new pull via Slack with the actually result of the pull.

Sounds cool? I think so as well, then, snap out of the dream and star following the steps I will put forth ahead!

Assuming you already have gitlab installed or an account (although I have this working in a private gitlab) and you have total control over your webserver

Also assuming that you already have a Slack that you can create webhooks, channels and so on

First, WebHook Receiver

Daemon Script

So then I built a very simple ruby script that will run a HTTP daemon in some port number, that will basically receive one specific nudge from GitLab, the daemon will then evaluate if the source is known with a simple key and then do the work we expect from him.

The second part, while doing the work, is to evaluate if the git pull or git clone was successful or not and send an appropriate message to a/your slack channel.

require 'webrick'
require 'json'
require "httparty"
require_relative 'slack'

include WEBrick
include HTTParty

LOCAL_KEY = 'y8dNNqsomekeysomekeysomekeysomekeyI3PLLqa'
TOP_DIR   = '/webserver/'
GIT       = '/usr/bin/git'

def start_webrick(config = {})
  config.update(:Port => 8899)       ### Set here the port number you want to run thi daemon
  server = HTTPServer.new(config)
  yield server if block_given?
  ['INT', 'TERM'].each {|signal|
    trap(signal) {server.shutdown}
  }
  server.start
end

class GITServlet < HTTPServlet::AbstractServlet

  def do_GET request, response
    response.status = '200'
    response['Content-Type'] = 'text/plain'
    response.body = "This is not a supported function, you druggie!"
  end

  def do_POST(req,resp)
    token = ''
    event = ''
    req.each { |head|
      if head == "x-gitlab-token"
        token = req[head]
      end
      if head == "x-gitlab-event"
        event = req[head]
      end
    }

    if token != LOCAL_KEY
      puts "::: Could not match Token information, connection is dropped."
      raise HTTPStatus::Error
      $stdout.sync = true
    end

    if event != "Push Hook"
      puts "::: That is not a push hook, connection is dropped."
      raise HTTPStatus::Error
      $stdout.sync = true
    end

    body_content = JSON.parse(req.body)

    workdir = [TOP_DIR,body_content['repository']['name']].join()
    project = body_content['repository']['name']
    commit_comment = body_content['commits'][0]['message']
    puts "Work Directory: #{workdir}"
    git_result=''
    mod_result=''
    notifier = SlackNotifier.new "https://hooks.slack.com/services/YOUR/OWN/sWjSslackWebhookURIURI6Q5"

    if Dir.exist?(workdir)
      puts "::: #{workdir} Exists -- Lets Pull!"
      git_result = `#{GIT} -C #{workdir} pull 2>&1`
      git_cmd_result=$?.exitstatus

      ## very cheap solution to 777, one can fix to more safe ways -- not really recommended
      mod_result = `/usr/bin/chmod 0777 #{workdir}/* -R ; /usr/bin/chown http. #{workdir}/* -R`   

      add_update = "Not necessary to push chmod"
    else
      puts "::: #{workdir} is New -- Lets Clone!"
      git_result = `#{GIT} -C #{TOP_DIR} clone #{body_content['repository']['git_ssh_url']} 2>&1`
      git_cmd_result=$?.exitstatus

      ## very cheap solution to 777, one can fix to more safe ways -- not really recommended
      mod_result = `/usr/bin/chmod 0777 #{workdir}/* -R ; /usr/bin/chown http. #{workdir}/* -R`   

      add_update = `cd #{workdir} && #{GIT} add * && #{GIT} commit -m "update for chmod" && #{GIT} push`
    end

    git_output = git_result

    if git_cmd_result == 0
      notifier.send "Deployment for project *#{project}* was successful: ```#{git_output}```\nCommented with: #{commit_comment}", ":heavy_check_mark:"
      puts "SUCCESSFUL on Deployment"
      puts git_output
    else
      notifier.send "Your pants are on fire with project *#{project}* : ```#{git_output}```\nCommented with: #{commit_comment}", ":bangbang:"
      puts "ERROR on Deployment"
      puts git_output
    end

    git_output = nil
    git_result = nil

    puts mod_result
    puts add_update
    $stdout.sync = true
    raise HTTPStatus::OK

  end
end

start_webrick { | server |
  server.mount('/', GITServlet)
}

So this first script will run out HTTP based daemon to receive information from GitLab, besides this script, you will also need another simple bit to allow you to use Slack.

Slack Script

I got this one from How to post to a Slack channel from your Ruby/Rails backend and did a very small modification to it.

# this class will send notifications to your slack team via a webhook
# setup the webhook here: https://slack.com/apps/A0F7XDUAZ-incoming-webhooks

class SlackNotifier
  # constructor
  # use as: SlackNotifier.new "YOUR-URL"
  def initialize(webhook_url, channel = false, username = "Arch-Alpha Deploy", icon_emoji = ":ghost:")
    @webhook_url = webhook_url
    @channel = channel
    @username = username
    @icon_emoji = icon_emoji
  end

  # sends a notification
  # returns true after a successfull pust
  def send(text, emoji)
    # send as json
    headers = { 'Content-Type' => 'application/json' }
    # payload
    body = { "text": text, "icon_emoji": emoji, username: @username}
    # add the channel if there is one
    # otherwise the default channel from the slack integration will be used
    if @channel
      body["channel"] = @channel
    end

    # rescue from request errors
    begin
      # make request
      r = HTTParty.post(@webhook_url, body: body.to_json, headers: headers )
      return (r.code == 200)
    rescue
      return false
    end
  end
end

I added the emoji parameter to the .send, so you can add a customized icon to the message , maybe in case of fail, success or different projects, who knows...

In order to make the daemon and the Slack function to work, you will need to also install the ruby gem called httparty.

# sudo gem install httparty

SystemD Service File

To help you, create also your systemd service file to load the daemon on boot time -- generally you can put this at /etc/systemd/system/webhook-deploy.service:

[Unit]
Description=WebHook Deploy Service
After=syslog.target network.target httpd.service

[Service]
# you may use the user that better suits you, he should obviously be capable of running git pull for ur projects
User=gitman
ExecStart=/usr/bin/ruby /webserver/app_devel/auto-deploy/webhook_push.rb 
GuessMainPID=yes

[Install]
WantedBy=multi-user.target

Make sure then it is enabled and started:

# systemctl enable webhook-deploy Then: # systemctl start webhook-deploy

View daemon online

You should see on your journalctl :

Mar 31 17:59:30 arch-alpha systemd[1]: Started WebHook Deploy Service.
Mar 31 17:59:30 arch-alpha ruby[2931]: [2018-03-31 17:59:30] INFO  WEBrick 1.4.2
Mar 31 17:59:30 arch-alpha ruby[2931]: [2018-03-31 17:59:30] INFO  ruby 2.5.0 (2017-12-25) [x86_64-linux]
Mar 31 17:59:30 arch-alpha ruby[2931]: [2018-03-31 17:59:30] INFO  WEBrick::HTTPServer#start: pid=2931 port=8899

And if everything is really in order:

arch-alpha ~ : # netstat -ant | grep -i 8899
tcp        0      0 0.0.0.0:8899            0.0.0.0:*               LISTEN     
tcp6       0      0 :::8899                 :::*                    LISTEN

Our daemon is not installed, and ready to receive information from GitLab.

assuming that the port 8899 is accessible to your gitlab webhook nudge -- in other words, you should know what these things means!

Second, Configure your GitLab Project

Main Gitlab Settings

On the last update from gitlab, they actually change the ability to gitlab to communicate to webhooks in other IP Addresses than himself, to change this behaviour, do to the main settings as admin and at the end of the page you will find:

Project Settings >> Integrations

Then go to the project you want to send notifications via webhook to our daemon:

I already have an internal address for my daemon, then with the port added and the key/secret to give some assurance we receiving the webhook from the source we know.

After adding the webhook, you can send a test:

And after have it running regularly, you will have the history of executions inline:

Also you can see in detail what was communicated:

Optionally, also configure GitLab to send information to Slack

On the same pages of Integrations of the project, towards the end of the page you will find Slack Integration as well:

Following the option you can configure your Slack webhook:

Third, USE IT!

Then you created a repository and did the integrations configurations.

You have cloned to your computer and is working on the project.

You then, commit changed to Gitlab, first, gitlab will tell you the push was received successfully and give you a link to see the commit on the gitlab interface:

Next, Gitlab will send the webhook to our daemon, on the server that have your project running, then our daemon will show you the project being pulled from gitlab:


0 Comments: