Merge pull request #146 from keel-hq/feature/custom-chan-for-approvals
Feature/custom chan for approvalspull/147/head 0.6.1
commit
1c5590f653
|
@ -3,3 +3,4 @@ deployment.yml
|
|||
.test_env.sh
|
||||
hack/deployment-norbac.yaml
|
||||
hack/deployment-rbac.yaml
|
||||
hack/deployment-norbac-helm.yaml
|
||||
|
|
|
@ -34,3 +34,6 @@ const (
|
|||
|
||||
// EnvNotificationLevel - minimum level for notifications, defaults to info
|
||||
const EnvNotificationLevel = "NOTIFICATION_LEVEL"
|
||||
|
||||
// KeelLogoURL - is a logo URL for bot icon
|
||||
const KeelLogoURL = "https://keel.sh/images/logo.png"
|
||||
|
|
|
@ -65,7 +65,12 @@ func (s *sender) Send(event types.EventNotification) error {
|
|||
From: s.botName,
|
||||
}
|
||||
|
||||
for _, channel := range s.channels {
|
||||
channels := s.channels
|
||||
if len(event.Channels) > 0 {
|
||||
channels = event.Channels
|
||||
}
|
||||
|
||||
for _, channel := range channels {
|
||||
_, err := s.hipchatClient.Room.Notification(channel, notification)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
|
|
|
@ -83,7 +83,6 @@ func (s *sender) Configure(config *notification.Config) (bool, error) {
|
|||
}
|
||||
|
||||
type notificationEnvelope struct {
|
||||
Chanel string `json:"channel"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
Text string `json:"text"`
|
||||
|
@ -92,7 +91,7 @@ type notificationEnvelope struct {
|
|||
func (s *sender) Send(event types.EventNotification) error {
|
||||
// Marshal notification.
|
||||
jsonNotification, err := json.Marshal(notificationEnvelope{
|
||||
IconURL: "https://keel.sh/images/logo.png",
|
||||
IconURL: constants.KeelLogoURL,
|
||||
Username: s.name,
|
||||
Text: fmt.Sprintf("#### %s \n %s", event.Type.String(), event.Message),
|
||||
})
|
||||
|
|
|
@ -62,6 +62,7 @@ func (s *sender) Configure(config *notification.Config) (bool, error) {
|
|||
func (s *sender) Send(event types.EventNotification) error {
|
||||
params := slack.NewPostMessageParameters()
|
||||
params.Username = s.botName
|
||||
params.IconURL = constants.KeelLogoURL
|
||||
|
||||
params.Attachments = []slack.Attachment{
|
||||
slack.Attachment{
|
||||
|
@ -79,7 +80,12 @@ func (s *sender) Send(event types.EventNotification) error {
|
|||
},
|
||||
}
|
||||
|
||||
for _, channel := range s.channels {
|
||||
chans := s.channels
|
||||
if len(event.Channels) > 0 {
|
||||
chans = event.Channels
|
||||
}
|
||||
|
||||
for _, channel := range chans {
|
||||
_, _, err := s.slackClient.PostMessage(channel, "", params)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
|
|
|
@ -70,12 +70,13 @@ type Root struct {
|
|||
|
||||
// KeelChartConfig - keel related configuration taken from values.yaml
|
||||
type KeelChartConfig struct {
|
||||
Policy types.PolicyType `json:"policy"`
|
||||
Trigger types.TriggerType `json:"trigger"`
|
||||
PollSchedule string `json:"pollSchedule"`
|
||||
Approvals int `json:"approvals"` // Minimum required approvals
|
||||
ApprovalDeadline int `json:"approvalDeadline"` // Deadline in hours
|
||||
Images []ImageDetails `json:"images"`
|
||||
Policy types.PolicyType `json:"policy"`
|
||||
Trigger types.TriggerType `json:"trigger"`
|
||||
PollSchedule string `json:"pollSchedule"`
|
||||
Approvals int `json:"approvals"` // Minimum required approvals
|
||||
ApprovalDeadline int `json:"approvalDeadline"` // Deadline in hours
|
||||
Images []ImageDetails `json:"images"`
|
||||
NotificationChannels []string `json:"notificationChannels"` // optional notification channels
|
||||
}
|
||||
|
||||
// ImageDetails - image details
|
||||
|
@ -285,6 +286,7 @@ func (p *Provider) applyPlans(plans []*UpdatePlan) error {
|
|||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationPreReleaseUpdate,
|
||||
Level: types.LevelDebug,
|
||||
Channels: plan.Config.NotificationChannels,
|
||||
})
|
||||
|
||||
err := updateHelmRelease(p.implementer, plan.Name, plan.Chart, plan.Values)
|
||||
|
@ -301,6 +303,7 @@ func (p *Provider) applyPlans(plans []*UpdatePlan) error {
|
|||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationReleaseUpdate,
|
||||
Level: types.LevelError,
|
||||
Channels: plan.Config.NotificationChannels,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
@ -311,6 +314,7 @@ func (p *Provider) applyPlans(plans []*UpdatePlan) error {
|
|||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationReleaseUpdate,
|
||||
Level: types.LevelSuccess,
|
||||
Channels: plan.Config.NotificationChannels,
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -400,6 +400,27 @@ keel:
|
|||
`
|
||||
valuesBasic, _ := chartutil.ReadValues([]byte(valuesBasicStr))
|
||||
|
||||
var valuesChannelsStr = `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
notificationChannels:
|
||||
- chan1
|
||||
- chan2
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
valuesChannels, _ := chartutil.ReadValues([]byte(valuesChannelsStr))
|
||||
|
||||
var valuesPollStr = `
|
||||
name: al Rashid
|
||||
where:
|
||||
|
@ -440,6 +461,18 @@ keel:
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom notification channels",
|
||||
args: args{vals: valuesChannels},
|
||||
want: &KeelChartConfig{
|
||||
Policy: types.PolicyTypeAll,
|
||||
Trigger: types.TriggerTypeDefault,
|
||||
NotificationChannels: []string{"chan1", "chan2"},
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct polling config",
|
||||
args: args{vals: valuesPoll},
|
||||
|
|
|
@ -204,6 +204,8 @@ func (p *Provider) updateDeployments(plans []*UpdatePlan) (updated []*v1beta1.De
|
|||
|
||||
deployment := plan.Deployment
|
||||
|
||||
notificationChannels := types.ParseEventNotificationChannels(deployment.Annotations)
|
||||
|
||||
reset, delta, err := checkForReset(deployment, p.implementer)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
|
@ -244,13 +246,13 @@ func (p *Provider) updateDeployments(plans []*UpdatePlan) (updated []*v1beta1.De
|
|||
}
|
||||
|
||||
deployment = refresh
|
||||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "preparing to update deployment after reset",
|
||||
Message: fmt.Sprintf("Preparing to update deployment %s/%s %s->%s (%s)", deployment.Namespace, deployment.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(getImages(&refresh), ", ")),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationPreDeploymentUpdate,
|
||||
Level: types.LevelDebug,
|
||||
Channels: notificationChannels,
|
||||
})
|
||||
|
||||
err = p.implementer.Update(&refresh)
|
||||
|
@ -267,6 +269,7 @@ func (p *Provider) updateDeployments(plans []*UpdatePlan) (updated []*v1beta1.De
|
|||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationDeploymentUpdate,
|
||||
Level: types.LevelError,
|
||||
Channels: notificationChannels,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
@ -277,6 +280,7 @@ func (p *Provider) updateDeployments(plans []*UpdatePlan) (updated []*v1beta1.De
|
|||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationDeploymentUpdate,
|
||||
Level: types.LevelSuccess,
|
||||
Channels: notificationChannels,
|
||||
})
|
||||
|
||||
updated = append(updated, &refresh)
|
||||
|
@ -291,6 +295,7 @@ func (p *Provider) updateDeployments(plans []*UpdatePlan) (updated []*v1beta1.De
|
|||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationPreDeploymentUpdate,
|
||||
Level: types.LevelDebug,
|
||||
Channels: notificationChannels,
|
||||
})
|
||||
|
||||
err = p.implementer.Update(&deployment)
|
||||
|
@ -307,6 +312,7 @@ func (p *Provider) updateDeployments(plans []*UpdatePlan) (updated []*v1beta1.De
|
|||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationDeploymentUpdate,
|
||||
Level: types.LevelError,
|
||||
Channels: notificationChannels,
|
||||
})
|
||||
|
||||
continue
|
||||
|
@ -318,6 +324,7 @@ func (p *Provider) updateDeployments(plans []*UpdatePlan) (updated []*v1beta1.De
|
|||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationDeploymentUpdate,
|
||||
Level: types.LevelSuccess,
|
||||
Channels: notificationChannels,
|
||||
})
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
|
|
|
@ -41,7 +41,7 @@ Keel provides several key features:
|
|||
* __Notifications__ - out of the box Keel has Slack and standard webhook notifications, more info [here](https://keel.sh/v1/guide/documentation.html#Notifications)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://keel.sh" target="_blank"><img width="500"src="https://keel.sh/images/keel-overview.png"></a>
|
||||
<a href="https://keel.sh" target="_blank"><img width="700"src="https://keel.sh/images/keel-overview.png"></a>
|
||||
</p>
|
||||
|
||||
### Support
|
||||
|
|
|
@ -101,7 +101,7 @@ func (g *DefaultGetter) lookupSecrets(image *types.TrackedImage) ([]string, erro
|
|||
"image": image.Image.Repository(),
|
||||
"pod_selector": selector,
|
||||
"pods_checked": len(podList.Items),
|
||||
}).Warn("secrets.defaultGetter.lookupSecrets: no secrets for image found")
|
||||
}).Debug("secrets.defaultGetter.lookupSecrets: no secrets for image found")
|
||||
}
|
||||
|
||||
return secrets, nil
|
||||
|
|
|
@ -33,6 +33,10 @@ const KeelPollDefaultSchedule = "@every 1m"
|
|||
// KeelDigestAnnotation - digest annotation
|
||||
const KeelDigestAnnotation = "keel.sh/digest"
|
||||
|
||||
// KeelNotificationChanAnnotation - optional notification to override
|
||||
// default notification channel(-s) per deployment/chart
|
||||
const KeelNotificationChanAnnotation = "keel.sh/notify"
|
||||
|
||||
// KeelMinimumApprovalsLabel - min approvals
|
||||
const KeelMinimumApprovalsLabel = "keel.sh/approvals"
|
||||
|
||||
|
@ -174,6 +178,27 @@ type EventNotification struct {
|
|||
CreatedAt time.Time `json:"createdAt"`
|
||||
Type Notification `json:"type"`
|
||||
Level Level `json:"level"`
|
||||
// Channels is an optional variable to override
|
||||
// default channel(-s) when performing an update
|
||||
Channels []string `json:"-"`
|
||||
}
|
||||
|
||||
// ParseEventNotificationChannels - parses deployment annotations or chart config
|
||||
// to get channel overrides
|
||||
func ParseEventNotificationChannels(annotations map[string]string) []string {
|
||||
channels := []string{}
|
||||
if annotations == nil {
|
||||
return channels
|
||||
}
|
||||
chanStr, ok := annotations[KeelNotificationChanAnnotation]
|
||||
if ok {
|
||||
chans := strings.Split(chanStr, ",")
|
||||
for _, c := range chans {
|
||||
channels = append(channels, strings.TrimSpace(c))
|
||||
}
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
// Notification - notification types used by notifier
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
@ -126,3 +127,42 @@ func TestNotExpired(t *testing.T) {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEventNotificationChannels(t *testing.T) {
|
||||
type args struct {
|
||||
annotations map[string]string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "no chans",
|
||||
args: args{map[string]string{"foo": "bar"}},
|
||||
want: []string{},
|
||||
},
|
||||
{
|
||||
name: "one chan",
|
||||
args: args{map[string]string{KeelNotificationChanAnnotation: "verychan"}},
|
||||
want: []string{"verychan"},
|
||||
},
|
||||
{
|
||||
name: "two chans with space",
|
||||
args: args{map[string]string{KeelNotificationChanAnnotation: "verychan, corp"}},
|
||||
want: []string{"verychan", "corp"},
|
||||
},
|
||||
{
|
||||
name: "two chans no space",
|
||||
args: args{map[string]string{KeelNotificationChanAnnotation: "verychan,corp"}},
|
||||
want: []string{"verychan", "corp"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ParseEventNotificationChannels(tt.args.annotations); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ParseEventNotificationChannels() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue