1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-11 14:39:28 +02:00

feat: announce: mattermost (#2536)

Signed-off-by: Carlos Panato <ctadeu@gmail.com>
This commit is contained in:
Carlos Tadeu Panato Junior 2021-09-29 01:18:35 +02:00 committed by GitHub
parent d2a63c1093
commit 519a4f67c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 312 additions and 6 deletions

View File

@ -8,6 +8,7 @@ import (
"github.com/goreleaser/goreleaser/internal/middleware/logging"
"github.com/goreleaser/goreleaser/internal/middleware/skip"
"github.com/goreleaser/goreleaser/internal/pipe/discord"
"github.com/goreleaser/goreleaser/internal/pipe/mattermost"
"github.com/goreleaser/goreleaser/internal/pipe/reddit"
"github.com/goreleaser/goreleaser/internal/pipe/slack"
"github.com/goreleaser/goreleaser/internal/pipe/smtp"
@ -27,6 +28,7 @@ type Announcer interface {
var announcers = []Announcer{
// XXX: keep asc sorting
discord.Pipe{},
mattermost.Pipe{},
reddit.Pipe{},
slack.Pipe{},
smtp.Pipe{},

View File

@ -0,0 +1,147 @@
package mattermost
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/apex/log"
"github.com/caarlos0/env/v6"
"github.com/goreleaser/goreleaser/internal/tmpl"
"github.com/goreleaser/goreleaser/pkg/context"
)
const (
defaultColor = "#2D313E"
defaultUsername = `GoReleaser`
defaultMessageTemplate = `{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .GitURL }}/releases/tag/{{ .Tag }}`
defaultMessageTitle = `{{ .ProjectName }} {{ .Tag }} is out!`
)
type Pipe struct{}
func (Pipe) String() string { return "mattermost" }
func (Pipe) Skip(ctx *context.Context) bool { return !ctx.Config.Announce.Mattermost.Enabled }
type Config struct {
Webhook string `env:"MATTERMOST_WEBHOOK,notEmpty"`
}
func (Pipe) Default(ctx *context.Context) error {
if ctx.Config.Announce.Mattermost.MessageTemplate == "" {
ctx.Config.Announce.Mattermost.MessageTemplate = defaultMessageTemplate
}
if ctx.Config.Announce.Mattermost.TitleTemplate == "" {
ctx.Config.Announce.Mattermost.TitleTemplate = defaultMessageTitle
}
if ctx.Config.Announce.Mattermost.Username == "" {
ctx.Config.Announce.Mattermost.Username = defaultUsername
}
if ctx.Config.Announce.Teams.Color == "" {
ctx.Config.Announce.Teams.Color = defaultColor
}
return nil
}
func (Pipe) Announce(ctx *context.Context) error {
msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mattermost.MessageTemplate)
if err != nil {
return fmt.Errorf("announce: failed to announce to mattermost: %w", err)
}
title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mattermost.TitleTemplate)
if err != nil {
return fmt.Errorf("announce: failed to announce to teams: %w", err)
}
var cfg Config
if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to mattermost: %w", err)
}
log.Infof("posting: %q", msg)
wm := &incomingWebhookRequest{
Username: ctx.Config.Announce.Mattermost.Username,
IconEmoji: ctx.Config.Announce.Mattermost.IconEmoji,
IconURL: ctx.Config.Announce.Mattermost.IconURL,
ChannelName: ctx.Config.Announce.Mattermost.Channel,
Attachments: []*mattermostAttachment{
{
Title: title,
Text: msg,
Color: ctx.Config.Announce.Teams.Color,
},
},
}
err = postWebhook(ctx, cfg.Webhook, wm)
if err != nil {
return fmt.Errorf("announce: failed to announce to mattermost: %w", err)
}
return nil
}
func postWebhook(ctx *context.Context, url string, msg *incomingWebhookRequest) error {
payload, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal the message: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
if err != nil {
return fmt.Errorf("failed new request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
r, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("announce: failed to announce to mattermost: %w", err)
}
closeBody(r)
return nil
}
func closeBody(r *http.Response) {
if r.Body != nil {
_, _ = io.Copy(ioutil.Discard, r.Body)
_ = r.Body.Close()
}
}
type incomingWebhookRequest struct {
Text string `json:"text"`
Username string `json:"username"`
IconURL string `json:"icon_url"`
ChannelName string `json:"channel"`
Attachments []*mattermostAttachment `json:"attachments"`
IconEmoji string `json:"icon_emoji"`
}
type mattermostAttachment 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"`
Fields []*mattermostAttachmentField `json:"fields"`
Footer string `json:"footer"`
FooterIcon string `json:"footer_icon"`
}
type mattermostAttachmentField struct {
Title string `json:"title"`
Value interface{} `json:"value"`
Short bool `json:"short"`
}

View File

