Skip to content

Commit

Permalink
Merge pull request #146 from keel-hq/feature/custom-chan-for-approvals
Browse files Browse the repository at this point in the history
Feature/custom chan for approvals
  • Loading branch information
rusenask authored Feb 25, 2018
2 parents 58e89c0 + ee9b6cb commit 1c5590f
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ deployment.yml
.test_env.sh
hack/deployment-norbac.yaml
hack/deployment-rbac.yaml
hack/deployment-norbac-helm.yaml
3 changes: 3 additions & 0 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
7 changes: 6 additions & 1 deletion extension/notification/hipchat/hipchat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
3 changes: 1 addition & 2 deletions extension/notification/mattermost/mattermost.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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),
})
Expand Down
8 changes: 7 additions & 1 deletion extension/notification/slack/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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{
Expand Down
16 changes: 10 additions & 6 deletions provider/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
Expand All @@ -311,6 +314,7 @@ func (p *Provider) applyPlans(plans []*UpdatePlan) error {
CreatedAt: time.Now(),
Type: types.NotificationReleaseUpdate,
Level: types.LevelSuccess,
Channels: plan.Config.NotificationChannels,
})

}
Expand Down
33 changes: 33 additions & 0 deletions provider/helm/helm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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},
Expand Down
9 changes: 8 additions & 1 deletion provider/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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{
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion secrets/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions types/types_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"reflect"
"testing"
"time"
)
Expand Down Expand Up @@ -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)
}
})
}
}

0 comments on commit 1c5590f

Please sign in to comment.