keel/approvals/approvals.go

369 lines
8.1 KiB
Go
Raw Normal View History

2017-08-13 21:07:10 +00:00
package approvals
import (
"context"
2018-10-07 13:07:03 +00:00
"encoding/json"
2017-08-13 21:07:10 +00:00
"errors"
2017-08-16 21:26:54 +00:00
"sync"
2017-08-19 18:30:10 +00:00
"sync/atomic"
2017-08-13 21:07:10 +00:00
"time"
2017-11-01 18:25:28 +00:00
"github.com/keel-hq/keel/cache"
"github.com/keel-hq/keel/types"
2017-08-13 21:07:10 +00:00
2018-03-03 11:32:00 +00:00
log "github.com/sirupsen/logrus"
2017-08-13 21:07:10 +00:00
)
// Manager is used to manage updates
type Manager interface {
2017-08-19 18:30:10 +00:00
// Subscribe for approval request events, subscriber should provide
2017-08-28 12:30:23 +00:00
// its name. Indented to be used by extensions that collect
// approvals
2017-08-19 18:30:10 +00:00
Subscribe(ctx context.Context) (<-chan *types.Approval, error)
2017-08-28 12:30:23 +00:00
// SubscribeApproved - is used to get approved events by the manager
SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error)
2017-08-13 21:07:10 +00:00
// request approval for deployment/release/etc..
Create(r *types.Approval) error
2017-08-16 21:26:54 +00:00
// Update whole approval object
2017-08-13 21:07:10 +00:00
Update(r *types.Approval) error
2017-08-16 21:26:54 +00:00
// Increases Approval votes by 1
2017-09-12 16:16:15 +00:00
Approve(identifier, voter string) (*types.Approval, error)
2017-08-16 21:26:54 +00:00
// Rejects Approval
2017-08-21 22:43:59 +00:00
Reject(identifier string) (*types.Approval, error)
2017-08-16 21:26:54 +00:00
2017-08-21 22:43:59 +00:00
Get(identifier string) (*types.Approval, error)
List() ([]*types.Approval, error)
Delete(identifier string) error
2017-09-12 13:37:21 +00:00
StartExpiryService(ctx context.Context) error
2017-08-13 21:07:10 +00:00
}
2017-08-16 21:26:54 +00:00
// Approvals related errors
2017-08-13 21:07:10 +00:00
var (
ErrApprovalAlreadyExists = errors.New("approval already exists")
)
2017-08-16 21:26:54 +00:00
// Approvals cache prefix
2017-08-13 21:07:10 +00:00
const (
ApprovalsPrefix = "approvals"
)
// DefaultManager - default manager implementation
type DefaultManager struct {
// cache is used to store approvals, key example:
// approvals/<provider name>/<identifier>
2018-10-07 13:07:03 +00:00
cache cache.Cache
2017-08-16 21:26:54 +00:00
2017-08-19 18:30:10 +00:00
// subscriber channels
2018-10-12 20:31:31 +00:00
channels map[uint32]chan *types.Approval
index uint32
2017-08-19 18:30:10 +00:00
2017-08-28 12:30:23 +00:00
// approved channels
2018-10-12 20:31:31 +00:00
approvedCh map[uint32]chan *types.Approval
2017-08-28 12:30:23 +00:00
mu *sync.Mutex
subMu *sync.RWMutex
2017-08-13 21:07:10 +00:00
}
// New create new instance of default manager
2018-10-07 13:07:03 +00:00
func New(cache cache.Cache) *DefaultManager {
2017-08-21 22:43:59 +00:00
man := &DefaultManager{
2017-08-13 21:07:10 +00:00
cache: cache,
2018-10-12 20:31:31 +00:00
channels: make(map[uint32]chan *types.Approval),
approvedCh: make(map[uint32]chan *types.Approval),
2017-08-19 18:30:10 +00:00
index: 0,
2017-08-16 21:26:54 +00:00
mu: &sync.Mutex{},
2017-08-28 12:30:23 +00:00
subMu: &sync.RWMutex{},
2017-08-13 21:07:10 +00:00
}
2017-08-21 22:43:59 +00:00
return man
2017-08-13 21:07:10 +00:00
}
2017-09-12 13:37:21 +00:00
// StartExpiryService - starts approval expiry service which deletes approvals
// that already reached their deadline
func (m *DefaultManager) StartExpiryService(ctx context.Context) error {
ticker := time.NewTicker(60 * time.Minute)
2018-03-31 13:25:02 +00:00
defer ticker.Stop()
2017-09-12 13:37:21 +00:00
err := m.expireEntries()
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("approvals.StartExpiryService: got error while performing initial expired approvals check")
}
for {
select {
case <-ctx.Done():
return nil
case <-ticker.C:
err := m.expireEntries()
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("approvals.StartExpiryService: got error while performing routinely expired approvals check")
}
}
}
}
func (m *DefaultManager) expireEntries() error {
approvals, err := m.cache.List(ApprovalsPrefix + "/")
if err != nil {
return err
}
for k, v := range approvals {
var approval types.Approval
2018-10-07 13:07:03 +00:00
err = json.Unmarshal(v, &approval)
2017-09-12 13:37:21 +00:00
if err != nil {
log.WithFields(log.Fields{
"error": err,
"identifier": k,
}).Error("approvals.expireEntries: failed to decode approval into value")
continue
}
if approval.Expired() {
err = m.Delete(approval.Identifier)
if err != nil {
log.WithFields(log.Fields{
"error": err,
"identifier": k,
}).Error("approvals.expireEntries: failed to delete expired approval")
}
}
}
return nil
}
2017-08-19 18:30:10 +00:00
// Subscribe - subscribe for approval events
func (m *DefaultManager) Subscribe(ctx context.Context) (<-chan *types.Approval, error) {
2017-08-28 12:30:23 +00:00
m.subMu.Lock()
2018-10-12 20:31:31 +00:00
index := atomic.AddUint32(&m.index, 1)
2017-08-19 18:30:10 +00:00
approvalsCh := make(chan *types.Approval, 10)
m.channels[index] = approvalsCh
2017-08-28 12:30:23 +00:00
m.subMu.Unlock()
2017-08-19 18:30:10 +00:00
go func() {
for {
select {
case <-ctx.Done():
2017-08-28 12:30:23 +00:00
m.subMu.Lock()
2017-08-19 18:30:10 +00:00
delete(m.channels, index)
2017-08-28 12:30:23 +00:00
m.subMu.Unlock()
2017-08-19 18:30:10 +00:00
return
}
}
}()
return approvalsCh, nil
}
2017-08-28 12:30:23 +00:00
// SubscribeApproved - subscribe for approved update requests
func (m *DefaultManager) SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error) {
m.subMu.Lock()
2018-10-12 20:31:31 +00:00
index := atomic.AddUint32(&m.index, 1)
2017-08-28 12:30:23 +00:00
approvedCh := make(chan *types.Approval, 10)
m.approvedCh[index] = approvedCh
m.subMu.Unlock()
go func() {
for {
select {
case <-ctx.Done():
m.subMu.Lock()
delete(m.approvedCh, index)
m.subMu.Unlock()
return
}
}
}()
return approvedCh, nil
}
func (m *DefaultManager) publishRequest(approval *types.Approval) error {
m.subMu.RLock()
defer m.subMu.RUnlock()
2017-08-19 18:30:10 +00:00
for _, subscriber := range m.channels {
subscriber <- approval
}
return nil
}
2017-08-28 12:30:23 +00:00
func (m *DefaultManager) publishApproved(approval *types.Approval) error {
m.subMu.RLock()
defer m.subMu.RUnlock()
for _, subscriber := range m.approvedCh {
subscriber <- approval
}
return nil
}
2017-08-19 18:30:10 +00:00
// Create - creates new approval request and publishes to all subscribers
2017-08-13 21:07:10 +00:00
func (m *DefaultManager) Create(r *types.Approval) error {
2017-08-21 22:43:59 +00:00
_, err := m.Get(r.Identifier)
2017-08-13 21:07:10 +00:00
if err == nil {
return ErrApprovalAlreadyExists
}
2017-08-28 19:16:40 +00:00
r.CreatedAt = time.Now()
r.UpdatedAt = time.Now()
2018-10-07 13:07:03 +00:00
bts, err := json.Marshal(r)
2017-08-13 21:07:10 +00:00
if err != nil {
return err
}
2017-09-12 13:37:21 +00:00
err = m.cache.Put(getKey(r.Identifier), bts)
2017-08-19 18:30:10 +00:00
if err != nil {
return err
}
2017-08-28 12:30:23 +00:00
return m.publishRequest(r)
2017-08-13 21:07:10 +00:00
}
2017-09-12 13:37:21 +00:00
// Update - update approval
2017-08-13 21:07:10 +00:00
func (m *DefaultManager) Update(r *types.Approval) error {
2017-08-21 22:43:59 +00:00
existing, err := m.Get(r.Identifier)
2017-08-13 21:07:10 +00:00
if err != nil {
return err
}
r.CreatedAt = existing.CreatedAt
r.UpdatedAt = time.Now()
2018-10-07 13:07:03 +00:00
bts, err := json.Marshal(r)
2017-08-13 21:07:10 +00:00
if err != nil {
return err
}
2017-08-21 22:43:59 +00:00
if r.Status() == types.ApprovalStatusApproved {
2017-08-28 12:30:23 +00:00
err = m.publishApproved(r)
2017-08-16 21:26:54 +00:00
if err != nil {
log.WithFields(log.Fields{
"error": err,
"approval": r.Identifier,
"provider": r.Provider,
}).Error("approvals.manager: failed to re-submit event after approvals were collected")
}
}
2017-08-13 21:07:10 +00:00
2017-09-12 13:37:21 +00:00
return m.cache.Put(getKey(r.Identifier), bts)
2017-08-13 21:07:10 +00:00
}
2017-08-16 21:26:54 +00:00
// Approve - increase VotesReceived by 1 and returns updated version
2017-09-12 16:16:15 +00:00
func (m *DefaultManager) Approve(identifier, voter string) (*types.Approval, error) {
2017-08-16 21:26:54 +00:00
m.mu.Lock()
defer m.mu.Unlock()
2017-08-21 22:43:59 +00:00
existing, err := m.Get(identifier)
2017-08-16 21:26:54 +00:00
if err != nil {
2017-08-28 12:30:23 +00:00
log.WithFields(log.Fields{
"identifier": identifier,
"error": err,
}).Error("approvals.manager: failed to get")
2017-08-16 21:26:54 +00:00
return nil, err
}
2017-09-12 16:16:15 +00:00
for _, v := range existing.Voters {
if v == voter {
// nothing to do, same voter
return existing, nil
}
}
existing.Voters = append(existing.Voters, voter)
2017-08-16 21:26:54 +00:00
existing.VotesReceived++
err = m.Update(existing)
if err != nil {
2017-08-28 12:30:23 +00:00
log.WithFields(log.Fields{
"identifier": identifier,
"error": err,
}).Error("approvals.manager: failed to update")
2017-08-16 21:26:54 +00:00
return nil, err
}
2017-08-28 12:30:23 +00:00
log.WithFields(log.Fields{
"identifier": identifier,
}).Info("approvals.manager: approved")
2017-08-16 21:26:54 +00:00
return existing, nil
}
// Reject - rejects approval (marks rejected=true), approval will not be valid even if it
// collects required votes
2017-08-21 22:43:59 +00:00
func (m *DefaultManager) Reject(identifier string) (*types.Approval, error) {
2017-08-16 21:26:54 +00:00
m.mu.Lock()
defer m.mu.Unlock()
2017-08-21 22:43:59 +00:00
existing, err := m.Get(identifier)
2017-08-16 21:26:54 +00:00
if err != nil {
return nil, err
}
existing.Rejected = true
err = m.Update(existing)
if err != nil {
return nil, err
}
return existing, nil
}
2017-09-12 13:37:21 +00:00
// Get - get specified approval
2017-08-21 22:43:59 +00:00
func (m *DefaultManager) Get(identifier string) (*types.Approval, error) {
2017-09-12 13:37:21 +00:00
bts, err := m.cache.Get(getKey(identifier))
2017-08-13 21:07:10 +00:00
if err != nil {
return nil, err
}
var approval types.Approval
2018-10-07 13:07:03 +00:00
err = json.Unmarshal(bts, &approval)
2017-08-13 21:07:10 +00:00
return &approval, err
}
2017-09-12 13:37:21 +00:00
// List - list approvals
2017-08-21 22:43:59 +00:00
func (m *DefaultManager) List() ([]*types.Approval, error) {
bts, err := m.cache.List(ApprovalsPrefix)
2017-08-13 21:07:10 +00:00
if err != nil {
return nil, err
}
var approvals []*types.Approval
for _, v := range bts {
var approval types.Approval
2018-10-07 13:07:03 +00:00
err = json.Unmarshal(v, &approval)
2017-08-13 21:07:10 +00:00
if err != nil {
log.WithFields(log.Fields{
2018-10-07 13:07:03 +00:00
"error": err,
"payload": string(v),
2017-08-13 21:07:10 +00:00
}).Error("approvals.manager: failed to decode payload")
continue
}
approvals = append(approvals, &approval)
}
return approvals, nil
}
2017-09-12 13:37:21 +00:00
// Delete - delete specified approval
2017-08-21 22:43:59 +00:00
func (m *DefaultManager) Delete(identifier string) error {
2017-09-12 13:37:21 +00:00
return m.cache.Delete(getKey(identifier))
2017-08-13 21:07:10 +00:00
}
2017-08-21 22:43:59 +00:00
func getKey(identifier string) string {
return ApprovalsPrefix + "/" + identifier
2017-08-13 21:07:10 +00:00
}