@ -0,0 +1,99 @@
package mattermost
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
)
func TestStringer(t *testing.T) {
require.Equal(t, Pipe{}.String(), "mattermost")
}
func TestDefault(t *testing.T) {
ctx := context.New(config.Project{})
require.NoError(t, Pipe{}.Default(ctx))
require.Equal(t, ctx.Config.Announce.Mattermost.MessageTemplate, defaultMessageTemplate)
}
func TestAnnounceInvalidTemplate(t *testing.T) {
ctx := context.New(config.Project{
Announce: config.Announce{
Mattermost: config.Mattermost{
MessageTemplate: "{{ .Foo }",
},
},
})
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to mattermost: template: tmpl:1: unexpected "}" in operand`)
}
func TestAnnounceMissingEnv(t *testing.T) {
ctx := context.New(config.Project{
Announce: config.Announce{
Mattermost: config.Mattermost{},
},
})
require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to mattermost: env: environment variable "MATTERMOST_WEBHOOK" should not be empty`)
}
func TestSkip(t *testing.T) {
t.Run("skip", func(t *testing.T) {
require.True(t, Pipe{}.Skip(context.New(config.Project{})))
})
t.Run("dont skip", func(t *testing.T) {
ctx := context.New(config.Project{
Announce: config.Announce{
Mattermost: config.Mattermost{
Enabled: true,
},
},
})
require.False(t, Pipe{}.Skip(ctx))
})
}
func TestPostWebhook(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rc := &incomingWebhookRequest{}
body, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(body, rc)
require.NoError(t, err)
require.Equal(t, defaultColor, rc.Attachments[0].Color)
require.Equal(t, "Honk v1.0.0 is out!", rc.Attachments[0].Title)
require.Equal(t, "Honk v1.0.0 is out! Check it out at https://github.com/honk/honk/releases/tag/v1.0.0", rc.Attachments[0].Text)
w.WriteHeader(200)
_, err = w.Write([]byte{})
require.NoError(t, err)
}))
defer ts.Close()
ctx := context.New(config.Project{
ProjectName: "Honk",
Announce: config.Announce{
Mattermost: config.Mattermost{
Enabled: true,
},
},
})
ctx.Git.CurrentTag = "v1.0.0"
ctx.Git.URL = "https://github.com/honk/honk"
os.Setenv("MATTERMOST_WEBHOOK", ts.URL)
defer os.Unsetenv("MATTERMOST_WEBHOOK")
require.NoError(t, Pipe{}.Default(ctx))
require.NoError(t, Pipe{}.Announce(ctx))
}

View File

@ -707,12 +707,13 @@ type GoMod struct {
}
type Announce struct {
Twitter Twitter `yaml:"twitter,omitempty"`
Reddit Reddit `yaml:"reddit,omitempty"`
Slack Slack `yaml:"slack,omitempty"`
Discord Discord `yaml:"discord,omitempty"`
Teams Teams `yaml:"teams,omitempty"`
SMTP SMTP `yaml:"smtp,omitempty"`
Twitter Twitter `yaml:"twitter,omitempty"`
Reddit Reddit `yaml:"reddit,omitempty"`
Slack Slack `yaml:"slack,omitempty"`
Discord Discord `yaml:"discord,omitempty"`
Teams Teams `yaml:"teams,omitempty"`
SMTP SMTP `yaml:"smtp,omitempty"`
Mattermost Mattermost `yaml:"mattermost,omitempty"`
}
type Twitter struct {
@ -754,6 +755,17 @@ type Teams struct {
IconURL string `yaml:"icon_url,omitempty"`
}
type Mattermost struct {
Enabled bool `yaml:"enabled,omitempty"`
MessageTemplate string `yaml:"message_template,omitempty"`
TitleTemplate string `yaml:"title_template,omitempty"`
Color string `yaml:"color,omitempty"`
Channel string `yaml:"channel,omitempty"`
Username string `yaml:"username,omitempty"`
IconEmoji string `yaml:"icon_emoji,omitempty"`
IconURL string `yaml:"icon_url,omitempty"`
}
type SMTP struct {
Enabled bool `yaml:"enabled,omitempty"`
Host string `yaml:"host,omitempty"`

View File

@ -15,6 +15,7 @@ import (
"github.com/goreleaser/goreleaser/internal/pipe/docker"
"github.com/goreleaser/goreleaser/internal/pipe/gofish"
"github.com/goreleaser/goreleaser/internal/pipe/gomod"
"github.com/goreleaser/goreleaser/internal/pipe/mattermost"
"github.com/goreleaser/goreleaser/internal/pipe/milestone"
"github.com/goreleaser/goreleaser/internal/pipe/nfpm"
"github.com/goreleaser/goreleaser/internal/pipe/project"
@ -69,5 +70,6 @@ var Defaulters = []Defaulter{
teams.Pipe{},
twitter.Pipe{},
smtp.Pipe{},
mattermost.Pipe{},
milestone.Pipe{},
}

View File

@ -0,0 +1,44 @@
# Mattermost
For it to work, you'll need to [create a new Incoming Webhook](https://docs.mattermost.com/developer/webhooks-incoming.html) in your own Mattermost deployment, and set some
environment variables on your pipeline:
- `MATTERMOST_WEBHOOK`
Then, you can add something like the following to your `.goreleaser.yml` config:
```yaml
# .goreleaser.yml
announce:
mattermost:
# Whether its enabled or not.
# Defaults to false.
enabled: true
# Title template to use while publishing.
# Defaults to `{{ .ProjectName }} {{ .Tag }} is out!`
title_template: 'GoReleaser {{ .Tag }} was just released!'
# Message template to use while publishing.
# Defaults to `{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .GitURL }}/releases/tag/{{ .Tag }}`
message_template: 'Awesome project {{.Tag}} is out!'
# Color code of the message. You have to use hexadecimal.
# Defaults to `#2D313E` - the grey-ish from goreleaser
color: ''
# The name of the channel that the user selected as a destination for webhook messages.
channel: '#channel'
# Set your Webhook's user name.
username: ''
# Emoji to use as the icon for this message. Overrides icon_url.
icon_emoji: ''
# URL to an image to use as the icon for this message.
icon_url: ''
```
!!! tip
Learn more about the [name template engine](/customization/templates/).