Skip to content
This repository has been archived by the owner on Jan 29, 2022. It is now read-only.

ChatOps Plugin Support #52

Merged
merged 8 commits into from
May 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,43 @@ Any configuration option is placed in `/etc/puppet_webhook/server.yml` or `/etc/
* `--ssl-key FILE`: Specify the SSL Private key to use. Pair with `--ssl-cert`. Requires `--ssl` or `ssl_enable: true` in config file.
* `-c FILE, --configfile FILE`: Specifies a config file to use. Must be a `.yml` file in YAML format.

#### Chatops Configuration

Puppet_webhook can post to chatops tools via various APIs and Clients. At this time, only `Slack` is supported.

To enable ChatOps support simply add the following to your `/etc/puppet_webhook/app.yml` file:
``` yaml
chatops: true
```

##### Slack Configuration

You can enable Slack notifications for the webhook. You will need a Slack webhook URL and the slack-notifier gem installed.

To get the Slack webhook URL you need to:

Go to https://slack.com/apps/A0F7XDUAZ-incoming-webhooks.
Choose your team, press Configure.
In configurations press Add configuration.
Choose channel, press Add Incoming WebHooks integration.
Then configure the webhook to add your Slack Webhook URL:

``` yaml
chatops: true
chatops_service: 'slack' # Required so the app knows that you're sending to Slack.
chatops_channel: '#channel' # deftaults to #general
chatops_user: 'r10k' # defaults to puppet_webhook
chatops_options:
icon_emoji: ':ocean:'
http_options:
proxy_address: 'http://proxy.example.com'
proxy_port: '3128'
proxy_from_env: false
```

**NOTE: The legacy `slack_webhook`, `slack_user`, `slack_channel`, `slack_emoji`, and `slack_proxy_url` still work, but will be removed in 3.0.0**

### Reference

#### Server Configuration File

Expand Down Expand Up @@ -190,28 +227,81 @@ Whether or not to use MCollective CLI command. REQUIRES MCOLLECTIVE AND MCOLLECT
MCollective Ruby discovery timeout. REQUIRES `use_mco_ruby` TO BE `true`.
* Default: `'10'`

##### chatops

Enable the use of notifications to Slack or other ChatOps tool.
* Valid options: [ `true`, `false` ]
* Default: `false`

##### chatops_service

Name of ChatOps tool to send notifications to.
* Valid options: [ `slack` ]
* Default: `slack`

##### chatops_url
*Replaces `slack_webhook`*

URL of the API or Webhook to send notifications to. See Documentation of your tool for details.
* Default: `''`

##### chatops_user
*Replaces `slack_user`*

User to post notification as.
* Default: `puppet_webhook`

##### chatops_channel
*Replaces `slack_channel`*

Channel/Team/Area to post to.
* Default: `#general`

##### chatops_options

Hash of options to pass to the Chatops plugin. Each set of options are unique to each tool, so please see your tool's documentation for more information.
* Default: `{}`

##### slack_webhook
***DEPRECATED* - Please use `chatops_url` instead**

URL of your Slack Webhook receiver, if you wish not to use a Slack Webhook, then simply leave the option on `false`, otherwise use the full Wwebhook URL for your community as per https://api.slack.com/incoming-webhooks.
* Valid options: [ `https://hooks.slack.com/services/<generated_hash>`, `false` ]
* Default: `false`

##### slack_channel
***DEPRECATED* - Please use `chatops_channel` instead**

Name of the Slack channel to post to. Ignored if `slack_webhook` is disabled.
Default: `#general`

##### slack_user
***DEPRECATED* - Please use `chatops_user` instead**

Name of the Slack user to post as. Ignored if `slack_webhook` is disabled.
Default: `puppet_webhook`

##### slack_emoji
***DEPRECATED* - Please use `chatops_options` instead.**
**Example for new config ONLY:**
``` yaml
chatops_options:
icon_emoji: ':ocean:'
```

Icon emoji for the Webhook to use when posting. Ignored if `slack_webhook` is disabled.
Default: `:ocean:`

