1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-10-30 23:58:09 +02:00

fix: only fail announcing phase in the end (#3666)

This prevents one announce failure to skip all other announcers.

The release will still report a failure in the end, but will not fail in
the first failure.

Also improved errors messages a little bit.

closes #3663

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
Carlos Alexandro Becker
2022-12-28 12:24:21 -03:00
committed by GitHub
parent 1d2842c419
commit 47ce9a9b33
27 changed files with 158 additions and 88 deletions

2
go.mod
View File

@@ -22,6 +22,7 @@ require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/goreleaser/fileglob v1.3.0 github.com/goreleaser/fileglob v1.3.0
github.com/goreleaser/nfpm/v2 v2.22.2 github.com/goreleaser/nfpm/v2 v2.22.2
github.com/hashicorp/go-multierror v1.1.1
github.com/imdario/mergo v0.3.13 github.com/imdario/mergo v0.3.13
github.com/invopop/jsonschema v0.7.0 github.com/invopop/jsonschema v0.7.0
github.com/jarcoal/httpmock v1.2.0 github.com/jarcoal/httpmock v1.2.0
@@ -132,6 +133,7 @@ require (
github.com/googleapis/gax-go/v2 v2.4.0 // indirect github.com/googleapis/gax-go/v2 v2.4.0 // indirect
github.com/goreleaser/chglog v0.2.2 // indirect github.com/goreleaser/chglog v0.2.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/go-version v1.2.1 // indirect github.com/hashicorp/go-version v1.2.1 // indirect

2
go.sum
View File

@@ -935,6 +935,7 @@ github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOj
github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@@ -950,6 +951,7 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=

View File

@@ -5,6 +5,7 @@ import (
"github.com/goreleaser/goreleaser/internal/middleware" "github.com/goreleaser/goreleaser/internal/middleware"
"github.com/goreleaser/goreleaser/internal/pipe" "github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/pkg/context" "github.com/goreleaser/goreleaser/pkg/context"
"github.com/hashicorp/go-multierror"
) )
// Handle handles an action error, ignoring and logging pipe skipped // Handle handles an action error, ignoring and logging pipe skipped
@@ -22,3 +23,31 @@ func Handle(action middleware.Action) middleware.Action {
return err return err
} }
} }
// Memo is a handler that memorizes errors, so you can grab them all in the end
// instead of returning each of them.
type Memo struct {
err error
}
// Error returns the underlying error.
func (m *Memo) Error() error {
return m.err
}
// Wrap the given action, memorizing its errors.
// The resulting action will always return a nil error.
func (m *Memo) Wrap(action middleware.Action) middleware.Action {
return func(ctx *context.Context) error {
err := action(ctx)
if err == nil {
return nil
}
if pipe.IsSkip(err) {
log.WithField("reason", err.Error()).Warn("pipe skipped")
return nil
}
m.err = multierror.Append(m.err, err)
return nil
}
}

View File

@@ -1,11 +1,13 @@
package errhandler package errhandler
import ( import (
"errors"
"fmt" "fmt"
"testing" "testing"
"github.com/goreleaser/goreleaser/internal/pipe" "github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/pkg/context" "github.com/goreleaser/goreleaser/pkg/context"
"github.com/hashicorp/go-multierror"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -28,3 +30,29 @@ func TestError(t *testing.T) {
})(nil)) })(nil))
}) })
} }
func TestErrorMemo(t *testing.T) {
memo := Memo{}
t.Run("no errors", func(t *testing.T) {
require.NoError(t, memo.Wrap(func(ctx *context.Context) error {
return nil
})(nil))
})
t.Run("pipe skipped", func(t *testing.T) {
require.NoError(t, memo.Wrap(func(ctx *context.Context) error {
return pipe.ErrSkipValidateEnabled
})(nil))
})
t.Run("some err", func(t *testing.T) {
require.NoError(t, memo.Wrap(func(ctx *context.Context) error {
return fmt.Errorf("pipe errored")
})(nil))
})
err := memo.Error()
merr := &multierror.Error{}
require.True(t, errors.As(err, &merr), "must be a multierror")
require.Len(t, merr.Errors, 1)
}

View File

