2017-08-13 21:07:10 +00:00
|
|
|
package approvals
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2017-08-16 21:26:54 +00:00
|
|
|
"sync"
|
2017-08-13 21:07:10 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/rusenask/keel/cache"
|
2017-08-16 21:26:54 +00:00
|
|
|
"github.com/rusenask/keel/provider"
|
2017-08-13 21:07:10 +00:00
|
|
|
"github.com/rusenask/keel/types"
|
|
|
|
"github.com/rusenask/keel/util/codecs"
|
|
|
|
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Manager is used to manage updates
|
|
|
|
type Manager interface {
|
|
|
|
// 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
|
|
|
|
Approve(provider types.ProviderType, identifier string) (*types.Approval, error)
|
|
|
|
// Rejects Approval
|
|
|
|
Reject(provider types.ProviderType, identifier string) (*types.Approval, error)
|
|
|
|
|
2017-08-13 21:07:10 +00:00
|
|
|
Get(provider types.ProviderType, identifier string) (*types.Approval, error)
|
|
|
|
List(provider types.ProviderType) ([]*types.Approval, error)
|
|
|
|
Delete(provider types.ProviderType, identifier string) error
|
|
|
|
}
|
|
|
|
|
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>
|
|
|
|
cache cache.Cache
|
|
|
|
serializer codecs.Serializer
|
2017-08-16 21:26:54 +00:00
|
|
|
|
|
|
|
// providers are used to re-submit event
|
|
|
|
// when all approvals are collected
|
|
|
|
providers provider.Providers
|
|
|
|
|
|
|
|
mu *sync.Mutex
|
2017-08-13 21:07:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// New create new instance of default manager
|
2017-08-16 21:26:54 +00:00
|
|
|
func New(cache cache.Cache, serializer codecs.Serializer, providers provider.Providers) *DefaultManager {
|
2017-08-13 21:07:10 +00:00
|
|
|
return &DefaultManager{
|
|
|
|
cache: cache,
|
|
|
|
serializer: serializer,
|
2017-08-16 21:26:54 +00:00
|
|
|
providers: providers,
|
|
|
|
mu: &sync.Mutex{},
|
2017-08-13 21:07:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *DefaultManager) Create(r *types.Approval) error {
|
|
|
|
_, err := m.Get(r.Provider, r.Identifier)
|
|
|
|
if err == nil {
|
|
|
|
return ErrApprovalAlreadyExists
|
|
|
|
}
|
|
|
|
|
|
|
|
bts, err := m.serializer.Encode(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := cache.SetContextExpiration(context.Background(), r.Deadline)
|
|
|
|
|
|
|
|
return m.cache.Put(ctx, getKey(r.Provider, r.Identifier), bts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *DefaultManager) Update(r *types.Approval) error {
|
|
|
|
existing, err := m.Get(r.Provider, r.Identifier)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
r.CreatedAt = existing.CreatedAt
|
|
|
|
r.UpdatedAt = time.Now()
|
|
|
|
|
|
|
|
bts, err := m.serializer.Encode(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-08-16 21:26:54 +00:00
|
|
|
if r.Approved() {
|
|
|
|
err = m.providers.Submit(*r.Event)
|
|
|
|
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-08-16 21:26:54 +00:00
|
|
|
ctx := cache.SetContextExpiration(context.Background(), r.Deadline)
|
2017-08-13 21:07:10 +00:00
|
|
|
return m.cache.Put(ctx, getKey(r.Provider, r.Identifier), bts)
|
|
|
|
}
|
|
|
|
|
2017-08-16 21:26:54 +00:00
|
|
|
// Approve - increase VotesReceived by 1 and returns updated version
|
|
|
|
func (m *DefaultManager) Approve(provider types.ProviderType, identifier string) (*types.Approval, error) {
|
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
|
|
|
existing, err := m.Get(provider, identifier)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
existing.VotesReceived++
|
|
|
|
|
|
|
|
err = m.Update(existing)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return existing, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reject - rejects approval (marks rejected=true), approval will not be valid even if it
|
|
|
|
// collects required votes
|
|
|
|
func (m *DefaultManager) Reject(provider types.ProviderType, identifier string) (*types.Approval, error) {
|
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
|
|
|
existing, err := m.Get(provider, identifier)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
existing.Rejected = true
|
|
|
|
|
|
|
|
err = m.Update(existing)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return existing, nil
|
|
|
|
}
|
|
|
|
|
2017-08-13 21:07:10 +00:00
|
|
|
func (m *DefaultManager) Get(provider types.ProviderType, identifier string) (*types.Approval, error) {
|
|
|
|
bts, err := m.cache.Get(context.Background(), getKey(provider, identifier))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var approval types.Approval
|
|
|
|
err = m.serializer.Decode(bts, &approval)
|
|
|
|
return &approval, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *DefaultManager) List(provider types.ProviderType) ([]*types.Approval, error) {
|
|
|
|
prefix := ""
|
|
|
|
if provider != types.ProviderTypeUnknown {
|
|
|
|
prefix = provider.String()
|
|
|
|
}
|
|
|
|
bts, err := m.cache.List(prefix)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var approvals []*types.Approval
|
|
|
|
for _, v := range bts {
|
|
|
|
var approval types.Approval
|
|
|
|
err = m.serializer.Decode(v, &approval)
|
|
|
|
if err != nil {
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"error": err,
|
|
|
|
}).Error("approvals.manager: failed to decode payload")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
approvals = append(approvals, &approval)
|
|
|
|
}
|
|
|
|
return approvals, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
func (m *DefaultManager) Delete(provider types.ProviderType, identifier string) error {
|
|
|
|
return m.cache.Delete(context.Background(), getKey(provider, identifier))
|
|
|
|
}
|
|
|
|
|
|
|
|
func getKey(provider types.ProviderType, identifier string) string {
|
|
|
|
return ApprovalsPrefix + "/" + provider.String() + "/" + identifier
|
|
|
|
}
|