github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/mattermost/mattermost.go (about) 1 package mattermost 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 10 "github.com/caarlos0/env/v9" 11 "github.com/caarlos0/log" 12 13 "github.com/goreleaser/goreleaser/internal/tmpl" 14 "github.com/goreleaser/goreleaser/pkg/context" 15 ) 16 17 const ( 18 defaultColor = "#2D313E" 19 defaultUsername = `GoReleaser` 20 defaultMessageTemplate = `{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}` 21 defaultMessageTitle = `{{ .ProjectName }} {{ .Tag }} is out!` 22 ) 23 24 type Pipe struct{} 25 26 func (Pipe) String() string { return "mattermost" } 27 func (Pipe) Skip(ctx *context.Context) bool { return !ctx.Config.Announce.Mattermost.Enabled } 28 29 type Config struct { 30 Webhook string `env:"MATTERMOST_WEBHOOK,notEmpty"` 31 } 32 33 func (Pipe) Default(ctx *context.Context) error { 34 if ctx.Config.Announce.Mattermost.MessageTemplate == "" { 35 ctx.Config.Announce.Mattermost.MessageTemplate = defaultMessageTemplate 36 } 37 38 if ctx.Config.Announce.Mattermost.TitleTemplate == "" { 39 ctx.Config.Announce.Mattermost.TitleTemplate = defaultMessageTitle 40 } 41 if ctx.Config.Announce.Mattermost.Username == "" { 42 ctx.Config.Announce.Mattermost.Username = defaultUsername 43 } 44 if ctx.Config.Announce.Teams.Color == "" { 45 ctx.Config.Announce.Teams.Color = defaultColor 46 } 47 48 return nil 49 } 50 51 func (Pipe) Announce(ctx *context.Context) error { 52 msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mattermost.MessageTemplate) 53 if err != nil { 54 return fmt.Errorf("mattermost: %w", err) 55 } 56 57 title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mattermost.TitleTemplate) 58 if err != nil { 59 return fmt.Errorf("teams: %w", err) 60 } 61 62 var cfg Config 63 if err := env.Parse(&cfg); err != nil { 64 return fmt.Errorf("mattermost: %w", err) 65 } 66 67 log.Infof("posting: %q", msg) 68 69 wm := &incomingWebhookRequest{ 70 Username: ctx.Config.Announce.Mattermost.Username, 71 IconEmoji: ctx.Config.Announce.Mattermost.IconEmoji, 72 IconURL: ctx.Config.Announce.Mattermost.IconURL, 73 ChannelName: ctx.Config.Announce.Mattermost.Channel, 74 Attachments: []*mattermostAttachment{ 75 { 76 Title: title, 77 Text: msg, 78 Color: ctx.Config.Announce.Teams.Color, 79 }, 80 }, 81 } 82 83 err = postWebhook(ctx, cfg.Webhook, wm) 84 if err != nil { 85 return fmt.Errorf("mattermost: %w", err) 86 } 87 88 return nil 89 } 90 91 func postWebhook(ctx *context.Context, url string, msg *incomingWebhookRequest) error { 92 payload, err := json.Marshal(msg) 93 if err != nil { 94 return fmt.Errorf("failed to marshal the message: %w", err) 95 } 96 97 req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload)) 98 if err != nil { 99 return fmt.Errorf("failed new request: %w", err) 100 } 101 req.Header.Set("Content-Type", "application/json") 102 103 r, err := http.DefaultClient.Do(req) 104 if err != nil { 105 return fmt.Errorf("mattermost: %w", err) 106 } 107 closeBody(r) 108 109 return nil 110 } 111 112 func closeBody(r *http.Response) { 113 if r.Body != nil { 114 _, _ = io.Copy(io.Discard, r.Body) 115 _ = r.Body.Close() 116 } 117 } 118 119 type incomingWebhookRequest struct { 120 Text string `json:"text"` 121 Username string `json:"username"` 122 IconURL string `json:"icon_url"` 123 ChannelName string `json:"channel"` 124 Attachments []*mattermostAttachment `json:"attachments"` 125 IconEmoji string `json:"icon_emoji"` 126 } 127 128 type mattermostAttachment struct { 129 Fallback string `json:"fallback"` 130 Color string `json:"color"` 131 Pretext string `json:"pretext"` 132 AuthorName string `json:"author_name"` 133 AuthorLink string `json:"author_link"` 134 AuthorIcon string `json:"author_icon"` 135 Title string `json:"title"` 136 TitleLink string `json:"title_link"` 137 Text string `json:"text"` 138 Fields []*mattermostAttachmentField `json:"fields"` 139 Footer string `json:"footer"` 140 FooterIcon string `json:"footer_icon"` 141 } 142 143 type mattermostAttachmentField struct { 144 Title string `json:"title"` 145 Value interface{} `json:"value"` 146 Short bool `json:"short"` 147 }