Merge pull request #146 from keel-hq/feature/custom-chan-for-approvals

Feature/custom chan for approvals
pull/147/head 0.6.1
Karolis Rusenas 2018-02-25 20:00:22 +00:00 committed by GitHub
commit 1c5590f653
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 136 additions and 13 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ deployment.yml
.test_env.sh
hack/deployment-norbac.yaml
hack/deployment-rbac.yaml
hack/deployment-norbac-helm.yaml

View File

@ -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"

View File

@ -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{

View File

@ -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),
})

View File

@ -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{

View File

@ -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,
})
}

View File

@ -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},

View File

@ -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{

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
}
})
}
}