##### slack_proxy_url
***DEPRECATED* - Please use `chatops_options` instead.**
**Example for new config ONLY:**
``` yaml
chatops_options:
http_options:
proxy_address: 'http://proxy.example.com'
proxy_port: '3128'
proxy_from_env: false
```

The proxy URL for Slack if used.
* MUST BE A VALID URL.
Expand Down
9 changes: 7 additions & 2 deletions config/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ use_mco_ruby: false
use_mcollective: false
discovery_timeout: '10'

# Slack Notifications
slack_webhook: false
# Chatops Notification
chatops: false
# Slack Example
# chatops_service: 'slack'
# chatops_channel: '#general'
# chatops_user: 'r10k'
# chatops_url: 'https://hooks.slack.com/services/<hash>/<hash>/<hash>'

# R10k
default_branch: production
Expand Down
15 changes: 8 additions & 7 deletions lib/helpers/deployments.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'plugins/mcollective'
require 'plugins/chatops'

module Deployments # rubocop:disable Style/Documentation
def deploy(branch, deleted)
Expand All @@ -23,18 +24,18 @@ def deploy(branch, deleted)
command = "#{settings.command_prefix} r10k deploy environment #{branch} #{settings.r10k_deploy_arguments}"
message = run_command(command)
end
status_message = { status: :success, message: message.to_s, branch: branch, status_code: 200 }
status_message = { status: :success, message: message.to_s, branch: branch, status_code: 202 }
LOGGER.info("message: #{message} branch: #{branch}")
unless deleted
generate_types(branch) if types?
end
notify_slack(status_message) if slack?
notification(status_message)
[status_message[:status_code], status_message.to_json]
rescue StandardError => e
status_message = { status: :fail, message: e.message, trace: e.backtrace, branch: branch, status_code: 500 }
LOGGER.error("message: #{e.message} trace: #{e.backtrace}")
status 500
notify_slack(status_message) if slack?
notification(status_message)
status_message.to_json
end

Expand All @@ -61,14 +62,14 @@ def deploy_module(module_name)
message = run_command(command)
end
LOGGER.info("message: #{message} module_name: #{module_name}")
status_message = { status: :success, message: message.to_s, module_name: module_name, status_code: 200 }
notify_slack(status_message) if slack?
status_message = { status: :success, message: message.to_s, module_name: module_name, status_code: 202 }
notification(status_message)
status_message.to_json
rescue StandardError => e
status_message = { status: :fail, message: e.message, trace: e.backtrace, branch: branch, status_code: 500 }
LOGGER.error("message: #{e.message} trace: #{e.backtrace}")
status 500
status_message = { status: :fail, message: e.message, trace: e.backtrace, module_name: module_name, status_code: 500 }
notify_slack(status_message) if slack?
notification(status_message)
status_message.to_json
end
end
93 changes: 40 additions & 53 deletions lib/helpers/tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def run_prefix_command(payload)

def run_command(command)
message = "forked: #{command}"
exec "#{command} &"
system "#{command} &"
message
end

Expand All @@ -58,66 +58,53 @@ def generate_types(environment)
message = run_command(command)
LOGGER.info("message: #{message} environment: #{environment}")
status_message = { status: :success, message: message.to_s, environment: environment, status_code: 200 }
notify_slack(status_message) if slack?
notification(status_message)
rescue StandardError => e
LOGGER.error("message: #{e.message} trace: #{e.backtrace}")
status_message = { status: :fail, message: e.message, trace: e.backtrace, environment: environment, status_code: 500 }
notify_slack(status_message) if slack?
notification(status_message)
end

def notify_slack(status_message)
return unless settings.slack_webhook

if settings.slack_proxy_url
uri = URI(settings.slack_proxy_url)
http_options = {
proxy_address: uri.hostname,
proxy_port: uri.port,
proxy_from_env: false
}
else
http_options = {}
end

notifier = Slack::Notifier.new settings.slack_webhook do
defaults channel: settings.slack_channel,
username: settings.slack_user,
icon_emoji: settings.slack_emoji,
http_options: http_options
end

