diff --git a/cmd/main.go b/cmd/main.go deleted file mode 100644 index c336218..0000000 --- a/cmd/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "flag" - - "github.com/asticode/go-slack" - "github.com/molotovtv/go-logger" - "github.com/molotovtv/go-toolbox" - "github.com/rs/xlog" -) - -// Flags -var ( - channel = flag.String("c", "", "the channel") - message = flag.String("m", "", "the message") -) - -func main() { - // Get subcommand - s := toolbox.Subcommand() - flag.Parse() - - // Init logger - l := xlog.New(logger.NewConfig(logger.FlagConfig())) - - // Init slack - sl := slack.New(slack.FlagConfig()) - sl.Logger = l - - // Log - l.Debugf("Subcommand is %s", s) - - // Switch on subcommand - switch s { - default: - // Init message - m := slack.Message{ - Channel: *channel, - Text: *message, - } - - // Slack - if err := sl.Slack(m); err != nil { - l.Fatal(err) - } - break - } -} diff --git a/configuration.go b/configuration.go index 2e12b98..48ebd9c 100644 --- a/configuration.go +++ b/configuration.go @@ -7,20 +7,29 @@ import ( // Vars var ( + ChannelPrefix = flag.String("slack-channel-prefix", "", "the slack channel prefix") IncomingWebhookURL = flag.String("slack-incoming-webhook-url", "", "the slack incoming webhook url") RequestTimeout = flag.Duration("slack-request-timeout", 0, "the duration after which a request is considered as having timed out") + RetryMax = flag.Int("slack-retry-max", 0, "the slack max retry") + RetrySleep = flag.Duration("slack-retry-sleep", 0, "the slack max sleep") ) // Configuration represents the slack configuration type Configuration struct { + ChannelPrefix string `toml:"channel_prefix"` IncomingWebhookURL string `toml:"incoming_webhook_url"` RequestTimeout time.Duration `toml:"request_timeout"` + RetryMax int `toml:"retry_max"` + RetrySleep time.Duration `toml:"retry_sleep"` } // FlagConfig generates a Configuration based on flags func FlagConfig() Configuration { return Configuration{ + ChannelPrefix: *ChannelPrefix, IncomingWebhookURL: *IncomingWebhookURL, RequestTimeout: *RequestTimeout, + RetryMax: *RetryMax, + RetrySleep: *RetrySleep, } } diff --git a/http.go b/http.go index 9b681d9..21b70d0 100644 --- a/http.go +++ b/http.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io/ioutil" + "net" "net/http" "time" ) @@ -13,11 +14,10 @@ var Send = func(req *http.Request, httpClient *http.Client) (*http.Response, err return httpClient.Do(req) } -// Send sends a new authorized OHE request -func (o *Slack) Send(hostname string, pattern string, method string, body []byte) (req *http.Request, resp *http.Response, err error) { +// Send sends a new slack +func (s *Slack) Send(hostname string, pattern string, method string, body []byte) (req *http.Request, resp *http.Response, err error) { // Log url := hostname + pattern - o.Logger.Debugf("Sending Slack %s request to %s with body %s", method, url, string(body)) // Create request req, err = http.NewRequest(method, url, bytes.NewReader(body)) @@ -25,39 +25,43 @@ func (o *Slack) Send(hostname string, pattern string, method string, body []byte return } req.Header.Add("Content-type", "application/json") + defer req.Body.Close() // Send request - resp, err = Send(req, o.HTTPClient) + resp, err = Send(req, s.HTTPClient) return } -// SendWithMaxRetries sends a new authorized OHE request and retries in case of specific conditions -func (o *Slack) SendWithMaxRetries(hostname string, pattern string, method string, body []byte, retryMax int, retrySleep time.Duration) (req *http.Request, resp *http.Response, err error) { +// SendWithMaxRetries sends a new slack and retries in case of specific conditions +func (s *Slack) SendWithMaxRetries(hostname string, pattern string, method string, body []byte) (req *http.Request, resp *http.Response, err error) { // Loop - for retriesLeft := retryMax; retriesLeft > 0; retriesLeft-- { + // We start at s.RetryMax + 1 so that it runs at least once even if RetryMax == 0 + for retriesLeft := s.RetryMax + 1; retriesLeft > 0; retriesLeft-- { // Send request - req, resp, err = o.Send(hostname, pattern, method, body) - if err != nil { - return + var retry bool + if req, resp, err = s.Send(hostname, pattern, method, body); err != nil { + // If error is temporary, retry + if netError, ok := err.(net.Error); ok && netError.Temporary() { + retry = true + } else { + return + } } // Retry if internal server or if too many requests - if resp.StatusCode >= http.StatusInternalServerError || resp.StatusCode == http.StatusTooManyRequests { + if retry || resp.StatusCode >= http.StatusInternalServerError || resp.StatusCode == http.StatusTooManyRequests { // Get body - b, e := ioutil.ReadAll(resp.Body) - if e != nil { - err = e - return + if resp != nil { + defer resp.Body.Close() + if _, err = ioutil.ReadAll(resp.Body); err != nil { + return + } } // Log - o.Logger.Debugf("Status code %d triggered a retry, sleeping %s and retrying... (%d retries left and body %s)", resp.StatusCode, retrySleep, retriesLeft-1, string(b)) - - // Close response body - resp.Body.Close() - - // Sleep - time.Sleep(retrySleep) + if retriesLeft > 1 { + time.Sleep(s.RetrySleep) + } continue } @@ -66,14 +70,14 @@ func (o *Slack) SendWithMaxRetries(hostname string, pattern string, method strin } // Max retries limit reached - err = fmt.Errorf("Max retries %d reached", retryMax) + err = fmt.Errorf("Max retries %d reached for request to %s", s.RetryMax, req.URL) return } // ProcessResponse processes an HTTP response var ProcessResponse = func(req *http.Request, resp *http.Response) error { if resp.StatusCode < 200 || resp.StatusCode > 299 { - return fmt.Errorf("Invalid status code %v on %v", resp.StatusCode, req.URL.Path) + return fmt.Errorf("Invalid status code %v on %v", resp.StatusCode, req.URL) } return nil } diff --git a/http_test.go b/http_test.go index fc59b76..282c7a8 100644 --- a/http_test.go +++ b/http_test.go @@ -8,16 +8,13 @@ import ( "testing" "time" - "github.com/asticode/go-slack" - "github.com/rs/xlog" + "github.com/molotovtv/go-slack" "github.com/stretchr/testify/assert" ) func TestSendWithMaxRetries(t *testing.T) { var count int - o := slack.Slack{ - Logger: xlog.NopLogger, - } + s := slack.Slack{RetrySleep: time.Nanosecond} slack.Send = func(req *http.Request, httpClient *http.Client) (*http.Response, error) { count++ if count == 1 { @@ -29,19 +26,23 @@ func TestSendWithMaxRetries(t *testing.T) { } return &http.Response{StatusCode: http.StatusBadRequest, ProtoMinor: 4, Body: ioutil.NopCloser(strings.NewReader(""))}, nil } - _, resp, err := o.SendWithMaxRetries("", "", "", nil, 1, time.Nanosecond) + s.RetryMax = 0 + _, resp, err := s.SendWithMaxRetries("", "", "", nil) assert.Error(t, err) assert.Equal(t, 1, resp.ProtoMinor) count = 0 - _, resp, err = o.SendWithMaxRetries("", "", "", nil, 2, time.Nanosecond) + s.RetryMax = 1 + _, resp, err = s.SendWithMaxRetries("", "", "", nil) assert.Error(t, err) assert.Equal(t, 2, resp.ProtoMinor) count = 0 - _, resp, err = o.SendWithMaxRetries("", "", "", nil, 3, time.Nanosecond) + s.RetryMax = 2 + _, resp, err = s.SendWithMaxRetries("", "", "", nil) assert.Error(t, err) assert.Equal(t, 3, resp.ProtoMinor) count = 0 - _, resp, err = o.SendWithMaxRetries("", "", "", nil, 4, time.Nanosecond) + s.RetryMax = 3 + _, resp, err = s.SendWithMaxRetries("", "", "", nil) assert.NoError(t, err) assert.Equal(t, 4, resp.ProtoMinor) } diff --git a/message.go b/message.go index 9ffcf09..6158f2f 100644 --- a/message.go +++ b/message.go @@ -2,18 +2,20 @@ package slack import ( "encoding/json" - "fmt" "net/http" - "time" ) // Message represents a message +//https://api.slack.com/messaging/sending +//https://api.slack.com/reference/messaging/payload type Message struct { Attachments []Attachment `json:"attachments,omitempty"` Channel string `json:"channel"` Markdown bool `json:"mrkdwn,omitempty"` Text string `json:"text,omitempty"` Username string `json:"username,omitempty"` + IconeURL string `json:"icon_url,omitempty"` + IconeEmoji string `json:"icon_emoji,omitempty"` } // Attachment represents an attachments @@ -45,15 +47,11 @@ type Field struct { // Slack sends a message to Slack func (s *Slack) Slack(m Message) (err error) { - // Log - l := fmt.Sprintf("Slacking message to %s", m.Channel) - s.Logger.Debugf("[Start] %s", l) - defer func(now time.Time) { - s.Logger.Debugf("[End] %s in %s", l, time.Since(now)) - }(time.Now()) - // TODO Make sure texts are HTML encoded + // Add channel prefix + m.Channel = s.ChannelPrefix + m.Channel + // Encode message var b []byte if b, err = json.Marshal(m); err != nil { @@ -61,7 +59,7 @@ func (s *Slack) Slack(m Message) (err error) { } // Send request - req, resp, err := s.SendWithMaxRetries(s.IncomingWebhookURL, "", http.MethodPost, b, RetryMax, RetrySleep) + req, resp, err := s.SendWithMaxRetries(s.IncomingWebhookURL, "", http.MethodPost, b) if err != nil { return } diff --git a/slack.go b/slack.go index 444a400..02b5d11 100644 --- a/slack.go +++ b/slack.go @@ -3,8 +3,6 @@ package slack import ( "net/http" "time" - - "github.com/rs/xlog" ) // Constants @@ -12,25 +10,27 @@ const ( ColorDanger = "danger" ColorGood = "good" ColorWarning = "warning" - RetryMax = 5 - RetrySleep = time.Minute ) // Slack represents a Slack communicator type Slack struct { + ChannelPrefix string HTTPClient *http.Client IncomingWebhookURL string - Logger xlog.Logger + RetryMax int + RetrySleep time.Duration } // New creates a new Slack communicator func New(c Configuration) *Slack { o := &Slack{ + ChannelPrefix: c.ChannelPrefix, HTTPClient: &http.Client{ Timeout: c.RequestTimeout, }, IncomingWebhookURL: c.IncomingWebhookURL, - Logger: xlog.NopLogger, + RetryMax: c.RetryMax, + RetrySleep: c.RetrySleep, } if c.RequestTimeout == 0 { o.HTTPClient.Timeout = time.Duration(10) * time.Second