@@ -68,16 +68,15 @@ func (Pipe) Skip(ctx *context.Context) bool {
// Run the pipe. // Run the pipe.
func (Pipe) Run(ctx *context.Context) error { func (Pipe) Run(ctx *context.Context) error {
memo := errhandler.Memo{}
for _, announcer := range announcers { for _, announcer := range announcers {
if err := skip.Maybe( _ = skip.Maybe(
announcer, announcer,
logging.PadLog( logging.PadLog(announcer.String(), memo.Wrap(announcer.Announce)),
announcer.String(), )(ctx)
errhandler.Handle(announcer.Announce), }
), if memo.Error() != nil {
)(ctx); err != nil { return fmt.Errorf("failed to announce release: %w", memo.Error())
return fmt.Errorf("%s: failed to announce release: %w", announcer.String(), err)
}
} }
return nil return nil
} }

View File

@@ -1,10 +1,12 @@
package announce package announce
import ( import (
"errors"
"testing" "testing"
"github.com/goreleaser/goreleaser/pkg/config" "github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context" "github.com/goreleaser/goreleaser/pkg/context"
"github.com/hashicorp/go-multierror"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -18,9 +20,17 @@ func TestAnnounce(t *testing.T) {
Twitter: config.Twitter{ Twitter: config.Twitter{
Enabled: true, Enabled: true,
}, },
Mastodon: config.Mastodon{
Enabled: true,
Server: "https://localhost:1234/",
},
}, },
}) })
require.Error(t, Pipe{}.Run(ctx)) err := Pipe{}.Run(ctx)
require.Error(t, err)
merr := &multierror.Error{}
require.True(t, errors.As(err, &merr), "must be a multierror")
require.Len(t, merr.Errors, 2)
} }
func TestAnnounceAllDisabled(t *testing.T) { func TestAnnounceAllDisabled(t *testing.T) {

View File

@@ -49,24 +49,24 @@ func (p Pipe) Default(ctx *context.Context) error {
func (p Pipe) Announce(ctx *context.Context) error { func (p Pipe) Announce(ctx *context.Context) error {
msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Discord.MessageTemplate) msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Discord.MessageTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to discord: %w", err) return fmt.Errorf("discord: %w", err)
} }
var cfg Config var cfg Config
if err = env.Parse(&cfg); err != nil { if err = env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to discord: %w", err) return fmt.Errorf("discord: %w", err)
} }
log.Infof("posting: '%s'", msg) log.Infof("posting: '%s'", msg)
webhookID, err := snowflake.Parse(cfg.WebhookID) webhookID, err := snowflake.Parse(cfg.WebhookID)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to discord: %w", err) return fmt.Errorf("discord: %w", err)
} }
color, err := strconv.Atoi(ctx.Config.Announce.Discord.Color) color, err := strconv.Atoi(ctx.Config.Announce.Discord.Color)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to discord: %w", err) return fmt.Errorf("discord: %w", err)
} }
if _, err = webhook.New(webhookID, cfg.WebhookToken).CreateMessage(discord.WebhookMessageCreate{ if _, err = webhook.New(webhookID, cfg.WebhookToken).CreateMessage(discord.WebhookMessageCreate{
Embeds: []discord.Embed{ Embeds: []discord.Embed{
@@ -80,7 +80,7 @@ func (p Pipe) Announce(ctx *context.Context) error {
}, },
}, },
}); err != nil { }); err != nil {
return fmt.Errorf("announce: failed to announce to discord: %w", err) return fmt.Errorf("discord: %w", err)
} }
return nil return nil
} }

View File

