github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/webhook/webhook.go (about) 1 package webhook 2 3 import ( 4 "crypto/tls" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 "net/url" 10 "strings" 11 12 "github.com/caarlos0/env/v9" 13 "github.com/caarlos0/log" 14 "github.com/goreleaser/goreleaser/internal/tmpl" 15 "github.com/goreleaser/goreleaser/pkg/context" 16 ) 17 18 const ( 19 defaultMessageTemplate = `{ "message": "{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}"}` 20 ContentTypeHeaderKey = "Content-Type" 21 UserAgentHeaderKey = "User-Agent" 22 UserAgentHeaderValue = "gorleaser" 23 AuthorizationHeaderKey = "Authorization" 24 DefaultContentType = "application/json; charset=utf-8" 25 ) 26 27 type Pipe struct{} 28 29 func (Pipe) String() string { return "webhook" } 30 func (Pipe) Skip(ctx *context.Context) bool { return !ctx.Config.Announce.Webhook.Enabled } 31 32 type Config struct { 33 BasicAuthHeader string `env:"BASIC_AUTH_HEADER_VALUE"` 34 BearerTokenHeader string `env:"BEARER_TOKEN_HEADER_VALUE"` 35 } 36 37 func (p Pipe) Default(ctx *context.Context) error { 38 if ctx.Config.Announce.Webhook.MessageTemplate == "" { 39 ctx.Config.Announce.Webhook.MessageTemplate = defaultMessageTemplate 40 } 41 if ctx.Config.Announce.Webhook.ContentType == "" { 42 ctx.Config.Announce.Webhook.ContentType = DefaultContentType 43 } 44 return nil 45 } 46 47 func (p Pipe) Announce(ctx *context.Context) error { 48 var cfg Config 49 if err := env.Parse(&cfg); err != nil { 50 return fmt.Errorf("webhook: %w", err) 51 } 52 53 endpointURLConfig, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Webhook.EndpointURL) 54 if err != nil { 55 return fmt.Errorf("webhook: %w", err) 56 } 57 if len(endpointURLConfig) == 0 { 58 return errors.New("webhook: no endpoint url") 59 } 60 61 if _, err := url.ParseRequestURI(endpointURLConfig); err != nil { 62 return fmt.Errorf("webhook: %w", err) 63 } 64 endpointURL, err := url.Parse(endpointURLConfig) 65 if err != nil { 66 return fmt.Errorf("webhook: %w", err) 67 } 68 69 msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Webhook.MessageTemplate) 70 if err != nil { 71 return fmt.Errorf("webhook: %w", err) 72 } 73 74 log.Infof("posting: '%s'", msg) 75 customTransport := http.DefaultTransport.(*http.Transport).Clone() 76 77 customTransport.TLSClientConfig = &tls.Config{ 78 InsecureSkipVerify: ctx.Config.Announce.Webhook.SkipTLSVerify, 79 } 80 81 client := &http.Client{ 82 Transport: customTransport, 83 } 84 85 req, err := http.NewRequest(http.MethodPost, endpointURL.String(), strings.NewReader(msg)) 86 if err != nil { 87 return fmt.Errorf("webhook: %w", err) 88 } 89 req.Header.Add(ContentTypeHeaderKey, ctx.Config.Announce.Webhook.ContentType) 90 req.Header.Add(UserAgentHeaderKey, UserAgentHeaderValue) 91 92 if cfg.BasicAuthHeader != "" { 93 log.Debugf("set basic auth header") 94 req.Header.Add(AuthorizationHeaderKey, cfg.BasicAuthHeader) 95 } else if cfg.BearerTokenHeader != "" { 96 log.Debugf("set bearer token header") 97 req.Header.Add(AuthorizationHeaderKey, cfg.BearerTokenHeader) 98 } 99 100 for key, value := range ctx.Config.Announce.Webhook.Headers { 101 log.Debugf("Header Key %s / Value %s", key, value) 102 req.Header.Add(key, value) 103 } 104 resp, err := client.Do(req) 105 if err != nil { 106 return fmt.Errorf("webhook: %w", err) 107 } 108 defer resp.Body.Close() 109 110 switch resp.StatusCode { 111 case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent: 112 log.Infof("Post OK: '%v'", resp.StatusCode) 113 body, _ := io.ReadAll(resp.Body) 114 log.Infof("Response : %v\n", string(body)) 115 return nil 116 default: 117 return fmt.Errorf("request failed with status %v", resp.Status) 118 } 119 }