From 4be6f66ad368318a84b37a7ee12e586d19293391 Mon Sep 17 00:00:00 2001 From: ataylorme Date: Mon, 26 Mar 2018 16:32:02 -0700 Subject: [PATCH] Add Slack example storing secrets in Lockr --- slack_notification_lockr/README.md | 54 +++++ .../slack_notification_lockr.php | 196 ++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 slack_notification_lockr/README.md create mode 100644 slack_notification_lockr/slack_notification_lockr.php diff --git a/slack_notification_lockr/README.md b/slack_notification_lockr/README.md new file mode 100644 index 0000000..c3c7d37 --- /dev/null +++ b/slack_notification_lockr/README.md @@ -0,0 +1,54 @@ +# Slack Integration with Lockr # + +This script shows how easy it is to integrate Slack notifications from your Pantheon project using Quicksilver. You will need a [Slack Webhook URL](https://api.slack.com/incoming-webhooks), which should not be exposed publicly. + + We **do not** recommend tracking sensitive information, such as API keys, in version control. Instead consider using a secure, managed service for storing sensitive credentials. This example uses [Lockr](https://www.lockr.io/), which has a free tier, but feel free to use your own solution. + +## Instructions ## + +1. [Enable Incoming Webhooks](https://my.slack.com/services/new/incoming-webhook/) for your Slack instance. +1. Install and activate the [Lockr WordPress plugin](https://wordpress.org/plugins/lockr/). +1. Save your webhook URL as `slack_url` in Lockr. +1. Save the Slack channel to post messages to as `slack_channel` in Lockr. +1. Add, and update as needed, the example `slack_notification.php` script to the `private` directory in the root of your site's codebase, that is under version control. +1. Add Quicksilver operations to your `pantheon.yml` +1. Test a deploy out! + - Note that using the `cache_clear` Quicksilver hook makes testing easy. Once things are working well change to the hooks below. + +Optionally, you may want to use the `terminus workflow:watch` command to get immediate debugging feedback. You may also want to customize your notifications further. The [Slack API](https://api.slack.com/incoming-webhooks) documentation has more information on formatting options. + +### Example `pantheon.yml` ### + +Here's an example of what your `pantheon.yml` would look like if this were the only Quicksilver operation you wanted to use. Pick and choose the exact workflows that you would like to see notifications for. + +```yaml +api_version: 1 + +workflows: + deploy_product: + after: + - type: webphp + description: Post to Slack after site creation + script: private/scripts/slack_notification_lockr.php + create_cloud_development_environment: + after: + - type: webphp + description: Post to Slack after Multidev creation + script: private/scripts/slack_notification_lockr.php + deploy: + after: + - type: webphp + description: Post to Slack after deploy + script: private/scripts/slack_notification_lockr.php + sync_code: + after: + - type: webphp + description: Post to Slack after code commit + script: private/scripts/slack_notification_lockr.php + clear_cache: + after: + - type: webphp + description: Someone is clearing the cache again + script: private/scripts/slack_notification_lockr.php +``` + diff --git a/slack_notification_lockr/slack_notification_lockr.php b/slack_notification_lockr/slack_notification_lockr.php new file mode 100644 index 0000000..e288e42 --- /dev/null +++ b/slack_notification_lockr/slack_notification_lockr.php @@ -0,0 +1,196 @@ + 'Pantheon-Quicksilver', + 'always_show_text' => false, +); + +// Load our hidden credentials. +// See the README.md for instructions on storing secrets. +$secret_keys = array( 'slack_url', 'slack_channel' ); +$secrets = _get_lockr_key($secret_keys, $defaults); + +// Build an array of fields to be rendered with Slack Attachments as a table +// attachment-style formatting: +// https://api.slack.com/docs/attachments +$fields = array( + array( + 'title' => 'Site', + 'value' => $_ENV['PANTHEON_SITE_NAME'], + 'short' => 'true' + ), + array( // Render Environment name with link to site, + 'title' => 'Environment', + 'value' => '', + 'short' => 'true' + ), + array( // Render Name with link to Email from Commit message + 'title' => 'By', + 'value' => $_POST['user_email'], + 'short' => 'true' + ), + array( // Render workflow phase that the message was sent + 'title' => 'Workflow', + 'value' => ucfirst($_POST['stage']) . ' ' . str_replace('_', ' ', $_POST['wf_type']), + 'short' => 'true' + ), + array( + 'title' => 'View Dashboard', + 'value' => '', + 'short' => 'true' + ), +); + +// Customize the message based on the workflow type. Note that slack_notification.php +// must appear in your pantheon.yml for each workflow type you wish to send notifications on. +switch($_POST['wf_type']) { + case 'deploy': + // Find out what tag we are on and get the annotation. + $deploy_tag = `git describe --tags`; + $deploy_message = $_POST['deploy_message']; + + // Prepare the slack payload as per: + // https://api.slack.com/incoming-webhooks + $text = 'Deploy to the '. $_ENV['PANTHEON_ENVIRONMENT']; + $text .= ' environment of '. $_ENV['PANTHEON_SITE_NAME'] .' by '. $_POST['user_email'] .' complete!'; + $text .= ' '; + // Build an array of fields to be rendered with Slack Attachments as a table + // attachment-style formatting: + // https://api.slack.com/docs/attachments + $fields[] = array( + 'title' => 'Details', + 'value' => $text, + 'short' => 'false' + ); + $fields[] = array( + 'title' => 'Deploy Note', + 'value' => $deploy_message, + 'short' => 'false' + ); + break; + + case 'sync_code': + // Get the committer, hash, and message for the most recent commit. + $committer = `git log -1 --pretty=%cn`; + $email = `git log -1 --pretty=%ce`; + $message = `git log -1 --pretty=%B`; + $hash = `git log -1 --pretty=%h`; + + // Prepare the slack payload as per: + // https://api.slack.com/incoming-webhooks + $text = 'Code sync to the ' . $_ENV['PANTHEON_ENVIRONMENT'] . ' environment of ' . $_ENV['PANTHEON_SITE_NAME'] . ' by ' . $_POST['user_email'] . "!\n"; + $text .= 'Most recent commit: ' . rtrim($hash) . ' by ' . rtrim($committer) . ': ' . $message; + // Build an array of fields to be rendered with Slack Attachments as a table + // attachment-style formatting: + // https://api.slack.com/docs/attachments + $fields += array( + array( + 'title' => 'Commit', + 'value' => rtrim($hash), + 'short' => 'true' + ), + array( + 'title' => 'Commit Message', + 'value' => $message, + 'short' => 'false' + ) + ); + break; + + case 'clear_cache': + $fields[] = array( + 'title' => 'Cleared caches', + 'value' => 'Cleared caches on the ' . $_ENV['PANTHEON_ENVIRONMENT'] . ' environment of ' . $_ENV['PANTHEON_SITE_NAME'] . "!\n", + 'short' => 'false' + ); + break; + + default: + $text = $_POST['qs_description']; + break; +} + +$attachment = array( + 'fallback' => $text, + 'pretext' => ($_POST['wf_type'] == 'clear_cache') ? 'Caches cleared :construction:' : 'Deploying :rocket:', + 'color' => $pantheon_yellow, // Can either be one of 'good', 'warning', 'danger', or any hex color code + 'fields' => $fields +); + +_slack_notification($secrets['slack_url'], $secrets['slack_channel'], $secrets['slack_username'], $text, $attachment, $secrets['always_show_text']); + + +/** + * Takes an existing array of keys and attempts to + * add a new key from Lockr + * + * @param string $key the machine name of the key to retrieve from Lockr + * @param array $incoming_keys existing array of items + * @return array array with Locker key added if it is found + */ +function _get_lockr_key($secret_keys,$defaults=array()){ + $status = false; + $wp_plugin_list = $lockr_keys = $current_keys = array(); + + exec("wp plugin list --name=lockr --format=count --status=active --no-color", $wp_plugin_list); + if( empty($wp_plugin_list) || $wp_plugin_list[0] == "0" ){ + die( 'The Lockr plugin is not active. Please install and activate the plugin.' ); + return $defaults; + } + + foreach( $secret_keys as $key ){ + + echo "Getting $key key from Lockr" . PHP_EOL; + exec("wp lockr get key $key --no-color", $current_keys, $status); + + if( 0 == $status && !empty($current_keys) ){ + echo "Key $key found in Lockr" . PHP_EOL; + // Use preg_replace to only remove Success: from the start + $lockr_keys[$key] = trim( preg_replace('/^Success: /', '', $current_keys[0]) ); + } else { + die( "Required key $key NOT found in Lockr. Make sure it exists" ); + } + + } + + return array_merge( $defaults, $lockr_keys ); +} + +/** + * Send a notification to slack + */ +function _slack_notification($slack_url, $channel, $username, $text, $attachment, $alwaysShowText = false) +{ + $attachment['fallback'] = $text; + $post = array( + 'username' => $username, + 'channel' => $channel, + 'icon_emoji' => ':lightning_cloud:', + 'attachments' => array($attachment) + ); + if ($alwaysShowText) { + $post['text'] = $text; + } + $payload = json_encode($post); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $slack_url); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + // Watch for messages with `terminus workflows watch --site=SITENAME` + print("\n==== Posting to Slack ====\n"); + $result = curl_exec($ch); + print("RESULT: $result"); + // $payload_pretty = json_encode($post,JSON_PRETTY_PRINT); // Uncomment to debug JSON + // print("JSON: $payload_pretty"); // Uncomment to Debug JSON + print("\n===== Post Complete! =====\n"); + curl_close($ch); +}