@@ -26,7 +26,7 @@ func TestAnnounceInvalidTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to discord: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `discord: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceMissingEnv(t *testing.T) { func TestAnnounceMissingEnv(t *testing.T) {
@@ -36,7 +36,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
}, },
}) })
require.NoError(t, Pipe{}.Default(ctx)) require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to discord: env: environment variable "DISCORD_WEBHOOK_ID" should not be empty; environment variable "DISCORD_WEBHOOK_TOKEN" should not be empty`) require.EqualError(t, Pipe{}.Announce(ctx), `discord: env: environment variable "DISCORD_WEBHOOK_ID" should not be empty; environment variable "DISCORD_WEBHOOK_TOKEN" should not be empty`)
} }
func TestSkip(t *testing.T) { func TestSkip(t *testing.T) {

View File

@@ -31,12 +31,12 @@ func (Pipe) Default(ctx *context.Context) error {
func (Pipe) Announce(ctx *context.Context) error { func (Pipe) Announce(ctx *context.Context) error {
message, err := tmpl.New(ctx).Apply(ctx.Config.Announce.LinkedIn.MessageTemplate) message, err := tmpl.New(ctx).Apply(ctx.Config.Announce.LinkedIn.MessageTemplate)
if err != nil { if err != nil {
return fmt.Errorf("failed to announce to linkedin: %w", err) return fmt.Errorf("linkedin: %w", err)
} }
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("failed to announce to linkedin: %w", err) return fmt.Errorf("linkedin: %w", err)
} }
c, err := createLinkedInClient(oauthClientConfig{ c, err := createLinkedInClient(oauthClientConfig{
@@ -44,12 +44,12 @@ func (Pipe) Announce(ctx *context.Context) error {
AccessToken: cfg.AccessToken, AccessToken: cfg.AccessToken,
}) })
if err != nil { if err != nil {
return fmt.Errorf("failed to announce to linkedin: %w", err) return fmt.Errorf("linkedin: %w", err)
} }
url, err := c.Share(message) url, err := c.Share(message)
if err != nil { if err != nil {
return fmt.Errorf("failed to announce to linkedin: %w", err) return fmt.Errorf("linkedin: %w", err)
} }
log.Infof("The text post is available at: %s\n", url) log.Infof("The text post is available at: %s\n", url)

View File

@@ -21,7 +21,7 @@ func TestDefault(t *testing.T) {
func TestAnnounceDisabled(t *testing.T) { func TestAnnounceDisabled(t *testing.T) {
ctx := context.New(config.Project{}) ctx := context.New(config.Project{})
require.NoError(t, Pipe{}.Default(ctx)) require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `failed to announce to linkedin: env: environment variable "LINKEDIN_ACCESS_TOKEN" should not be empty`) require.EqualError(t, Pipe{}.Announce(ctx), `linkedin: env: environment variable "LINKEDIN_ACCESS_TOKEN" should not be empty`)
} }
func TestAnnounceInvalidTemplate(t *testing.T) { func TestAnnounceInvalidTemplate(t *testing.T) {
@@ -33,7 +33,7 @@ func TestAnnounceInvalidTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `failed to announce to linkedin: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `linkedin: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceMissingEnv(t *testing.T) { func TestAnnounceMissingEnv(t *testing.T) {
@@ -45,7 +45,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
}, },
}) })
require.NoError(t, Pipe{}.Default(ctx)) require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `failed to announce to linkedin: env: environment variable "LINKEDIN_ACCESS_TOKEN" should not be empty`) require.EqualError(t, Pipe{}.Announce(ctx), `linkedin: env: environment variable "LINKEDIN_ACCESS_TOKEN" should not be empty`)
} }
func TestSkip(t *testing.T) { func TestSkip(t *testing.T) {

View File

@@ -36,12 +36,12 @@ func (Pipe) Default(ctx *context.Context) error {
func (Pipe) Announce(ctx *context.Context) error { func (Pipe) Announce(ctx *context.Context) error {
msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mastodon.MessageTemplate) msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mastodon.MessageTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to mastodon: %w", err) return fmt.Errorf("mastodon: %w", err)
} }
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to mastodon: %w", err) return fmt.Errorf("mastodon: %w", err)
} }
client := mastodon.NewClient(&mastodon.Config{ client := mastodon.NewClient(&mastodon.Config{
@@ -55,7 +55,7 @@ func (Pipe) Announce(ctx *context.Context) error {
if _, err := client.PostStatus(ctx, &mastodon.Toot{ if _, err := client.PostStatus(ctx, &mastodon.Toot{
Status: msg, Status: msg,
}); err != nil { }); err != nil {
return fmt.Errorf("announce: failed to announce to mastodon: %w", err) return fmt.Errorf("mastodon: %w", err)
} }
return nil return nil
} }

View File

@@ -26,7 +26,7 @@ func TestAnnounceInvalidTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to mastodon: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `mastodon: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceMissingEnv(t *testing.T) { func TestAnnounceMissingEnv(t *testing.T) {
@@ -36,7 +36,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
}, },
}) })
require.NoError(t, Pipe{}.Default(ctx)) require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to mastodon: env: environment variable "MASTODON_CLIENT_ID" should not be empty; environment variable "MASTODON_CLIENT_SECRET" should not be empty; environment variable "MASTODON_ACCESS_TOKEN" should not be empty`) require.EqualError(t, Pipe{}.Announce(ctx), `mastodon: env: environment variable "MASTODON_CLIENT_ID" should not be empty; environment variable "MASTODON_CLIENT_SECRET" should not be empty; environment variable "MASTODON_ACCESS_TOKEN" should not be empty`)
} }
func TestSkip(t *testing.T) { func TestSkip(t *testing.T) {

View File

@@ -51,17 +51,17 @@ func (Pipe) Default(ctx *context.Context) error {
func (Pipe) Announce(ctx *context.Context) error { func (Pipe) Announce(ctx *context.Context) error {
msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mattermost.MessageTemplate) msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mattermost.MessageTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to mattermost: %w", err) return fmt.Errorf("mattermost: %w", err)
} }
title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mattermost.TitleTemplate) title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Mattermost.TitleTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to teams: %w", err) return fmt.Errorf("teams: %w", err)
} }
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to mattermost: %w", err) return fmt.Errorf("mattermost: %w", err)
} }
log.Infof("posting: %q", msg) log.Infof("posting: %q", msg)
@@ -82,7 +82,7 @@ func (Pipe) Announce(ctx *context.Context) error {
err = postWebhook(ctx, cfg.Webhook, wm) err = postWebhook(ctx, cfg.Webhook, wm)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to mattermost: %w", err) return fmt.Errorf("mattermost: %w", err)
} }
return nil return nil
@@ -102,7 +102,7 @@ func postWebhook(ctx *context.Context, url string, msg *incomingWebhookRequest)
r, err := http.DefaultClient.Do(req) r, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to mattermost: %w", err) return fmt.Errorf("mattermost: %w", err)
} }
closeBody(r) closeBody(r)

