#!/usr/bin/env ruby require 'json' require 'date' require 'time' require 'logger' require 'trollop' Opts = Trollop::options do version Shamebot::VERSION banner Shamebot::ART + "\n\n" + <<-EOS.gsub(/^ /, '') #{Shamebot::SUMMARY} Usage: shamebot [] Options: EOS opt :config, 'Path to configuration', type: :string, required: true, short: 'c' opt :debug, 'Enable debugging', default: false, short: 'd' opt :port, 'Port to listen on', default: 4567, short: 'p' opt :env, 'Sinatra environment', default: 'production', short: 'e' opt :bind, 'Interface to listen on', default: '0.0.0.0', short: 'b' end DEBUG = Opts[:debug] CONFIG = JSON.parse File.read(Opts[:config]) unless DEBUG at_exit do puts "Writing back to '#{Opts[:config]}'" File.open(Opts[:config], 'w') do |f| f.puts JSON.pretty_generate(CONFIG) end end end # Part 1. Setup configuration from JSON file JIRA_TAG = /\b([a-z]{2,4})\-(\d+)\b/i MERGE_COMMIT = /\b(merge|merging)\b/i def die message $stderr.puts message exit 1 end unless CONFIG.has_key?('hipchat') && CONFIG['hipchat'].has_key?('auth_token') die "Error: 'hipchat:auth_token' required in configuration" end GITLAB = { 'endpoint' => (CONFIG['gitlab']['endpoint'] rescue die("Error: 'gitlab:endpoint' required in configuration")), 'private_token' => (CONFIG['gitlab']['private_token'] rescue die("Error: 'gitlab:private_token' required in configuration")), 'user_agent' => (CONFIG['gitlab']['user_agent'] rescue die("Error: 'gitlab:user_agent' required in configuration")) } BOTNAME = CONFIG.has_key?('nick') ? CONFIG['nick'] : 'shamebot' ROOM = CONFIG.has_key?('room') ? CONFIG['room'] : 'test' TEMPLATES = CONFIG.has_key?('templates') ? CONFIG['templates'] : [ '%{name}, shame on you for not including a valid JIRA tag in your commit message! %{urls}' ] def random_template TEMPLATES[rand(0...TEMPLATES.length)] end CONFIG['shamings'] = CONFIG.has_key?('shamings') ? CONFIG['shamings'] : {} # Part 2. Get connected to Hipchat and Gitlab require 'hipchat' require 'gitlab' HIPCHAT = HipChat::Client.new CONFIG['hipchat']['auth_token'] Gitlab.configure do |config| config.endpoint = GITLAB['endpoint'] config.private_token = GITLAB['private_token'] config.user_agent = GITLAB['user_agent'] end def shame_user msg, args, room, hipchat, botname hipchat[room].send(botname, msg % args, { :notify => true, :color => 'red', :message_format => 'text' }) end # Part 3. Take requests from Gitlab WebHooks require 'sinatra' set :port, Opts[:port] set :environment, Opts[:env] set :bind, Opts[:bind] set :raise_errors, true set :dump_errors, true set :show_exceptions, true set :logging, ::Logger::DEBUG if DEBUG get '/' do content_type :text "shamebot #{Shamebot::VERSION}" end post '/' do now = Time.now last_half_hour = now - 30 * 60 request.body.rewind data = JSON.parse request.body.read # Bad commits don't contain JIRA tags... # And they aren't merge commits commits = data['commits'].delete_if { |c| c['message'] =~ MERGE_COMMIT } good_commits, bad_commits = commits.partition { |c| c['message'] =~ JIRA_TAG } # Good commits might contain bogus JIRA tags bad_commits += good_commits.keep_if do |c| good_tag = false c['message'].scan(JIRA_TAG).each do |(project, number)| tag = '%s-%d' % [project.upcase, number.to_i] tag_page = `curl --silent http://jira.bluejeansnet.com/browse/#{tag}` rescue '' next if tag_page =~ /The issue you are trying to view does not exist/i good_tag = true break end !good_tag end # Grab user info from Gitlab begin user = Gitlab.user(data['user_id'].to_i) rescue $stderr.puts "Warning: Gitlab user for commit author not found" return end # For whatever reason we get a lot of duplicate POSTs, so we need to # keep track of which commits have already triggered shamings so we # don't end up repeatedly shaming users for the same mistake. We also # make sure the commits aren't too old. new_bad_commits = [] bad_commits.each do |commit| unless DEBUG next if CONFIG['shamings'].has_key? commit['id'] commit_time = DateTime.strptime(commit['timestamp']).to_time next unless commit_time >= last_half_hour end new_bad_commits << commit CONFIG['shamings'][commit['id']] = true end # Shame the user with a random template commit_ids = new_bad_commits.map { |c| c['id'] } commit_urls = new_bad_commits.map { |c| c['url'] } puts "%s: %s" % [ user.username, commit_ids.inspect ] hipchat_users = [] 0.upto(5) do |i| hipchat_users += HTTParty.get("https://api.hipchat.com/v2/user", :query => { 'start-index' => 100 * i, :start_index => 100 * i, 'max-results' => 100, :max_results => 1000, :auth_token => CONFIG['hipchat']['auth_token_v2'] })['items'] end hipchat_user = hipchat_users.select { |u| u['name'] =~ /#{user.name}/i } $stderr.puts hipchat_user if hipchat_user.empty? hipchat_user = user.name else hipchat_user = '@' + hipchat_user.first['mention_name'] end shame_user(random_template, { :name => hipchat_user, :nick => user.username, :urls => commit_urls.join(' ') }, ROOM, HIPCHAT, BOTNAME) unless commit_ids.empty? || DEBUG return commit_ids.inspect end