if status_message[:branch]
target = status_message[:branch]
elsif status_message[:module]
target = status_message[:module]
end

message = {
author: 'r10k for Puppet',
title: "r10k deployment of Puppet environment #{target}"
}

case status_message[:status_code]
when 200
message.merge!(
color: 'good',
text: "Successfully deployed #{target}",
fallback: "Successfully deployed #{target}"
)
when 500
message.merge!(
color: 'bad',
text: "Failed to deploy #{target}",
fallback: "Failed to deploy #{target}"
)
end
def notification(message)
return unless settings.chatops || settings.slack_webhook
slack_settings if settings.chatops == false && settings.slack_webhook != false
PuppetWebhook::Chatops.new(settings.chatops_service,
settings.chatops_url,
settings.chatops_channel,
settings.chatops_user,
settings.chatops_options).notify(message)
end

notifier.post text: message[:fallback], attachments: [message]
# Deprecated
# TODO: Remove in release 3.0.0
def slack_settings
settings.chatops_service = 'slack'
LOGGER.warn('settings.slack_webhook is deprecated and will be removed in puppet_webhook 3.0.0')
settings.chatops_url = settings.slack_webhook
LOGGER.warn('settings.slack_user is deprecated and will be removed in puppet_webhook 3.0.0')
settings.chatops_user = settings.slack_user
LOGGER.warn('settings.slack_channel is deprecated and will be removed in puppet_webhook 3.0.0')
settings.chatops_channel = settings.slack_channel
LOGGER.warn('settings.slack_emoji is deprecated and will be removed in puppet_webhook 3.0.0')
settings.chatops_options[:icon_emoji] = settings.slack_emoji
LOGGER.warn('settings.slack_proxy_url is deprecated and will be removed in puppet_webhook 3.0.0')
settings.chatops_options[:http_options] = if settings.slack_proxy_url
slack_proxy
else
{}
end
end

def slack?
return false if settings.slack_webhook.nil?
settings.slack_webhook
# Deprecated
# TODO: Remove in release 3.0.0
def slack_proxy
uri = URI(settings.slack_proxy_url)
http_options = {
proxy_address: uri.hostname,
proxy_port: uri.port,
proxy_from_env: false
}
http_options
end

def types?
Expand Down
29 changes: 29 additions & 0 deletions lib/plugins/chatops.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'plugins/chatops/slack'

class PuppetWebhook
# Chatops object for sending webhook notifications to chatops tools
class Chatops
def initialize(service, url, channel, user, options = {})
@service = service
@url = url
@channel = channel
@user = user
@args = options
end

def notify(message)
case @service
when 'slack'
LOGGER.info("Sending Slack webhook message to #{@url}")
Chatops::Slack.new(@channel,
@url,
@user,
message,
http_options: @args[:http_options] || {},
icon_emoji: @args[:icon_emoji]).notify
else
LOGGER.error("Service #{@service} is not currently supported")
end
end
end
end
60 changes: 60 additions & 0 deletions lib/plugins/chatops/slack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'slack-notifier'

class PuppetWebhook
class Chatops
# Sets up Slack object that will send notifications to Slack via a webhook.
class Slack
def initialize(channel, url, user, message, options = {})
@channel = channel
@url = url
@user = user
@message = message
@options = options
end

def notify
notifier = ::Slack::Notifier.new @url, http_options: @options[:http_options]

target = if @message[:branch]
@message[:branch]
elsif @message[:module]
@message[:module]
end

msg = format_message(target)

notifier.post text: msg[:fallback],
channel: @channel,
username: @user,
icon_emoji: @options[:icon_emoji],
attachments: [msg]
end

private

def format_message(target)
message = {
author: 'r10k for Puppet',
title: "r10k deployment of Puppet environment #{target}"
}

case @message[:status_code]
when 202
message.merge!(
color: 'good',
text: "Successfully started deployment of #{target}",
fallback: "Successfully started deployment of #{target}"
)
when 500
message.merge!(
color: 'bad',
text: "Failed to deploy #{target}",
fallback: "Failed to deploy #{target}"
)
end

message
end
end
end
end
Loading