View File

@@ -31,7 +31,7 @@ func TestAnnounceInvalidTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to mattermost: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `mattermost: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceMissingEnv(t *testing.T) { func TestAnnounceMissingEnv(t *testing.T) {
@@ -41,7 +41,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
}, },
}) })
require.NoError(t, Pipe{}.Default(ctx)) 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`) require.EqualError(t, Pipe{}.Announce(ctx), `mattermost: env: environment variable "MATTERMOST_WEBHOOK" should not be empty`)
} }
func TestSkip(t *testing.T) { func TestSkip(t *testing.T) {

View File

@@ -40,12 +40,12 @@ func (Pipe) Default(ctx *context.Context) error {
func (Pipe) Announce(ctx *context.Context) error { func (Pipe) Announce(ctx *context.Context) error {
title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Reddit.TitleTemplate) title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Reddit.TitleTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to reddit: %w", err) return fmt.Errorf("reddit: %w", err)
} }
url, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Reddit.URLTemplate) url, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Reddit.URLTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to reddit: %w", err) return fmt.Errorf("reddit: %w", err)
} }
linkRequest := reddit.SubmitLinkRequest{ linkRequest := reddit.SubmitLinkRequest{
@@ -56,21 +56,21 @@ func (Pipe) Announce(ctx *context.Context) error {
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to reddit: %w", err) return fmt.Errorf("reddit: %w", err)
} }
credentials := reddit.Credentials{ID: ctx.Config.Announce.Reddit.ApplicationID, Secret: cfg.Secret, Username: ctx.Config.Announce.Reddit.Username, Password: cfg.Password} credentials := reddit.Credentials{ID: ctx.Config.Announce.Reddit.ApplicationID, Secret: cfg.Secret, Username: ctx.Config.Announce.Reddit.Username, Password: cfg.Password}
client, err := reddit.NewClient(credentials) client, err := reddit.NewClient(credentials)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to reddit: %w", err) return fmt.Errorf("reddit: %w", err)
} }
post, _, err := client.Post.SubmitLink(ctx, linkRequest) post, _, err := client.Post.SubmitLink(ctx, linkRequest)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to reddit: %w", err) return fmt.Errorf("reddit: %w", err)
} }
log.Infof("announce: The text post is available at: %s\n", post.URL) log.Infof("The text post is available at: %s\n", post.URL)
return nil return nil
} }

