github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/slack/slack.go (about) 1 package slack 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 8 "github.com/caarlos0/env/v9" 9 "github.com/caarlos0/log" 10 "github.com/goreleaser/goreleaser/internal/tmpl" 11 "github.com/goreleaser/goreleaser/pkg/context" 12 "github.com/slack-go/slack" 13 ) 14 15 const ( 16 defaultUsername = `GoReleaser` 17 defaultMessageTemplate = `{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}` 18 ) 19 20 type Pipe struct{} 21 22 func (Pipe) String() string { return "slack" } 23 func (Pipe) Skip(ctx *context.Context) bool { return !ctx.Config.Announce.Slack.Enabled } 24 25 type Config struct { 26 Webhook string `env:"SLACK_WEBHOOK,notEmpty"` 27 } 28 29 func (Pipe) Default(ctx *context.Context) error { 30 if ctx.Config.Announce.Slack.MessageTemplate == "" { 31 ctx.Config.Announce.Slack.MessageTemplate = defaultMessageTemplate 32 } 33 if ctx.Config.Announce.Slack.Username == "" { 34 ctx.Config.Announce.Slack.Username = defaultUsername 35 } 36 return nil 37 } 38 39 func (Pipe) Announce(ctx *context.Context) error { 40 msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Slack.MessageTemplate) 41 if err != nil { 42 return fmt.Errorf("slack: %w", err) 43 } 44 45 var cfg Config 46 if err := env.Parse(&cfg); err != nil { 47 return fmt.Errorf("slack: %w", err) 48 } 49 50 log.Infof("posting: '%s'", msg) 51 52 // optional processing of advanced formatting options 53 blocks, attachments, err := parseAdvancedFormatting(ctx) 54 if err != nil { 55 return err 56 } 57 58 wm := &slack.WebhookMessage{ 59 Username: ctx.Config.Announce.Slack.Username, 60 IconEmoji: ctx.Config.Announce.Slack.IconEmoji, 61 IconURL: ctx.Config.Announce.Slack.IconURL, 62 Channel: ctx.Config.Announce.Slack.Channel, 63 Text: msg, 64 65 // optional enrichments 66 Blocks: blocks, 67 Attachments: attachments, 68 } 69 70 err = slack.PostWebhook(cfg.Webhook, wm) 71 if err != nil { 72 return fmt.Errorf("slack: %w", err) 73 } 74 75 return nil 76 } 77 78 func parseAdvancedFormatting(ctx *context.Context) (*slack.Blocks, []slack.Attachment, error) { 79 var blocks *slack.Blocks 80 if in := ctx.Config.Announce.Slack.Blocks; len(in) > 0 { 81 blocks = &slack.Blocks{BlockSet: make([]slack.Block, 0, len(in))} 82 83 if err := unmarshal(ctx, in, blocks); err != nil { 84 return nil, nil, fmt.Errorf("slack blocks: %w", err) 85 } 86 } 87 88 var attachments []slack.Attachment 89 if in := ctx.Config.Announce.Slack.Attachments; len(in) > 0 { 90 attachments = make([]slack.Attachment, 0, len(in)) 91 92 if err := unmarshal(ctx, in, &attachments); err != nil { 93 return nil, nil, fmt.Errorf("slack attachments: %w", err) 94 } 95 } 96 97 return blocks, attachments, nil 98 } 99 100 func unmarshal(ctx *context.Context, in interface{}, target interface{}) error { 101 jazon, err := json.Marshal(in) 102 if err != nil { 103 return fmt.Errorf("failed to marshal input as JSON: %w", err) 104 } 105 106 body := string(jazon) 107 // ensure that double quotes that are inside the string get un-escaped so they can be interpreted for templates 108 body = strings.ReplaceAll(body, "\\\"", "\"") 109 110 tplApplied, err := tmpl.New(ctx).Apply(body) 111 if err != nil { 112 return fmt.Errorf("failed to evaluate template: %w", err) 113 } 114 115 if err = json.Unmarshal([]byte(tplApplied), target); err != nil { 116 return fmt.Errorf("failed to unmarshal into target: %w", err) 117 } 118 119 return nil 120 }