Skip to content

Commit

Permalink
Add configuration management, and some other things
Browse files Browse the repository at this point in the history
  • Loading branch information
pcarranza committed Sep 23, 2018
1 parent 81b472f commit ff729f5
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 50 deletions.
9 changes: 9 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions configuration/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ func Load(in []byte) error {
func GetConfiguration() Configuration {
return config
}

// GetChannel returns the specific channel for the repo, or the default one if there is no specific set.
func (c Configuration) GetChannel(repoFullName string) string {
channel, ok := c.Repositories[repoFullName]
if ok {
return channel
}
return c.DefaultChannel
}
2 changes: 1 addition & 1 deletion core/propaganda.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ type Parser interface {

// Announcer provides a simple interface to announce things
type Announcer interface {
Announce(Announcement)
Announce(Announcement) error
}
9 changes: 8 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type Args struct {

WebhookURL string
MatchString string

ConfigFile string
}

func parseArgs() Args {
Expand All @@ -58,10 +60,15 @@ func parseArgs() Args {
flag.StringVar(&args.MetricsPath, "metrics", "/metrics", "metrics path")
flag.StringVar(&args.WebhookURL, "webhook-url", os.Getenv("SLACK_WEBHOOK_URL"), "slack webhook url")
flag.StringVar(&args.MatchString, "match-pattern", "\\[announce\\]", "match string")
flag.StringVar(&args.ConfigFile, "config", "propaganda.yml", "configuration file to use")
flag.Parse()

if args.WebhookURL == "" {
logrus.Fatalf("No slack webhook url, define it through -webhook-url argument or SLACK_WEBHOOK_URL env var")
logrus.Fatalf("no slack webhook url, define it through -webhook-url argument or SLACK_WEBHOOK_URL env var")
}

if _, err := os.Stat(args.ConfigFile); err != nil {
logrus.Fatalf("failed to stat configuration file %s: %s", args.ConfigFile, err)
}

return args
Expand Down
4 changes: 3 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ func (s Server) handle(w http.ResponseWriter, r *http.Request) {
metrics.WebhooksValid.WithLabelValues(a.ProjectName()).Inc()

logrus.Debugf("announcing webhook %#v", a)
s.announcer.Announce(a)
if err = s.announcer.Announce(a); err != nil {
http.Error(w, fmt.Sprintf("failed to announce change: %s", err), http.StatusBadGateway)
}

w.WriteHeader(http.StatusAccepted)
// logrus.Debugf("received Webhook\nHeaders: %#v\nPayload: %s", r.Header, string(body))
Expand Down
72 changes: 25 additions & 47 deletions slack/announcer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package slack
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"

conf "gitlab.com/yakshaving.art/propaganda/configuration"
"gitlab.com/yakshaving.art/propaganda/core"
"gitlab.com/yakshaving.art/propaganda/metrics"

Expand All @@ -15,37 +18,35 @@ import (
type Announcer struct {
WebhookURL string
Proxy string
// Channel string
}

// Announce implements core.Announcer interface
func (a Announcer) Announce(announcement core.Announcement) {

func (a Announcer) Announce(announcement core.Announcement) error {
body, err := json.Marshal(payload{
Markdown: true,
Text: announcement.Text(),
Markdown: true,
Text: announcement.Text(),
Channel: conf.GetConfiguration().GetChannel(announcement.ProjectName()),
UnfurlLinks: false,
UnfurlMedia: false,
})
if err != nil {
metrics.AnnouncementErrors.WithLabelValues("encoding").Inc()
logrus.Errorf("failed to encode payload as json: %s", err)
return
return fmt.Errorf("failed to encode payload as json: %s", err)
}

logrus.Debugf("posting payload: %s", string(body))

req, err := http.NewRequest(http.MethodPost, a.WebhookURL, bytes.NewReader(body))
if err != nil {
metrics.AnnouncementErrors.WithLabelValues("request").Inc()
logrus.Errorf("failed to create POST request: %s", err)
return
return fmt.Errorf("failed to create POST request: %s", err)
}
req.Header.Add("Content-type", "application/json")
resp, err := http.DefaultClient.Do(req)

if err != nil {
metrics.AnnouncementErrors.WithLabelValues("unknown").Inc()
logrus.Errorf("failed to call slack webhook: %s", err)
return
return fmt.Errorf("failed to call slack webhook: %s", err)
}

switch {
Expand All @@ -54,45 +55,22 @@ func (a Announcer) Announce(announcement core.Announcement) {
metrics.AnnouncementSuccesses.WithLabelValues(announcement.ProjectName()).Inc()

default:
logrus.Debugf("payload failed to slack with response: %#v", resp)
metrics.AnnouncementErrors.WithLabelValues(resp.Status).Inc()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to push payload with code %d, and also to read the response body: %s", resp.StatusCode, err)
}
defer resp.Body.Close()
return fmt.Errorf("failed to push payload to slack with code %d: %s", resp.StatusCode, string(b))
}
return nil
}

// Payload is the slack payload object used to send data
type payload struct {
// Username string `json:"username,omitempty"`
Text string `json:"text,omitempty"`
Markdown bool `json:"mrkdwn,omitempty"`
// Parse string `json:"parse,omitempty"`
// IconUrl string `json:"icon_url,omitempty"`
// IconEmoji string `json:"icon_emoji,omitempty"`
// Channel string `json:"channel,omitempty"`
// LinkNames string `json:"link_names,omitempty"`
// Attachments []Attachment `json:"attachments,omitempty"`
// UnfurlLinks bool `json:"unfurl_links,omitempty"`
// UnfurlMedia bool `json:"unfurl_media,omitempty"`
Text string `json:"text,omitempty"`
Markdown bool `json:"mrkdwn,omitempty"`
Channel string `json:"channel,omitempty"`
UnfurlLinks bool `json:"unfurl_links,omitempty"`
UnfurlMedia bool `json:"unfurl_media,omitempty"`
}

// type Field struct {
// Title string `json:"title"`
// Value string `json:"value"`
// Short bool `json:"short"`
// }

// type Attachment struct {
// Fallback *string `json:"fallback"`
// Color *string `json:"color"`
// PreText *string `json:"pretext"`
// AuthorName *string `json:"author_name"`
// AuthorLink *string `json:"author_link"`
// AuthorIcon *string `json:"author_icon"`
// Title *string `json:"title"`
// TitleLink *string `json:"title_link"`
// Text *string `json:"text"`
// ImageUrl *string `json:"image_url"`
// Fields []*Field `json:"fields"`
// Footer *string `json:"footer"`
// FooterIcon *string `json:"footer_icon"`
// Timestamp *int64 `json:"ts"`
// MarkdownIn *[]string `json:"mrkdwn_in"`
// }
76 changes: 76 additions & 0 deletions slack/announcer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package slack_test

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"

"gitlab.com/yakshaving.art/propaganda/configuration"
"gitlab.com/yakshaving.art/propaganda/slack"

"github.com/stretchr/testify/assert"
)

type announcement struct {
project string
text string
}

func (a announcement) ProjectName() string {
return a.project
}

func (a announcement) Text() string {
return a.text
}

func (a announcement) ShouldAnnounce() bool {
return true
}

func TestSlackAnnouncerCanSucceed(t *testing.T) {
configuration.Load([]byte("default_channel: general"))

a := announcement{
text: "test text",
project: "some/project",
}
ass := assert.New(t)

s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
ass.NoError(err)
defer r.Body.Close()

if !ass.JSONEq(`{"channel":"general", "text":"test text", "mrkdwn":true}`, string(b)) {
w.WriteHeader(400)
} else {
w.WriteHeader(200)
}

}))
defer s.Close()

announcer := slack.Announcer{
WebhookURL: s.URL,
}

ass.NoError(announcer.Announce(a))
}

func TestSlackAnnouncerCanFail(t *testing.T) {
ass := assert.New(t)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "invalid payload", 400)
}))
defer s.Close()

announcer := slack.Announcer{
WebhookURL: s.URL,
}
ass.Errorf(announcer.Announce(announcement{
text: "invalid test text",
project: "some/project",
}), "failed to push payload to slack with code 400: invalid payload")
}

0 comments on commit ff729f5

Please sign in to comment.