Skip to content

Commit

Permalink
slack: add action
Browse files Browse the repository at this point in the history
  • Loading branch information
tj committed Dec 20, 2018
1 parent 7b798be commit aebe0c6
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 4 deletions.
1 change: 1 addition & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- [Up](./up) — Deploy serverless applications and APIs to AWS Lambda
- [Go](./go) — Build Go applications
- [Slack](./slack) — Send Slack messages

## Resources

Expand Down
14 changes: 14 additions & 0 deletions slack/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:1.11

LABEL version="1.0.0"
LABEL maintainer="Apex"
LABEL repository="http://github.com/apex/actions"
LABEL homepage="http://github.com/apex/actions/slack"
LABEL "com.github.actions.name"="Slack"
LABEL "com.github.actions.description"="Send a Slack message"
LABEL "com.github.actions.icon"="slack"
LABEL "com.github.actions.color"="white"

RUN go get github.com/apex/actions/slack/cmd/slack

ENTRYPOINT ["slack"]
43 changes: 43 additions & 0 deletions slack/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Slack

GitHub Action for sending Slack messages which were defined by previous action(s) in ./slack.json.

## Secrets

- `SLACK_WEBHOOK_URL` - *Required* The Slack webhook URL.
- `SLACK_CHANNEL` - *Optional* The Slack channel name.
- `SLACK_USERNAME` - *Optional* The Slack message username.
- `SLACK_ICON` - *Optional* The Slack message icon.

## Example

This example sends a Slack notification after a deployment is complete. The `apex/actions/up`
action generates a slack.json to provide a message.

```
workflow "Deployment" {
on = "push"
resolves = ["Deploy Notification"]
}
action "Build" {
uses = "apex/actions/go@master"
}
action "Deploy" {
needs = "Build"
uses = "apex/actions/up@master"
secrets = ["AWS_SECRET_ACCESS_KEY", "AWS_ACCESS_KEY_ID"]
args = "deploy production"
}
action "Deploy Notification" {
needs = "Deploy"
uses = "apex/actions/slack@master"
secrets = ["SLACK_WEBHOOK_URL"]
}
```

## Links

- Message format: https://api.slack.com/docs/messages/builder
53 changes: 53 additions & 0 deletions slack/cmd/slack/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package main

import (
"fmt"
"log"
"os"

"github.com/apex/actions/slack"
)

func main() {
var msg slack.Message

// read message
err := slack.ReadMessage("slack.json", &msg)

if os.IsNotExist(err) {
log.Fatalf("Missing ./slack.json file, a previous action should populate it.")
}

if err != nil {
log.Fatalf("error reading message: %s", err)
}

// webhook
webhook := os.Getenv("SLACK_WEBHOOK_URL")

if webhook == "" {
log.Fatalf("Missing SLACK_WEBHOOK_URL environment variable")
}

// channel
if s := os.Getenv("SLACK_CHANNEL"); s != "" {
msg.Channel = s
}

// username
if s := os.Getenv("SLACK_USERNAME"); s != "" {
msg.Username = s
}

// icon
if s := os.Getenv("SLACK_ICON"); s != "" {
msg.IconURL = s
}

err = slack.Send(webhook, &msg)
if err != nil {
log.Fatalf("error sending message: %s", err)
}

fmt.Printf("Slack message sent!\n")
}
161 changes: 161 additions & 0 deletions slack/slack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package slack

import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"

"github.com/pkg/errors"
)

// AttachmentField contains information for an attachment field
// An Attachment can contain multiple of these
type AttachmentField struct {
Title string `json:"title"`
Value string `json:"value"`
Short bool `json:"short"`
}

// AttachmentAction is a button or menu to be included in the attachment. Required when
// using message buttons or menus and otherwise not useful. A maximum of 5 actions may be
// provided per attachment.
type AttachmentAction struct {
Name string `json:"name"` // Required.
Text string `json:"text"` // Required.
Style string `json:"style,omitempty"` // Optional. Allowed values: "default", "primary", "danger".
Type string `json:"type"` // Required. Must be set to "button" or "select".
Value string `json:"value,omitempty"` // Optional.
DataSource string `json:"data_source,omitempty"` // Optional.
MinQueryLength int `json:"min_query_length,omitempty"` // Optional. Default value is 1.
Options []AttachmentActionOption `json:"options,omitempty"` // Optional. Maximum of 100 options can be provided in each menu.
SelectedOptions []AttachmentActionOption `json:"selected_options,omitempty"` // Optional. The first element of this array will be set as the pre-selected option for this menu.
OptionGroups []AttachmentActionOptionGroup `json:"option_groups,omitempty"` // Optional.
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
URL string `json:"url,omitempty"` // Optional.
}

