#!/usr/bin/env ruby require "optparse" require "net/https" require "uri" require "json" OPTIONS = [:channel, :username, :icon, :state, :message, :message_file, :file, :title, :tail, :webhook].freeze options = {} OptionParser.new do |opts| opts.banner = "Usage: #{File.basename(__FILE__)} [options]" opts.on("-c", "--channel CHAN", "Send to channel") do |c| options[:channel] = c end opts.on("-u", "--username USER", "Send as username") do |u| options[:username] = u end opts.on("-i", "--icon URL", "User icon image") do |i| options[:icon] = i end opts.on("-s", "--state STATE", "Message state (success, warn, error, or color code)") do |s| options[:state] = s end opts.on("-m", "--message MESSAGE", "Message to send") do |m| options[:message] = m end opts.on("-M", "--message-file MESSAGE_FILE", "Use file contents as message") do |m| options[:message_file] = m end opts.on("-f", "--file MESSAGE_FILE", "Send raw contents of file in message") do |f| options[:file] = f end opts.on("-t", "--title TITLE", "Message title") do |t| options[:title] = t end opts.on("-T", "--tail N", "Send last N lines of content from raw message file") do |t| options[:tail] = t end opts.on("-w", "--webhook HOOK", "Slack webhook") do |w| options[:webhook] = w end opts.on("-h", "--help", "Print help") do puts opts exit end end.parse! OPTIONS.each do |key| if !options.key?(key) env_key = "SLACK_#{key.to_s.upcase}" if ENV[env_key] options[key] = ENV[env_key] end end end if !options[:webhook] $stderr.puts "ERROR: Webhook is required!" exit 1 end if ENV["CIRCLECI"] options[:icon] = "https://emoji.slack-edge.com/TF1GCKJNM/circleci/054b58d488e65138.png" unless options[:icon] options[:username] = "circleci" unless options[:username] options[:footer] = "CircleCI - <#{ENV["CIRCLE_BUILD_URL"]}|#{ENV["CIRCLE_PROJECT_USERNAME"]}/#{ENV["CIRCLE_PROJECT_REPONAME"]}>" options[:footer_icon] = "https://emoji.slack-edge.com/TF1GCKJNM/circleci/054b58d488e65138.png" end if ENV["GITHUB_ACTIONS"] options[:icon] = "https://ca.slack-edge.com/T024UT03C-WG8NDATGT-f82ae03b9fca-48" unless options[:icon] options[:username] = "github" unless options[:username] options[:footer] = "Actions - " options[:footer_icon] = "https://ca.slack-edge.com/T024UT03C-WG8NDATGT-f82ae03b9fca-48" end options[:state] = "success" unless options[:state] case options[:state] when "success", "good" options[:state] = "good" when "warn", "warning" options[:state] = "warning" when "error", "danger" options[:state] = "danger" else if !options[:state].start_with?("#") $stderr.puts "ERROR: Invalid value for `state` (#{options[:state]})" exit 1 end end msg = options[:message] # NOTE: Message provided from CLI argument will end up with # double escaped newlines so remove one msg.gsub!("\\n", "\n") if msg if options[:message_file] if !File.exist?(options[:message_file]) $stderr.puts "ERROR: Message file does not exist `#{options[:message_file]}`" exit 1 end msg_c = File.read(options[:message_file]) msg = msg ? "#{msg}\n\n#{msg_c}" : msg_c end if options[:file] if !File.exist?(options[:file]) $stderr.puts "ERROR: Message file does not exist `#{options[:file]}`" exit 1 end if (tail = options[:tail].to_i) > 0 content = "" buffer = 0 File.open(options[:file], "r") do |f| until (content.split("\n").size > tail) || buffer >= f.size buffer += 1000 buffer = f.size if buffer > f.size f.seek(f.size - buffer) content = f.read end end parts = content.split("\n") if parts.size > tail parts = parts.slice(-tail, tail) end fmsg = parts ? parts.join("\n") : "" else fmsg = File.read(options[:file]) end fmsg = "```\n#{fmsg}\n```" if msg msg = msg << "\n\n" << fmsg end end if msg.to_s.empty? $stderr.puts "ERROR: No message content provided!" exit 1 end attach = {text: msg, fallback: msg, color: options[:state], mrkdn: true} attach[:title] = options[:title] if options[:title] attach[:footer] = options[:footer] if options[:footer] attach[:footer_icon] = options[:footer_icon] if options[:footer_icon] attach[:ts] = Time.now.to_i payload = {}.tap do |pd| pd[:username] = options.fetch(:username, "packet-exec") pd[:channel] = options[:channel] if options[:channel] pd[:icon_url] = options[:icon] if options[:icon] pd[:attachments] = [attach] end result = Net::HTTP.post(URI(options[:webhook]), payload.to_json, "Content-Type" => "application/json") if !result.code.start_with?("2") $stderr.puts "Failed to send slack message" exit 1 else $stdout.puts "ok" end