View File

@@ -26,7 +26,7 @@ func TestAnnounceInvalidURLTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to reddit: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `reddit: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceInvalidTitleTemplate(t *testing.T) { func TestAnnounceInvalidTitleTemplate(t *testing.T) {
@@ -37,7 +37,7 @@ func TestAnnounceInvalidTitleTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to reddit: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `reddit: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceMissingEnv(t *testing.T) { func TestAnnounceMissingEnv(t *testing.T) {
@@ -47,7 +47,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
}, },
}) })
require.NoError(t, Pipe{}.Default(ctx)) require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to reddit: env: environment variable "REDDIT_SECRET" should not be empty; environment variable "REDDIT_PASSWORD" should not be empty`) require.EqualError(t, Pipe{}.Announce(ctx), `reddit: env: environment variable "REDDIT_SECRET" should not be empty; environment variable "REDDIT_PASSWORD" should not be empty`)
} }
func TestSkip(t *testing.T) { func TestSkip(t *testing.T) {

View File

@@ -38,12 +38,12 @@ func (Pipe) Default(ctx *context.Context) error {
func (Pipe) Announce(ctx *context.Context) error { func (Pipe) Announce(ctx *context.Context) error {
msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Slack.MessageTemplate) msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Slack.MessageTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to slack: %w", err) return fmt.Errorf("slack: %w", err)
} }
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to slack: %w", err) return fmt.Errorf("slack: %w", err)
} }
log.Infof("posting: '%s'", msg) log.Infof("posting: '%s'", msg)
@@ -68,7 +68,7 @@ func (Pipe) Announce(ctx *context.Context) error {
err = slack.PostWebhook(cfg.Webhook, wm) err = slack.PostWebhook(cfg.Webhook, wm)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to slack: %w", err) return fmt.Errorf("slack: %w", err)
} }
return nil return nil
@@ -80,7 +80,7 @@ func parseAdvancedFormatting(ctx *context.Context) (*slack.Blocks, []slack.Attac
blocks = &slack.Blocks{BlockSet: make([]slack.Block, 0, len(in))} blocks = &slack.Blocks{BlockSet: make([]slack.Block, 0, len(in))}
if err := unmarshal(ctx, in, blocks); err != nil { if err := unmarshal(ctx, in, blocks); err != nil {
return nil, nil, fmt.Errorf("announce: slack blocks: %w", err) return nil, nil, fmt.Errorf("slack blocks: %w", err)
} }
} }
@@ -89,7 +89,7 @@ func parseAdvancedFormatting(ctx *context.Context) (*slack.Blocks, []slack.Attac
attachments = make([]slack.Attachment, 0, len(in)) attachments = make([]slack.Attachment, 0, len(in))
if err := unmarshal(ctx, in, &attachments); err != nil { if err := unmarshal(ctx, in, &attachments); err != nil {
return nil, nil, fmt.Errorf("announce: slack attachments: %w", err) return nil, nil, fmt.Errorf("slack attachments: %w", err)
} }
} }
@@ -99,16 +99,16 @@ func parseAdvancedFormatting(ctx *context.Context) (*slack.Blocks, []slack.Attac
func unmarshal(ctx *context.Context, in interface{}, target interface{}) error { func unmarshal(ctx *context.Context, in interface{}, target interface{}) error {
jazon, err := json.Marshal(in) jazon, err := json.Marshal(in)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to marshal input as JSON: %w", err) return fmt.Errorf("failed to marshal input as JSON: %w", err)
} }
tplApplied, err := tmpl.New(ctx).Apply(string(jazon)) tplApplied, err := tmpl.New(ctx).Apply(string(jazon))
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to evaluate template: %w", err) return fmt.Errorf("failed to evaluate template: %w", err)
} }
if err = json.Unmarshal([]byte(tplApplied), target); err != nil { if err = json.Unmarshal([]byte(tplApplied), target); err != nil {
return fmt.Errorf("announce: failed to unmarshal into target: %w", err) return fmt.Errorf("failed to unmarshal into target: %w", err)
} }
return nil return nil

View File

@@ -30,7 +30,7 @@ func TestAnnounceInvalidTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to slack: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `slack: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceMissingEnv(t *testing.T) { func TestAnnounceMissingEnv(t *testing.T) {
@@ -40,7 +40,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
}, },
}) })
require.NoError(t, Pipe{}.Default(ctx)) require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to slack: env: environment variable "SLACK_WEBHOOK" should not be empty`) require.EqualError(t, Pipe{}.Announce(ctx), `slack: env: environment variable "SLACK_WEBHOOK" should not be empty`)
} }
func TestSkip(t *testing.T) { func TestSkip(t *testing.T) {

View File

@@ -43,12 +43,12 @@ func (Pipe) Default(ctx *context.Context) error {
func (Pipe) Announce(ctx *context.Context) error { func (Pipe) Announce(ctx *context.Context) error {
subject, err := tmpl.New(ctx).Apply(ctx.Config.Announce.SMTP.SubjectTemplate) subject, err := tmpl.New(ctx).Apply(ctx.Config.Announce.SMTP.SubjectTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to SMTP: %w", err) return fmt.Errorf("SMTP: %w", err)
} }
body, err := tmpl.New(ctx).Apply(ctx.Config.Announce.SMTP.BodyTemplate) body, err := tmpl.New(ctx).Apply(ctx.Config.Announce.SMTP.BodyTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to SMTP: %w", err) return fmt.Errorf("SMTP: %w", err)
} }
m := gomail.NewMessage() m := gomail.NewMessage()
@@ -68,7 +68,7 @@ func (Pipe) Announce(ctx *context.Context) error {
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to SMTP: %w", err) return fmt.Errorf("SMTP: %w", err)
} }
// Settings for SMTP server // Settings for SMTP server
@@ -80,10 +80,10 @@ func (Pipe) Announce(ctx *context.Context) error {
// Now send E-Mail // Now send E-Mail
if err := d.DialAndSend(m); err != nil { if err := d.DialAndSend(m); err != nil {
return fmt.Errorf("announce: failed to announce to SMTP: %w", err) return fmt.Errorf("SMTP: %w", err)
} }
log.Infof("announce: The mail has been send from %s to %s\n", ctx.Config.Announce.SMTP.From, receivers) log.Infof("The mail has been send from %s to %s\n", ctx.Config.Announce.SMTP.From, receivers)
return nil return nil
} }

View File

@@ -46,17 +46,17 @@ func (p Pipe) Default(ctx *context.Context) error {
func (p Pipe) Announce(ctx *context.Context) error { func (p Pipe) Announce(ctx *context.Context) error {
title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Teams.TitleTemplate) title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Teams.TitleTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to teams: %w", err) return fmt.Errorf("teams: %w", err)
} }
msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Teams.MessageTemplate) msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Teams.MessageTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to teams: %w", err) return fmt.Errorf("teams: %w", err)
} }
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to teams: %w", err) return fmt.Errorf("teams: %w", err)
} }
log.Infof("posting: '%s'", msg) log.Infof("posting: '%s'", msg)
@@ -73,11 +73,11 @@ func (p Pipe) Announce(ctx *context.Context) error {
messageCardSection.ActivityImage = ctx.Config.Announce.Teams.IconURL messageCardSection.ActivityImage = ctx.Config.Announce.Teams.IconURL
err = msgCard.AddSection(messageCardSection) err = msgCard.AddSection(messageCardSection)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to teams: %w", err) return fmt.Errorf("teams: %w", err)
} }
err = client.Send(cfg.Webhook, msgCard) err = client.Send(cfg.Webhook, msgCard)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to teams: %w", err) return fmt.Errorf("teams: %w", err)
} }
return nil return nil
} }

View File

@@ -27,7 +27,7 @@ func TestAnnounceInvalidTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to teams: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `teams: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceMissingEnv(t *testing.T) { func TestAnnounceMissingEnv(t *testing.T) {
@@ -39,7 +39,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
}, },
}) })
require.NoError(t, Pipe{}.Default(ctx)) require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to teams: env: environment variable "TEAMS_WEBHOOK" should not be empty`) require.EqualError(t, Pipe{}.Announce(ctx), `teams: env: environment variable "TEAMS_WEBHOOK" should not be empty`)
} }
func TestSkip(t *testing.T) { func TestSkip(t *testing.T) {

View File

@@ -31,25 +31,25 @@ func (Pipe) Default(ctx *context.Context) error {
func (Pipe) Announce(ctx *context.Context) error { func (Pipe) Announce(ctx *context.Context) error {
msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Telegram.MessageTemplate) msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Telegram.MessageTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to telegram: %w", err) return fmt.Errorf("telegram: %w", err)
} }
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to telegram: %w", err) return fmt.Errorf("telegram: %w", err)
} }
log.Infof("posting: '%s'", msg) log.Infof("posting: '%s'", msg)
bot, err := api.NewBotAPI(cfg.ConsumerToken) bot, err := api.NewBotAPI(cfg.ConsumerToken)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to telegram: %w", err) return fmt.Errorf("telegram: %w", err)
} }
tm := api.NewMessage(ctx.Config.Announce.Telegram.ChatID, msg) tm := api.NewMessage(ctx.Config.Announce.Telegram.ChatID, msg)
tm.ParseMode = "MarkdownV2" tm.ParseMode = "MarkdownV2"
_, err = bot.Send(tm) _, err = bot.Send(tm)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to telegram: %w", err) return fmt.Errorf("telegram: %w", err)
} }
log.Debug("message sent") log.Debug("message sent")
return nil return nil

View File

@@ -26,7 +26,7 @@ func TestAnnounceInvalidTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to telegram: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `telegram: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceMissingEnv(t *testing.T) { func TestAnnounceMissingEnv(t *testing.T) {
@@ -36,7 +36,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
}, },
}) })
require.NoError(t, Pipe{}.Default(ctx)) require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to telegram: env: environment variable "TELEGRAM_TOKEN" should not be empty`) require.EqualError(t, Pipe{}.Announce(ctx), `telegram: env: environment variable "TELEGRAM_TOKEN" should not be empty`)
} }
func TestSkip(t *testing.T) { func TestSkip(t *testing.T) {

View File

@@ -35,12 +35,12 @@ func (Pipe) Default(ctx *context.Context) error {
func (Pipe) Announce(ctx *context.Context) error { func (Pipe) Announce(ctx *context.Context) error {
msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Twitter.MessageTemplate) msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Twitter.MessageTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to twitter: %w", err) return fmt.Errorf("twitter: %w", err)
} }
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to twitter: %w", err) return fmt.Errorf("twitter: %w", err)
} }
log.Infof("posting: '%s'", msg) log.Infof("posting: '%s'", msg)
@@ -48,7 +48,7 @@ func (Pipe) Announce(ctx *context.Context) error {
token := oauth1.NewToken(cfg.AccessToken, cfg.AccessSecret) token := oauth1.NewToken(cfg.AccessToken, cfg.AccessSecret)
client := twitter.NewClient(config.Client(oauth1.NoContext, token)) client := twitter.NewClient(config.Client(oauth1.NoContext, token))
if _, _, err := client.Statuses.Update(msg, nil); err != nil { if _, _, err := client.Statuses.Update(msg, nil); err != nil {
return fmt.Errorf("announce: failed to announce to twitter: %w", err) return fmt.Errorf("twitter: %w", err)
} }
return nil return nil
} }

View File

@@ -26,7 +26,7 @@ func TestAnnounceInvalidTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to twitter: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `twitter: template: tmpl:1: unexpected "}" in operand`)
} }
func TestAnnounceMissingEnv(t *testing.T) { func TestAnnounceMissingEnv(t *testing.T) {
@@ -36,7 +36,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
}, },
}) })
require.NoError(t, Pipe{}.Default(ctx)) require.NoError(t, Pipe{}.Default(ctx))
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to twitter: env: environment variable "TWITTER_CONSUMER_KEY" should not be empty; environment variable "TWITTER_CONSUMER_SECRET" should not be empty; environment variable "TWITTER_ACCESS_TOKEN" should not be empty; environment variable "TWITTER_ACCESS_TOKEN_SECRET" should not be empty`) require.EqualError(t, Pipe{}.Announce(ctx), `twitter: env: environment variable "TWITTER_CONSUMER_KEY" should not be empty; environment variable "TWITTER_CONSUMER_SECRET" should not be empty; environment variable "TWITTER_ACCESS_TOKEN" should not be empty; environment variable "TWITTER_ACCESS_TOKEN_SECRET" should not be empty`)
} }
func TestSkip(t *testing.T) { func TestSkip(t *testing.T) {

View File

@@ -47,28 +47,28 @@ func (p Pipe) Default(ctx *context.Context) error {
func (p Pipe) Announce(ctx *context.Context) error { func (p Pipe) Announce(ctx *context.Context) error {
var cfg Config var cfg Config
if err := env.Parse(&cfg); err != nil { if err := env.Parse(&cfg); err != nil {
return fmt.Errorf("announce: failed to announce to webhook: %w", err) return fmt.Errorf("webhook: %w", err)
} }
endpointURLConfig, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Webhook.EndpointURL) endpointURLConfig, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Webhook.EndpointURL)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to webhook: %w", err) return fmt.Errorf("webhook: %w", err)
} }
if len(endpointURLConfig) == 0 { if len(endpointURLConfig) == 0 {
return errors.New("announce: failed to announce to webhook: no endpoint url") return errors.New("webhook: no endpoint url")
} }
if _, err := url.ParseRequestURI(endpointURLConfig); err != nil { if _, err := url.ParseRequestURI(endpointURLConfig); err != nil {
return fmt.Errorf("announce: failed to announce to webhook: %w", err) return fmt.Errorf("webhook: %w", err)
} }
endpointURL, err := url.Parse(endpointURLConfig) endpointURL, err := url.Parse(endpointURLConfig)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to webhook: %w", err) return fmt.Errorf("webhook: %w", err)
} }
msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Webhook.MessageTemplate) msg, err := tmpl.New(ctx).Apply(ctx.Config.Announce.Webhook.MessageTemplate)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to webhook: %s", err) return fmt.Errorf("webhook: %s", err)
} }
log.Infof("posting: '%s'", msg) log.Infof("posting: '%s'", msg)
@@ -84,7 +84,7 @@ func (p Pipe) Announce(ctx *context.Context) error {
req, err := http.NewRequest(http.MethodPost, endpointURL.String(), strings.NewReader(msg)) req, err := http.NewRequest(http.MethodPost, endpointURL.String(), strings.NewReader(msg))
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to webhook: %w", err) return fmt.Errorf("webhook: %w", err)
} }
req.Header.Add(ContentTypeHeaderKey, ctx.Config.Announce.Webhook.ContentType) req.Header.Add(ContentTypeHeaderKey, ctx.Config.Announce.Webhook.ContentType)
req.Header.Add(UserAgentHeaderKey, UserAgentHeaderValue) req.Header.Add(UserAgentHeaderKey, UserAgentHeaderValue)
@@ -103,7 +103,7 @@ func (p Pipe) Announce(ctx *context.Context) error {
} }
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("announce: failed to announce to webhook: %w", err) return fmt.Errorf("webhook: %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()

View File

@@ -25,7 +25,7 @@ func TestNoEndpoint(t *testing.T) {
Webhook: config.Webhook{}, Webhook: config.Webhook{},
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to webhook: no endpoint url`) require.EqualError(t, Pipe{}.Announce(ctx), `webhook: no endpoint url`)
} }
func TestMalformedEndpoint(t *testing.T) { func TestMalformedEndpoint(t *testing.T) {
@@ -36,7 +36,7 @@ func TestMalformedEndpoint(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to webhook: Post "httxxx://example.com": unsupported protocol scheme "httxxx"`) require.EqualError(t, Pipe{}.Announce(ctx), `webhook: Post "httxxx://example.com": unsupported protocol scheme "httxxx"`)
} }
func TestAnnounceInvalidMessageTemplate(t *testing.T) { func TestAnnounceInvalidMessageTemplate(t *testing.T) {
@@ -48,7 +48,7 @@ func TestAnnounceInvalidMessageTemplate(t *testing.T) {
}, },
}, },
}) })
require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to webhook: template: tmpl:1: unexpected "}" in operand`) require.EqualError(t, Pipe{}.Announce(ctx), `webhook: template: tmpl:1: unexpected "}" in operand`)
} }
type WebHookServerMockMessage struct { type WebHookServerMockMessage struct {