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  }