mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-30 04:50:45 +02:00
feat(announce): added Slack notification options (#2988)
* feat(announce): added Slack notification options This feature adds support for specifying a richer content in Slack announcements. We may now specify "blocks" and "attachments" to produce better-looking announcement messages. * fixes #2986 The goreleaser configuration only exposes the top-level structures and does not check the validity of the Slack API internal structures. This way, we do not inject hard dependencies on changes in the Slack API. Notice: untyped config parsing introduces a little hack to have yaml and JSON marshaling work together properly. This hack won't be necessary with yaml.v3. How this has been tested? ------------------------- * Added unit tests for the config parsing * Added a (skipped) e2e test. For now, this requires a valid Slack webhook, so I've been able to test this manually. Signed-off-by: Frederic BIDON <fredbi@yahoo.com> * added more unit tests Signed-off-by: Frederic BIDON <fredbi@yahoo.com> * removed yaml.v2 hack Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
This commit is contained in:
parent
8d6ef40020
commit
905a1640f1
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/apex/log"
|
||||
@ -47,12 +48,22 @@ func (Pipe) Announce(ctx *context.Context) error {
|
||||
|
||||
log.Infof("posting: '%s'", msg)
|
||||
|
||||
// optional processing of advanced formatting options
|
||||
blocks, attachments, err := parseAdvancedFormatting(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wm := &slack.WebhookMessage{
|
||||
Username: ctx.Config.Announce.Slack.Username,
|
||||
IconEmoji: ctx.Config.Announce.Slack.IconEmoji,
|
||||
IconURL: ctx.Config.Announce.Slack.IconURL,
|
||||
Channel: ctx.Config.Announce.Slack.Channel,
|
||||
Text: msg,
|
||||
|
||||
// optional enrichments
|
||||
Blocks: blocks,
|
||||
Attachments: attachments,
|
||||
}
|
||||
|
||||
err = slack.PostWebhook(cfg.Webhook, wm)
|
||||
@ -62,3 +73,43 @@ func (Pipe) Announce(ctx *context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseAdvancedFormatting(ctx *context.Context) (*slack.Blocks, []slack.Attachment, error) {
|
||||
var blocks *slack.Blocks
|
||||
if in := ctx.Config.Announce.Slack.Blocks; len(in) > 0 {
|
||||
blocks = &slack.Blocks{BlockSet: make([]slack.Block, 0, len(in))}
|
||||
|
||||
if err := unmarshal(ctx, in, blocks); err != nil {
|
||||
return nil, nil, fmt.Errorf("announce: slack blocks: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var attachments []slack.Attachment
|
||||
if in := ctx.Config.Announce.Slack.Attachments; len(in) > 0 {
|
||||
attachments = make([]slack.Attachment, 0, len(in))
|
||||
|
||||
if err := unmarshal(ctx, in, &attachments); err != nil {
|
||||
return nil, nil, fmt.Errorf("announce: slack attachments: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return blocks, attachments, nil
|
||||
}
|
||||
|
||||
func unmarshal(ctx *context.Context, in interface{}, target interface{}) error {
|
||||
jazon, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("announce: failed to marshal input as JSON: %w", err)
|
||||
}
|
||||
|
||||
tplApplied, err := tmpl.New(ctx).Apply(string(jazon))
|
||||
if err != nil {
|
||||
return fmt.Errorf("announce: failed to evaluate template: %w", err)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(tplApplied), target); err != nil {
|
||||
return fmt.Errorf("announce: failed to unmarshal into target: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/internal/yaml"
|
||||
"github.com/goreleaser/goreleaser/pkg/config"
|
||||
"github.com/goreleaser/goreleaser/pkg/context"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -55,3 +59,292 @@ func TestSkip(t *testing.T) {
|
||||
require.False(t, Pipe{}.Skip(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
const testVersion = "v1.2.3"
|
||||
|
||||
func TestParseRichText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("parse only - full slack config with blocks and attachments", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var project config.Project
|
||||
require.NoError(t, yaml.Unmarshal(goodRichSlackConf(), &project))
|
||||
|
||||
ctx := context.New(project)
|
||||
ctx.Version = testVersion
|
||||
|
||||
blocks, attachments, err := parseAdvancedFormatting(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, blocks.BlockSet, 4)
|
||||
require.Len(t, attachments, 2)
|
||||
})
|
||||
|
||||
t.Run("parse only - slack config with bad blocks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var project config.Project
|
||||
require.NoError(t, yaml.Unmarshal(badBlocksSlackConf(), &project))
|
||||
|
||||
ctx := context.New(project)
|
||||
ctx.Version = testVersion
|
||||
|
||||
_, _, err := parseAdvancedFormatting(ctx)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "json")
|
||||
})
|
||||
|
||||
t.Run("parse only - slack config with bad attachments", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var project config.Project
|
||||
require.NoError(t, yaml.Unmarshal(badAttachmentsSlackConf(), &project))
|
||||
|
||||
ctx := context.New(project)
|
||||
ctx.Version = testVersion
|
||||
|
||||
_, _, err := parseAdvancedFormatting(ctx)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "json")
|
||||
})
|
||||
}
|
||||
|
||||
func TestRichText(t *testing.T) {
|
||||
t.Parallel()
|
||||
os.Setenv("SLACK_WEBHOOK", slackTestHook())
|
||||
|
||||
t.Run("e2e - full slack config with blocks and attachments", func(t *testing.T) {
|
||||
t.SkipNow() // requires a valid webhook for integration testing
|
||||
t.Parallel()
|
||||
|
||||
var project config.Project
|
||||
require.NoError(t, yaml.Unmarshal(goodRichSlackConf(), &project))
|
||||
|
||||
ctx := context.New(project)
|
||||
ctx.Version = testVersion
|
||||
|
||||
require.NoError(t, Pipe{}.Announce(ctx))
|
||||
})
|
||||
|
||||
t.Run("slack config with bad blocks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var project config.Project
|
||||
require.NoError(t, yaml.Unmarshal(badBlocksSlackConf(), &project))
|
||||
|
||||
ctx := context.New(project)
|
||||
ctx.Version = testVersion
|
||||
|
||||
err := Pipe{}.Announce(ctx)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "json")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshall(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("happy unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.New(config.Project{})
|
||||
ctx.Version = testVersion
|
||||
|
||||
var blocks slack.Blocks
|
||||
require.NoError(t, unmarshal(ctx, []interface{}{map[string]interface{}{"type": "divider"}}, &blocks))
|
||||
})
|
||||
|
||||
t.Run("unmarshal fails on MarshalJSON", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.New(config.Project{})
|
||||
ctx.Version = testVersion
|
||||
|
||||
var blocks slack.Blocks
|
||||
require.Error(t, unmarshal(ctx, []interface{}{map[string]interface{}{"type": func() {}}}, &blocks))
|
||||
})
|
||||
|
||||
t.Run("unmarshal happy to resolve template", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var project config.Project
|
||||
require.NoError(t, yaml.Unmarshal(goodTemplateSlackConf(), &project))
|
||||
ctx := context.New(project)
|
||||
ctx.Version = testVersion
|
||||
|
||||
var blocks slack.Blocks
|
||||
require.NoError(t, unmarshal(ctx, ctx.Config.Announce.Slack.Blocks, &blocks))
|
||||
|
||||
require.Len(t, blocks.BlockSet, 1)
|
||||
header, ok := blocks.BlockSet[0].(*slack.HeaderBlock)
|
||||
require.True(t, ok)
|
||||
require.Contains(t, header.Text.Text, testVersion)
|
||||
})
|
||||
|
||||
t.Run("unmarshal fails on resolve template", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var project config.Project
|
||||
require.NoError(t, yaml.Unmarshal(badTemplateSlackConf(), &project))
|
||||
ctx := context.New(project)
|
||||
ctx.Version = testVersion
|
||||
|
||||
var blocks slack.Blocks
|
||||
require.Error(t, unmarshal(ctx, ctx.Config.Announce.Slack.Blocks, &blocks))
|
||||
})
|
||||
}
|
||||
|
||||
func slackTestHook() string {
|
||||
// redacted: replace this by a real Slack Web Incoming Hook to test the featue end to end.
|
||||
const hook = "https://hooks.slack.com/services/*********/***********/************************"
|
||||
|
||||
return hook
|
||||
}
|
||||
|
||||
func goodRichSlackConf() []byte {
|
||||
const conf = `
|
||||
project_name: test
|
||||
announce:
|
||||
slack:
|
||||
enabled: true
|
||||
message_template: fallback
|
||||
channel: my_channel
|
||||
blocks:
|
||||
- type: header
|
||||
text:
|
||||
type: plain_text
|
||||
text: '{{ .Version }}'
|
||||
- type: section
|
||||
text:
|
||||
type: mrkdwn
|
||||
text: |
|
||||
Heading
|
||||
=======
|
||||
|
||||
# Other Heading
|
||||
|
||||
*Bold*
|
||||
_italic_
|
||||
~Strikethrough~
|
||||
|
||||
## Heading 2
|
||||
### Heading 3
|
||||
* List item 1
|
||||
* List item 2
|
||||
|
||||
- List item 3
|
||||
- List item 4
|
||||
|
||||
[link](https://example.com)
|
||||
<https://example.com|link>
|
||||
|
||||
:)
|
||||
|
||||
:star:
|
||||
|
||||
- type: divider
|
||||
- type: section
|
||||
text:
|
||||
type: mrkdwn
|
||||
text: |
|
||||
my release
|
||||
attachments:
|
||||
-
|
||||
title: Release artifacts
|
||||
color: '#2eb886'
|
||||
text: |
|
||||
*Helm chart packages*
|
||||
- fallback: full changelog
|
||||
color: '#2eb886'
|
||||
title: Full Change Log
|
||||
text: |
|
||||
* this link
|
||||
* that link
|
||||
`
|
||||
|
||||
buf := bytes.NewBufferString(conf)
|
||||
|
||||
return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" "))
|
||||
}
|
||||
|
||||
func badBlocksSlackConf() []byte {
|
||||
const conf = `
|
||||
project_name: test
|
||||
announce:
|
||||
slack:
|
||||
enabled: true
|
||||
message_template: fallback
|
||||
channel: my_channel
|
||||
blocks:
|
||||
- type: header
|
||||
text: invalid # <- wrong type for Slack API
|
||||
`
|
||||
|
||||
buf := bytes.NewBufferString(conf)
|
||||
|
||||
return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" "))
|
||||
}
|
||||
|
||||
func badAttachmentsSlackConf() []byte {
|
||||
const conf = `
|
||||
project_name: test
|
||||
announce:
|
||||
slack:
|
||||
enabled: true
|
||||
message_template: fallback
|
||||
channel: my_channel
|
||||
attachments:
|
||||
-
|
||||
title:
|
||||
- Release artifacts
|
||||
- wrong # <- title is not an array
|
||||
color: '#2eb886'
|
||||
text: |
|
||||
*Helm chart packages*
|
||||
`
|
||||
|
||||
buf := bytes.NewBufferString(conf)
|
||||
|
||||
return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" "))
|
||||
}
|
||||
|
||||
func goodTemplateSlackConf() []byte {
|
||||
const conf = `
|
||||
project_name: test
|
||||
announce:
|
||||
slack:
|
||||
enabled: true
|
||||
message_template: '{{ .Version }}'
|
||||
channel: my_channel
|
||||
blocks:
|
||||
- type: header
|
||||
text:
|
||||
type: plain_text
|
||||
text: '{{ .Version }}'
|
||||
`
|
||||
|
||||
buf := bytes.NewBufferString(conf)
|
||||
|
||||
return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" "))
|
||||
}
|
||||
|
||||
func badTemplateSlackConf() []byte {
|
||||
const conf = `
|
||||
project_name: test
|
||||
announce:
|
||||
slack:
|
||||
enabled: true
|
||||
message_template: fallback
|
||||
channel: my_channel
|
||||
blocks:
|
||||
- type: header
|
||||
text:
|
||||
type: plain_text
|
||||
text: '{{ .Wrong }}'
|
||||
`
|
||||
|
||||
buf := bytes.NewBufferString(conf)
|
||||
|
||||
return bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" "))
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
@ -979,12 +980,14 @@ type Reddit struct {
|
||||
}
|
||||
|
||||
type Slack struct {
|
||||
Enabled bool `yaml:"enabled,omitempty"`
|
||||
MessageTemplate string `yaml:"message_template,omitempty"`
|
||||
Channel string `yaml:"channel,omitempty"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
IconEmoji string `yaml:"icon_emoji,omitempty"`
|
||||
IconURL string `yaml:"icon_url,omitempty"`
|
||||
Enabled bool `yaml:"enabled,omitempty"`
|
||||
MessageTemplate string `yaml:"message_template,omitempty"`
|
||||
Channel string `yaml:"channel,omitempty"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
IconEmoji string `yaml:"icon_emoji,omitempty"`
|
||||
IconURL string `yaml:"icon_url,omitempty"`
|
||||
Blocks []SlackBlock `yaml:"blocks,omitempty"`
|
||||
Attachments []SlackAttachment `yaml:"attachments,omitempty"`
|
||||
}
|
||||
|
||||
type Discord struct {
|
||||
@ -1058,3 +1061,47 @@ func LoadReader(fd io.Reader) (config Project, err error) {
|
||||
log.WithField("config", config).Debug("loaded config file")
|
||||
return config, err
|
||||
}
|
||||
|
||||
// SlackBlock represents the untyped structure of a rich slack message layout.
|
||||
type SlackBlock struct {
|
||||
Internal interface{}
|
||||
}
|
||||
|
||||
// UnmarshalYAML is a custom unmarshaler that unmarshals a YAML slack block as untyped interface{}.
|
||||
func (a *SlackBlock) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var yamlv2 interface{}
|
||||
if err := unmarshal(&yamlv2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Internal = yamlv2
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON marshals a slack block as JSON.
|
||||
func (a SlackBlock) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(a.Internal)
|
||||
}
|
||||
|
||||
// SlackAttachment represents the untyped structure of a slack message attachment.
|
||||
type SlackAttachment struct {
|
||||
Internal interface{}
|
||||
}
|
||||
|
||||
// UnmarshalYAML is a custom unmarshaler that unmarshals a YAML slack attachment as untyped interface{}.
|
||||
func (a *SlackAttachment) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var yamlv2 interface{}
|
||||
if err := unmarshal(&yamlv2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Internal = yamlv2
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON marshals a slack attachment as JSON.
|
||||
func (a SlackAttachment) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(a.Internal)
|
||||
}
|
||||
|
246
pkg/config/config_slack_test.go
Normal file
246
pkg/config/config_slack_test.go
Normal file
@ -0,0 +1,246 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnmarshalSlackBlocks(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("valid blocks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prop, err := LoadReader(goodBlocksSlackConf())
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedBlocks := []SlackBlock{
|
||||
{
|
||||
Internal: map[string]interface{}{
|
||||
"type": "header",
|
||||
"text": map[string]interface{}{
|
||||
"type": "plain_text",
|
||||
"text": "{{ .Version }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Internal: map[string]interface{}{
|
||||
"text": map[string]interface{}{
|
||||
"type": "mrkdwn",
|
||||
"text": "Heading\n=======\n\n**Bold**\n",
|
||||
},
|
||||
"type": "section",
|
||||
},
|
||||
},
|
||||
}
|
||||
// assert Unmarshal from YAML
|
||||
require.Equal(t, expectedBlocks, prop.Announce.Slack.Blocks)
|
||||
|
||||
jazon, err := json.Marshal(prop.Announce.Slack.Blocks)
|
||||
require.NoError(t, err)
|
||||
|
||||
var untyped []SlackBlock
|
||||
require.NoError(t, json.Unmarshal(jazon, &untyped))
|
||||
|
||||
// assert that JSON Marshal didn't alter the struct
|
||||
require.Equal(t, expectedBlocks, prop.Announce.Slack.Blocks)
|
||||
})
|
||||
|
||||
t.Run("invalid blocks", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := LoadReader(badBlocksSlackConf())
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalSlackAttachments(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("valid attachments", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prop, err := LoadReader(goodAttachmentsSlackConf())
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedAttachments := []SlackAttachment{
|
||||
{
|
||||
Internal: map[string]interface{}{
|
||||
"color": "#46a64f",
|
||||
"fields": []interface{}{
|
||||
map[string]interface{}{
|
||||
"short": false,
|
||||
"title": "field 1",
|
||||
"value": "value 1",
|
||||
},
|
||||
},
|
||||
"footer": "a footer",
|
||||
"mrkdwn_in": []interface{}{
|
||||
"text",
|
||||
},
|
||||
"pretext": "optional",
|
||||
"text": "another",
|
||||
"title": "my_title",
|
||||
},
|
||||
},
|
||||
}
|
||||
// assert Unmarshal from YAML
|
||||
require.Equal(t, expectedAttachments, prop.Announce.Slack.Attachments)
|
||||
|
||||
jazon, err := json.Marshal(prop.Announce.Slack.Attachments)
|
||||
require.NoError(t, err)
|
||||
|
||||
var untyped []SlackAttachment
|
||||
require.NoError(t, json.Unmarshal(jazon, &untyped))
|
||||
|
||||
// assert that JSON Marshal didn't alter the struct
|
||||
require.Equal(t, expectedAttachments, prop.Announce.Slack.Attachments)
|
||||
})
|
||||
|
||||
t.Run("invalid attachments", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := LoadReader(badAttachmentsSlackConf())
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalYAMLSlackBlocks(t *testing.T) {
|
||||
// func (a *SlackAttachment) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
t.Parallel()
|
||||
|
||||
const testError = "testError"
|
||||
erf := func(_ interface{}) error {
|
||||
return errors.New(testError)
|
||||
}
|
||||
|
||||
t.Run("SlackBlock.UnmarshalYAML error case", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var block SlackBlock
|
||||
err := block.UnmarshalYAML(erf)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, testError)
|
||||
})
|
||||
|
||||
t.Run("SlackAttachment.UnmarshalYAML error case", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var attachment SlackAttachment
|
||||
err := attachment.UnmarshalYAML(erf)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, testError)
|
||||
})
|
||||
}
|
||||
|
||||
func goodBlocksSlackConf() io.Reader {
|
||||
const conf = `
|
||||
announce:
|
||||
slack:
|
||||
enabled: true
|
||||
username: my_user
|
||||
message_template: fallback
|
||||
channel: my_channel
|
||||
blocks:
|
||||
- type: header
|
||||
text:
|
||||
type: plain_text
|
||||
text: '{{ .Version }}'
|
||||
- type: section
|
||||
text:
|
||||
type: mrkdwn
|
||||
text: |
|
||||
Heading
|
||||
=======
|
||||
|
||||
**Bold**
|
||||
`
|
||||
|
||||
buf := bytes.NewBufferString(conf)
|
||||
|
||||
return bytes.NewReader(bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" ")))
|
||||
}
|
||||
|
||||
func badBlocksSlackConf() io.Reader {
|
||||
const conf = `
|
||||
announce:
|
||||
slack:
|
||||
enabled: true
|
||||
username: my_user
|
||||
message_template: fallback
|
||||
channel: my_channel
|
||||
blocks:
|
||||
type: header
|
||||
text:
|
||||
type: plain_text
|
||||
text: '{{ .Version }}'
|
||||
type: section
|
||||
text:
|
||||
type: mrkdwn
|
||||
text: |
|
||||
**Bold**
|
||||
`
|
||||
|
||||
buf := bytes.NewBufferString(conf)
|
||||
|
||||
return bytes.NewReader(bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" ")))
|
||||
}
|
||||
|
||||
func goodAttachmentsSlackConf() io.Reader {
|
||||
const conf = `
|
||||
announce:
|
||||
slack:
|
||||
enabled: true
|
||||
username: my_user
|
||||
message_template: fallback
|
||||
channel: my_channel
|
||||
attachments:
|
||||
- mrkdwn_in: ["text"]
|
||||
color: '#46a64f'
|
||||
pretext: optional
|
||||
title: my_title
|
||||
text: another
|
||||
fields:
|
||||
- title: field 1
|
||||
value: value 1
|
||||
short: false
|
||||
footer: a footer
|
||||
`
|
||||
|
||||
buf := bytes.NewBufferString(conf)
|
||||
|
||||
return bytes.NewReader(bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" ")))
|
||||
}
|
||||
|
||||
func badAttachmentsSlackConf() io.Reader {
|
||||
const conf = `
|
||||
announce:
|
||||
slack:
|
||||
enabled: true
|
||||
username: my_user
|
||||
message_template: fallback
|
||||
channel: my_channel
|
||||
attachments:
|
||||
key:
|
||||
mrkdwn_in: ["text"]
|
||||
color: #46a64f
|
||||
pretext: optional
|
||||
title: my_title
|
||||
text: another
|
||||
fields:
|
||||
- title: field 1
|
||||
value: value 1
|
||||
short: false
|
||||
footer: a footer
|
||||
`
|
||||
|
||||
buf := bytes.NewBufferString(conf)
|
||||
|
||||
return bytes.NewReader(bytes.ReplaceAll(buf.Bytes(), []byte("\t"), []byte(" ")))
|
||||
}
|
@ -30,6 +30,23 @@ announce:
|
||||
|
||||
# URL to an image to use as the icon for this message.
|
||||
icon_url: ''
|
||||
|
||||
# Blocks for advanced formatting, see: https://api.slack.com/messaging/webhooks#advanced_message_formatting
|
||||
# and https://api.slack.com/messaging/composing/layouts#adding-blocks.
|
||||
#
|
||||
# Templating is possible inside this structure.
|
||||
#
|
||||
# Attention: goreleaser doesn't check the full structure of the Slack API: please make sure that
|
||||
# your configuration for advanced message formatting abides by this API.
|
||||
blocks: []
|
||||
|
||||
# Attachments, see: https://api.slack.com/reference/messaging/attachments
|
||||
#
|
||||
# Templating is possible inside this structure.
|
||||
#
|
||||
# Attention: goreleaser doesn't check the full structure of the Slack API: please make sure that
|
||||
# your configuration for advanced message formatting abides by this API.
|
||||
attachments: []
|
||||
```
|
||||
|
||||
!!! tip
|
||||
|
Loading…
x
Reference in New Issue
Block a user