// AttachmentActionOption the individual option to appear in action menu.
type AttachmentActionOption struct {
Text string `json:"text"` // Required.
Value string `json:"value"` // Required.
Description string `json:"description,omitempty"` // Optional. Up to 30 characters.
}

// AttachmentActionOptionGroup is a semi-hierarchal way to list available options to appear in action menu.
type AttachmentActionOptionGroup struct {
Text string `json:"text"` // Required.
Options []AttachmentActionOption `json:"options"` // Required.
}

// ActionCallback specific fields for the action callback.
type ActionCallback struct {
MessageTs string `json:"message_ts"`
AttachmentID string `json:"attachment_id"`
Actions []AttachmentAction `json:"actions"`
}

// ConfirmationField are used to ask users to confirm actions
type ConfirmationField struct {
Title string `json:"title,omitempty"` // Optional.
Text string `json:"text"` // Required.
OkText string `json:"ok_text,omitempty"` // Optional. Defaults to "Okay"
DismissText string `json:"dismiss_text,omitempty"` // Optional. Defaults to "Cancel"
}

// Attachment contains all the information for an attachment
type Attachment struct {
Color string `json:"color,omitempty"`
Fallback string `json:"fallback"`

CallbackID string `json:"callback_id,omitempty"`
ID int `json:"id,omitempty"`

AuthorID string `json:"author_id,omitempty"`
AuthorName string `json:"author_name,omitempty"`
AuthorSubname string `json:"author_subname,omitempty"`
AuthorLink string `json:"author_link,omitempty"`
AuthorIcon string `json:"author_icon,omitempty"`

Title string `json:"title,omitempty"`
TitleLink string `json:"title_link,omitempty"`
Pretext string `json:"pretext,omitempty"`
Text string `json:"text"`

ImageURL string `json:"image_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`

Fields []AttachmentField `json:"fields,omitempty"`
Actions []AttachmentAction `json:"actions,omitempty"`
MarkdownIn []string `json:"mrkdwn_in,omitempty"`

Footer string `json:"footer,omitempty"`
FooterIcon string `json:"footer_icon,omitempty"`

Ts json.Number `json:"ts,omitempty"`
}

// Message is a Slack message payload.
type Message struct {
ResponseType string `json:"response_tyoe,omitempty"`
Text string `json:"text,omitempty"`
Channel string `json:"channel,omitempty"`
Username string `json:"username,omitempty"`
IconURL string `json:"icon_url,omitempty"`
IconEmoji string `json:"icon_emoji,omitempty"`
UnfurlLinks bool `json:"unfurl_links,omitempty"`
LinkNames string `json:"link_names,omitempty"`
Attachments []*Attachment `json:"attachments,omitempty"`
}

// ReadMessage reads a message from the given file.
func ReadMessage(path string, msg *Message) error {
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}

err = json.Unmarshal(b, msg)
if err != nil {
return errors.Wrap(err, "unmarshaling")
}

return nil
}

// WriteMessage writes a message to the given file.
func WriteMessage(path string, msg *Message) error {
b, err := json.MarshalIndent(msg, "", " ")
if err != nil {
return errors.Wrap(err, "marshaling")
}

err = ioutil.WriteFile(path, b, 0755)
if err != nil {
return err
}

return nil
}

// Send a message to the given webhook url.
func Send(url string, msg *Message) error {
var buf bytes.Buffer

err := json.NewEncoder(&buf).Encode(msg)
if err != nil {
return errors.Wrap(err, "marshaling")
}

res, err := http.Post(url, "application/json", &buf)
if err != nil {
return errors.Wrap(err, "requesting")
}
defer res.Body.Close()

if res.StatusCode >= 300 {
return errors.Errorf("%s response", res.Status)
}

return nil
}
8 changes: 4 additions & 4 deletions up/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine:3.8
FROM golang:1.11

LABEL version="1.0.0"
LABEL maintainer="Apex"
Expand All @@ -9,11 +9,11 @@ LABEL "com.github.actions.description"="Perform Up application operations"
LABEL "com.github.actions.icon"="chevron-up"
LABEL "com.github.actions.color"="white"

RUN apk add --update curl jq && rm -rf /var/cache/apk/*

ENV CI true
RUN curl -sf https://up.apex.sh/install | sh
RUN chmod +x /usr/local/bin/up

ENTRYPOINT ["/usr/local/bin/up"]
RUN go get github.com/apex/actions/up/cmd/up-wrapper

ENTRYPOINT ["up-wrapper"]
CMD ["deploy", "--no-build"]
4 changes: 4 additions & 0 deletions up/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ action "Deploy" {
args = "deploy production --no-build"
}
```

## Notes

This action generates a Slack message upon deployment.
Loading

0 comments on commit aebe0c6

Please sign in to comment.