diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72bcb00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +.idea +*.iml +dist/bin/* +slack-notifier +.build-harness + +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..57f5298 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +sudo: required +language: go +go: + - 1.9.x +addons: + apt: + packages: + - git + - make + - curl + +env: + - DOCKER_IMAGE_NAME=cloudposse/slack-notifier + +services: +- docker + +install: +- make init +- make travis:docker-login +- make go:deps-build +- make go:deps-dev +- make go-get + +script: +- make go:deps +- make go:test +- make go:lint +- make go:build-all +- ls -l release/ +- make docker:build + +after_success: +- make travis:docker-tag-and-push + +deploy: + - provider: releases + api_key: "$GITHUB_API_KEY" + file_glob: true + file: "release/*" + skip_cleanup: true + on: + tags: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..41e40ad --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.9.3 as builder +RUN mkdir -p /go/src/github.com/cloudposse/slack-notifier +WORKDIR /go/src/github.com/cloudposse/slack-notifier +COPY . . +RUN go get && CGO_ENABLED=0 go build -v -o "./dist/bin/slack-notifier" *.go + + +FROM alpine:3.6 +RUN apk add --no-cache ca-certificates +COPY --from=builder /go/src/github.com/cloudposse/slack-notifier/dist/bin/slack-notifier /usr/bin/slack-notifier +ENV PATH $PATH:/usr/bin +ENTRYPOINT ["slack-notifier"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c37833f --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Cloud Posse, LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..12c56fe --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +SHELL = /bin/bash + +PATH:=$(PATH):$(GOPATH)/bin + +include $(shell curl --silent -o .build-harness "https://raw.githubusercontent.com/cloudposse/build-harness/master/templates/Makefile.build-harness"; echo .build-harness) + + +.PHONY : go-get +go-get: + go get + + +.PHONY : go-build +go-build: go-get + CGO_ENABLED=0 go build -v -o "./dist/bin/slack-notifier" *.go diff --git a/README.md b/README.md index 99c604f..68b7ae1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,252 @@ -# slack-notifier -CLI to send notifications to Slack channels +# slack-notifier [![Build Status](https://travis-ci.org/cloudposse/slack-notifier.svg?branch=master)](https://travis-ci.org/cloudposse/slack-notifier) + + +Command line utility to send messages with [attachments](https://api.slack.com/docs/message-attachments) +to [Slack](https://slack.com) channels via [Incoming Webhooks](https://api.slack.com/incoming-webhooks). + + +![GitHub Commit Status](images/slack-notification-example.png) + + +## Usage + +__NOTE__: The module accepts parameters as command-line arguments or as ENV variables +(or any combination of command-line arguments and ENV vars). +Command-line arguments take precedence over ENV vars. + + +__NOTE__: The module supports up to 5 Fields in an [attachment](https://api.slack.com/docs/message-attachments) +### + + +| Command-line argument | ENV var | Description | +|:----------------------|:--------------------|:-------------------------------------------------------------------------------------------------------------------------------------| +| webhook_url | SLACK_WEBHOOK_URL | Slack [Webhook URL](https://get.slack.help/hc/en-us/articles/115005265063-Incoming-WebHooks-for-Slack) | +| user_name | SLACK_USER_NAME | Slack user name (the username from which the messages will be sent) | +| icon_emoji | SLACK_ICON_EMOJI | Slack icon [emoji](https://www.webpagefx.com/tools/emoji-cheat-sheet) for the user's avatar | +| fallback | SLACK_FALLBACK | A plain-text summary of the attachment. This text will be used in clients that don't show formatted text | +| color | SLACK_COLOR | An optional value that can either be one of `good`, `warning`, `danger`, or a color code (_e.g._ `#439FE0`) | +| pretext | SLACK_PRETEXT | Optional text that appears above the message attachment block | +| author_name | SLACK_AUTHOR_NAME | Small text to display the attachment author's name | +| author_link | SLACK_AUTHOR_LINK | URL that will hyperlink the author's name. Will only work if `author_name` is present | +| author_icon | SLACK_AUTHOR_ICON | URL of a small 16x16px image to the left of the author's name. Will only work if `author_name` is present | +| title | SLACK_TITLE | The `title` is displayed as larger, bold text near the top of a message attachment | +| title_link | SLACK_TITLE_LINK | URL for the `title` text to be hyperlinked | +| text | SLACK_TEXT | Main text in a message attachment | +| thumb_url | SLACK_THUMB_URL | URL to an image file that will be displayed as a thumbnail on the right side of a message attachment | +| footer | SLACK_FOOTER | Brief text to help contextualize and identify an attachment | +| footer_icon | SLACK_FOOTER_ICON | URL of a small icon beside the `footer` text | +| image_url | SLACK_IMAGE_URL | URL to an image file that will be displayed inside a message attachment | +| field1_title | SLACK_FIELD1_TITLE | Field1 title | +| field1_value | SLACK_FIELD1_VALUE | Field1 value | +| field1_short | SLACK_FIELD1_SHORT | An optional boolean indicating whether the `value` is short enough to be displayed side-by-side with other values (default `false`) | +| field2_title | SLACK_FIELD2_TITLE | Field2 title | +| field2_value | SLACK_FIELD2_VALUE | Field2 value | +| field2_short | SLACK_FIELD2_SHORT | An optional boolean indicating whether the `value` is short enough to be displayed side-by-side with other values (default `false`) | +| field3_title | SLACK_FIELD3_TITLE | Field3 title | +| field3_value | SLACK_FIELD3_VALUE | Field3 value | +| field3_short | SLACK_FIELD3_SHORT | An optional boolean indicating whether the `value` is short enough to be displayed side-by-side with other values (default `false`) | +| field4_title | SLACK_FIELD4_TITLE | Field4 title | +| field4_value | SLACK_FIELD4_VALUE | Field4 value | +| field4_short | SLACK_FIELD4_SHORT | An optional boolean indicating whether the `value` is short enough to be displayed side-by-side with other values (default `false`) | +| field5_title | SLACK_FIELD5_TITLE | Field5 title | +| field5_value | SLACK_FIELD5_VALUE | Field5 value | +| field5_short | SLACK_FIELD5_SHORT | An optional boolean indicating whether the `value` is short enough to be displayed side-by-side with other values (default `false`) | + + + +### build the Go program locally + +```sh +go get + +CGO_ENABLED=0 go build -v -o "./dist/bin/slack-notifier" *.go +``` + + +### run locally with ENV vars + +```sh +export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXXXXXXX/XXXXXXXX/XXXXXXXXXXXXXXXXXXXXXX" +export SLACK_USER_NAME="CodeFresh" +export SLACK_ICON_EMOJI=":white_check_mark:" +export SLACK_FALLBACK="Deployed to Staging environment" +export SLACK_COLOR="good" +export SLACK_PRETEXT="Added XYZ to feature-104" +export SLACK_AUTHOR_NAME="Auto Deploy Robot" +export SLACK_AUTHOR_LINK="https://cloudposse.com/wp-content/uploads/sites/29/2018/02/small-cute-robot-square.png" +export SLACK_AUTHOR_ICON="https://cloudposse.com/wp-content/uploads/sites/29/2018/02/small-cute-robot-square.png" +export SLACK_TITLE="Environment Updated" +export SLACK_TITLE_LINK="http://demo1.cloudposse.com" +export SLACK_TEXT="The latest changes have been deployed" +export SLACK_THUMB_URL="https://cloudposse.com/wp-content/uploads/sites/29/2018/02/SquareLogo2.png" +export SLACK_FOOTER="Helm Deployment" +export SLACK_FOOTER_ICON="https://cloudposse.com/wp-content/uploads/sites/29/2018/02/kubernetes.png" +export SLACK_FIELD1_TITLE="Environment" +export SLACK_FIELD1_VALUE="Staging" +export SLACK_FIELD1_SHORT="true" +export SLACK_FIELD2_TITLE="Namespace" +export SLACK_FIELD2_VALUE="feature-104" +export SLACK_FIELD2_SHORT="true" + +./dist/bin/slack-notifier +``` + + +### run locally with command-line arguments + +```sh +./dist/bin/slack-notifier \ + -webhook_url "https://hooks.slack.com/services/XXXXXXXX/XXXXXXXX/XXXXXXXXXXXXXXXXXXXXXX" \ + -user_name "CodeFresh" \ + -icon_emoji ":white_check_mark:" \ + -fallback "Deployed to Staging environment" \ + -color "good" \ + -pretext "Added XYZ to feature-104" \ + -author_name "Auto Deploy Robot" \ + -author_link "https://cloudposse.com/wp-content/uploads/sites/29/2018/02/small-cute-robot-square.png" \ + -author_icon "https://cloudposse.com/wp-content/uploads/sites/29/2018/02/small-cute-robot-square.png" \ + -title "Environment Updated" \ + -title_link "http://demo1.cloudposse.com" \ + -text "The latest changes have been deployed" \ + -thumb_url "https://cloudposse.com/wp-content/uploads/sites/29/2018/02/SquareLogo2.png" \ + -footer "Helm Deployment" \ + -footer_icon "https://cloudposse.com/wp-content/uploads/sites/29/2018/02/kubernetes.png" \ + -field1_title "Environment" \ + -field1_value "Staging" \ + -field1_short true \ + -field2_title "Namespace" \ + -field2_value "feature-104" \ + -field2_short true +``` + + + +### build the Docker image +__NOTE__: it will download all `Go` dependencies and then build the program inside the container (see [`Dockerfile`](Dockerfile)) + + +```sh +docker build --tag slack-notifier --no-cache=true . +``` + + + +### run in a Docker container with ENV vars + +```sh +docker run -i --rm \ + -e SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXXXXXXX/XXXXXXXX/XXXXXXXXXXXXXXXXXXXXXX" \ + -e SLACK_USER_NAME="CodeFresh" \ + -e SLACK_ICON_EMOJI=":white_check_mark:" \ + -e SLACK_FALLBACK="Deployed to Staging environment" \ + -e SLACK_COLOR="good" \ + -e SLACK_PRETEXT="Added XYZ to feature-104" \ + -e SLACK_AUTHOR_NAME="Auto Deploy Robot" \ + -e SLACK_AUTHOR_LINK="https://cloudposse.com/wp-content/uploads/sites/29/2018/02/small-cute-robot-square.png" \ + -e SLACK_AUTHOR_ICON="https://cloudposse.com/wp-content/uploads/sites/29/2018/02/small-cute-robot-square.png" \ + -e SLACK_TITLE="Environment Updated" \ + -e SLACK_TITLE_LINK="http://demo1.cloudposse.com" \ + -e SLACK_TEXT="The latest changes have been deployed" \ + -e SLACK_THUMB_URL="https://cloudposse.com/wp-content/uploads/sites/29/2018/02/SquareLogo2.png" \ + -e SLACK_FOOTER="Helm Deployment" \ + -e SLACK_FOOTER_ICON="https://cloudposse.com/wp-content/uploads/sites/29/2018/02/kubernetes.png" \ + -e SLACK_FIELD1_TITLE="Environment" \ + -e SLACK_FIELD1_VALUE="Staging" \ + -e SLACK_FIELD1_SHORT="true" \ + -e SLACK_FIELD2_TITLE="Namespace" \ + -e SLACK_FIELD2_VALUE="feature-104" \ + -e SLACK_FIELD2_SHORT="true" \ + slack-notifier +``` + + + +## References +* https://get.slack.help/hc/en-us/articles/115005265063-Incoming-WebHooks-for-Slack +* https://api.slack.com/incoming-webhooks +* https://api.slack.com/docs/message-attachments +* https://api.slack.com/docs/message-formatting +* https://www.webpagefx.com/tools/emoji-cheat-sheet + + +## Help + +**Got a question?** + +File a GitHub [issue](https://github.com/cloudposse/slack-notifier/issues), send us an [email](mailto:hello@cloudposse.com) or reach out to us on [Gitter](https://gitter.im/cloudposse/). + + +## Contributing + +### Bug Reports & Feature Requests + +Please use the [issue tracker](https://github.com/cloudposse/slack-notifier/issues) to report any bugs or file feature requests. + +### Developing + +If you are interested in being a contributor and want to get involved in developing `slack-notifier`, we would love to hear from you! Shoot us an [email](mailto:hello@cloudposse.com). + +In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow. + + 1. **Fork** the repo on GitHub + 2. **Clone** the project to your own machine + 3. **Commit** changes to your own branch + 4. **Push** your work back up to your fork + 5. Submit a **Pull request** so that we can review your changes + +**NOTE:** Be sure to merge the latest from "upstream" before making a pull request! + + +## License + +[APACHE 2.0](LICENSE) © 2018 [Cloud Posse, LLC](https://cloudposse.com) + +See [LICENSE](LICENSE) for full details. + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + + +## About + +`slack-notifier` is maintained and funded by [Cloud Posse, LLC][website]. + +![Cloud Posse](https://cloudposse.com/logo-300x69.png) + + +Like it? Please let us know at + +We love [Open Source Software](https://github.com/cloudposse/)! + +See [our other projects][community] +or [hire us][hire] to help build your next cloud platform. + + [website]: https://cloudposse.com/ + [community]: https://github.com/cloudposse/ + [hire]: https://cloudposse.com/contact/ + + +### Contributors + +| [![Erik Osterman][erik_img]][erik_web]
[Erik Osterman][erik_web] | [![Andriy Knysh][andriy_img]][andriy_web]
[Andriy Knysh][andriy_web] | +|-------------------------------------------------------|------------------------------------------------------------------| + + [erik_img]: http://s.gravatar.com/avatar/88c480d4f73b813904e00a5695a454cb?s=144 + [erik_web]: https://github.com/osterman/ + [andriy_img]: https://avatars0.githubusercontent.com/u/7356997?v=4&u=ed9ce1c9151d552d985bdf5546772e14ef7ab617&s=144 + [andriy_web]: https://github.com/aknysh/ diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..47beb3f --- /dev/null +++ b/glide.yaml @@ -0,0 +1 @@ +package: github.com/cloudposse/slack-notifier diff --git a/images/slack-notification-example.png b/images/slack-notification-example.png new file mode 100644 index 0000000..6f58516 Binary files /dev/null and b/images/slack-notification-example.png differ diff --git a/images/slack-notification-example2.png b/images/slack-notification-example2.png new file mode 100644 index 0000000..1425882 Binary files /dev/null and b/images/slack-notification-example2.png differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..ba507fa --- /dev/null +++ b/main.go @@ -0,0 +1,125 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "strconv" +) + +var ( + webhook_url = flag.String("webhook_url", os.Getenv("SLACK_WEBHOOK_URL"), "Slack Webhook URL") + user_name = flag.String("user_name", os.Getenv("SLACK_USER_NAME"), "Slack user name (the username from which the messages will be sent)") + icon_emoji = flag.String("icon_emoji", os.Getenv("SLACK_ICON_EMOJI"), "Slack icon emoji for the user's avatar. https://www.webpagefx.com/tools/emoji-cheat-sheet") + fallback = flag.String("fallback", os.Getenv("SLACK_FALLBACK"), "A plain-text summary of the attachment. This text will be used in clients that don't show formatted text") + color = flag.String("color", os.Getenv("SLACK_COLOR"), "An optional value that can either be one of good, warning, danger, or any hex color code (e.g. #439FE0)") + pretext = flag.String("pretext", os.Getenv("SLACK_PRETEXT"), "Optional text that appears above the message attachment block") + author_name = flag.String("author_name", os.Getenv("SLACK_AUTHOR_NAME"), "Small text to display the attachment author's name") + author_link = flag.String("author_link", os.Getenv("SLACK_AUTHOR_LINK"), "URL that will hyperlink the author's name. Will only work if author_name is present") + author_icon = flag.String("author_icon", os.Getenv("SLACK_AUTHOR_ICON"), "URL of a small 16x16px image to the left of the author's name. Will only work if `author_name` is present") + title = flag.String("title", os.Getenv("SLACK_TITLE"), "The title is displayed as larger, bold text near the top of a message attachment") + title_link = flag.String("title_link", os.Getenv("SLACK_TITLE_LINK"), "URL for the title text to be hyperlinked") + text = flag.String("text", os.Getenv("SLACK_TEXT"), "Main text in a message attachment") + thumb_url = flag.String("thumb_url", os.Getenv("SLACK_THUMB_URL"), "URL to an image file that will be displayed as a thumbnail on the right side of a message attachment") + footer = flag.String("footer", os.Getenv("SLACK_FOOTER"), "Brief text to help contextualize and identify an attachment") + footer_icon = flag.String("footer_icon", os.Getenv("SLACK_FOOTER_ICON"), "URL of a small icon beside the footer text") + image_url = flag.String("image_url", os.Getenv("SLACK_IMAGE_URL"), "URL to an image file that will be displayed inside a message attachment") + field1_title = flag.String("field1_title", os.Getenv("SLACK_FIELD1_TITLE"), "Field1 title") + field1_value = flag.String("field1_value", os.Getenv("SLACK_FIELD1_VALUE"), "Field1 value") + field1_short = flag.String("field1_short", os.Getenv("SLACK_FIELD1_SHORT"), "An optional boolean indicating whether the 'value' is short enough to be displayed side-by-side with other values (default 'false')") + field2_title = flag.String("field2_title", os.Getenv("SLACK_FIELD2_TITLE"), "Field2 title") + field2_value = flag.String("field2_value", os.Getenv("SLACK_FIELD2_VALUE"), "Field2 value") + field2_short = flag.String("field2_short", os.Getenv("SLACK_FIELD2_SHORT"), "An optional boolean indicating whether the 'value' is short enough to be displayed side-by-side with other values (default 'false')") + field3_title = flag.String("field3_title", os.Getenv("SLACK_FIELD3_TITLE"), "Field3 title") + field3_value = flag.String("field3_value", os.Getenv("SLACK_FIELD3_VALUE"), "Field3 value") + field3_short = flag.String("field3_short", os.Getenv("SLACK_FIELD3_SHORT"), "An optional boolean indicating whether the 'value' is short enough to be displayed side-by-side with other values (default 'false')") + field4_title = flag.String("field4_title", os.Getenv("SLACK_FIELD4_TITLE"), "Field4 title") + field4_value = flag.String("field4_value", os.Getenv("SLACK_FIELD4_VALUE"), "Field4 value") + field4_short = flag.String("field4_short", os.Getenv("SLACK_FIELD4_SHORT"), "An optional boolean indicating whether the 'value' is short enough to be displayed side-by-side with other values (default 'false')") + field5_title = flag.String("field5_title", os.Getenv("SLACK_FIELD5_TITLE"), "Field5 title") + field5_value = flag.String("field5_value", os.Getenv("SLACK_FIELD5_VALUE"), "Field5 value") + field5_short = flag.String("field5_short", os.Getenv("SLACK_FIELD5_SHORT"), "An optional boolean indicating whether the 'value' is short enough to be displayed side-by-side with other values (default 'false')") +) + +func addField(fields []Field, fieldTitle string, fieldValue string, fieldShort string) []Field { + if fieldTitle != "" && fieldValue != "" { + var short = false + var err error + + if fieldShort != "" { + if short, err = strconv.ParseBool(fieldShort); err != nil { + fmt.Println("slack-notifier: Error: ", err.Error()) + short = false + } + } + + fields = append(fields, Field{ + Title: fieldTitle, + Value: fieldValue, + Short: short, + }) + } + + return fields +} + +func main() { + flag.Parse() + + if *webhook_url == "" { + flag.PrintDefaults() + log.Fatal("-webhook_url or SLACK_WEBHOOK_URL required") + } + if *user_name == "" { + flag.PrintDefaults() + log.Fatal("-user_name or SLACK_USER_NAME required") + } + if *icon_emoji == "" { + flag.PrintDefaults() + log.Fatal("-icon_emoji or SLACK_ICON_EMOJI required") + } + + attachment := Attachment{ + MrkdwnIn: []string{"text", "pretext"}, + AuthorIcon: *author_icon, + AuthorLink: *author_link, + AuthorName: *author_name, + Color: *color, + Fallback: *fallback, + FooterIcon: *footer_icon, + Footer: *footer, + ImageURL: *image_url, + Pretext: *pretext, + Text: *text, + ThumbURL: *thumb_url, + TitleLink: *title_link, + Title: *title, + } + + fields := addField([]Field{}, *field1_title, *field1_value, *field1_short) + fields = addField(fields, *field2_title, *field2_value, *field2_short) + fields = addField(fields, *field3_title, *field3_value, *field3_short) + fields = addField(fields, *field4_title, *field4_value, *field4_short) + fields = addField(fields, *field5_title, *field5_value, *field5_short) + + if len(fields) > 0 { + attachment.Fields = fields + } + + payload := Payload{ + Attachments: []Attachment{attachment}, + LinkNames: true, + Mrkdwn: true, + Username: *user_name, + IconEmoji: *icon_emoji, + } + + notifier := NewSlackNotifier(*webhook_url) + err := notifier.Notify(payload) + if err != nil { + fmt.Println("slack-notifier: Failed to sent message to Webhook URL. Error: ", err.Error()) + } else { + fmt.Println("slack-notifier: Sent message to Webhook URL") + } +} diff --git a/slack_notifier.go b/slack_notifier.go new file mode 100644 index 0000000..48d162b --- /dev/null +++ b/slack_notifier.go @@ -0,0 +1,81 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +type SlackNotifier struct { + WebhookUrl string + DryRun bool +} + +type Payload struct { + Attachments []Attachment `json:"attachments"` + LinkNames bool `json:"link_names"` + Mrkdwn bool `json:"mrkdwn"` + IconEmoji string `json:"icon_emoji"` + Username string `json:"username"` +} + +// https://api.slack.com/docs/message-attachments +type Attachment struct { + AuthorIcon string `json:"author_icon"` + AuthorLink string `json:"author_link"` + AuthorName string `json:"author_name"` + Color string `json:"color"` + Fallback string `json:"fallback"` + Fields []Field `json:"fields"` + FooterIcon string `json:"footer_icon"` + Footer string `json:"footer"` + ImageURL string `json:"image_url"` + MrkdwnIn []string `json:"mrkdwn_in"` + Pretext string `json:"pretext"` + Text string `json:"text"` + ThumbURL string `json:"thumb_url"` + TitleLink string `json:"title_link"` + Title string `json:"title"` + Ts int64 `json:"ts"` +} + +type Field struct { + Short bool `json:"short"` + Title string `json:"title"` + Value string `json:"value"` +} + +func NewSlackNotifier(webhookURL string) SlackNotifier { + return SlackNotifier{ + WebhookUrl: webhookURL, + } +} + +func (sn SlackNotifier) Notify(message Payload) error { + data, err := json.Marshal(message) + if err != nil { + return err + } + + if sn.DryRun { + fmt.Println(string(data)) + return nil + } + + body := bytes.NewBuffer(data) + request, err := http.NewRequest("POST", sn.WebhookUrl, body) + if err != nil { + return err + } + + request.Header.Add("Content-Type", "application/json; charset=utf-8") + + client := &http.Client{} + _, err = client.Do(request) + if err != nil { + return err + } + + return nil +}