commit
8b07db1f47
|
@ -0,0 +1,369 @@
|
|||
package approvals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/cache"
|
||||
"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 {
|
||||
// Subscribe for approval request events, subscriber should provide
|
||||
// its name. Indented to be used by extensions that collect
|
||||
// approvals
|
||||
Subscribe(ctx context.Context) (<-chan *types.Approval, error)
|
||||
|
||||
// SubscribeApproved - is used to get approved events by the manager
|
||||
SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error)
|
||||
|
||||
// request approval for deployment/release/etc..
|
||||
Create(r *types.Approval) error
|
||||
// Update whole approval object
|
||||
Update(r *types.Approval) error
|
||||
|
||||
// Increases Approval votes by 1
|
||||
Approve(identifier, voter string) (*types.Approval, error)
|
||||
// Rejects Approval
|
||||
Reject(identifier string) (*types.Approval, error)
|
||||
|
||||
Get(identifier string) (*types.Approval, error)
|
||||
List() ([]*types.Approval, error)
|
||||
Delete(identifier string) error
|
||||
|
||||
StartExpiryService(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Approvals related errors
|
||||
var (
|
||||
ErrApprovalAlreadyExists = errors.New("approval already exists")
|
||||
)
|
||||
|
||||
// Approvals cache prefix
|
||||
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
|
||||
|
||||
// subscriber channels
|
||||
channels map[uint64]chan *types.Approval
|
||||
index uint64
|
||||
|
||||
// approved channels
|
||||
approvedCh map[uint64]chan *types.Approval
|
||||
|
||||
mu *sync.Mutex
|
||||
subMu *sync.RWMutex
|
||||
}
|
||||
|
||||
// New create new instance of default manager
|
||||
func New(cache cache.Cache, serializer codecs.Serializer) *DefaultManager {
|
||||
man := &DefaultManager{
|
||||
cache: cache,
|
||||
serializer: serializer,
|
||||
channels: make(map[uint64]chan *types.Approval),
|
||||
approvedCh: make(map[uint64]chan *types.Approval),
|
||||
index: 0,
|
||||
mu: &sync.Mutex{},
|
||||
subMu: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
return man
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
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
|
||||
err = m.serializer.Decode(v, &approval)
|
||||
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
|
||||
}
|
||||
|
||||
// Subscribe - subscribe for approval events
|
||||
func (m *DefaultManager) Subscribe(ctx context.Context) (<-chan *types.Approval, error) {
|
||||
m.subMu.Lock()
|
||||
index := atomic.AddUint64(&m.index, 1)
|
||||
approvalsCh := make(chan *types.Approval, 10)
|
||||
m.channels[index] = approvalsCh
|
||||
m.subMu.Unlock()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.subMu.Lock()
|
||||
|
||||
delete(m.channels, index)
|
||||
|
||||
m.subMu.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return approvalsCh, nil
|
||||
}
|
||||
|
||||
// SubscribeApproved - subscribe for approved update requests
|
||||
func (m *DefaultManager) SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error) {
|
||||
m.subMu.Lock()
|
||||
index := atomic.AddUint64(&m.index, 1)
|
||||
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()
|
||||
|
||||
for _, subscriber := range m.channels {
|
||||
subscriber <- approval
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *DefaultManager) publishApproved(approval *types.Approval) error {
|
||||
m.subMu.RLock()
|
||||
defer m.subMu.RUnlock()
|
||||
|
||||
for _, subscriber := range m.approvedCh {
|
||||
subscriber <- approval
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create - creates new approval request and publishes to all subscribers
|
||||
func (m *DefaultManager) Create(r *types.Approval) error {
|
||||
_, err := m.Get(r.Identifier)
|
||||
if err == nil {
|
||||
return ErrApprovalAlreadyExists
|
||||
}
|
||||
|
||||
r.CreatedAt = time.Now()
|
||||
r.UpdatedAt = time.Now()
|
||||
|
||||
bts, err := m.serializer.Encode(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.cache.Put(getKey(r.Identifier), bts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.publishRequest(r)
|
||||
}
|
||||
|
||||
// Update - update approval
|
||||
func (m *DefaultManager) Update(r *types.Approval) error {
|
||||
existing, err := m.Get(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
|
||||
}
|
||||
|
||||
if r.Status() == types.ApprovalStatusApproved {
|
||||
err = m.publishApproved(r)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
return m.cache.Put(getKey(r.Identifier), bts)
|
||||
}
|
||||
|
||||
// Approve - increase VotesReceived by 1 and returns updated version
|
||||
func (m *DefaultManager) Approve(identifier, voter string) (*types.Approval, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
existing, err := m.Get(identifier)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"identifier": identifier,
|
||||
"error": err,
|
||||
}).Error("approvals.manager: failed to get")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range existing.Voters {
|
||||
if v == voter {
|
||||
// nothing to do, same voter
|
||||
return existing, nil
|
||||
}
|
||||
}
|
||||
|
||||
existing.Voters = append(existing.Voters, voter)
|
||||
existing.VotesReceived++
|
||||
|
||||
err = m.Update(existing)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"identifier": identifier,
|
||||
"error": err,
|
||||
}).Error("approvals.manager: failed to update")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"identifier": identifier,
|
||||
}).Info("approvals.manager: approved")
|
||||
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
// Reject - rejects approval (marks rejected=true), approval will not be valid even if it
|
||||
// collects required votes
|
||||
func (m *DefaultManager) Reject(identifier string) (*types.Approval, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
existing, err := m.Get(identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing.Rejected = true
|
||||
|
||||
err = m.Update(existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
// Get - get specified approval
|
||||
func (m *DefaultManager) Get(identifier string) (*types.Approval, error) {
|
||||
bts, err := m.cache.Get(getKey(identifier))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var approval types.Approval
|
||||
err = m.serializer.Decode(bts, &approval)
|
||||
return &approval, err
|
||||
}
|
||||
|
||||
// List - list approvals
|
||||
func (m *DefaultManager) List() ([]*types.Approval, error) {
|
||||
bts, err := m.cache.List(ApprovalsPrefix)
|
||||
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
|
||||
}
|
||||
|
||||
// Delete - delete specified approval
|
||||
func (m *DefaultManager) Delete(identifier string) error {
|
||||
return m.cache.Delete(getKey(identifier))
|
||||
}
|
||||
|
||||
func getKey(identifier string) string {
|
||||
return ApprovalsPrefix + "/" + identifier
|
||||
}
|
|
@ -0,0 +1,366 @@
|
|||
package approvals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
)
|
||||
|
||||
func TestCreateApproval(t *testing.T) {
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
am := New(mem, codecs.DefaultSerializer())
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
stored, err := am.Get("xxx/app-1")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get approval: %s", err)
|
||||
}
|
||||
|
||||
if stored.CurrentVersion != "1.2.3" {
|
||||
t.Errorf("unexpected version: %s", stored.CurrentVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteApproval(t *testing.T) {
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
am := New(mem, codecs.DefaultSerializer())
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
err = am.Delete("xxx/app-1")
|
||||
if err != nil {
|
||||
t.Errorf("failed to delete approval: %s", err)
|
||||
}
|
||||
|
||||
_, err = am.Get("xxx/app-1")
|
||||
if err == nil {
|
||||
t.Errorf("expected to get an error when retrieving deleted approval")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateApproval(t *testing.T) {
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
am := New(mem, codecs.DefaultSerializer())
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
VotesRequired: 1,
|
||||
VotesReceived: 0,
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
Event: &types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "very/repo",
|
||||
Tag: "1.2.5",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
ch, err := am.SubscribeApproved(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to subscribe: %s", err)
|
||||
}
|
||||
|
||||
err = am.Update(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
VotesRequired: 1,
|
||||
VotesReceived: 1,
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
Event: &types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "very/repo",
|
||||
Tag: "1.2.5",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
approved := <-ch
|
||||
|
||||
if approved.Event.Repository.Name != "very/repo" {
|
||||
t.Errorf("unexpected repo name in re-submitted event: %s", approved.Event.Repository.Name)
|
||||
}
|
||||
if approved.Event.Repository.Tag != "1.2.5" {
|
||||
t.Errorf("unexpected repo tag in re-submitted event: %s", approved.Event.Repository.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateApprovalRejected(t *testing.T) {
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
am := New(mem, codecs.DefaultSerializer())
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
VotesRequired: 1,
|
||||
VotesReceived: 0,
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
Event: &types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "very/repo",
|
||||
Tag: "1.2.5",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
ch, err := am.SubscribeApproved(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to subscribe: %s", err)
|
||||
}
|
||||
|
||||
// rejecting
|
||||
err = am.Update(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
VotesRequired: 1,
|
||||
VotesReceived: 0,
|
||||
Rejected: true,
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
Event: &types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "very/repo",
|
||||
Tag: "1.2.5",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to update approval: %s", err)
|
||||
}
|
||||
|
||||
// sending vote
|
||||
err = am.Update(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
VotesRequired: 1,
|
||||
VotesReceived: 1,
|
||||
Rejected: true,
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
Event: &types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "very/repo",
|
||||
Tag: "1.2.5",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to update approval: %s", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
// success
|
||||
return
|
||||
case approval := <-ch:
|
||||
t.Errorf("unexpected approval got: %s", approval.Identifier)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestApprove(t *testing.T) {
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
am := New(mem, codecs.DefaultSerializer())
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1:1.2.5",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
VotesRequired: 2,
|
||||
VotesReceived: 0,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
am.Approve("xxx/app-1:1.2.5", "warda")
|
||||
|
||||
stored, err := am.Get("xxx/app-1:1.2.5")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get approval: %s", err)
|
||||
}
|
||||
|
||||
if stored.VotesReceived != 1 {
|
||||
t.Errorf("unexpected number of received votes: %d", stored.VotesReceived)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApproveTwiceSameVoter(t *testing.T) {
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
am := New(mem, codecs.DefaultSerializer())
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1:1.2.5",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
VotesRequired: 2,
|
||||
VotesReceived: 0,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
am.Approve("xxx/app-1:1.2.5", "warda")
|
||||
am.Approve("xxx/app-1:1.2.5", "warda")
|
||||
|
||||
stored, err := am.Get("xxx/app-1:1.2.5")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get approval: %s", err)
|
||||
}
|
||||
|
||||
// should still be the same
|
||||
if stored.VotesReceived != 1 {
|
||||
t.Errorf("unexpected number of received votes: %d", stored.VotesReceived)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApproveTwoVoters(t *testing.T) {
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
am := New(mem, codecs.DefaultSerializer())
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1:1.2.5",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
VotesRequired: 2,
|
||||
VotesReceived: 0,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
am.Approve("xxx/app-1:1.2.5", "w")
|
||||
am.Approve("xxx/app-1:1.2.5", "k")
|
||||
|
||||
stored, err := am.Get("xxx/app-1:1.2.5")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get approval: %s", err)
|
||||
}
|
||||
|
||||
// should still be the same
|
||||
if stored.VotesReceived != 2 {
|
||||
t.Errorf("unexpected number of received votes: %d", stored.VotesReceived)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReject(t *testing.T) {
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
am := New(mem, codecs.DefaultSerializer())
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
Deadline: time.Now().Add(5 * time.Minute),
|
||||
VotesRequired: 2,
|
||||
VotesReceived: 0,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
am.Reject("xxx/app-1")
|
||||
|
||||
stored, err := am.Get("xxx/app-1")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get approval: %s", err)
|
||||
}
|
||||
|
||||
if !stored.Rejected {
|
||||
t.Errorf("unexpected approval to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpire(t *testing.T) {
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
am := New(mem, codecs.DefaultSerializer())
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: "xxx/app-1",
|
||||
CurrentVersion: "1.2.3",
|
||||
NewVersion: "1.2.5",
|
||||
Deadline: time.Now().Add(-5 * time.Minute),
|
||||
VotesRequired: 2,
|
||||
VotesReceived: 0,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
err = am.expireEntries()
|
||||
if err != nil {
|
||||
t.Errorf("got error while expiring entries: %s", err)
|
||||
}
|
||||
|
||||
_, err = am.Get("xxx/app-1")
|
||||
if err == nil {
|
||||
t.Errorf("expected approval to be deleted but didn't get an error")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
package bot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nlopes/slack"
|
||||
"github.com/rusenask/keel/bot/formatter"
|
||||
"github.com/rusenask/keel/types"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (b *Bot) subscribeForApprovals() error {
|
||||
approvalsCh, err := b.approvalsManager.Subscribe(b.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
return nil
|
||||
case a := <-approvalsCh:
|
||||
err = b.requestApproval(a)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"approval": a.Identifier,
|
||||
}).Error("bot.subscribeForApprovals: approval request failed")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request - request approval
|
||||
func (b *Bot) requestApproval(req *types.Approval) error {
|
||||
return b.postMessage(
|
||||
"Approval required",
|
||||
req.Message,
|
||||
types.LevelSuccess.Color(),
|
||||
[]slack.AttachmentField{
|
||||
slack.AttachmentField{
|
||||
Title: "Approval required!",
|
||||
Value: req.Message + "\n" + fmt.Sprintf("To vote for change type '%s approve %s' to reject it: '%s reject %s'.", b.name, req.Identifier, b.name, req.Identifier),
|
||||
Short: false,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Votes",
|
||||
Value: fmt.Sprintf("%d/%d", req.VotesReceived, req.VotesRequired),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Delta",
|
||||
Value: req.Delta(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Identifier",
|
||||
Value: req.Identifier,
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Provider",
|
||||
Value: req.Provider.String(),
|
||||
Short: true,
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
func (b *Bot) processApprovalResponses() error {
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
return nil
|
||||
case resp := <-b.approvalsRespCh:
|
||||
|
||||
switch resp.Status {
|
||||
case types.ApprovalStatusApproved:
|
||||
err := b.processApprovedResponse(resp)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("bot.processApprovalResponses: failed to process approval response message")
|
||||
}
|
||||
case types.ApprovalStatusRejected:
|
||||
err := b.processRejectedResponse(resp)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("bot.processApprovalResponses: failed to process approval reject response message")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bot) processApprovedResponse(approvalResponse *approvalResponse) error {
|
||||
trimmed := strings.TrimPrefix(approvalResponse.Text, approvalResponseKeyword)
|
||||
identifiers := strings.Split(trimmed, " ")
|
||||
if len(identifiers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, identifier := range identifiers {
|
||||
if identifier == "" {
|
||||
continue
|
||||
}
|
||||
approval, err := b.approvalsManager.Approve(identifier, approvalResponse.User)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"identifier": identifier,
|
||||
}).Error("bot.processApprovedResponse: failed to approve")
|
||||
continue
|
||||
}
|
||||
|
||||
err = b.replyToApproval(approval)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"identifier": identifier,
|
||||
}).Error("bot.processApprovedResponse: got error while replying after processing approved approval")
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bot) processRejectedResponse(approvalResponse *approvalResponse) error {
|
||||
trimmed := strings.TrimPrefix(approvalResponse.Text, rejectResponseKeyword)
|
||||
identifiers := strings.Split(trimmed, " ")
|
||||
if len(identifiers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, identifier := range identifiers {
|
||||
approval, err := b.approvalsManager.Reject(identifier)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"identifier": identifier,
|
||||
}).Error("bot.processApprovedResponse: failed to reject")
|
||||
continue
|
||||
}
|
||||
|
||||
err = b.replyToApproval(approval)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"identifier": identifier,
|
||||
}).Error("bot.processApprovedResponse: got error while replying after processing rejected approval")
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bot) replyToApproval(approval *types.Approval) error {
|
||||
switch approval.Status() {
|
||||
case types.ApprovalStatusPending:
|
||||
b.postMessage(
|
||||
"Vote received",
|
||||
"All approvals received, thanks for voting!",
|
||||
types.LevelInfo.Color(),
|
||||
[]slack.AttachmentField{
|
||||
slack.AttachmentField{
|
||||
Title: "vote received!",
|
||||
Value: "Waiting for remaining votes.",
|
||||
Short: false,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Votes",
|
||||
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Delta",
|
||||
Value: approval.Delta(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Identifier",
|
||||
Value: approval.Identifier,
|
||||
Short: true,
|
||||
},
|
||||
})
|
||||
case types.ApprovalStatusRejected:
|
||||
b.postMessage(
|
||||
"Change rejected",
|
||||
"Change was rejected",
|
||||
types.LevelWarn.Color(),
|
||||
[]slack.AttachmentField{
|
||||
slack.AttachmentField{
|
||||
Title: "change rejected",
|
||||
Value: "Change was rejected.",
|
||||
Short: false,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Status",
|
||||
Value: approval.Status().String(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Votes",
|
||||
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Delta",
|
||||
Value: approval.Delta(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Identifier",
|
||||
Value: approval.Identifier,
|
||||
Short: true,
|
||||
},
|
||||
})
|
||||
case types.ApprovalStatusApproved:
|
||||
b.postMessage(
|
||||
"approval received",
|
||||
"All approvals received, thanks for voting!",
|
||||
types.LevelSuccess.Color(),
|
||||
[]slack.AttachmentField{
|
||||
slack.AttachmentField{
|
||||
Title: "update approved!",
|
||||
Value: "All approvals received, thanks for voting!",
|
||||
Short: false,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Votes",
|
||||
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Delta",
|
||||
Value: approval.Delta(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
Title: "Identifier",
|
||||
Value: approval.Identifier,
|
||||
Short: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bot) approvalsResponse() string {
|
||||
approvals, err := b.approvalsManager.List()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("got error while fetching approvals: %s", err)
|
||||
}
|
||||
|
||||
if len(approvals) == 0 {
|
||||
return fmt.Sprintf("there are currently no request waiting to be approved.")
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
approvalCtx := formatter.Context{
|
||||
Output: buf,
|
||||
Format: formatter.NewApprovalsFormat(formatter.TableFormatKey, false),
|
||||
}
|
||||
err = formatter.ApprovalWrite(approvalCtx, approvals)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Sprintf("got error while formatting approvals: %s", err)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (b *Bot) removeApprovalHandler(identifier string) string {
|
||||
err := b.approvalsManager.Delete(identifier)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("failed to remove '%s' approval: %s.", identifier, err)
|
||||
}
|
||||
return fmt.Sprintf("approval '%s' removed.", identifier)
|
||||
}
|
122
bot/bot.go
122
bot/bot.go
|
@ -2,22 +2,35 @@ package bot
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nlopes/slack"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/provider/kubernetes"
|
||||
"github.com/rusenask/keel/types"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
removeApprovalPrefix = "rm approval"
|
||||
)
|
||||
|
||||
var (
|
||||
botEventTextToResponse = map[string][]string{
|
||||
"help": {
|
||||
`Here's a list of supported commands`,
|
||||
`- "get deployments" -> get a list of all deployments`,
|
||||
`- "get approvals" -> get a list of approvals`,
|
||||
`- "rm approval <approval identifier>" -> remove approval`,
|
||||
`- "approve <approval identifier>" -> approve update request`,
|
||||
`- "reject <approval identifier>" -> reject update request`,
|
||||
// `- "get deployments all" -> get a list of all deployments`,
|
||||
// `- "describe deployment <deployment>" -> get details for specified deployment`,
|
||||
},
|
||||
|
@ -25,14 +38,30 @@ var (
|
|||
|
||||
// static bot commands can be used straight away
|
||||
staticBotCommands = map[string]bool{
|
||||
"get deployments": true,
|
||||
"get deployments all": true,
|
||||
"get deployments": true,
|
||||
"get approvals": true,
|
||||
}
|
||||
|
||||
// dynamic bot command prefixes have to be matched
|
||||
dynamicBotCommandPrefixes = []string{"describe deployment"}
|
||||
dynamicBotCommandPrefixes = []string{removeApprovalPrefix}
|
||||
|
||||
approvalResponseKeyword = "approve"
|
||||
rejectResponseKeyword = "reject"
|
||||
)
|
||||
|
||||
// SlackImplementer - implementes slack HTTP functionality, used to
|
||||
// send messages with attachments
|
||||
type SlackImplementer interface {
|
||||
PostMessage(channel, text string, params slack.PostMessageParameters) (string, string, error)
|
||||
}
|
||||
|
||||
// approvalResponse - used to track approvals once vote begins
|
||||
type approvalResponse struct {
|
||||
User string
|
||||
Status types.ApprovalStatus
|
||||
Text string
|
||||
}
|
||||
|
||||
// Bot - main slack bot container
|
||||
type Bot struct {
|
||||
id string // bot id
|
||||
|
@ -45,20 +74,31 @@ type Bot struct {
|
|||
slackClient *slack.Client
|
||||
slackRTM *slack.RTM
|
||||
|
||||
slackHTTPClient SlackImplementer
|
||||
|
||||
approvalsRespCh chan *approvalResponse
|
||||
|
||||
approvalsManager approvals.Manager
|
||||
|
||||
k8sImplementer kubernetes.Implementer
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// New - create new bot instance
|
||||
func New(name, token string, k8sImplementer kubernetes.Implementer) *Bot {
|
||||
func New(name, token string, k8sImplementer kubernetes.Implementer, approvalsManager approvals.Manager) *Bot {
|
||||
client := slack.New(token)
|
||||
|
||||
return &Bot{
|
||||
slackClient: client,
|
||||
k8sImplementer: k8sImplementer,
|
||||
name: name,
|
||||
bot := &Bot{
|
||||
slackClient: client,
|
||||
slackHTTPClient: client,
|
||||
k8sImplementer: k8sImplementer,
|
||||
name: name,
|
||||
approvalsManager: approvalsManager,
|
||||
approvalsRespCh: make(chan *approvalResponse), // don't add buffer to make it blocking
|
||||
}
|
||||
|
||||
return bot
|
||||
}
|
||||
|
||||
// Start - start bot
|
||||
|
@ -90,8 +130,15 @@ func (b *Bot) Start(ctx context.Context) error {
|
|||
|
||||
b.msgPrefix = strings.ToLower("<@" + b.id + ">")
|
||||
|
||||
// processing messages coming from slack RTM client
|
||||
go b.startInternal()
|
||||
|
||||
// processing slack approval responses
|
||||
go b.processApprovalResponses()
|
||||
|
||||
// subscribing for approval requests
|
||||
go b.subscribeForApprovals()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -140,6 +187,49 @@ func (b *Bot) startInternal() error {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *Bot) postMessage(title, message, color string, fields []slack.AttachmentField) error {
|
||||
params := slack.NewPostMessageParameters()
|
||||
params.Username = b.name
|
||||
|
||||
params.Attachments = []slack.Attachment{
|
||||
slack.Attachment{
|
||||
Fallback: message,
|
||||
Color: color,
|
||||
Fields: fields,
|
||||
Footer: "https://keel.sh",
|
||||
Ts: json.Number(strconv.Itoa(int(time.Now().Unix()))),
|
||||
},
|
||||
}
|
||||
|
||||
_, _, err := b.slackHTTPClient.PostMessage("general", "", params)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("bot.postMessage: failed to send message")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Bot) isApproval(event *slack.MessageEvent, eventText string) (resp *approvalResponse, ok bool) {
|
||||
if strings.HasPrefix(strings.ToLower(eventText), approvalResponseKeyword) {
|
||||
return &approvalResponse{
|
||||
User: event.User,
|
||||
Status: types.ApprovalStatusApproved,
|
||||
Text: eventText,
|
||||
}, true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(eventText), rejectResponseKeyword) {
|
||||
return &approvalResponse{
|
||||
User: event.User,
|
||||
Status: types.ApprovalStatusRejected,
|
||||
Text: eventText,
|
||||
}, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (b *Bot) handleMessage(event *slack.MessageEvent) {
|
||||
if event.BotID != "" || event.User == "" || event.SubType == "bot_message" {
|
||||
log.WithFields(log.Fields{
|
||||
|
@ -152,12 +242,16 @@ func (b *Bot) handleMessage(event *slack.MessageEvent) {
|
|||
|
||||
eventText := strings.Trim(strings.ToLower(event.Text), " \n\r")
|
||||
|
||||
// All messages past this point are directed to @gopher itself
|
||||
if !b.isBotMessage(event, eventText) {
|
||||
return
|
||||
}
|
||||
|
||||
eventText = b.trimBot(eventText)
|
||||
approval, ok := b.isApproval(event, eventText)
|
||||
if ok {
|
||||
b.approvalsRespCh <- approval
|
||||
return
|
||||
}
|
||||
|
||||
// Responses that are just a canned string response
|
||||
if responseLines, ok := botEventTextToResponse[eventText]; ok {
|
||||
|
@ -200,6 +294,16 @@ func (b *Bot) handleCommand(event *slack.MessageEvent, eventText string) {
|
|||
response := b.deploymentsResponse(Filter{})
|
||||
b.respond(event, formatAsSnippet(response))
|
||||
return
|
||||
case "get approvals":
|
||||
response := b.approvalsResponse()
|
||||
b.respond(event, formatAsSnippet(response))
|
||||
return
|
||||
}
|
||||
|
||||
// handle dynamic commands
|
||||
if strings.HasPrefix(eventText, removeApprovalPrefix) {
|
||||
b.respond(event, formatAsSnippet(b.removeApprovalHandler(strings.TrimSpace(strings.TrimPrefix(eventText, removeApprovalPrefix)))))
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("command not found")
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
package bot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/nlopes/slack"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/constants"
|
||||
"github.com/rusenask/keel/extension/approval"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
|
||||
"testing"
|
||||
|
||||
testutil "github.com/rusenask/keel/util/testing"
|
||||
)
|
||||
|
||||
type fakeProvider struct {
|
||||
submitted []types.Event
|
||||
images []*types.TrackedImage
|
||||
}
|
||||
|
||||
func (p *fakeProvider) Submit(event types.Event) error {
|
||||
p.submitted = append(p.submitted, event)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fakeProvider) TrackedImages() ([]*types.TrackedImage, error) {
|
||||
return p.images, nil
|
||||
}
|
||||
|
||||
func (p *fakeProvider) List() []string {
|
||||
return []string{"fakeprovider"}
|
||||
}
|
||||
func (p *fakeProvider) Stop() {
|
||||
return
|
||||
}
|
||||
func (p *fakeProvider) GetName() string {
|
||||
return "fp"
|
||||
}
|
||||
|
||||
type postedMessage struct {
|
||||
channel string
|
||||
text string
|
||||
params slack.PostMessageParameters
|
||||
}
|
||||
|
||||
type fakeSlackImplementer struct {
|
||||
postedMessages []postedMessage
|
||||
}
|
||||
|
||||
func (i *fakeSlackImplementer) PostMessage(channel, text string, params slack.PostMessageParameters) (string, string, error) {
|
||||
i.postedMessages = append(i.postedMessages, postedMessage{
|
||||
channel: channel,
|
||||
text: text,
|
||||
params: params,
|
||||
})
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func TestBotRequest(t *testing.T) {
|
||||
f8s := &testutil.FakeK8sImplementer{}
|
||||
|
||||
fi := &fakeSlackImplementer{}
|
||||
mem := memory.NewMemoryCache(100*time.Second, 100*time.Second, 10*time.Second)
|
||||
|
||||
token := os.Getenv(constants.EnvSlackToken)
|
||||
if token == "" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
|
||||
bot := New("keel", token, f8s, am)
|
||||
// replacing slack client so we can receive webhooks
|
||||
bot.slackHTTPClient = fi
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
err := bot.Start(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start bot: %s", err)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = am.Create(&types.Approval{
|
||||
Identifier: "k8s/project/repo:1.2.3",
|
||||
VotesRequired: 1,
|
||||
CurrentVersion: "2.3.4",
|
||||
NewVersion: "3.4.5",
|
||||
Event: &types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "project/repo",
|
||||
Tag: "2.3.4",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error while creating : %s", err)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if len(fi.postedMessages) != 1 {
|
||||
t.Errorf("expected to find one message, but got: %d", len(fi.postedMessages))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessApprovedResponse(t *testing.T) {
|
||||
f8s := &testutil.FakeK8sImplementer{}
|
||||
fi := &fakeSlackImplementer{}
|
||||
mem := memory.NewMemoryCache(100*time.Second, 100*time.Second, 10*time.Second)
|
||||
|
||||
token := os.Getenv(constants.EnvSlackToken)
|
||||
if token == "" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
|
||||
bot := New("keel", token, f8s, am)
|
||||
// replacing slack client so we can receive webhooks
|
||||
bot.slackHTTPClient = fi
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
err := bot.Start(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start bot: %s", err)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
err = am.Create(&types.Approval{
|
||||
Identifier: "k8s/project/repo:1.2.3",
|
||||
VotesRequired: 1,
|
||||
CurrentVersion: "2.3.4",
|
||||
NewVersion: "3.4.5",
|
||||
Event: &types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "project/repo",
|
||||
Tag: "2.3.4",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error while creating : %s", err)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if len(fi.postedMessages) != 1 {
|
||||
t.Errorf("expected to find one message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessApprovalReply(t *testing.T) {
|
||||
f8s := &testutil.FakeK8sImplementer{}
|
||||
fi := &fakeSlackImplementer{}
|
||||
mem := memory.NewMemoryCache(100*time.Second, 100*time.Second, 10*time.Second)
|
||||
|
||||
token := os.Getenv(constants.EnvSlackToken)
|
||||
if token == "" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
|
||||
identifier := "k8s/project/repo:1.2.3"
|
||||
|
||||
// creating initial approve request
|
||||
err := am.Create(&types.Approval{
|
||||
Identifier: identifier,
|
||||
VotesRequired: 2,
|
||||
CurrentVersion: "2.3.4",
|
||||
NewVersion: "3.4.5",
|
||||
Event: &types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "project/repo",
|
||||
Tag: "2.3.4",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error while creating : %s", err)
|
||||
}
|
||||
|
||||
bot := New("keel", token, f8s, am)
|
||||
// replacing slack client so we can receive webhooks
|
||||
bot.slackHTTPClient = fi
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
bot.ctx = ctx
|
||||
|
||||
go bot.processApprovalResponses()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// approval resp
|
||||
bot.approvalsRespCh <- &approvalResponse{
|
||||
User: "123",
|
||||
Status: types.ApprovalStatusApproved,
|
||||
Text: fmt.Sprintf("%s %s", approvalResponseKeyword, identifier),
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
updated, err := am.Get(identifier)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get approval, error: %s", err)
|
||||
}
|
||||
|
||||
if updated.VotesReceived != 1 {
|
||||
t.Errorf("expected to find 1 received vote, found %d", updated.VotesReceived)
|
||||
}
|
||||
|
||||
if updated.Status() != types.ApprovalStatusPending {
|
||||
t.Errorf("expected approval to be in status pending but got: %s", updated.Status())
|
||||
}
|
||||
|
||||
if len(fi.postedMessages) != 1 {
|
||||
t.Errorf("expected to find one message")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestProcessRejectedReply(t *testing.T) {
|
||||
f8s := &testutil.FakeK8sImplementer{}
|
||||
fi := &fakeSlackImplementer{}
|
||||
mem := memory.NewMemoryCache(100*time.Hour, 100*time.Hour, 100*time.Hour)
|
||||
|
||||
// token := os.Getenv(constants.EnvSlackToken)
|
||||
// if token == "" {
|
||||
// t.Skip()
|
||||
// }
|
||||
|
||||
identifier := "k8s/project/repo:1.2.3"
|
||||
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
// creating initial approve request
|
||||
err := am.Create(&types.Approval{
|
||||
Identifier: identifier,
|
||||
VotesRequired: 2,
|
||||
CurrentVersion: "2.3.4",
|
||||
NewVersion: "3.4.5",
|
||||
Event: &types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "project/repo",
|
||||
Tag: "2.3.4",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error while creating : %s", err)
|
||||
}
|
||||
|
||||
bot := New("keel", "random", f8s, am)
|
||||
|
||||
collector := approval.New()
|
||||
collector.Configure(am)
|
||||
|
||||
// replacing slack client so we can receive webhooks
|
||||
bot.slackHTTPClient = fi
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
bot.ctx = ctx
|
||||
|
||||
go bot.processApprovalResponses()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// approval resp
|
||||
bot.approvalsRespCh <- &approvalResponse{
|
||||
User: "123",
|
||||
Status: types.ApprovalStatusRejected,
|
||||
Text: fmt.Sprintf("%s %s", rejectResponseKeyword, identifier),
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
updated, err := am.Get(identifier)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get approval, error: %s", err)
|
||||
}
|
||||
|
||||
if updated.VotesReceived != 0 {
|
||||
t.Errorf("expected to find 0 received vote, found %d", updated.VotesReceived)
|
||||
}
|
||||
|
||||
// if updated.Status() != types.ApprovalStatusRejected {
|
||||
if updated.Status() != types.ApprovalStatusRejected {
|
||||
t.Errorf("expected approval to be in status rejected but got: %s", updated.Status())
|
||||
}
|
||||
|
||||
fmt.Println(updated.Status())
|
||||
|
||||
if len(fi.postedMessages) != 1 {
|
||||
t.Errorf("expected to find one message")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/rusenask/keel/types"
|
||||
)
|
||||
|
||||
// Formatter headers
|
||||
const (
|
||||
defaultApprovalQuietFormat = "{{.Identifier}} {{.Delta}}"
|
||||
defaultApprovalTableFormat = "table {{.Identifier}}\t{{.Delta}}\t{{.Votes}}\t{{.Rejected}}\t{{.Provider}}\t{{.Created}}"
|
||||
|
||||
ApprovalIdentifierHeader = "Identifier"
|
||||
ApprovalDeltaHeader = "Delta"
|
||||
ApprovalVotesHeader = "Votes"
|
||||
ApprovalRejectedHeader = "Rejected"
|
||||
ApprovalProviderHeader = "Provider"
|
||||
ApprovalCreatedHeader = "Created"
|
||||
)
|
||||
|
||||
// NewApprovalsFormat returns a format for use with a approval Context
|
||||
func NewApprovalsFormat(source string, quiet bool) Format {
|
||||
switch source {
|
||||
case TableFormatKey:
|
||||
if quiet {
|
||||
return defaultApprovalQuietFormat
|
||||
}
|
||||
return defaultApprovalTableFormat
|
||||
case RawFormatKey:
|
||||
if quiet {
|
||||
return `name: {{.Identifier}}`
|
||||
}
|
||||
return `name: {{.Identifier}}\n`
|
||||
}
|
||||
return Format(source)
|
||||
}
|
||||
|
||||
// ApprovalWrite writes formatted approvals using the Context
|
||||
func ApprovalWrite(ctx Context, approvals []*types.Approval) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
for _, approval := range approvals {
|
||||
if err := format(&ApprovalContext{v: *approval}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ctx.Write(&DeploymentContext{}, render)
|
||||
}
|
||||
|
||||
// ApprovalContext - approval context is a container for each line
|
||||
type ApprovalContext struct {
|
||||
HeaderContext
|
||||
v types.Approval
|
||||
}
|
||||
|
||||
// MarshalJSON - marshal to json (inspect)
|
||||
func (c *ApprovalContext) MarshalJSON() ([]byte, error) {
|
||||
return marshalJSON(c)
|
||||
}
|
||||
|
||||
func (c *ApprovalContext) Identifier() string {
|
||||
c.AddHeader(ApprovalIdentifierHeader)
|
||||
return c.v.Identifier
|
||||
}
|
||||
|
||||
func (c *ApprovalContext) Delta() string {
|
||||
c.AddHeader(ApprovalDeltaHeader)
|
||||
return c.v.Delta()
|
||||
}
|
||||
|
||||
func (c *ApprovalContext) Votes() string {
|
||||
c.AddHeader(ApprovalVotesHeader)
|
||||
return fmt.Sprintf("%d/%d", c.v.VotesReceived, c.v.VotesRequired)
|
||||
}
|
||||
|
||||
func (c *ApprovalContext) Rejected() string {
|
||||
c.AddHeader(ApprovalRejectedHeader)
|
||||
return strconv.FormatBool(c.v.Rejected)
|
||||
}
|
||||
|
||||
func (c *ApprovalContext) Provider() string {
|
||||
c.AddHeader(ApprovalProviderHeader)
|
||||
return c.v.Provider.String()
|
||||
}
|
||||
|
||||
func (c *ApprovalContext) Created() string {
|
||||
c.AddHeader(ApprovalCreatedHeader)
|
||||
return c.v.CreatedAt.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cache - generic cache interface
|
||||
// type Cache interface {
|
||||
// Put(ctx context.Context, key string, value []byte) error
|
||||
// Get(ctx context.Context, key string) (value []byte, err error)
|
||||
// Delete(ctx context.Context, key string) error
|
||||
// List(prefix string) ([][]byte, error)
|
||||
// }
|
||||
type Cache interface {
|
||||
Put(key string, value []byte) error
|
||||
Get(key string) (value []byte, err error)
|
||||
Delete(key string) error
|
||||
List(prefix string) (map[string][]byte, error)
|
||||
}
|
||||
|
||||
type expirationContextKeyType int
|
||||
|
||||
const expirationContextKey expirationContextKeyType = 1
|
||||
|
||||
// SetContextExpiration - set cache expiration context
|
||||
func SetContextExpiration(ctx context.Context, expiration time.Duration) context.Context {
|
||||
return context.WithValue(ctx, expirationContextKey, expiration)
|
||||
}
|
||||
|
||||
// GetContextExpiration - gets expiration from context, returns it and also returns
|
||||
// ok - true/false to indicate whether ctx value was found
|
||||
func GetContextExpiration(ctx context.Context) (exp time.Duration, ok bool) {
|
||||
expiration := ctx.Value(expirationContextKey)
|
||||
if expiration != nil {
|
||||
return expiration.(time.Duration), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrExpired = errors.New("entry expired")
|
||||
)
|
|
@ -0,0 +1,46 @@
|
|||
package kubekv
|
||||
|
||||
import (
|
||||
"github.com/rusenask/keel/cache"
|
||||
|
||||
"github.com/rusenask/k8s-kv/kv"
|
||||
)
|
||||
|
||||
type KubeKV struct {
|
||||
kv *kv.KV
|
||||
}
|
||||
|
||||
func New(implementer kv.ConfigMapInterface, bucket string) (*KubeKV, error) {
|
||||
|
||||
kvdb, err := kv.New(implementer, "keel", bucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &KubeKV{
|
||||
kv: kvdb,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *KubeKV) Put(key string, value []byte) error {
|
||||
return k.kv.Put(key, value)
|
||||
}
|
||||
|
||||
func (k *KubeKV) Get(key string) (value []byte, err error) {
|
||||
value, err = k.kv.Get(key)
|
||||
if err != nil {
|
||||
if err == kv.ErrNotFound {
|
||||
return []byte(""), cache.ErrNotFound
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (k *KubeKV) Delete(key string) error {
|
||||
return k.kv.Delete(key)
|
||||
}
|
||||
|
||||
func (k *KubeKV) List(prefix string) (map[string][]byte, error) {
|
||||
return k.kv.List(prefix)
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/cache"
|
||||
)
|
||||
|
||||
type requestType int
|
||||
|
||||
// Request types
|
||||
const (
|
||||
GET requestType = iota
|
||||
SET
|
||||
DELETE
|
||||
EXPIRE
|
||||
COPY
|
||||
)
|
||||
|
||||
type (
|
||||
// Value - value is stored together with access and creation time
|
||||
Value struct {
|
||||
ctime time.Time
|
||||
atime time.Time
|
||||
value []byte
|
||||
}
|
||||
|
||||
// Cache - cache container with a map of values and defaults
|
||||
Cache struct {
|
||||
cache map[string]*Value
|
||||
ctimeExpiry time.Duration // creation time
|
||||
atimeExpiry time.Duration // access time
|
||||
expiryTick time.Duration
|
||||
requestChannel chan *request
|
||||
}
|
||||
|
||||
request struct {
|
||||
requestType
|
||||
key string
|
||||
value []byte
|
||||
responseChannel chan *response
|
||||
}
|
||||
response struct {
|
||||
error
|
||||
existingValue []byte
|
||||
mapCopy map[string][]byte
|
||||
value []byte
|
||||
}
|
||||
)
|
||||
|
||||
func (c *Cache) isOld(v *Value) bool {
|
||||
if (c.ctimeExpiry != time.Duration(0)) && (time.Now().Sub(v.ctime) > c.ctimeExpiry) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (c.atimeExpiry != time.Duration(0)) && (time.Now().Sub(v.atime) > c.atimeExpiry) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// NewMemoryCache - creates new cache
|
||||
func NewMemoryCache(ctimeExpiry, atimeExpiry, expiryTick time.Duration) *Cache {
|
||||
c := &Cache{
|
||||
cache: make(map[string]*Value),
|
||||
ctimeExpiry: ctimeExpiry,
|
||||
atimeExpiry: atimeExpiry,
|
||||
expiryTick: expiryTick,
|
||||
requestChannel: make(chan *request),
|
||||
}
|
||||
go c.service()
|
||||
if ctimeExpiry != time.Duration(0) || atimeExpiry != time.Duration(0) {
|
||||
go c.expiryService()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Cache) service() {
|
||||
for {
|
||||
req := <-c.requestChannel
|
||||
resp := &response{}
|
||||
switch req.requestType {
|
||||
case GET:
|
||||
val, ok := c.cache[req.key]
|
||||
if !ok {
|
||||
resp.error = cache.ErrNotFound
|
||||
} else if c.isOld(val) {
|
||||
resp.error = cache.ErrExpired
|
||||
delete(c.cache, req.key)
|
||||
} else {
|
||||
// update atime
|
||||
val.atime = time.Now()
|
||||
c.cache[req.key] = val
|
||||
resp.value = val.value
|
||||
}
|
||||
req.responseChannel <- resp
|
||||
case SET:
|
||||
c.cache[req.key] = &Value{
|
||||
value: req.value,
|
||||
ctime: time.Now(),
|
||||
atime: time.Now(),
|
||||
}
|
||||
req.responseChannel <- resp
|
||||
case DELETE:
|
||||
delete(c.cache, req.key)
|
||||
req.responseChannel <- resp
|
||||
case EXPIRE:
|
||||
for k, v := range c.cache {
|
||||
if c.isOld(v) {
|
||||
delete(c.cache, k)
|
||||
}
|
||||
}
|
||||
// no response
|
||||
case COPY:
|
||||
resp.mapCopy = make(map[string][]byte)
|
||||
for k, v := range c.cache {
|
||||
resp.mapCopy[k] = v.value
|
||||
}
|
||||
req.responseChannel <- resp
|
||||
default:
|
||||
resp.error = fmt.Errorf("invalid request type: %v", req.requestType)
|
||||
req.responseChannel <- resp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get - looks up value and returns it
|
||||
func (c *Cache) Get(key string) ([]byte, error) {
|
||||
respChannel := make(chan *response)
|
||||
c.requestChannel <- &request{
|
||||
requestType: GET,
|
||||
key: key,
|
||||
responseChannel: respChannel,
|
||||
}
|
||||
resp := <-respChannel
|
||||
return resp.value, resp.error
|
||||
}
|
||||
|
||||
// Put - sets key/string. Overwrites existing key
|
||||
func (c *Cache) Put(key string, value []byte) error {
|
||||
respChannel := make(chan *response)
|
||||
c.requestChannel <- &request{
|
||||
requestType: SET,
|
||||
key: key,
|
||||
value: value,
|
||||
responseChannel: respChannel,
|
||||
}
|
||||
resp := <-respChannel
|
||||
return resp.error
|
||||
}
|
||||
|
||||
// Delete - deletes key
|
||||
func (c *Cache) Delete(key string) error {
|
||||
respChannel := make(chan *response)
|
||||
c.requestChannel <- &request{
|
||||
requestType: DELETE,
|
||||
key: key,
|
||||
responseChannel: respChannel,
|
||||
}
|
||||
resp := <-respChannel
|
||||
return resp.error
|
||||
}
|
||||
|
||||
// List all values for specified prefix
|
||||
func (c *Cache) List(prefix string) (map[string][]byte, error) {
|
||||
respChannel := make(chan *response)
|
||||
c.requestChannel <- &request{
|
||||
requestType: COPY,
|
||||
responseChannel: respChannel,
|
||||
}
|
||||
resp := <-respChannel
|
||||
|
||||
list := make(map[string][]byte)
|
||||
|
||||
for k, v := range resp.mapCopy {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Copy - makes a copy of inmemory map
|
||||
func (c *Cache) Copy() map[string][]byte {
|
||||
respChannel := make(chan *response)
|
||||
c.requestChannel <- &request{
|
||||
requestType: COPY,
|
||||
responseChannel: respChannel,
|
||||
}
|
||||
resp := <-respChannel
|
||||
return resp.mapCopy
|
||||
}
|
||||
|
||||
func (c *Cache) expiryService() {
|
||||
ticker := time.NewTicker(c.expiryTick)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
c.requestChannel <- &request{
|
||||
requestType: EXPIRE,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCacheSetGet(t *testing.T) {
|
||||
c := NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
err := c.Put("a", []byte("b"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to SET a key, got error: %s", err)
|
||||
}
|
||||
|
||||
val, err := c.Get("a")
|
||||
if err != nil {
|
||||
t.Errorf("failed to GET a key, got error: %s", err)
|
||||
}
|
||||
|
||||
if string(val) != "b" {
|
||||
log.Panicf("value %v", val)
|
||||
}
|
||||
|
||||
cc := c.Copy()
|
||||
if len(cc) != 1 {
|
||||
t.Errorf("expected 1 item, got: %d", len(cc))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheDel(t *testing.T) {
|
||||
c := NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
err := c.Put("a", []byte("b"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to SET a key, got error: %s", err)
|
||||
}
|
||||
|
||||
val, err := c.Get("a")
|
||||
if err != nil {
|
||||
t.Errorf("failed to GET a key, got error: %s", err)
|
||||
}
|
||||
|
||||
if string(val) != "b" {
|
||||
log.Panicf("value %v", val)
|
||||
}
|
||||
|
||||
err = c.Delete("a")
|
||||
if err != nil {
|
||||
t.Errorf("faield to delete entry, got error: %s", err)
|
||||
}
|
||||
|
||||
_, err = c.Get("a")
|
||||
if err == nil {
|
||||
t.Errorf("expected to get an error after deletion, but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheExpiration(t *testing.T) {
|
||||
c := NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
|
||||
err := c.Put("a", []byte("b"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to SET a key, got error: %s", err)
|
||||
}
|
||||
|
||||
val, err := c.Get("a")
|
||||
if err != nil {
|
||||
t.Errorf("failed to GET a key, got error: %s", err)
|
||||
}
|
||||
|
||||
if string(val) != "b" {
|
||||
log.Panicf("value %v", val)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
_, err = c.Get("a")
|
||||
if err == nil {
|
||||
t.Errorf("expected to get an error after deletion, but got nil")
|
||||
}
|
||||
}
|
|
@ -3,6 +3,9 @@ package constants
|
|||
// DefaultDockerRegistry - default docker registry
|
||||
const DefaultDockerRegistry = "https://index.docker.io"
|
||||
|
||||
// DefaultNamespace - default namespace to initialise configmaps based kv
|
||||
const DefaultNamespace = "kube-system"
|
||||
|
||||
// WebhookEndpointEnv if set - enables webhook notifications
|
||||
const WebhookEndpointEnv = "WEBHOOK_ENDPOINT"
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package approval
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
collectorsM sync.RWMutex
|
||||
collectors = make(map[string]Collector)
|
||||
)
|
||||
|
||||
// Collector - generic interface for implementing approval mechanisms
|
||||
type Collector interface {
|
||||
Configure(approvalsManager approvals.Manager) (bool, error)
|
||||
}
|
||||
|
||||
func RegisterCollector(name string, c Collector) {
|
||||
if name == "" {
|
||||
panic("approval collector:: could not register a Sender with an empty name")
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
panic("approval collector:: could not register a nil Sender")
|
||||
}
|
||||
|
||||
collectorsM.Lock()
|
||||
defer collectorsM.Unlock()
|
||||
|
||||
if _, dup := collectors[name]; dup {
|
||||
panic("approval collector: RegisterCollector called twice for " + name)
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"name": name,
|
||||
}).Info("approval.RegisterCollector: collector registered")
|
||||
|
||||
collectors[name] = c
|
||||
}
|
||||
|
||||
// MainCollector holds all registered collectors
|
||||
type MainCollector struct {
|
||||
approvalsManager approvals.Manager
|
||||
}
|
||||
|
||||
// New - create new sender
|
||||
func New() *MainCollector {
|
||||
return &MainCollector{}
|
||||
}
|
||||
|
||||
// Configure - configure is used to register multiple notification senders
|
||||
func (m *MainCollector) Configure(approvalsManager approvals.Manager) (bool, error) {
|
||||
m.approvalsManager = approvalsManager
|
||||
// Configure registered notifiers.
|
||||
for collectorName, collector := range m.Collectors() {
|
||||
if configured, err := collector.Configure(approvalsManager); configured {
|
||||
log.WithFields(log.Fields{
|
||||
"name": collectorName,
|
||||
}).Info("extension.approval.Configure: collector configured")
|
||||
} else {
|
||||
m.UnregisterCollector(collectorName)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"name": collectorName,
|
||||
"error": err,
|
||||
}).Error("extension.approval.Configure: could not configure collector")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Collectors returns the list of the registered Collectors.
|
||||
func (m *MainCollector) Collectors() map[string]Collector {
|
||||
collectorsM.RLock()
|
||||
defer collectorsM.RUnlock()
|
||||
|
||||
ret := make(map[string]Collector)
|
||||
for k, v := range collectors {
|
||||
ret[k] = v
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// UnregisterCollector removes a Collector with a particular name from the list.
|
||||
func (m *MainCollector) UnregisterCollector(name string) {
|
||||
collectorsM.Lock()
|
||||
defer collectorsM.Unlock()
|
||||
|
||||
delete(collectors, name)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
hash: 4f5cf366af304cb50b974df8ea9c020057f1d3b2bf1d1f9bd462a1e436c4f13b
|
||||
updated: 2017-08-04T22:06:13.059396344+01:00
|
||||
hash: 7745d67060c2a5cdcec48b6caad7fe970628b1a8cf19496cce7060f9ede1421a
|
||||
updated: 2017-09-12T19:36:03.621062651+03:00
|
||||
imports:
|
||||
- name: cloud.google.com/go
|
||||
version: b4e9a381a01e953e880e6d2cf7fd02d412977cae
|
||||
|
@ -75,11 +75,11 @@ imports:
|
|||
- name: github.com/google/gofuzz
|
||||
version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
|
||||
- name: github.com/googleapis/gax-go
|
||||
version: 84ed26760e7f6f80887a2fbfb50db3cc415d2cea
|
||||
version: 8c160ca1523d8eea3932fbaa494c8964b7724aa8
|
||||
- name: github.com/gorilla/context
|
||||
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
|
||||
- name: github.com/gorilla/mux
|
||||
version: bcd8bc72b08df0f70df986b97f95590779502d31
|
||||
version: 24fca303ac6da784b9e8269f724ddeb0b2eea5e7
|
||||
- name: github.com/howeyc/gopass
|
||||
version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d
|
||||
- name: github.com/imdario/mergo
|
||||
|
@ -108,8 +108,16 @@ imports:
|
|||
version: 315973e9173738626b8c81cb39ba247f8cb190e5
|
||||
subpackages:
|
||||
- registry
|
||||
- name: github.com/rusenask/k8s-kv
|
||||
version: 5a87a1fe426104552279cfe8e8de6b48e537a2d9
|
||||
subpackages:
|
||||
- kv
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: 181d419aa9e2223811b824e8f0b4af96f9ba9302
|
||||
version: 89742aefa4b206dcf400792f3bd35b542998eb3b
|
||||
repo: https://github.com/sirupsen/logrus.git
|
||||
vcs: git
|
||||
- name: github.com/sirupsen/logrus
|
||||
version: 89742aefa4b206dcf400792f3bd35b542998eb3b
|
||||
- name: github.com/spf13/pflag
|
||||
version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7
|
||||
- name: github.com/ugorji/go
|
||||
|
@ -119,7 +127,7 @@ imports:
|
|||
- name: github.com/urfave/negroni
|
||||
version: fde5e16d32adc7ad637e9cd9ad21d4ebc6192535
|
||||
- name: golang.org/x/crypto
|
||||
version: 42ff06aea7c329876e5a0fe94acc96902accf0ad
|
||||
version: 88e95fbb56610f02dbc78ebc3b207bec8cf56b86
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
- name: golang.org/x/net
|
||||
|
@ -191,7 +199,7 @@ imports:
|
|||
- socket
|
||||
- urlfetch
|
||||
- name: google.golang.org/genproto
|
||||
version: 09f6ed296fc66555a25fe4ce95173148778dfa85
|
||||
version: 595979c8a7bf586b2d293fb42246bf91a0b893d9
|
||||
subpackages:
|
||||
- googleapis/api/annotations
|
||||
- googleapis/iam/v1
|
||||
|
@ -345,7 +353,7 @@ imports:
|
|||
- util/homedir
|
||||
- util/integer
|
||||
- name: k8s.io/helm
|
||||
version: 7cf31e8d9a026287041bae077b09165be247ae66
|
||||
version: bbc1f71dc03afc5f00c6ac84b9308f8ecb4f39ac
|
||||
subpackages:
|
||||
- pkg/chartutil
|
||||
- pkg/helm
|
||||
|
|
|
@ -12,7 +12,12 @@ import:
|
|||
- internal
|
||||
- package: github.com/Masterminds/semver
|
||||
version: ^1.3.1
|
||||
- package: github.com/sirupsen/logrus
|
||||
version: master
|
||||
- package: github.com/Sirupsen/logrus
|
||||
repo: https://github.com/sirupsen/logrus.git
|
||||
vcs: git
|
||||
version: master
|
||||
- package: github.com/docker/distribution
|
||||
version: ^2.6.2
|
||||
subpackages:
|
||||
|
|
52
main.go
52
main.go
|
@ -9,7 +9,10 @@ import (
|
|||
|
||||
netContext "golang.org/x/net/context"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/bot"
|
||||
"github.com/rusenask/keel/cache/kubekv"
|
||||
|
||||
"github.com/rusenask/keel/constants"
|
||||
"github.com/rusenask/keel/extension/notification"
|
||||
"github.com/rusenask/keel/provider"
|
||||
|
@ -21,6 +24,7 @@ import (
|
|||
"github.com/rusenask/keel/trigger/poll"
|
||||
"github.com/rusenask/keel/trigger/pubsub"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
"github.com/rusenask/keel/version"
|
||||
|
||||
// extensions
|
||||
|
@ -36,6 +40,8 @@ const (
|
|||
EnvTriggerPoll = "POLL" // set to 1 or something to enable poll trigger
|
||||
EnvProjectID = "PROJECT_ID"
|
||||
|
||||
EnvNamespace = "NAMESPACE" // Keel's namespace
|
||||
|
||||
EnvHelmProvider = "HELM_PROVIDER" // helm provider
|
||||
EnvHelmTillerAddress = "TILLER_ADDRESS" // helm provider
|
||||
)
|
||||
|
@ -58,7 +64,7 @@ func main() {
|
|||
"version": ver.Version,
|
||||
"go_version": ver.GoVersion,
|
||||
"arch": ver.Arch,
|
||||
}).Info("keel starting..")
|
||||
}).Info("keel starting...")
|
||||
|
||||
if os.Getenv(EnvDebug) != "" {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
@ -95,14 +101,33 @@ func main() {
|
|||
}).Fatal("main: failed to create kubernetes implementer")
|
||||
}
|
||||
|
||||
keelsNamespace := constants.DefaultNamespace
|
||||
if os.Getenv(EnvNamespace) != "" {
|
||||
keelsNamespace = os.Getenv(EnvNamespace)
|
||||
}
|
||||
|
||||
kkv, err := kubekv.New(implementer.ConfigMaps(keelsNamespace), "approvals")
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"namespace": keelsNamespace,
|
||||
}).Fatal("main: failed to initialise kube-kv")
|
||||
}
|
||||
|
||||
serializer := codecs.DefaultSerializer()
|
||||
// mem := memory.NewMemoryCache(24*time.Hour, 24*time.Hour, 1*time.Minute)
|
||||
approvalsManager := approvals.New(kkv, serializer)
|
||||
|
||||
go approvalsManager.StartExpiryService(ctx)
|
||||
|
||||
// setting up providers
|
||||
providers := setupProviders(implementer, sender)
|
||||
providers := setupProviders(implementer, sender, approvalsManager)
|
||||
|
||||
secretsGetter := secrets.NewGetter(implementer)
|
||||
|
||||
teardownTriggers := setupTriggers(ctx, providers, secretsGetter)
|
||||
teardownTriggers := setupTriggers(ctx, providers, secretsGetter, approvalsManager)
|
||||
|
||||
teardownBot, err := setupBot(implementer)
|
||||
teardownBot, err := setupBot(implementer, approvalsManager)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
|
@ -141,10 +166,10 @@ func main() {
|
|||
|
||||
// setupProviders - setting up available providers. New providers should be initialised here and added to
|
||||
// provider map
|
||||
func setupProviders(k8sImplementer kubernetes.Implementer, sender notification.Sender) (providers provider.Providers) {
|
||||
func setupProviders(k8sImplementer kubernetes.Implementer, sender notification.Sender, approvalsManager approvals.Manager) (providers provider.Providers) {
|
||||
var enabledProviders []provider.Provider
|
||||
|
||||
k8sProvider, err := kubernetes.NewProvider(k8sImplementer, sender)
|
||||
k8sProvider, err := kubernetes.NewProvider(k8sImplementer, sender, approvalsManager)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
|
@ -156,18 +181,18 @@ func setupProviders(k8sImplementer kubernetes.Implementer, sender notification.S
|
|||
if os.Getenv(EnvHelmProvider) == "1" {
|
||||
tillerAddr := os.Getenv(EnvHelmTillerAddress)
|
||||
helmImplementer := helm.NewHelmImplementer(tillerAddr)
|
||||
helmProvider := helm.NewProvider(helmImplementer, sender)
|
||||
helmProvider := helm.NewProvider(helmImplementer, sender, approvalsManager)
|
||||
|
||||
go helmProvider.Start()
|
||||
enabledProviders = append(enabledProviders, helmProvider)
|
||||
}
|
||||
|
||||
providers = provider.New(enabledProviders)
|
||||
providers = provider.New(enabledProviders, approvalsManager)
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func setupBot(k8sImplementer kubernetes.Implementer) (teardown func(), err error) {
|
||||
func setupBot(k8sImplementer kubernetes.Implementer, approvalsManager approvals.Manager) (teardown func(), err error) {
|
||||
|
||||
if os.Getenv(constants.EnvSlackToken) != "" {
|
||||
botName := "keel"
|
||||
|
@ -177,7 +202,7 @@ func setupBot(k8sImplementer kubernetes.Implementer) (teardown func(), err error
|
|||
}
|
||||
|
||||
token := os.Getenv(constants.EnvSlackToken)
|
||||
slackBot := bot.New(botName, token, k8sImplementer)
|
||||
slackBot := bot.New(botName, token, k8sImplementer, approvalsManager)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
|
@ -200,12 +225,13 @@ func setupBot(k8sImplementer kubernetes.Implementer) (teardown func(), err error
|
|||
|
||||
// setupTriggers - setting up triggers. New triggers should be added to this function. Each trigger
|
||||
// should go through all providers (or not if there is a reason) and submit events)
|
||||
func setupTriggers(ctx context.Context, providers provider.Providers, secretsGetter secrets.Getter) (teardown func()) {
|
||||
func setupTriggers(ctx context.Context, providers provider.Providers, secretsGetter secrets.Getter, approvalsManager approvals.Manager) (teardown func()) {
|
||||
|
||||
// setting up generic http webhook server
|
||||
whs := http.NewTriggerServer(&http.Opts{
|
||||
Port: types.KeelDefaultPort,
|
||||
Providers: providers,
|
||||
Port: types.KeelDefaultPort,
|
||||
Providers: providers,
|
||||
ApprovalManager: approvalsManager,
|
||||
})
|
||||
|
||||
go whs.Start()
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package helm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/cache"
|
||||
"github.com/rusenask/keel/types"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// namespace/release name/version
|
||||
func getIdentifier(namespace, name, version string) string {
|
||||
return namespace + "/" + name + ":" + version
|
||||
}
|
||||
|
||||
func (p *Provider) checkForApprovals(event *types.Event, plans []*UpdatePlan) (approvedPlans []*UpdatePlan) {
|
||||
approvedPlans = []*UpdatePlan{}
|
||||
for _, plan := range plans {
|
||||
approved, err := p.isApproved(event, plan)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"release_name": plan.Name,
|
||||
"namespace": plan.Namespace,
|
||||
}).Error("provider.helm: failed to check approval status for deployment")
|
||||
continue
|
||||
}
|
||||
if approved {
|
||||
approvedPlans = append(approvedPlans, plan)
|
||||
}
|
||||
}
|
||||
return approvedPlans
|
||||
}
|
||||
|
||||
func (p *Provider) isApproved(event *types.Event, plan *UpdatePlan) (bool, error) {
|
||||
if plan.Config.Approvals == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
identifier := getIdentifier(plan.Namespace, plan.Name, plan.NewVersion)
|
||||
|
||||
// checking for existing approval
|
||||
existing, err := p.approvalManager.Get(identifier)
|
||||
if err != nil {
|
||||
if err == cache.ErrNotFound {
|
||||
|
||||
// creating new one
|
||||
approval := &types.Approval{
|
||||
Provider: types.ProviderTypeHelm,
|
||||
Identifier: identifier,
|
||||
Event: event,
|
||||
CurrentVersion: plan.CurrentVersion,
|
||||
NewVersion: plan.NewVersion,
|
||||
VotesRequired: plan.Config.Approvals,
|
||||
VotesReceived: 0,
|
||||
Rejected: false,
|
||||
Deadline: time.Now().Add(time.Duration(plan.Config.ApprovalDeadline) * time.Hour),
|
||||
}
|
||||
|
||||
approval.Message = fmt.Sprintf("New image is available for release %s/%s (%s).",
|
||||
plan.Namespace,
|
||||
plan.Name,
|
||||
approval.Delta(),
|
||||
)
|
||||
|
||||
return false, p.approvalManager.Create(approval)
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
return existing.Status() == types.ApprovalStatusApproved, nil
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/image"
|
||||
"github.com/rusenask/keel/util/version"
|
||||
|
@ -37,11 +38,18 @@ type UpdatePlan struct {
|
|||
Namespace string
|
||||
Name string
|
||||
|
||||
Config *KeelChartConfig
|
||||
|
||||
// chart
|
||||
Chart *hapi_chart.Chart
|
||||
|
||||
// values to update path=value
|
||||
Values map[string]string
|
||||
|
||||
// Current (last seen cluster version)
|
||||
CurrentVersion string
|
||||
// New version that's already in the deployment
|
||||
NewVersion string
|
||||
}
|
||||
|
||||
// keel:
|
||||
|
@ -62,10 +70,12 @@ 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// ImageDetails - image details
|
||||
|
@ -80,17 +90,20 @@ type Provider struct {
|
|||
|
||||
sender notification.Sender
|
||||
|
||||
approvalManager approvals.Manager
|
||||
|
||||
events chan *types.Event
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
// NewProvider - create new Helm provider
|
||||
func NewProvider(implementer Implementer, sender notification.Sender) *Provider {
|
||||
func NewProvider(implementer Implementer, sender notification.Sender, approvalManager approvals.Manager) *Provider {
|
||||
return &Provider{
|
||||
implementer: implementer,
|
||||
sender: sender,
|
||||
events: make(chan *types.Event, 100),
|
||||
stop: make(chan struct{}),
|
||||
implementer: implementer,
|
||||
approvalManager: approvalManager,
|
||||
sender: sender,
|
||||
events: make(chan *types.Event, 100),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,7 +219,9 @@ func (p *Provider) processEvent(event *types.Event) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
return p.applyPlans(plans)
|
||||
approved := p.checkForApprovals(event, plans)
|
||||
|
||||
return p.applyPlans(approved)
|
||||
}
|
||||
|
||||
func (p *Provider) createUpdatePlans(event *types.Event) ([]*UpdatePlan, error) {
|
||||
|
@ -228,7 +243,7 @@ func (p *Provider) createUpdatePlans(event *types.Event) ([]*UpdatePlan, error)
|
|||
"error": err,
|
||||
"deployment": release.Name,
|
||||
"namespace": release.Namespace,
|
||||
}).Error("provider.kubernetes: got error while checking unversioned release")
|
||||
}).Error("provider.helm: got error while checking unversioned release")
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -266,7 +281,7 @@ func (p *Provider) applyPlans(plans []*UpdatePlan) error {
|
|||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "update release",
|
||||
Message: fmt.Sprintf("Preparing to update release %s/%s (%s)", plan.Namespace, plan.Name, strings.Join(mapToSlice(plan.Values), ", ")),
|
||||
Message: fmt.Sprintf("Preparing to update release %s/%s %s->%s (%s)", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", ")),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationPreReleaseUpdate,
|
||||
Level: types.LevelDebug,
|
||||
|
@ -282,7 +297,7 @@ func (p *Provider) applyPlans(plans []*UpdatePlan) error {
|
|||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "update release",
|
||||
Message: fmt.Sprintf("Release update feailed %s/%s (%s), error: %s", plan.Namespace, plan.Name, strings.Join(mapToSlice(plan.Values), ", "), err),
|
||||
Message: fmt.Sprintf("Release update feailed %s/%s %s->%s (%s), error: %s", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", "), err),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationReleaseUpdate,
|
||||
Level: types.LevelError,
|
||||
|
@ -292,7 +307,7 @@ func (p *Provider) applyPlans(plans []*UpdatePlan) error {
|
|||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "update release",
|
||||
Message: fmt.Sprintf("Successfully updated release %s/%s (%s)", plan.Namespace, plan.Name, strings.Join(mapToSlice(plan.Values), ", ")),
|
||||
Message: fmt.Sprintf("Successfully updated release %s/%s %s->%s (%s)", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", ")),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationReleaseUpdate,
|
||||
Level: types.LevelSuccess,
|
||||
|
|
|
@ -3,10 +3,14 @@ package helm
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/extension/notification"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/helm"
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
|
@ -14,6 +18,12 @@ import (
|
|||
rls "k8s.io/helm/pkg/proto/hapi/services"
|
||||
)
|
||||
|
||||
func approver() *approvals.DefaultManager {
|
||||
cache := memory.NewMemoryCache(10*time.Minute, 10*time.Minute, 10*time.Minute)
|
||||
|
||||
return approvals.New(cache, codecs.DefaultSerializer())
|
||||
}
|
||||
|
||||
type fakeSender struct {
|
||||
sentEvent types.EventNotification
|
||||
}
|
||||
|
@ -166,7 +176,7 @@ keel:
|
|||
},
|
||||
}
|
||||
|
||||
prov := NewProvider(fakeImpl, &fakeSender{})
|
||||
prov := NewProvider(fakeImpl, &fakeSender{}, approver())
|
||||
|
||||
tracked, _ := prov.TrackedImages()
|
||||
|
||||
|
@ -214,7 +224,7 @@ keel:
|
|||
},
|
||||
}
|
||||
|
||||
prov := NewProvider(fakeImpl, &fakeSender{})
|
||||
prov := NewProvider(fakeImpl, &fakeSender{}, approver())
|
||||
|
||||
tracked, _ := prov.TrackedImages()
|
||||
|
||||
|
@ -316,7 +326,7 @@ keel:
|
|||
},
|
||||
}
|
||||
|
||||
provider := NewProvider(fakeImpl, &fakeSender{})
|
||||
provider := NewProvider(fakeImpl, &fakeSender{}, approver())
|
||||
|
||||
err := provider.processEvent(&types.Event{
|
||||
Repository: types.Repository{
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"k8s.io/helm/pkg/helm"
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
rls "k8s.io/helm/pkg/proto/hapi/services"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TillerAddress - default tiller address
|
||||
|
@ -26,6 +28,8 @@ type HelmImplementer struct {
|
|||
func NewHelmImplementer(address string) *HelmImplementer {
|
||||
if address == "" {
|
||||
address = TillerAddress
|
||||
} else {
|
||||
log.Infof("provider.helm: tiller address '%s' supplied", address)
|
||||
}
|
||||
|
||||
return &HelmImplementer{
|
||||
|
|
|
@ -73,6 +73,9 @@ func checkUnversionedRelease(repo *types.Repository, namespace, name string, cha
|
|||
|
||||
path, value := getUnversionedPlanValues(repo.Tag, imageRef, &imageDetails)
|
||||
plan.Values[path] = value
|
||||
plan.NewVersion = repo.Tag
|
||||
plan.CurrentVersion = imageRef.Tag()
|
||||
plan.Config = keelCfg
|
||||
shouldUpdateRelease = true
|
||||
}
|
||||
|
||||
|
|
|
@ -77,10 +77,23 @@ keel:
|
|||
config: &hapi_chart.Config{Raw: ""},
|
||||
},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1",
|
||||
Chart: helloWorldChart,
|
||||
Values: map[string]string{"image.tag": "latest"}},
|
||||
Namespace: "default",
|
||||
Name: "release-1",
|
||||
Chart: helloWorldChart,
|
||||
Values: map[string]string{"image.tag": "latest"},
|
||||
CurrentVersion: "1.1.0",
|
||||
NewVersion: "latest",
|
||||
Config: &KeelChartConfig{
|
||||
Policy: types.PolicyTypeForce,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{
|
||||
RepositoryPath: "image.repository",
|
||||
TagPath: "image.tag",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantShouldUpdateRelease: true,
|
||||
wantErr: false,
|
||||
},
|
||||
|
|
|
@ -69,7 +69,9 @@ func checkVersionedRelease(newVersion *types.Version, repo *types.Repository, na
|
|||
if keelCfg.Policy == types.PolicyTypeForce || imageRef.Tag() == "latest" {
|
||||
path, value := getPlanValues(newVersion, imageRef, &imageDetails)
|
||||
plan.Values[path] = value
|
||||
|
||||
plan.NewVersion = newVersion.String()
|
||||
plan.CurrentVersion = imageRef.Tag()
|
||||
plan.Config = keelCfg
|
||||
shouldUpdateRelease = true
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
|
@ -125,7 +127,9 @@ func checkVersionedRelease(newVersion *types.Version, repo *types.Repository, na
|
|||
if shouldUpdate {
|
||||
path, value := getPlanValues(newVersion, imageRef, &imageDetails)
|
||||
plan.Values[path] = value
|
||||
|
||||
plan.NewVersion = newVersion.String()
|
||||
plan.CurrentVersion = currentVersion.String()
|
||||
plan.Config = keelCfg
|
||||
shouldUpdateRelease = true
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
|
|
|
@ -139,7 +139,21 @@ image:
|
|||
chart: helloWorldChart,
|
||||
config: &hapi_chart.Config{Raw: ""},
|
||||
},
|
||||
wantPlan: &UpdatePlan{Namespace: "default", Name: "release-1", Chart: helloWorldChart, Values: map[string]string{"image.tag": "1.1.2"}},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1",
|
||||
Chart: helloWorldChart,
|
||||
Values: map[string]string{"image.tag": "1.1.2"},
|
||||
NewVersion: "1.1.2",
|
||||
CurrentVersion: "1.1.0",
|
||||
Config: &KeelChartConfig{
|
||||
Policy: types.PolicyTypeAll,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantShouldUpdateRelease: true,
|
||||
wantErr: false,
|
||||
},
|
||||
|
@ -181,7 +195,21 @@ image:
|
|||
chart: helloWorldNonSemverChart,
|
||||
config: &hapi_chart.Config{Raw: ""},
|
||||
},
|
||||
wantPlan: &UpdatePlan{Namespace: "default", Name: "release-1", Chart: helloWorldNonSemverChart, Values: map[string]string{"image.tag": "1.1.0"}},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1",
|
||||
Chart: helloWorldNonSemverChart,
|
||||
Values: map[string]string{"image.tag": "1.1.0"},
|
||||
NewVersion: "1.1.0",
|
||||
CurrentVersion: "alpha",
|
||||
Config: &KeelChartConfig{
|
||||
Policy: types.PolicyTypeForce,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantShouldUpdateRelease: true,
|
||||
wantErr: false,
|
||||
},
|
||||
|
@ -209,7 +237,21 @@ image:
|
|||
chart: helloWorldNoTagChart,
|
||||
config: &hapi_chart.Config{Raw: ""},
|
||||
},
|
||||
wantPlan: &UpdatePlan{Namespace: "default", Name: "release-1-no-tag", Chart: helloWorldNoTagChart, Values: map[string]string{"image.repository": "gcr.io/v2-namespace/hello-world:1.1.0"}},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1-no-tag",
|
||||
Chart: helloWorldNoTagChart,
|
||||
Values: map[string]string{"image.repository": "gcr.io/v2-namespace/hello-world:1.1.0"},
|
||||
NewVersion: "1.1.0",
|
||||
CurrentVersion: "1.0.0",
|
||||
Config: &KeelChartConfig{
|
||||
Policy: types.PolicyTypeMajor,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantShouldUpdateRelease: true,
|
||||
wantErr: false,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/cache"
|
||||
"github.com/rusenask/keel/types"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func getIdentifier(namespace, name, version string) string {
|
||||
return namespace + "/" + name + ":" + version
|
||||
}
|
||||
|
||||
// checkForApprovals - filters out deployments and only passes forward approved ones
|
||||
func (p *Provider) checkForApprovals(event *types.Event, plans []*UpdatePlan) (approvedPlans []*UpdatePlan) {
|
||||
approvedPlans = []*UpdatePlan{}
|
||||
for _, plan := range plans {
|
||||
approved, err := p.isApproved(event, plan)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"deployment": plan.Deployment.Name,
|
||||
"namespace": plan.Deployment.Namespace,
|
||||
}).Error("provider.kubernetes: failed to check approval status for deployment")
|
||||
continue
|
||||
}
|
||||
if approved {
|
||||
approvedPlans = append(approvedPlans, plan)
|
||||
}
|
||||
}
|
||||
return approvedPlans
|
||||
}
|
||||
|
||||
func (p *Provider) isApproved(event *types.Event, plan *UpdatePlan) (bool, error) {
|
||||
labels := plan.Deployment.GetLabels()
|
||||
|
||||
minApprovalsStr, ok := labels[types.KeelMinimumApprovalsLabel]
|
||||
if !ok {
|
||||
// no approvals required - passing
|
||||
return true, nil
|
||||
}
|
||||
|
||||
minApprovals, err := strconv.Atoi(minApprovalsStr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if minApprovals == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
deadline := types.KeelApprovalDeadlineDefault
|
||||
|
||||
// deadline
|
||||
deadlineStr, ok := labels[types.KeelApprovalDeadlineLabel]
|
||||
if ok {
|
||||
d, err := strconv.Atoi(deadlineStr)
|
||||
if err == nil {
|
||||
deadline = d
|
||||
}
|
||||
}
|
||||
|
||||
identifier := getIdentifier(plan.Deployment.Namespace, plan.Deployment.Name, plan.NewVersion)
|
||||
|
||||
// checking for existing approval
|
||||
existing, err := p.approvalManager.Get(identifier)
|
||||
if err != nil {
|
||||
if err == cache.ErrNotFound {
|
||||
|
||||
// creating new one
|
||||
approval := &types.Approval{
|
||||
Provider: types.ProviderTypeKubernetes,
|
||||
Identifier: identifier,
|
||||
Event: event,
|
||||
CurrentVersion: plan.CurrentVersion,
|
||||
NewVersion: plan.NewVersion,
|
||||
VotesRequired: minApprovals,
|
||||
VotesReceived: 0,
|
||||
Rejected: false,
|
||||
Deadline: time.Now().Add(time.Duration(deadline) * time.Hour),
|
||||
}
|
||||
|
||||
approval.Message = fmt.Sprintf("New image is available for deployment %s/%s (%s).",
|
||||
plan.Deployment.Namespace,
|
||||
plan.Deployment.Name,
|
||||
approval.Delta(),
|
||||
)
|
||||
|
||||
fmt.Println("requesting approval, ns: ", plan.Deployment.Namespace)
|
||||
|
||||
return false, p.approvalManager.Create(approval)
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
return existing.Status() == types.ApprovalStatusApproved, nil
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/types"
|
||||
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
)
|
||||
|
||||
func TestCheckRequestedApproval(t *testing.T) {
|
||||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
v1.NamespaceStatus{},
|
||||
},
|
||||
},
|
||||
}
|
||||
fp.deploymentList = &v1beta1.DeploymentList{
|
||||
Items: []v1beta1.Deployment{
|
||||
v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all", types.KeelMinimumApprovalsLabel: "1"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
},
|
||||
}
|
||||
approver := approver()
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
// creating "new version" event
|
||||
repo := types.Repository{
|
||||
Name: "gcr.io/v2-namespace/hello-world",
|
||||
Tag: "1.1.2",
|
||||
}
|
||||
|
||||
deps, err := provider.processEvent(&types.Event{Repository: repo})
|
||||
if err != nil {
|
||||
t.Errorf("failed to get deployments: %s", err)
|
||||
}
|
||||
|
||||
if len(deps) != 0 {
|
||||
t.Errorf("expected to find 0 updated deployment but found %d", len(deps))
|
||||
}
|
||||
|
||||
// checking approvals
|
||||
approval, err := provider.approvalManager.Get("xxxx/dep-1:1.1.2")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find approval, err: %s", err)
|
||||
}
|
||||
|
||||
if approval.Provider != types.ProviderTypeKubernetes {
|
||||
t.Errorf("wrong provider: %s", approval.Provider)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApprovedCheck(t *testing.T) {
|
||||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
v1.NamespaceStatus{},
|
||||
},
|
||||
},
|
||||
}
|
||||
fp.deploymentList = &v1beta1.DeploymentList{
|
||||
Items: []v1beta1.Deployment{
|
||||
v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all", types.KeelMinimumApprovalsLabel: "1"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
},
|
||||
}
|
||||
approver := approver()
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
||||
// approving event
|
||||
err = provider.approvalManager.Create(&types.Approval{
|
||||
Identifier: "xxxx/dep-1:1.1.2",
|
||||
VotesReceived: 2,
|
||||
VotesRequired: 2,
|
||||
Deadline: time.Now().Add(10 * time.Second),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
appr, _ := provider.approvalManager.Get("xxxx/dep-1:1.1.2")
|
||||
if appr.Status() != types.ApprovalStatusApproved {
|
||||
t.Fatalf("approval not approved")
|
||||
}
|
||||
|
||||
// creating "new version" event
|
||||
repo := types.Repository{
|
||||
Name: "gcr.io/v2-namespace/hello-world",
|
||||
Tag: "1.1.2",
|
||||
}
|
||||
|
||||
deps, err := provider.processEvent(&types.Event{Repository: repo})
|
||||
if err != nil {
|
||||
t.Errorf("failed to get deployments: %s", err)
|
||||
}
|
||||
|
||||
if len(deps) != 1 {
|
||||
t.Errorf("expected to find 1 updated deployment but found %d", len(deps))
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/client-go/rest"
|
||||
|
@ -21,6 +22,7 @@ type Implementer interface {
|
|||
Update(deployment *v1beta1.Deployment) error
|
||||
Secret(namespace, name string) (*v1.Secret, error)
|
||||
Pods(namespace, labelSelector string) (*v1.PodList, error)
|
||||
ConfigMaps(namespace string) core_v1.ConfigMapInterface
|
||||
}
|
||||
|
||||
// KubernetesImplementer - default kubernetes client implementer, uses
|
||||
|
@ -112,3 +114,8 @@ func (i *KubernetesImplementer) Secret(namespace, name string) (*v1.Secret, erro
|
|||
func (i *KubernetesImplementer) Pods(namespace, labelSelector string) (*v1.PodList, error) {
|
||||
return i.client.Pods(namespace).List(meta_v1.ListOptions{LabelSelector: labelSelector})
|
||||
}
|
||||
|
||||
// ConfigMaps - returns an interface to config maps for a specified namespace
|
||||
func (i *KubernetesImplementer) ConfigMaps(namespace string) core_v1.ConfigMapInterface {
|
||||
return i.client.ConfigMaps(namespace)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/extension/notification"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/image"
|
||||
|
@ -31,23 +32,36 @@ const forceUpdateImageAnnotation = "keel.sh/update-image"
|
|||
// forceUpdateResetTag - tag used to reset container to force pull image
|
||||
const forceUpdateResetTag = "0.0.0"
|
||||
|
||||
// UpdatePlan - deployment update plan
|
||||
type UpdatePlan struct {
|
||||
// Updated deployment version
|
||||
Deployment v1beta1.Deployment
|
||||
// Current (last seen cluster version)
|
||||
CurrentVersion string
|
||||
// New version that's already in the deployment
|
||||
NewVersion string
|
||||
}
|
||||
|
||||
// Provider - kubernetes provider for auto update
|
||||
type Provider struct {
|
||||
implementer Implementer
|
||||
|
||||
sender notification.Sender
|
||||
|
||||
approvalManager approvals.Manager
|
||||
|
||||
events chan *types.Event
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
// NewProvider - create new kubernetes based provider
|
||||
func NewProvider(implementer Implementer, sender notification.Sender) (*Provider, error) {
|
||||
func NewProvider(implementer Implementer, sender notification.Sender, approvalManager approvals.Manager) (*Provider, error) {
|
||||
return &Provider{
|
||||
implementer: implementer,
|
||||
events: make(chan *types.Event, 100),
|
||||
stop: make(chan struct{}),
|
||||
sender: sender,
|
||||
implementer: implementer,
|
||||
approvalManager: approvalManager,
|
||||
events: make(chan *types.Event, 100),
|
||||
stop: make(chan struct{}),
|
||||
sender: sender,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -101,7 +115,7 @@ func (p *Provider) TrackedImages() ([]*types.TrackedImage, error) {
|
|||
"schedule": schedule,
|
||||
"deployment": deployment.Name,
|
||||
"namespace": deployment.Namespace,
|
||||
}).Error("trigger.poll.manager: failed to parse poll schedule, setting default schedule")
|
||||
}).Error("provider.kubernetes: failed to parse poll schedule, setting default schedule")
|
||||
schedule = types.KeelPollDefaultSchedule
|
||||
}
|
||||
} else {
|
||||
|
@ -165,24 +179,30 @@ func (p *Provider) startInternal() error {
|
|||
}
|
||||
|
||||
func (p *Provider) processEvent(event *types.Event) (updated []*v1beta1.Deployment, err error) {
|
||||
impacted, err := p.impactedDeployments(&event.Repository)
|
||||
plans, err := p.createUpdatePlans(&event.Repository)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(impacted) == 0 {
|
||||
if len(plans) == 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"image": event.Repository.Name,
|
||||
"tag": event.Repository.Tag,
|
||||
}).Info("provider.kubernetes: no impacted deployments found for this event")
|
||||
}).Info("provider.kubernetes: no plans for deployment updates found for this event")
|
||||
return
|
||||
}
|
||||
|
||||
return p.updateDeployments(impacted)
|
||||
approvedPlans := p.checkForApprovals(event, plans)
|
||||
|
||||
return p.updateDeployments(approvedPlans)
|
||||
}
|
||||
|
||||
func (p *Provider) updateDeployments(deployments []v1beta1.Deployment) (updated []*v1beta1.Deployment, err error) {
|
||||
for _, deployment := range deployments {
|
||||
// func (p *Provider) updateDeployments(deployments []v1beta1.Deployment) (updated []*v1beta1.Deployment, err error) {
|
||||
func (p *Provider) updateDeployments(plans []*UpdatePlan) (updated []*v1beta1.Deployment, err error) {
|
||||
// for _, deployment := range plans {
|
||||
for _, plan := range plans {
|
||||
|
||||
deployment := plan.Deployment
|
||||
|
||||
reset, delta, err := checkForReset(deployment, p.implementer)
|
||||
if err != nil {
|
||||
|
@ -227,7 +247,7 @@ func (p *Provider) updateDeployments(deployments []v1beta1.Deployment) (updated
|
|||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "preparing to update deployment after reset",
|
||||
Message: fmt.Sprintf("Preparing to update deployment %s/%s (%s)", deployment.Namespace, deployment.Name, strings.Join(getImages(&refresh), ", ")),
|
||||
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,
|
||||
|
@ -243,7 +263,7 @@ func (p *Provider) updateDeployments(deployments []v1beta1.Deployment) (updated
|
|||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "update deployment after",
|
||||
Message: fmt.Sprintf("Deployment %s/%s update failed, error: %s", refresh.Namespace, refresh.Name, err),
|
||||
Message: fmt.Sprintf("Deployment %s/%s update %s->%s failed, error: %s", refresh.Namespace, refresh.Name, plan.CurrentVersion, plan.NewVersion, err),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationDeploymentUpdate,
|
||||
Level: types.LevelError,
|
||||
|
@ -253,7 +273,7 @@ func (p *Provider) updateDeployments(deployments []v1beta1.Deployment) (updated
|
|||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "update deployment after reset",
|
||||
Message: fmt.Sprintf("Successfully updated deployment %s/%s (%s)", refresh.Namespace, refresh.Name, strings.Join(getImages(&refresh), ", ")),
|
||||
Message: fmt.Sprintf("Successfully updated deployment %s/%s %s->%s (%s)", refresh.Namespace, refresh.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(getImages(&refresh), ", ")),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationDeploymentUpdate,
|
||||
Level: types.LevelSuccess,
|
||||
|
@ -267,7 +287,7 @@ func (p *Provider) updateDeployments(deployments []v1beta1.Deployment) (updated
|
|||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "preparing to update deployment",
|
||||
Message: fmt.Sprintf("Preparing to update deployment %s/%s (%s)", deployment.Namespace, deployment.Name, strings.Join(getImages(&deployment), ", ")),
|
||||
Message: fmt.Sprintf("Preparing to update deployment %s/%s %s->%s (%s)", deployment.Namespace, deployment.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(getImages(&deployment), ", ")),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationPreDeploymentUpdate,
|
||||
Level: types.LevelDebug,
|
||||
|
@ -283,7 +303,7 @@ func (p *Provider) updateDeployments(deployments []v1beta1.Deployment) (updated
|
|||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "update deployment",
|
||||
Message: fmt.Sprintf("Deployment %s/%s update failed, error: %s", deployment.Namespace, deployment.Name, err),
|
||||
Message: fmt.Sprintf("Deployment %s/%s update %s->%s failed, error: %s", deployment.Namespace, deployment.Name, plan.CurrentVersion, plan.NewVersion, err),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationDeploymentUpdate,
|
||||
Level: types.LevelError,
|
||||
|
@ -294,7 +314,7 @@ func (p *Provider) updateDeployments(deployments []v1beta1.Deployment) (updated
|
|||
|
||||
p.sender.Send(types.EventNotification{
|
||||
Name: "update deployment",
|
||||
Message: fmt.Sprintf("Successfully updated deployment %s/%s (%s)", deployment.Namespace, deployment.Name, strings.Join(getImages(&deployment), ", ")),
|
||||
Message: fmt.Sprintf("Successfully updated deployment %s/%s %s->%s (%s)", deployment.Namespace, deployment.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(getImages(&deployment), ", ")),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationDeploymentUpdate,
|
||||
Level: types.LevelSuccess,
|
||||
|
@ -402,8 +422,9 @@ func (p *Provider) getDeployment(namespace, name string) (*v1beta1.Deployment, e
|
|||
return p.implementer.Deployment(namespace, name)
|
||||
}
|
||||
|
||||
// gets impacted deployments by changed repository
|
||||
func (p *Provider) impactedDeployments(repo *types.Repository) ([]v1beta1.Deployment, error) {
|
||||
// createUpdatePlans - impacted deployments by changed repository
|
||||
// func (p *Provider) impactedDeployments(repo *types.Repository) ([]v1beta1.Deployment, error) {
|
||||
func (p *Provider) createUpdatePlans(repo *types.Repository) ([]*UpdatePlan, error) {
|
||||
|
||||
deploymentLists, err := p.deployments()
|
||||
if err != nil {
|
||||
|
@ -413,7 +434,8 @@ func (p *Provider) impactedDeployments(repo *types.Repository) ([]v1beta1.Deploy
|
|||
return nil, err
|
||||
}
|
||||
|
||||
impacted := []v1beta1.Deployment{}
|
||||
// impacted := []v1beta1.Deployment{}
|
||||
impacted := []*UpdatePlan{}
|
||||
|
||||
for _, deploymentList := range deploymentLists {
|
||||
for _, deployment := range deploymentList.Items {
|
||||
|
|
|
@ -2,15 +2,43 @@ package kubernetes
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/extension/notification"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
)
|
||||
|
||||
type fakeProvider struct {
|
||||
submitted []types.Event
|
||||
images []*types.TrackedImage
|
||||
}
|
||||
|
||||
func (p *fakeProvider) Submit(event types.Event) error {
|
||||
p.submitted = append(p.submitted, event)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fakeProvider) TrackedImages() ([]*types.TrackedImage, error) {
|
||||
return p.images, nil
|
||||
}
|
||||
func (p *fakeProvider) List() []string {
|
||||
return []string{"fakeprovider"}
|
||||
}
|
||||
func (p *fakeProvider) Stop() {
|
||||
return
|
||||
}
|
||||
func (p *fakeProvider) GetName() string {
|
||||
return "fp"
|
||||
}
|
||||
|
||||
type fakeImplementer struct {
|
||||
namespaces *v1.NamespaceList
|
||||
deployment *v1beta1.Deployment
|
||||
|
@ -49,6 +77,10 @@ func (i *fakeImplementer) Pods(namespace, labelSelector string) (*v1.PodList, er
|
|||
return i.podList, nil
|
||||
}
|
||||
|
||||
func (i *fakeImplementer) ConfigMaps(namespace string) core_v1.ConfigMapInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeSender struct {
|
||||
sentEvent types.EventNotification
|
||||
}
|
||||
|
@ -62,6 +94,12 @@ func (s *fakeSender) Send(event types.EventNotification) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func approver() *approvals.DefaultManager {
|
||||
cache := memory.NewMemoryCache(10*time.Minute, 10*time.Minute, 10*time.Minute)
|
||||
|
||||
return approvals.New(cache, codecs.DefaultSerializer())
|
||||
}
|
||||
|
||||
func TestGetNamespaces(t *testing.T) {
|
||||
fi := &fakeImplementer{
|
||||
namespaces: &v1.NamespaceList{
|
||||
|
@ -76,7 +114,7 @@ func TestGetNamespaces(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fi, &fakeSender{})
|
||||
provider, err := NewProvider(fi, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
@ -135,7 +173,7 @@ func TestGetDeployments(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fp, &fakeSender{})
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
@ -210,7 +248,7 @@ func TestGetImpacted(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fp, &fakeSender{})
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
@ -221,17 +259,17 @@ func TestGetImpacted(t *testing.T) {
|
|||
Tag: "1.1.2",
|
||||
}
|
||||
|
||||
deps, err := provider.impactedDeployments(repo)
|
||||
plans, err := provider.createUpdatePlans(repo)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get deployments: %s", err)
|
||||
}
|
||||
|
||||
if len(deps) != 1 {
|
||||
t.Errorf("expected to find 1 deployment but found %d", len(deps))
|
||||
if len(plans) != 1 {
|
||||
t.Errorf("expected to find 1 deployment update plan but found %d", len(plans))
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, c := range deps[0].Spec.Template.Spec.Containers {
|
||||
for _, c := range plans[0].Deployment.Spec.Template.Spec.Containers {
|
||||
|
||||
containerImageName := versionreg.ReplaceAllString(c.Image, "")
|
||||
|
||||
|
@ -303,7 +341,7 @@ func TestProcessEvent(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fp, &fakeSender{})
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
@ -361,7 +399,7 @@ func TestProcessEventBuildNumber(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fp, &fakeSender{})
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
@ -443,7 +481,7 @@ func TestGetImpactedTwoContainersInSameDeployment(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fp, &fakeSender{})
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
@ -454,17 +492,17 @@ func TestGetImpactedTwoContainersInSameDeployment(t *testing.T) {
|
|||
Tag: "1.1.2",
|
||||
}
|
||||
|
||||
deps, err := provider.impactedDeployments(repo)
|
||||
plans, err := provider.createUpdatePlans(repo)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get deployments: %s", err)
|
||||
}
|
||||
|
||||
if len(deps) != 1 {
|
||||
t.Errorf("expected to find 1 deployment but found %d", len(deps))
|
||||
if len(plans) != 1 {
|
||||
t.Errorf("expected to find 1 deployment but found %d", len(plans))
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, c := range deps[0].Spec.Template.Spec.Containers {
|
||||
for _, c := range plans[0].Deployment.Spec.Template.Spec.Containers {
|
||||
|
||||
containerImageName := versionreg.ReplaceAllString(c.Image, "")
|
||||
|
||||
|
@ -540,7 +578,7 @@ func TestGetImpactedTwoSameContainersInSameDeployment(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fp, &fakeSender{})
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
@ -551,17 +589,17 @@ func TestGetImpactedTwoSameContainersInSameDeployment(t *testing.T) {
|
|||
Tag: "1.1.2",
|
||||
}
|
||||
|
||||
deps, err := provider.impactedDeployments(repo)
|
||||
plans, err := provider.createUpdatePlans(repo)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get deployments: %s", err)
|
||||
}
|
||||
|
||||
if len(deps) != 1 {
|
||||
t.Errorf("expected to find 1 deployment but found %d", len(deps))
|
||||
if len(plans) != 1 {
|
||||
t.Errorf("expected to find 1 deployment but found %d", len(plans))
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, c := range deps[0].Spec.Template.Spec.Containers {
|
||||
for _, c := range plans[0].Deployment.Spec.Template.Spec.Containers {
|
||||
|
||||
containerImageName := versionreg.ReplaceAllString(c.Image, "")
|
||||
|
||||
|
@ -635,7 +673,7 @@ func TestGetImpactedUntaggedImage(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fp, &fakeSender{})
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
@ -646,17 +684,17 @@ func TestGetImpactedUntaggedImage(t *testing.T) {
|
|||
Tag: "1.1.2",
|
||||
}
|
||||
|
||||
deps, err := provider.impactedDeployments(repo)
|
||||
plans, err := provider.createUpdatePlans(repo)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get deployments: %s", err)
|
||||
}
|
||||
|
||||
if len(deps) != 1 {
|
||||
t.Errorf("expected to find 1 deployment but found %d", len(deps))
|
||||
if len(plans) != 1 {
|
||||
t.Errorf("expected to find 1 deployment but found %d", len(plans))
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, c := range deps[0].Spec.Template.Spec.Containers {
|
||||
for _, c := range plans[0].Deployment.Spec.Template.Spec.Containers {
|
||||
|
||||
containerImageName := versionreg.ReplaceAllString(c.Image, "")
|
||||
|
||||
|
@ -731,7 +769,7 @@ func TestGetImpactedUntaggedOneImage(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fp, &fakeSender{})
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
@ -742,22 +780,24 @@ func TestGetImpactedUntaggedOneImage(t *testing.T) {
|
|||
Tag: "1.1.2",
|
||||
}
|
||||
|
||||
deps, err := provider.impactedDeployments(repo)
|
||||
plans, err := provider.createUpdatePlans(repo)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get deployments: %s", err)
|
||||
}
|
||||
|
||||
if len(deps) != 2 {
|
||||
t.Errorf("expected to find 2 deployment but found %d", len(deps))
|
||||
if len(plans) != 2 {
|
||||
t.Fatalf("expected to find 2 deployment but found %d", len(plans))
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, c := range deps[0].Spec.Template.Spec.Containers {
|
||||
for _, plan := range plans {
|
||||
for _, c := range plan.Deployment.Spec.Template.Spec.Containers {
|
||||
|
||||
containerImageName := versionreg.ReplaceAllString(c.Image, "")
|
||||
containerImageName := versionreg.ReplaceAllString(c.Image, "")
|
||||
|
||||
if containerImageName == repo.Name {
|
||||
found = true
|
||||
if containerImageName == repo.Name {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -809,7 +849,7 @@ func TestTrackedImages(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
provider, err := NewProvider(fp, &fakeSender{})
|
||||
provider, err := NewProvider(fp, &fakeSender{}, approver())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get provider: %s", err)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,10 @@ import (
|
|||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (p *Provider) checkUnversionedDeployment(policy types.PolicyType, repo *types.Repository, deployment v1beta1.Deployment) (updated v1beta1.Deployment, shouldUpdateDeployment bool, err error) {
|
||||
// func (p *Provider) checkUnversionedDeployment(policy types.PolicyType, repo *types.Repository, deployment v1beta1.Deployment) (updated v1beta1.Deployment, shouldUpdateDeployment bool, err error) {
|
||||
func (p *Provider) checkUnversionedDeployment(policy types.PolicyType, repo *types.Repository, deployment v1beta1.Deployment) (updatePlan *UpdatePlan, shouldUpdateDeployment bool, err error) {
|
||||
updatePlan = &UpdatePlan{}
|
||||
|
||||
eventRepoRef, err := image.Parse(repo.Name)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -82,6 +85,10 @@ func (p *Provider) checkUnversionedDeployment(policy types.PolicyType, repo *typ
|
|||
|
||||
deployment.SetAnnotations(annotations)
|
||||
|
||||
updatePlan.CurrentVersion = containerImageRef.Tag()
|
||||
updatePlan.NewVersion = repo.Tag
|
||||
updatePlan.Deployment = deployment
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"parsed_image": containerImageRef.Remote(),
|
||||
"raw_image_name": c.Image,
|
||||
|
@ -92,5 +99,5 @@ func (p *Provider) checkUnversionedDeployment(policy types.PolicyType, repo *typ
|
|||
|
||||
}
|
||||
|
||||
return deployment, shouldUpdateDeployment, nil
|
||||
return updatePlan, shouldUpdateDeployment, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/extension/notification"
|
||||
"github.com/rusenask/keel/types"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
|
@ -12,9 +14,11 @@ import (
|
|||
|
||||
func TestProvider_checkUnversionedDeployment(t *testing.T) {
|
||||
type fields struct {
|
||||
implementer Implementer
|
||||
events chan *types.Event
|
||||
stop chan struct{}
|
||||
implementer Implementer
|
||||
sender notification.Sender
|
||||
approvalManager approvals.Manager
|
||||
events chan *types.Event
|
||||
stop chan struct{}
|
||||
}
|
||||
type args struct {
|
||||
policy types.PolicyType
|
||||
|
@ -25,7 +29,7 @@ func TestProvider_checkUnversionedDeployment(t *testing.T) {
|
|||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantUpdated v1beta1.Deployment
|
||||
wantUpdatePlan *UpdatePlan
|
||||
wantShouldUpdateDeployment bool
|
||||
wantErr bool
|
||||
}{
|
||||
|
@ -56,26 +60,30 @@ func TestProvider_checkUnversionedDeployment(t *testing.T) {
|
|||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
},
|
||||
wantUpdated: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{forceUpdateImageAnnotation: "gcr.io/v2-namespace/hello-world:latest"},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:latest",
|
||||
wantUpdatePlan: &UpdatePlan{
|
||||
Deployment: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{forceUpdateImageAnnotation: "gcr.io/v2-namespace/hello-world:latest"},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
NewVersion: "latest",
|
||||
CurrentVersion: "latest",
|
||||
},
|
||||
wantShouldUpdateDeployment: true,
|
||||
wantErr: false,
|
||||
|
@ -107,26 +115,8 @@ func TestProvider_checkUnversionedDeployment(t *testing.T) {
|
|||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
},
|
||||
wantUpdated: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/goodbye-world:earliest",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
wantUpdatePlan: &UpdatePlan{
|
||||
Deployment: v1beta1.Deployment{},
|
||||
},
|
||||
wantShouldUpdateDeployment: false,
|
||||
wantErr: false,
|
||||
|
@ -158,26 +148,30 @@ func TestProvider_checkUnversionedDeployment(t *testing.T) {
|
|||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
},
|
||||
wantUpdated: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{forceUpdateImageAnnotation: "karolisr/keel:0.2.0"},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "force"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "karolisr/keel:0.2.0",
|
||||
wantUpdatePlan: &UpdatePlan{
|
||||
Deployment: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{forceUpdateImageAnnotation: "karolisr/keel:0.2.0"},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "force"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "karolisr/keel:0.2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
NewVersion: "0.2.0",
|
||||
CurrentVersion: "latest",
|
||||
},
|
||||
wantShouldUpdateDeployment: true,
|
||||
wantErr: false,
|
||||
|
@ -186,17 +180,19 @@ func TestProvider_checkUnversionedDeployment(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := &Provider{
|
||||
implementer: tt.fields.implementer,
|
||||
events: tt.fields.events,
|
||||
stop: tt.fields.stop,
|
||||
implementer: tt.fields.implementer,
|
||||
sender: tt.fields.sender,
|
||||
approvalManager: tt.fields.approvalManager,
|
||||
events: tt.fields.events,
|
||||
stop: tt.fields.stop,
|
||||
}
|
||||
gotUpdated, gotShouldUpdateDeployment, err := p.checkUnversionedDeployment(tt.args.policy, tt.args.repo, tt.args.deployment)
|
||||
gotUpdatePlan, gotShouldUpdateDeployment, err := p.checkUnversionedDeployment(tt.args.policy, tt.args.repo, tt.args.deployment)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Provider.checkUnversionedDeployment() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotUpdated, tt.wantUpdated) {
|
||||
t.Errorf("Provider.checkUnversionedDeployment() gotUpdated = %v, want %v", gotUpdated, tt.wantUpdated)
|
||||
if !reflect.DeepEqual(gotUpdatePlan, tt.wantUpdatePlan) {
|
||||
t.Errorf("Provider.checkUnversionedDeployment() gotUpdatePlan = %v, want %v", gotUpdatePlan, tt.wantUpdatePlan)
|
||||
}
|
||||
if gotShouldUpdateDeployment != tt.wantShouldUpdateDeployment {
|
||||
t.Errorf("Provider.checkUnversionedDeployment() gotShouldUpdateDeployment = %v, want %v", gotShouldUpdateDeployment, tt.wantShouldUpdateDeployment)
|
||||
|
|
|
@ -14,7 +14,10 @@ import (
|
|||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (p *Provider) checkVersionedDeployment(newVersion *types.Version, policy types.PolicyType, repo *types.Repository, deployment v1beta1.Deployment) (updated v1beta1.Deployment, shouldUpdateDeployment bool, err error) {
|
||||
// func (p *Provider) checkVersionedDeployment(newVersion *types.Version, policy types.PolicyType, repo *types.Repository, deployment v1beta1.Deployment) (updated v1beta1.Deployment, shouldUpdateDeployment bool, err error) {
|
||||
func (p *Provider) checkVersionedDeployment(newVersion *types.Version, policy types.PolicyType, repo *types.Repository, deployment v1beta1.Deployment) (updatePlan *UpdatePlan, shouldUpdateDeployment bool, err error) {
|
||||
updatePlan = &UpdatePlan{}
|
||||
|
||||
eventRepoRef, err := image.Parse(repo.Name)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -88,6 +91,10 @@ func (p *Provider) checkVersionedDeployment(newVersion *types.Version, policy ty
|
|||
"policy": policy,
|
||||
}).Info("provider.kubernetes: impacted deployment container found")
|
||||
|
||||
updatePlan.CurrentVersion = conatinerImageRef.Tag()
|
||||
updatePlan.NewVersion = newVersion.Original
|
||||
updatePlan.Deployment = deployment
|
||||
|
||||
// success, moving to next container
|
||||
continue
|
||||
}
|
||||
|
@ -148,6 +155,10 @@ func (p *Provider) checkVersionedDeployment(newVersion *types.Version, policy ty
|
|||
}
|
||||
deployment.SetAnnotations(annotations)
|
||||
|
||||
updatePlan.CurrentVersion = currentVersion.Original
|
||||
updatePlan.NewVersion = newVersion.Original
|
||||
updatePlan.Deployment = deployment
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"parsed_image": conatinerImageRef.Remote(),
|
||||
"raw_image_name": c.Image,
|
||||
|
@ -158,7 +169,7 @@ func (p *Provider) checkVersionedDeployment(newVersion *types.Version, policy ty
|
|||
}
|
||||
}
|
||||
|
||||
return deployment, shouldUpdateDeployment, nil
|
||||
return updatePlan, shouldUpdateDeployment, nil
|
||||
}
|
||||
|
||||
func updateContainer(container v1.Container, ref *image.Reference, version string) v1.Container {
|
||||
|
|
|
@ -4,10 +4,13 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/extension/notification"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/version"
|
||||
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
)
|
||||
|
@ -22,9 +25,11 @@ func unsafeGetVersion(ver string) *types.Version {
|
|||
|
||||
func TestProvider_checkVersionedDeployment(t *testing.T) {
|
||||
type fields struct {
|
||||
implementer Implementer
|
||||
events chan *types.Event
|
||||
stop chan struct{}
|
||||
implementer Implementer
|
||||
sender notification.Sender
|
||||
approvalManager approvals.Manager
|
||||
events chan *types.Event
|
||||
stop chan struct{}
|
||||
}
|
||||
type args struct {
|
||||
newVersion *types.Version
|
||||
|
@ -36,7 +41,7 @@ func TestProvider_checkVersionedDeployment(t *testing.T) {
|
|||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantUpdated v1beta1.Deployment
|
||||
wantUpdatePlan *UpdatePlan
|
||||
wantShouldUpdateDeployment bool
|
||||
wantErr bool
|
||||
}{
|
||||
|
@ -68,26 +73,30 @@ func TestProvider_checkVersionedDeployment(t *testing.T) {
|
|||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
},
|
||||
wantUpdated: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
wantUpdatePlan: &UpdatePlan{
|
||||
Deployment: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
NewVersion: "1.1.2",
|
||||
CurrentVersion: "1.1.1",
|
||||
},
|
||||
wantShouldUpdateDeployment: true,
|
||||
wantErr: false,
|
||||
|
@ -120,26 +129,10 @@ func TestProvider_checkVersionedDeployment(t *testing.T) {
|
|||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
},
|
||||
wantUpdated: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
wantUpdatePlan: &UpdatePlan{
|
||||
Deployment: v1beta1.Deployment{},
|
||||
NewVersion: "",
|
||||
CurrentVersion: "",
|
||||
},
|
||||
wantShouldUpdateDeployment: false,
|
||||
wantErr: false,
|
||||
|
@ -175,29 +168,33 @@ func TestProvider_checkVersionedDeployment(t *testing.T) {
|
|||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
},
|
||||
wantUpdated: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
v1.Container{
|
||||
Image: "yo-world:1.1.1",
|
||||
wantUpdatePlan: &UpdatePlan{
|
||||
Deployment: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "all"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
v1.Container{
|
||||
Image: "yo-world:1.1.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
NewVersion: "1.1.2",
|
||||
CurrentVersion: "1.1.1",
|
||||
},
|
||||
wantShouldUpdateDeployment: true,
|
||||
wantErr: false,
|
||||
|
@ -233,29 +230,33 @@ func TestProvider_checkVersionedDeployment(t *testing.T) {
|
|||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
},
|
||||
wantUpdated: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{forceUpdateImageAnnotation: "gcr.io/v2-namespace/hello-world:1.1.2"},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "force"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
v1.Container{
|
||||
Image: "yo-world:1.1.1",
|
||||
wantUpdatePlan: &UpdatePlan{
|
||||
Deployment: v1beta1.Deployment{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{
|
||||
Name: "dep-1",
|
||||
Namespace: "xxxx",
|
||||
Annotations: map[string]string{forceUpdateImageAnnotation: "gcr.io/v2-namespace/hello-world:1.1.2"},
|
||||
Labels: map[string]string{types.KeelPolicyLabel: "force"},
|
||||
},
|
||||
v1beta1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
v1.Container{
|
||||
Image: "yo-world:1.1.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
},
|
||||
v1beta1.DeploymentStatus{},
|
||||
NewVersion: "1.1.2",
|
||||
CurrentVersion: "latest",
|
||||
},
|
||||
wantShouldUpdateDeployment: true,
|
||||
wantErr: false,
|
||||
|
@ -264,17 +265,19 @@ func TestProvider_checkVersionedDeployment(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := &Provider{
|
||||
implementer: tt.fields.implementer,
|
||||
events: tt.fields.events,
|
||||
stop: tt.fields.stop,
|
||||
implementer: tt.fields.implementer,
|
||||
sender: tt.fields.sender,
|
||||
approvalManager: tt.fields.approvalManager,
|
||||
events: tt.fields.events,
|
||||
stop: tt.fields.stop,
|
||||
}
|
||||
gotUpdated, gotShouldUpdateDeployment, err := p.checkVersionedDeployment(tt.args.newVersion, tt.args.policy, tt.args.repo, tt.args.deployment)
|
||||
gotUpdatePlan, gotShouldUpdateDeployment, err := p.checkVersionedDeployment(tt.args.newVersion, tt.args.policy, tt.args.repo, tt.args.deployment)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Provider.checkVersionedDeployment() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotUpdated, tt.wantUpdated) {
|
||||
t.Errorf("Provider.checkVersionedDeployment() gotUpdated = %v, want %v", gotUpdated, tt.wantUpdated)
|
||||
if !reflect.DeepEqual(gotUpdatePlan, tt.wantUpdatePlan) {
|
||||
t.Errorf("Provider.checkVersionedDeployment() gotUpdatePlan = %v, want %v", gotUpdatePlan, tt.wantUpdatePlan)
|
||||
}
|
||||
if gotShouldUpdateDeployment != tt.wantShouldUpdateDeployment {
|
||||
t.Errorf("Provider.checkVersionedDeployment() gotShouldUpdateDeployment = %v, want %v", gotShouldUpdateDeployment, tt.wantShouldUpdateDeployment)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/types"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
@ -23,7 +26,7 @@ type Providers interface {
|
|||
}
|
||||
|
||||
// New - new providers registry
|
||||
func New(providers []Provider) *DefaultProviders {
|
||||
func New(providers []Provider, approvalsManager approvals.Manager) *DefaultProviders {
|
||||
pvs := make(map[string]Provider)
|
||||
|
||||
for _, p := range providers {
|
||||
|
@ -31,14 +34,46 @@ func New(providers []Provider) *DefaultProviders {
|
|||
log.Infof("provider.defaultProviders: provider '%s' registered", p.GetName())
|
||||
}
|
||||
|
||||
return &DefaultProviders{
|
||||
providers: pvs,
|
||||
dp := &DefaultProviders{
|
||||
providers: pvs,
|
||||
approvalsManager: approvalsManager,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
// subscribing to approved events
|
||||
// TODO: create Start() function for DefaultProviders
|
||||
go dp.subscribeToApproved()
|
||||
|
||||
return dp
|
||||
}
|
||||
|
||||
// DefaultProviders - default providers container
|
||||
type DefaultProviders struct {
|
||||
providers map[string]Provider
|
||||
providers map[string]Provider
|
||||
approvalsManager approvals.Manager
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
func (p *DefaultProviders) subscribeToApproved() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
approvedCh, err := p.approvalsManager.SubscribeApproved(ctx)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Fatal("provider.subscribeToApproved: failed to subscribe for approved reqs")
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case approval := <-approvedCh:
|
||||
p.Submit(*approval.Event)
|
||||
case <-p.stopCh:
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Submit - submit event to all providers
|
||||
|
@ -51,7 +86,7 @@ func (p *DefaultProviders) Submit(event types.Event) error {
|
|||
"provider": provider.GetName(),
|
||||
"event": event.Repository,
|
||||
"trigger": event.TriggerName,
|
||||
}).Error("provider.defaultProviders: submit event failed")
|
||||
}).Error("provider.Submit: submit event failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ func (g *DefaultGetter) lookupSecrets(image *types.TrackedImage) ([]string, erro
|
|||
"image": image.Image.Repository(),
|
||||
"pod_selector": selector,
|
||||
"secrets": podSecrets,
|
||||
}).Info("secrets.defaultGetter.lookupSecrets: pod secrets found")
|
||||
}).Debug("secrets.defaultGetter.lookupSecrets: pod secrets found")
|
||||
secrets = append(secrets, podSecrets...)
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ func (g *DefaultGetter) lookupSecrets(image *types.TrackedImage) ([]string, erro
|
|||
"image": image.Image.Repository(),
|
||||
"pod_selector": selector,
|
||||
"pods_checked": len(podList.Items),
|
||||
}).Info("secrets.defaultGetter.lookupSecrets: no secrets for image found")
|
||||
}).Warn("secrets.defaultGetter.lookupSecrets: no secrets for image found")
|
||||
}
|
||||
|
||||
return secrets, nil
|
||||
|
@ -210,7 +210,7 @@ func (g *DefaultGetter) getCredentialsFromSecret(image *types.TrackedImage) (*ty
|
|||
"provider": image.Provider,
|
||||
"registry": image.Image.Registry(),
|
||||
"image": image.Image.Repository(),
|
||||
}).Info("secrets.defaultGetter: secret looked up successfully")
|
||||
}).Debug("secrets.defaultGetter: secret looked up successfully")
|
||||
|
||||
return credentials, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/rusenask/keel/types"
|
||||
)
|
||||
|
||||
func (s *TriggerServer) approvalsHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
// unknown lists all
|
||||
approvals, err := s.approvalsManager.List()
|
||||
if err != nil {
|
||||
fmt.Fprintf(resp, "%s", err)
|
||||
resp.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if len(approvals) == 0 {
|
||||
approvals = make([]*types.Approval, 0)
|
||||
}
|
||||
|
||||
bts, err := json.Marshal(&approvals)
|
||||
if err != nil {
|
||||
fmt.Fprintf(resp, "%s", err)
|
||||
resp.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp.Write(bts)
|
||||
}
|
||||
|
||||
func (s *TriggerServer) approvalDeleteHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
identifier := getID(req)
|
||||
|
||||
err := s.approvalsManager.Delete(identifier)
|
||||
if err != nil {
|
||||
fmt.Fprintf(resp, "%s", err)
|
||||
resp.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(resp, identifier)
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/provider"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
)
|
||||
|
||||
func TestListApprovals(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
mem := memory.NewMemoryCache(100*time.Second, 100*time.Second, 10*time.Second)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
srv := NewTriggerServer(&Opts{Providers: providers, ApprovalManager: am})
|
||||
srv.registerRoutes(srv.router)
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Identifier: "123",
|
||||
VotesRequired: 5,
|
||||
NewVersion: "2.0.0",
|
||||
CurrentVersion: "1.0.0",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
// listing
|
||||
req, err := http.NewRequest("GET", "/v1/approvals", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create req: %s", err)
|
||||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
srv.router.ServeHTTP(rec, req)
|
||||
if rec.Code != 200 {
|
||||
t.Errorf("unexpected status code: %d", rec.Code)
|
||||
|
||||
t.Log(rec.Body.String())
|
||||
}
|
||||
|
||||
var approvals []*types.Approval
|
||||
|
||||
err = json.Unmarshal(rec.Body.Bytes(), &approvals)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal response into approvals: %s", err)
|
||||
}
|
||||
|
||||
if len(approvals) != 1 {
|
||||
t.Fatalf("expected to find 1 approval but found: %d", len(approvals))
|
||||
}
|
||||
|
||||
if approvals[0].VotesRequired != 5 {
|
||||
t.Errorf("unexpected votes required")
|
||||
}
|
||||
if approvals[0].NewVersion != "2.0.0" {
|
||||
t.Errorf("unexpected new version: %s", approvals[0].NewVersion)
|
||||
}
|
||||
if approvals[0].CurrentVersion != "1.0.0" {
|
||||
t.Errorf("unexpected current version: %s", approvals[0].CurrentVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteApproval(t *testing.T) {
|
||||
fp := &fakeProvider{}
|
||||
mem := memory.NewMemoryCache(100*time.Second, 100*time.Second, 10*time.Second)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
srv := NewTriggerServer(&Opts{Providers: providers, ApprovalManager: am})
|
||||
srv.registerRoutes(srv.router)
|
||||
|
||||
err := am.Create(&types.Approval{
|
||||
Identifier: "12345",
|
||||
VotesRequired: 5,
|
||||
NewVersion: "2.0.0",
|
||||
CurrentVersion: "1.0.0",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create approval: %s", err)
|
||||
}
|
||||
|
||||
// listing
|
||||
req, err := http.NewRequest("DELETE", "/v1/approvals/12345", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create req: %s", err)
|
||||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
srv.router.ServeHTTP(rec, req)
|
||||
if rec.Code != 200 {
|
||||
t.Errorf("unexpected status code: %d", rec.Code)
|
||||
|
||||
t.Log(rec.Body.String())
|
||||
}
|
||||
|
||||
_, err = am.Get("12345")
|
||||
if err == nil {
|
||||
t.Errorf("expected approval to be deleted")
|
||||
}
|
||||
|
||||
}
|
|
@ -3,9 +3,12 @@ package http
|
|||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/provider"
|
||||
// "github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -41,7 +44,9 @@ var fakeRequest = `{
|
|||
func TestDockerhubWebhookHandler(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
srv := NewTriggerServer(&Opts{Providers: providers})
|
||||
srv.registerRoutes(srv.router)
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
"github.com/urfave/negroni"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/provider"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/version"
|
||||
|
@ -23,22 +24,26 @@ type Opts struct {
|
|||
|
||||
// available providers
|
||||
Providers provider.Providers
|
||||
|
||||
ApprovalManager approvals.Manager
|
||||
}
|
||||
|
||||
// TriggerServer - webhook trigger & healthcheck server
|
||||
type TriggerServer struct {
|
||||
providers provider.Providers
|
||||
port int
|
||||
server *http.Server
|
||||
router *mux.Router
|
||||
providers provider.Providers
|
||||
approvalsManager approvals.Manager
|
||||
port int
|
||||
server *http.Server
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
// NewTriggerServer - create new HTTP trigger based server
|
||||
func NewTriggerServer(opts *Opts) *TriggerServer {
|
||||
return &TriggerServer{
|
||||
port: opts.Port,
|
||||
providers: opts.Providers,
|
||||
router: mux.NewRouter(),
|
||||
port: opts.Port,
|
||||
providers: opts.Providers,
|
||||
approvalsManager: opts.ApprovalManager,
|
||||
router: mux.NewRouter(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +71,10 @@ func (s *TriggerServer) Stop() {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
s.server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func getID(req *http.Request) string {
|
||||
return mux.Vars(req)["id"]
|
||||
}
|
||||
|
||||
func (s *TriggerServer) registerRoutes(mux *mux.Router) {
|
||||
|
@ -74,6 +82,11 @@ func (s *TriggerServer) registerRoutes(mux *mux.Router) {
|
|||
mux.HandleFunc("/healthz", s.healthHandler).Methods("GET", "OPTIONS")
|
||||
// version handler
|
||||
mux.HandleFunc("/version", s.versionHandler).Methods("GET", "OPTIONS")
|
||||
|
||||
// approvals
|
||||
mux.HandleFunc("/v1/approvals", s.approvalsHandler).Methods("GET", "OPTIONS")
|
||||
mux.HandleFunc("/v1/approvals/{id}", s.approvalDeleteHandler).Methods("DELETE", "OPTIONS")
|
||||
|
||||
// native webhooks handler
|
||||
mux.HandleFunc("/v1/webhooks/native", s.nativeHandler).Methods("POST", "OPTIONS")
|
||||
|
||||
|
|
|
@ -3,9 +3,13 @@ package http
|
|||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/provider"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -37,7 +41,9 @@ func TestNativeWebhookHandler(t *testing.T) {
|
|||
|
||||
fp := &fakeProvider{}
|
||||
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
|
||||
srv := NewTriggerServer(&Opts{Providers: providers})
|
||||
srv.registerRoutes(srv.router)
|
||||
|
@ -64,7 +70,9 @@ func TestNativeWebhookHandler(t *testing.T) {
|
|||
func TestNativeWebhookHandlerNoRepoName(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
srv := NewTriggerServer(&Opts{Providers: providers})
|
||||
srv.registerRoutes(srv.router)
|
||||
|
||||
|
|
|
@ -3,8 +3,12 @@ package http
|
|||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/provider"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -25,7 +29,9 @@ var fakeQuayWebhook = `{
|
|||
func TestQuayWebhookHandler(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
srv := NewTriggerServer(&Opts{Providers: providers})
|
||||
srv.registerRoutes(srv.router)
|
||||
|
||||
|
|
|
@ -2,9 +2,13 @@ package poll
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/provider"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
"github.com/rusenask/keel/util/image"
|
||||
|
||||
"testing"
|
||||
|
@ -39,7 +43,9 @@ func TestCheckDeployment(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
|
||||
// returning some sha
|
||||
frc := &fakeRegistryClient{
|
||||
|
|
|
@ -2,10 +2,14 @@ package poll
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/provider"
|
||||
"github.com/rusenask/keel/registry"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
"github.com/rusenask/keel/util/image"
|
||||
)
|
||||
|
||||
|
@ -53,7 +57,9 @@ func (p *fakeProvider) TrackedImages() ([]*types.TrackedImage, error) {
|
|||
func TestWatchTagJob(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
|
||||
frc := &fakeRegistryClient{
|
||||
digestToReturn: "sha256:0604af35299dd37ff23937d115d103532948b568a9dd8197d14c256a8ab8b0bb",
|
||||
|
@ -96,7 +102,9 @@ func TestWatchTagJob(t *testing.T) {
|
|||
func TestWatchTagJobLatest(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
|
||||
frc := &fakeRegistryClient{
|
||||
digestToReturn: "sha256:0604af35299dd37ff23937d115d103532948b568a9dd8197d14c256a8ab8b0bb",
|
||||
|
@ -139,7 +147,9 @@ func TestWatchTagJobLatest(t *testing.T) {
|
|||
func TestWatchAllTagsJob(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
|
||||
frc := &fakeRegistryClient{
|
||||
tagsToReturn: []string{"1.1.2", "1.1.3", "0.9.1"},
|
||||
|
@ -171,7 +181,9 @@ func TestWatchAllTagsJob(t *testing.T) {
|
|||
func TestWatchAllTagsJobCurrentLatest(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
|
||||
frc := &fakeRegistryClient{
|
||||
tagsToReturn: []string{"1.1.2", "1.1.3", "0.9.1"},
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package pubsub
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/provider"
|
||||
"github.com/rusenask/keel/types"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
"github.com/rusenask/keel/util/image"
|
||||
|
||||
"testing"
|
||||
|
@ -62,7 +66,10 @@ func TestCheckDeployment(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
|
||||
fs := &fakeSubscriber{}
|
||||
mng := &DefaultManager{
|
||||
|
|
|
@ -2,11 +2,15 @@ package pubsub
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/pubsub"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/rusenask/keel/approvals"
|
||||
"github.com/rusenask/keel/cache/memory"
|
||||
"github.com/rusenask/keel/provider"
|
||||
"github.com/rusenask/keel/util/codecs"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
@ -21,7 +25,9 @@ func fakeDoneFunc(id string, done bool) {
|
|||
func TestCallback(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
sub := &PubsubSubscriber{disableAck: true, providers: providers}
|
||||
|
||||
dataMsg := &Message{Action: "INSERT", Tag: "gcr.io/v2-namespace/hello-world:1.1.1"}
|
||||
|
@ -46,7 +52,9 @@ func TestCallback(t *testing.T) {
|
|||
func TestCallbackTagNotSemver(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
sub := &PubsubSubscriber{disableAck: true, providers: providers}
|
||||
|
||||
dataMsg := &Message{Action: "INSERT", Tag: "gcr.io/stemnapp/alpine-website:latest"}
|
||||
|
@ -72,7 +80,9 @@ func TestCallbackTagNotSemver(t *testing.T) {
|
|||
func TestCallbackNoTag(t *testing.T) {
|
||||
|
||||
fp := &fakeProvider{}
|
||||
providers := provider.New([]provider.Provider{fp})
|
||||
mem := memory.NewMemoryCache(100*time.Millisecond, 100*time.Millisecond, 10*time.Millisecond)
|
||||
am := approvals.New(mem, codecs.DefaultSerializer())
|
||||
providers := provider.New([]provider.Provider{fp}, am)
|
||||
sub := &PubsubSubscriber{disableAck: true, providers: providers}
|
||||
|
||||
dataMsg := &Message{Action: "INSERT", Tag: "gcr.io/stemnapp/alpine-website"}
|
||||
|
@ -92,5 +102,4 @@ func TestCallbackNoTag(t *testing.T) {
|
|||
if fp.submitted[0].Repository.Tag != "latest" {
|
||||
t.Errorf("expected repo tag %s but got %s", "latest", fp.submitted[0].Repository.Tag)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ var (
|
|||
"PostProviderSubmitNotification": PostProviderSubmitNotification,
|
||||
"NotificationPreDeploymentUpdate": NotificationPreDeploymentUpdate,
|
||||
"NotificationDeploymentUpdate": NotificationDeploymentUpdate,
|
||||
"NotificationPreReleaseUpdate": NotificationPreReleaseUpdate,
|
||||
"NotificationReleaseUpdate": NotificationReleaseUpdate,
|
||||
}
|
||||
|
||||
_NotificationValueToName = map[Notification]string{
|
||||
|
@ -20,6 +22,8 @@ var (
|
|||
PostProviderSubmitNotification: "PostProviderSubmitNotification",
|
||||
NotificationPreDeploymentUpdate: "NotificationPreDeploymentUpdate",
|
||||
NotificationDeploymentUpdate: "NotificationDeploymentUpdate",
|
||||
NotificationPreReleaseUpdate: "NotificationPreReleaseUpdate",
|
||||
NotificationReleaseUpdate: "NotificationReleaseUpdate",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -31,6 +35,8 @@ func init() {
|
|||
interface{}(PostProviderSubmitNotification).(fmt.Stringer).String(): PostProviderSubmitNotification,
|
||||
interface{}(NotificationPreDeploymentUpdate).(fmt.Stringer).String(): NotificationPreDeploymentUpdate,
|
||||
interface{}(NotificationDeploymentUpdate).(fmt.Stringer).String(): NotificationDeploymentUpdate,
|
||||
interface{}(NotificationPreReleaseUpdate).(fmt.Stringer).String(): NotificationPreReleaseUpdate,
|
||||
interface{}(NotificationReleaseUpdate).(fmt.Stringer).String(): NotificationReleaseUpdate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
// generated by jsonenums -type=ProviderType; DO NOT EDIT
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
_ProviderTypeNameToValue = map[string]ProviderType{
|
||||
"ProviderTypeUnknown": ProviderTypeUnknown,
|
||||
"ProviderTypeKubernetes": ProviderTypeKubernetes,
|
||||
"ProviderTypeHelm": ProviderTypeHelm,
|
||||
}
|
||||
|
||||
_ProviderTypeValueToName = map[ProviderType]string{
|
||||
ProviderTypeUnknown: "ProviderTypeUnknown",
|
||||
ProviderTypeKubernetes: "ProviderTypeKubernetes",
|
||||
ProviderTypeHelm: "ProviderTypeHelm",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
var v ProviderType
|
||||
if _, ok := interface{}(v).(fmt.Stringer); ok {
|
||||
_ProviderTypeNameToValue = map[string]ProviderType{
|
||||
interface{}(ProviderTypeUnknown).(fmt.Stringer).String(): ProviderTypeUnknown,
|
||||
interface{}(ProviderTypeKubernetes).(fmt.Stringer).String(): ProviderTypeKubernetes,
|
||||
interface{}(ProviderTypeHelm).(fmt.Stringer).String(): ProviderTypeHelm,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON is generated so ProviderType satisfies json.Marshaler.
|
||||
func (r ProviderType) MarshalJSON() ([]byte, error) {
|
||||
if s, ok := interface{}(r).(fmt.Stringer); ok {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
s, ok := _ProviderTypeValueToName[r]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid ProviderType: %d", r)
|
||||
}
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
// UnmarshalJSON is generated so ProviderType satisfies json.Unmarshaler.
|
||||
func (r *ProviderType) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return fmt.Errorf("ProviderType should be a string, got %s", data)
|
||||
}
|
||||
v, ok := _ProviderTypeNameToValue[s]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid ProviderType %q", s)
|
||||
}
|
||||
*r = v
|
||||
return nil
|
||||
}
|
127
types/types.go
127
types/types.go
|
@ -1,7 +1,9 @@
|
|||
// Package types holds most of the types used across Keel
|
||||
//go:generate jsonenums -type=Notification
|
||||
//go:generate jsonenums -type=Level
|
||||
//go:generate jsonenums -type=PolicyType
|
||||
//go:generate jsonenums -type=TriggerType
|
||||
//go:generate jsonenums -type=ProviderType
|
||||
package types
|
||||
|
||||
import (
|
||||
|
@ -30,6 +32,15 @@ const KeelPollDefaultSchedule = "@every 1m"
|
|||
// KeelDigestAnnotation - digest annotation
|
||||
const KeelDigestAnnotation = "keel.sh/digest"
|
||||
|
||||
// KeelMinimumApprovalsLabel - min approvals
|
||||
const KeelMinimumApprovalsLabel = "keel.sh/approvals"
|
||||
|
||||
// KeelApprovalDeadlineLabel - approval deadline
|
||||
const KeelApprovalDeadlineLabel = "keel.sh/approvalDeadline"
|
||||
|
||||
// KeelApprovalDeadlineDefault - default deadline in hours
|
||||
const KeelApprovalDeadlineDefault = 24
|
||||
|
||||
// Repository - represents main docker repository fields that
|
||||
// keel cares about
|
||||
type Repository struct {
|
||||
|
@ -230,3 +241,119 @@ func (l Level) Color() string {
|
|||
return "#9E9E9E"
|
||||
}
|
||||
}
|
||||
|
||||
// ProviderType - provider type used to differentiate different providers
|
||||
// when used with plugins
|
||||
type ProviderType int
|
||||
|
||||
// Known provider types
|
||||
const (
|
||||
ProviderTypeUnknown ProviderType = iota
|
||||
ProviderTypeKubernetes
|
||||
ProviderTypeHelm
|
||||
)
|
||||
|
||||
func (t ProviderType) String() string {
|
||||
switch t {
|
||||
case ProviderTypeUnknown:
|
||||
return "unknown"
|
||||
case ProviderTypeKubernetes:
|
||||
return "kubernetes"
|
||||
case ProviderTypeHelm:
|
||||
return "helm"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Approval used to store and track updates
|
||||
type Approval struct {
|
||||
// Provider name - Kubernetes/Helm
|
||||
Provider ProviderType `json:"provider,omitempty"`
|
||||
|
||||
// Identifier is used to inform user about specific
|
||||
// Helm release or k8s deployment
|
||||
// ie: k8s <namespace>/<deployment name>
|
||||
// helm: <namespace>/<release name>
|
||||
Identifier string `json:"identifier,omitempty"`
|
||||
|
||||
// Event that triggered evaluation
|
||||
Event *Event `json:"event,omitempty"`
|
||||
|
||||
Message string `json:"message,omitempty"`
|
||||
|
||||
CurrentVersion string `json:"currentVersion,omitempty"`
|
||||
NewVersion string `json:"newVersion,omitempty"`
|
||||
|
||||
// Requirements for the update such as number of votes
|
||||
// and deadline
|
||||
VotesRequired int `json:"votesRequired,omitempty"`
|
||||
VotesReceived int `json:"votesReceived,omitempty"`
|
||||
|
||||
// Voters is a list of voter
|
||||
// IDs for audit
|
||||
Voters []string `json:"voters,omitempty"`
|
||||
|
||||
// Explicitly rejected approval
|
||||
// can be set directly by user
|
||||
// so even if deadline is not reached approval
|
||||
// could be turned down
|
||||
Rejected bool `json:"rejected,omitempty"`
|
||||
|
||||
// Deadline for this request
|
||||
Deadline time.Time `json:"deadline,omitempty"`
|
||||
|
||||
// When this approval was created
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
// WHen this approval was updated
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
// ApprovalStatus - approval status type used in approvals
|
||||
// to determine whether it was rejected/approved or still pending
|
||||
type ApprovalStatus int
|
||||
|
||||
// Available approval status types
|
||||
const (
|
||||
ApprovalStatusUnknown ApprovalStatus = iota
|
||||
ApprovalStatusPending
|
||||
ApprovalStatusApproved
|
||||
ApprovalStatusRejected
|
||||
)
|
||||
|
||||
func (s ApprovalStatus) String() string {
|
||||
switch s {
|
||||
case ApprovalStatusPending:
|
||||
return "pending"
|
||||
case ApprovalStatusApproved:
|
||||
return "approved"
|
||||
case ApprovalStatusRejected:
|
||||
return "rejected"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Status - returns current approval status
|
||||
func (a *Approval) Status() ApprovalStatus {
|
||||
if a.Rejected {
|
||||
return ApprovalStatusRejected
|
||||
}
|
||||
|
||||
if a.VotesReceived >= a.VotesRequired {
|
||||
return ApprovalStatusApproved
|
||||
}
|
||||
|
||||
return ApprovalStatusPending
|
||||
}
|
||||
|
||||
// Expired - checks if approval is already expired
|
||||
func (a *Approval) Expired() bool {
|
||||
return a.Deadline.Before(time.Now())
|
||||
}
|
||||
|
||||
// Delta of what's changed
|
||||
// ie: webhookrelay/webhook-demo:0.15.0 -> webhookrelay/webhook-demo:0.16.0
|
||||
func (a *Approval) Delta() string {
|
||||
return fmt.Sprintf("%s -> %s", a.CurrentVersion, a.NewVersion)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package types
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParsePolicy(t *testing.T) {
|
||||
|
@ -104,3 +105,24 @@ func TestVersion_String(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpired(t *testing.T) {
|
||||
aprv := Approval{
|
||||
Deadline: time.Now().Add(-5 * time.Second),
|
||||
}
|
||||
|
||||
if !aprv.Expired() {
|
||||
t.Errorf("expected approval to be expired")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotExpired(t *testing.T) {
|
||||
aprv := Approval{
|
||||
Deadline: time.Now().Add(5 * time.Second),
|
||||
}
|
||||
|
||||
if aprv.Expired() {
|
||||
t.Errorf("expected approval to be not expired")
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package codecs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var bufferPool = sync.Pool{New: allocBuffer}
|
||||
|
||||
func allocBuffer() interface{} {
|
||||
return &bytes.Buffer{}
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufferPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func releaseBuffer(v *bytes.Buffer) {
|
||||
v.Reset()
|
||||
v.Grow(0)
|
||||
bufferPool.Put(v)
|
||||
}
|
||||
|
||||
// Serializer - generic serializer interface
|
||||
type Serializer interface {
|
||||
Encode(source interface{}) ([]byte, error)
|
||||
Decode(data []byte, target interface{}) error
|
||||
}
|
||||
|
||||
// DefaultSerializer - returns default serializer
|
||||
func DefaultSerializer() Serializer {
|
||||
return &GobSerializer{}
|
||||
}
|
||||
|
||||
// GobSerializer - gob based serializer
|
||||
type GobSerializer struct{}
|
||||
|
||||
// Encode - encodes source into bytes using Gob encoder
|
||||
func (s *GobSerializer) Encode(source interface{}) ([]byte, error) {
|
||||
buf := getBuffer()
|
||||
defer releaseBuffer(buf)
|
||||
enc := gob.NewEncoder(buf)
|
||||
err := enc.Encode(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Decode - decodes given bytes into target struct
|
||||
func (s *GobSerializer) Decode(data []byte, target interface{}) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
dec := gob.NewDecoder(buf)
|
||||
return dec.Decode(target)
|
||||
}
|
||||
|
||||
// JSONSerializer - JSON based serializer
|
||||
type JSONSerializer struct{}
|
||||
|
||||
// Encode - encodes source into bytes using JSON encoder
|
||||
func (s *JSONSerializer) Encode(source interface{}) ([]byte, error) {
|
||||
buf := getBuffer()
|
||||
defer releaseBuffer(buf)
|
||||
enc := json.NewEncoder(buf)
|
||||
err := enc.Encode(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Decode - decodes given bytes into target struct
|
||||
func (s *JSONSerializer) Decode(data []byte, target interface{}) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
dec := json.NewDecoder(buf)
|
||||
return dec.Decode(target)
|
||||
}
|
||||
|
||||
// Type - shows serializer type
|
||||
func (s *JSONSerializer) Type() string {
|
||||
return "JSON"
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package testing
|
||||
|
||||
import (
|
||||
core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
)
|
||||
|
@ -55,3 +56,9 @@ func (i *FakeK8sImplementer) Secret(namespace, name string) (*v1.Secret, error)
|
|||
func (i *FakeK8sImplementer) Pods(namespace, labelSelector string) (*v1.PodList, error) {
|
||||
return i.AvailablePods, nil
|
||||
}
|
||||
|
||||
// ConfigMaps - returns nothing (not implemented)
|
||||
func (i *FakeK8sImplementer) ConfigMaps(namespace string) core_v1.ConfigMapInterface {
|
||||
panic("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
# 1.0.3
|
||||
|
||||
* Replace example files with testable examples
|
||||
|
||||
# 1.0.2
|
||||
|
||||
* bug: quote non-string values in text formatter (#583)
|
||||
|
|
|
@ -247,6 +247,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
|
|||
| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
|
||||
| [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) |
|
||||
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
|
||||
| [AzureTableHook](https://github.com/kpfaulkner/azuretablehook/) | Hook for logging to Azure Table Storage|
|
||||
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
|
||||
| [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) |
|
||||
|
@ -260,7 +261,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
|
|||
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
|
||||
| [Influxus](http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB](http://influxdata.com/) |
|
||||
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||
| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
|
||||
| [KafkaLogrus](https://github.com/tracer0tong/kafkalogrus) | Hook for logging to Kafka |
|
||||
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
|
||||
| [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||
| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||
|
@ -285,6 +286,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
|
|||
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
||||
| [Syslog](https://github.com/sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||
| [Syslog TLS](https://github.com/shinji62/logrus-syslog-ng) | Send errors to remote syslog server with TLS support. |
|
||||
| [Telegram](https://github.com/rossmcdonald/telegram_hook) | Hook for logging errors to [Telegram](https://telegram.org/) |
|
||||
| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) |
|
||||
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
||||
| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
|
||||
|
|
|
@ -94,7 +94,10 @@ func (entry Entry) log(level Level, msg string) {
|
|||
entry.Level = level
|
||||
entry.Message = msg
|
||||
|
||||
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
err := entry.Logger.Hooks.Fire(level, &entry)
|
||||
entry.Logger.mu.Unlock()
|
||||
if err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||
entry.Logger.mu.Unlock()
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
// "os"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.JSONFormatter)
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||
// if err == nil {
|
||||
// log.Out = file
|
||||
// } else {
|
||||
// log.Info("Failed to log to file, using default stderr")
|
||||
// }
|
||||
|
||||
log.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"err": err,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
||||
}()
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 8,
|
||||
}).Debug("Started observing beach")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"temperature": -4,
|
||||
}).Debug("Temperature changes")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "orca",
|
||||
"size": 9009,
|
||||
}).Panic("It's over 9000!")
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package logrus
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -120,3 +121,24 @@ func TestErrorHookShouldFireOnError(t *testing.T) {
|
|||
assert.Equal(t, hook.Fired, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddHookRace(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
hook := new(ErrorHook)
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
log.AddHook(hook)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
log.Error("test")
|
||||
}()
|
||||
wg.Wait()
|
||||
}, func(fields Fields) {
|
||||
// the line may have been logged
|
||||
// before the hook was added, so we can't
|
||||
// actually assert on the hook
|
||||
})
|
||||
}
|
||||
|
|
|
@ -315,3 +315,9 @@ func (logger *Logger) level() Level {
|
|||
func (logger *Logger) SetLevel(level Level) {
|
||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||
}
|
||||
|
||||
func (logger *Logger) AddHook(hook Hook) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.Hooks.Add(hook)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
|
|||
|
||||
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
|
||||
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
|
||||
* URL hosts and paths can have variables with an optional regular expression.
|
||||
* URL hosts, paths and query values can have variables with an optional regular expression.
|
||||
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
|
||||
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
|
||||
|
||||
|
@ -24,9 +24,9 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
|
|||
* [Install](#install)
|
||||
* [Examples](#examples)
|
||||
* [Matching Routes](#matching-routes)
|
||||
* [Listing Routes](#listing-routes)
|
||||
* [Static Files](#static-files)
|
||||
* [Registered URLs](#registered-urls)
|
||||
* [Walking Routes](#walking-routes)
|
||||
* [Full Example](#full-example)
|
||||
|
||||
---
|
||||
|
@ -168,7 +168,6 @@ s.HandleFunc("/{key}/", ProductHandler)
|
|||
// "/products/{key}/details"
|
||||
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
||||
```
|
||||
|
||||
### Listing Routes
|
||||
|
||||
Routes on a mux can be listed using the Router.Walk method—useful for generating documentation:
|
||||
|
@ -191,9 +190,9 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
|||
func main() {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", handler)
|
||||
r.Methods("POST").HandleFunc("/products", handler)
|
||||
r.Methods("GET").HandleFunc("/articles", handler)
|
||||
r.Methods("GET", "PUT").HandleFunc("/articles/{id}", handler)
|
||||
r.HandleFunc("/products", handler).Methods("POST")
|
||||
r.HandleFunc("/articles", handler).Methods("GET")
|
||||
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
|
||||
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||
t, err := route.GetPathTemplate()
|
||||
if err != nil {
|
||||
|
@ -269,19 +268,21 @@ url, err := r.Get("article").URL("category", "technology", "id", "42")
|
|||
"/articles/technology/42"
|
||||
```
|
||||
|
||||
This also works for host variables:
|
||||
This also works for host and query value variables:
|
||||
|
||||
```go
|
||||
r := mux.NewRouter()
|
||||
r.Host("{subdomain}.domain.com").
|
||||
Path("/articles/{category}/{id:[0-9]+}").
|
||||
Queries("filter", "{filter}").
|
||||
HandlerFunc(ArticleHandler).
|
||||
Name("article")
|
||||
|
||||
// url.String() will be "http://news.domain.com/articles/technology/42"
|
||||
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
||||
url, err := r.Get("article").URL("subdomain", "news",
|
||||
"category", "technology",
|
||||
"id", "42")
|
||||
"id", "42",
|
||||
"filter", "gorilla")
|
||||
```
|
||||
|
||||
All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
|
||||
|
@ -319,6 +320,37 @@ url, err := r.Get("article").URL("subdomain", "news",
|
|||
"id", "42")
|
||||
```
|
||||
|
||||
### Walking Routes
|
||||
|
||||
The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
|
||||
the following prints all of the registered routes:
|
||||
|
||||
```go
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", handler)
|
||||
r.HandleFunc("/products", handler).Methods("POST")
|
||||
r.HandleFunc("/articles", handler).Methods("GET")
|
||||
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
|
||||
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||
t, err := route.GetPathTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// p will contain a regular expression that is compatible with regular expressions in Perl, Python, and other languages.
|
||||
// For example, the regular expression for path '/articles/{id}' will be '^/articles/(?P<v0>[^/]+)$'.
|
||||
p, err := route.GetPathRegexp()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := route.GetMethods()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(strings.Join(m, ","), t, p)
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
## Full Example
|
||||
|
||||
Here's a complete, runnable example of a small `mux` based server:
|
||||
|
|
|
@ -12,8 +12,8 @@ or other conditions. The main features are:
|
|||
|
||||
* Requests can be matched based on URL host, path, path prefix, schemes,
|
||||
header and query values, HTTP methods or using custom matchers.
|
||||
* URL hosts and paths can have variables with an optional regular
|
||||
expression.
|
||||
* URL hosts, paths and query values can have variables with an optional
|
||||
regular expression.
|
||||
* Registered URLs can be built, or "reversed", which helps maintaining
|
||||
references to resources.
|
||||
* Routes can be used as subrouters: nested routes are only tested if the
|
||||
|
@ -188,18 +188,20 @@ key/value pairs for the route variables. For the previous route, we would do:
|
|||
|
||||
"/articles/technology/42"
|
||||
|
||||
This also works for host variables:
|
||||
This also works for host and query value variables:
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.Host("{subdomain}.domain.com").
|
||||
Path("/articles/{category}/{id:[0-9]+}").
|
||||
Queries("filter", "{filter}").
|
||||
HandlerFunc(ArticleHandler).
|
||||
Name("article")
|
||||
|
||||
// url.String() will be "http://news.domain.com/articles/technology/42"
|
||||
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
||||
url, err := r.Get("article").URL("subdomain", "news",
|
||||
"category", "technology",
|
||||
"id", "42")
|
||||
"id", "42",
|
||||
"filter", "gorilla")
|
||||
|
||||
All variables defined in the route are required, and their values must
|
||||
conform to the corresponding patterns. These requirements guarantee that a
|
||||
|
|
|
@ -13,6 +13,10 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMethodMismatch = errors.New("method is not allowed")
|
||||
)
|
||||
|
||||
// NewRouter returns a new router instance.
|
||||
func NewRouter() *Router {
|
||||
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
|
||||
|
@ -39,6 +43,10 @@ func NewRouter() *Router {
|
|||
type Router struct {
|
||||
// Configurable Handler to be used when no route matches.
|
||||
NotFoundHandler http.Handler
|
||||
|
||||
// Configurable Handler to be used when the request method does not match the route.
|
||||
MethodNotAllowedHandler http.Handler
|
||||
|
||||
// Parent route, if this is a subrouter.
|
||||
parent parentRoute
|
||||
// Routes to be matched, in order.
|
||||
|
@ -65,6 +73,11 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
|||
}
|
||||
}
|
||||
|
||||
if match.MatchErr == ErrMethodMismatch && r.MethodNotAllowedHandler != nil {
|
||||
match.Handler = r.MethodNotAllowedHandler
|
||||
return true
|
||||
}
|
||||
|
||||
// Closest match for a router (includes sub-routers)
|
||||
if r.NotFoundHandler != nil {
|
||||
match.Handler = r.NotFoundHandler
|
||||
|
@ -105,9 +118,15 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
req = setVars(req, match.Vars)
|
||||
req = setCurrentRoute(req, match.Route)
|
||||
}
|
||||
|
||||
if handler == nil && match.MatchErr == ErrMethodMismatch {
|
||||
handler = methodNotAllowedHandler()
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
handler = http.NotFoundHandler()
|
||||
}
|
||||
|
||||
if !r.KeepContext {
|
||||
defer contextClear(req)
|
||||
}
|
||||
|
@ -176,6 +195,13 @@ func (r *Router) UseEncodedPath() *Router {
|
|||
// parentRoute
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (r *Router) getBuildScheme() string {
|
||||
if r.parent != nil {
|
||||
return r.parent.getBuildScheme()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getNamedRoutes returns the map where named routes are registered.
|
||||
func (r *Router) getNamedRoutes() map[string]*Route {
|
||||
if r.namedRoutes == nil {
|
||||
|
@ -299,10 +325,6 @@ type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
|
|||
|
||||
func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
|
||||
for _, t := range r.routes {
|
||||
if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
err := walkFn(t, r, ancestors)
|
||||
if err == SkipRouter {
|
||||
continue
|
||||
|
@ -312,10 +334,12 @@ func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
|
|||
}
|
||||
for _, sr := range t.matchers {
|
||||
if h, ok := sr.(*Router); ok {
|
||||
ancestors = append(ancestors, t)
|
||||
err := h.walk(walkFn, ancestors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ancestors = ancestors[:len(ancestors)-1]
|
||||
}
|
||||
}
|
||||
if h, ok := t.handler.(*Router); ok {
|
||||
|
@ -339,6 +363,11 @@ type RouteMatch struct {
|
|||
Route *Route
|
||||
Handler http.Handler
|
||||
Vars map[string]string
|
||||
|
||||
// MatchErr is set to appropriate matching error
|
||||
// It is set to ErrMethodMismatch if there is a mismatch in
|
||||
// the request method and route method
|
||||
MatchErr error
|
||||
}
|
||||
|
||||
type contextKey int
|
||||
|
@ -458,7 +487,7 @@ func mapFromPairsToString(pairs ...string) (map[string]string, error) {
|
|||
return m, nil
|
||||
}
|
||||
|
||||
// mapFromPairsToRegex converts variadic string paramers to a
|
||||
// mapFromPairsToRegex converts variadic string parameters to a
|
||||
// string to regex map.
|
||||
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
|
||||
length, err := checkPairs(pairs...)
|
||||
|
@ -540,3 +569,12 @@ func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]s
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// methodNotAllowed replies to the request with an HTTP status code 405.
|
||||
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// methodNotAllowedHandler returns a simple request handler
|
||||
// that replies to each request with a status code 405.
|
||||
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
@ -35,6 +36,7 @@ type routeTest struct {
|
|||
scheme string // the expected scheme of the built URL
|
||||
host string // the expected host of the built URL
|
||||
path string // the expected path of the built URL
|
||||
query string // the expected query string of the built URL
|
||||
pathTemplate string // the expected path template of the route
|
||||
hostTemplate string // the expected host template of the route
|
||||
methods []string // the expected route methods
|
||||
|
@ -743,6 +745,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=bar&baz=ding",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -752,6 +755,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=bar&baz=ding",
|
||||
pathTemplate: `/api`,
|
||||
hostTemplate: `www.example.com`,
|
||||
shouldMatch: true,
|
||||
|
@ -763,6 +767,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=bar&baz=ding",
|
||||
pathTemplate: `/api`,
|
||||
hostTemplate: `www.example.com`,
|
||||
shouldMatch: true,
|
||||
|
@ -783,6 +788,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v1": "bar"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=bar",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -792,6 +798,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v1": "bar", "v2": "ding"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=bar&baz=ding",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -801,6 +808,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v1": "10"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=10",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -819,6 +827,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v1": "1"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=1",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -828,6 +837,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v1": "1"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=1",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -846,6 +856,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v1": "1a"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=1a",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -864,6 +875,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v-1": "bar"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=bar",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -873,6 +885,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v-1": "bar", "v-2": "ding"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=bar&baz=ding",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -882,6 +895,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v-1": "10"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=10",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -891,6 +905,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"v-1": "1a"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=1a",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -900,6 +915,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -918,6 +934,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -945,6 +962,7 @@ func TestQueries(t *testing.T) {
|
|||
vars: map[string]string{"foo": ""},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
|
@ -956,6 +974,16 @@ func TestQueries(t *testing.T) {
|
|||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Queries route with pattern, match, escaped value",
|
||||
route: new(Route).Queries("foo", "{v1}"),
|
||||
request: newRequest("GET", "http://localhost?foo=%25bar%26%20%2F%3D%3F"),
|
||||
vars: map[string]string{"v1": "%bar& /=?"},
|
||||
host: "",
|
||||
path: "",
|
||||
query: "foo=%25bar%26+%2F%3D%3F",
|
||||
shouldMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -1187,6 +1215,28 @@ func TestSubRouter(t *testing.T) {
|
|||
pathTemplate: `/{category}`,
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Build with scheme on parent router",
|
||||
route: new(Route).Schemes("ftp").Host("google.com").Subrouter().Path("/"),
|
||||
request: newRequest("GET", "ftp://google.com/"),
|
||||
scheme: "ftp",
|
||||
host: "google.com",
|
||||
path: "/",
|
||||
pathTemplate: `/`,
|
||||
hostTemplate: `google.com`,
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Prefer scheme on child route when building URLs",
|
||||
route: new(Route).Schemes("https", "ftp").Host("google.com").Subrouter().Schemes("ftp").Path("/"),
|
||||
request: newRequest("GET", "ftp://google.com/"),
|
||||
scheme: "ftp",
|
||||
host: "google.com",
|
||||
path: "/",
|
||||
pathTemplate: `/`,
|
||||
hostTemplate: `google.com`,
|
||||
shouldMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -1382,11 +1432,55 @@ func TestWalkNested(t *testing.T) {
|
|||
l2 := l1.PathPrefix("/l").Subrouter()
|
||||
l2.Path("/a")
|
||||
|
||||
paths := []string{"/g", "/g/o", "/g/o/r", "/g/o/r/i", "/g/o/r/i/l", "/g/o/r/i/l/l", "/g/o/r/i/l/l/a"}
|
||||
testCases := []struct {
|
||||
path string
|
||||
ancestors []*Route
|
||||
}{
|
||||
{"/g", []*Route{}},
|
||||
{"/g/o", []*Route{g.parent.(*Route)}},
|
||||
{"/g/o/r", []*Route{g.parent.(*Route), o.parent.(*Route)}},
|
||||
{"/g/o/r/i", []*Route{g.parent.(*Route), o.parent.(*Route), r.parent.(*Route)}},
|
||||
{"/g/o/r/i/l", []*Route{g.parent.(*Route), o.parent.(*Route), r.parent.(*Route), i.parent.(*Route)}},
|
||||
{"/g/o/r/i/l/l", []*Route{g.parent.(*Route), o.parent.(*Route), r.parent.(*Route), i.parent.(*Route), l1.parent.(*Route)}},
|
||||
{"/g/o/r/i/l/l/a", []*Route{g.parent.(*Route), o.parent.(*Route), r.parent.(*Route), i.parent.(*Route), l1.parent.(*Route), l2.parent.(*Route)}},
|
||||
}
|
||||
|
||||
idx := 0
|
||||
err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
|
||||
path := testCases[idx].path
|
||||
tpl := route.regexp.path.template
|
||||
if tpl != path {
|
||||
t.Errorf(`Expected %s got %s`, path, tpl)
|
||||
}
|
||||
currWantAncestors := testCases[idx].ancestors
|
||||
if !reflect.DeepEqual(currWantAncestors, ancestors) {
|
||||
t.Errorf(`Expected %+v got %+v`, currWantAncestors, ancestors)
|
||||
}
|
||||
idx++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if idx != len(testCases) {
|
||||
t.Errorf("Expected %d routes, found %d", len(testCases), idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkSubrouters(t *testing.T) {
|
||||
router := NewRouter()
|
||||
|
||||
g := router.Path("/g").Subrouter()
|
||||
o := g.PathPrefix("/o").Subrouter()
|
||||
o.Methods("GET")
|
||||
o.Methods("PUT")
|
||||
|
||||
// all 4 routes should be matched, but final 2 routes do not have path templates
|
||||
paths := []string{"/g", "/g/o", "", ""}
|
||||
idx := 0
|
||||
err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
|
||||
path := paths[idx]
|
||||
tpl := route.regexp.path.template
|
||||
tpl, _ := route.GetPathTemplate()
|
||||
if tpl != path {
|
||||
t.Errorf(`Expected %s got %s`, path, tpl)
|
||||
}
|
||||
|
@ -1492,6 +1586,7 @@ func testRoute(t *testing.T, test routeTest) {
|
|||
route := test.route
|
||||
vars := test.vars
|
||||
shouldMatch := test.shouldMatch
|
||||
query := test.query
|
||||
shouldRedirect := test.shouldRedirect
|
||||
uri := url.URL{
|
||||
Scheme: test.scheme,
|
||||
|
@ -1561,6 +1656,13 @@ func testRoute(t *testing.T, test routeTest) {
|
|||
return
|
||||
}
|
||||
}
|
||||
if query != "" {
|
||||
u, _ := route.URL(mapToPairs(match.Vars)...)
|
||||
if query != u.RawQuery {
|
||||
t.Errorf("(%v) URL query not equal: expected %v, got %v", test.title, query, u.RawQuery)
|
||||
return
|
||||
}
|
||||
}
|
||||
if shouldRedirect && match.Handler == nil {
|
||||
t.Errorf("(%v) Did not redirect", test.title)
|
||||
return
|
||||
|
@ -1769,3 +1871,42 @@ func newRequest(method, url string) *http.Request {
|
|||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func TestNoMatchMethodErrorHandler(t *testing.T) {
|
||||
func1 := func(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
r := NewRouter()
|
||||
r.HandleFunc("/", func1).Methods("GET", "POST")
|
||||
|
||||
req, _ := http.NewRequest("PUT", "http://localhost/", nil)
|
||||
match := new(RouteMatch)
|
||||
matched := r.Match(req, match)
|
||||
|
||||
if matched {
|
||||
t.Error("Should not have matched route for methods")
|
||||
}
|
||||
|
||||
if match.MatchErr != ErrMethodMismatch {
|
||||
t.Error("Should get ErrMethodMismatch error")
|
||||
}
|
||||
|
||||
resp := NewRecorder()
|
||||
r.ServeHTTP(resp, req)
|
||||
if resp.Code != 405 {
|
||||
t.Errorf("Expecting code %v", 405)
|
||||
}
|
||||
|
||||
// Add matching route
|
||||
r.HandleFunc("/", func1).Methods("PUT")
|
||||
|
||||
match = new(RouteMatch)
|
||||
matched = r.Match(req, match)
|
||||
|
||||
if !matched {
|
||||
t.Error("Should have matched route for methods")
|
||||
}
|
||||
|
||||
if match.MatchErr != nil {
|
||||
t.Error("Should not have any matching error. Found:", match.MatchErr)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,12 +121,7 @@ func TestRouteMatchers(t *testing.T) {
|
|||
var routeMatch RouteMatch
|
||||
matched := router.Match(request, &routeMatch)
|
||||
if matched != shouldMatch {
|
||||
// Need better messages. :)
|
||||
if matched {
|
||||
t.Errorf("Should match.")
|
||||
} else {
|
||||
t.Errorf("Should not match.")
|
||||
}
|
||||
t.Errorf("Expected: %v\nGot: %v\nRequest: %v %v", shouldMatch, matched, request.Method, url)
|
||||
}
|
||||
|
||||
if matched {
|
||||
|
@ -188,7 +183,6 @@ func TestRouteMatchers(t *testing.T) {
|
|||
match(true)
|
||||
|
||||
// 2nd route --------------------------------------------------------------
|
||||
|
||||
// Everything match.
|
||||
reset2()
|
||||
match(true)
|
||||
|
|
|
@ -35,7 +35,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash,
|
|||
// Now let's parse it.
|
||||
defaultPattern := "[^/]+"
|
||||
if matchQuery {
|
||||
defaultPattern = "[^?&]*"
|
||||
defaultPattern = ".*"
|
||||
} else if matchHost {
|
||||
defaultPattern = "[^.]+"
|
||||
matchPrefix = false
|
||||
|
@ -178,6 +178,9 @@ func (r *routeRegexp) url(values map[string]string) (string, error) {
|
|||
if !ok {
|
||||
return "", fmt.Errorf("mux: missing route variable %q", v)
|
||||
}
|
||||
if r.matchQuery {
|
||||
value = url.QueryEscape(value)
|
||||
}
|
||||
urlValues[k] = value
|
||||
}
|
||||
rv := fmt.Sprintf(r.reverse, urlValues...)
|
||||
|
|
|
@ -52,12 +52,27 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
|||
if r.buildOnly || r.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var matchErr error
|
||||
|
||||
// Match everything.
|
||||
for _, m := range r.matchers {
|
||||
if matched := m.Match(req, match); !matched {
|
||||
if _, ok := m.(methodMatcher); ok {
|
||||
matchErr = ErrMethodMismatch
|
||||
continue
|
||||
}
|
||||
matchErr = nil
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if matchErr != nil {
|
||||
match.MatchErr = matchErr
|
||||
return false
|
||||
}
|
||||
|
||||
match.MatchErr = nil
|
||||
// Yay, we have a match. Let's collect some info about it.
|
||||
if match.Route == nil {
|
||||
match.Route = r
|
||||
|
@ -68,6 +83,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
|||
if match.Vars == nil {
|
||||
match.Vars = make(map[string]string)
|
||||
}
|
||||
|
||||
// Set variables.
|
||||
if r.regexp != nil {
|
||||
r.regexp.setMatch(req, match, r)
|
||||
|
@ -482,13 +498,14 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
|||
return nil, err
|
||||
}
|
||||
var scheme, host, path string
|
||||
queries := make([]string, 0, len(r.regexp.queries))
|
||||
if r.regexp.host != nil {
|
||||
if host, err = r.regexp.host.url(values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scheme = "http"
|
||||
if r.buildScheme != "" {
|
||||
scheme = r.buildScheme
|
||||
if s := r.getBuildScheme(); s != "" {
|
||||
scheme = s
|
||||
}
|
||||
}
|
||||
if r.regexp.path != nil {
|
||||
|
@ -496,10 +513,18 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, q := range r.regexp.queries {
|
||||
var query string
|
||||
if query, err = q.url(values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, query)
|
||||
}
|
||||
return &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
Path: path,
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
Path: path,
|
||||
RawQuery: strings.Join(queries, "&"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -525,8 +550,8 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
|||
Scheme: "http",
|
||||
Host: host,
|
||||
}
|
||||
if r.buildScheme != "" {
|
||||
u.Scheme = r.buildScheme
|
||||
if s := r.getBuildScheme(); s != "" {
|
||||
u.Scheme = s
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
@ -640,11 +665,22 @@ func (r *Route) buildVars(m map[string]string) map[string]string {
|
|||
|
||||
// parentRoute allows routes to know about parent host and path definitions.
|
||||
type parentRoute interface {
|
||||
getBuildScheme() string
|
||||
getNamedRoutes() map[string]*Route
|
||||
getRegexpGroup() *routeRegexpGroup
|
||||
buildVars(map[string]string) map[string]string
|
||||
}
|
||||
|
||||
func (r *Route) getBuildScheme() string {
|
||||
if r.buildScheme != "" {
|
||||
return r.buildScheme
|
||||
}
|
||||
if r.parent != nil {
|
||||
return r.parent.getBuildScheme()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getNamedRoutes returns the map where named routes are registered.
|
||||
func (r *Route) getNamedRoutes() map[string]*Route {
|
||||
if r.parent == nil {
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017 Karolis Rusenas
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,94 @@
|
|||
# Kubernetes backed KV
|
||||
|
||||
[](https://godoc.org/github.com/rusenask/k8s-kv/kv)
|
||||
|
||||
Use Kubernetes config maps as key/value store!
|
||||
When to use k8s-kv:
|
||||
* You have a simple application that has a need to store some configuration and you can't be bothered to set up EBS like volumes or use some fancy external KV store.
|
||||
* You have a stateless application that suddenly got to store state and you are not into converting
|
||||
it into full stateless app that will use a proper database.
|
||||
|
||||
When __not to__ use k8s-kv:
|
||||
* You have a read/write heavy multi-node application (k8s-kv doesn't have cross-app locking).
|
||||
* You want to store bigger values than 1MB. Even though k8s-kv uses compression for the data stored in bucket - it's wise to not try the limits. It's there because of the limit in Etcd. In this case use something else.
|
||||
|
||||
|
||||
## Basics
|
||||
|
||||
Package API:
|
||||
|
||||
```
|
||||
// Pyt key/value pair into the store
|
||||
Put(key string, value []byte) error
|
||||
// Get value of the specified key
|
||||
Get(key string) (value []byte, err error)
|
||||
// Delete key/value pair from the store
|
||||
Delete(key string) error
|
||||
// List all key/value pairs under specified prefix
|
||||
List(prefix string) (data map[string][]byte, err error)
|
||||
// Delete config map (results in deleted data)
|
||||
Teardown() error
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
* Don't be silly, you can't put a lot of stuff here.
|
||||
|
||||
## Example
|
||||
|
||||
Usage example:
|
||||
|
||||
1. Get minikube or your favourite k8s environment running.
|
||||
|
||||
2. In your app you will probably want to use this: https://github.com/kubernetes/client-go/tree/master/examples/in-cluster-client-configuration
|
||||
|
||||
3. Get ConfigMaps interface and supply it to this lib:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rusenask/k8s-kv/kv"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// get ConfigMapInterface to access config maps in "default" namespace
|
||||
func getImplementer() (implementer core_v1.ConfigMapInterface) {
|
||||
cfg, err := clientcmd.BuildConfigFromFlags("", ".kubeconfig") // in your app you could replace it with in-cluster-config
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return client.ConfigMaps("default")
|
||||
}
|
||||
|
||||
func main() {
|
||||
impl := getImplementer()
|
||||
|
||||
// getting acces to k8s-kv. "my-app" will become a label
|
||||
// for this config map, this way it's easier to manage configs
|
||||
// "bucket1" will be config map's name and represent one entry in config maps list
|
||||
kvdb, err := kv.New(impl, "my-app", "bucket1")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// insert a key "foo" with value "hello kubernetes world"
|
||||
kvdb.Put("foo", []byte("hello kubernetes world"))
|
||||
|
||||
// get value of key "foo"
|
||||
stored, _ := kvdb.Get("foo")
|
||||
|
||||
fmt.Println(string(stored))
|
||||
}
|
||||
```
|
|
@ -0,0 +1,40 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rusenask/k8s-kv/kv"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
func getImplementer() (implementer core_v1.ConfigMapInterface) {
|
||||
cfg, err := clientcmd.BuildConfigFromFlags("", ".kubeconfig") // in your app you could replace it with in-cluster-config
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return client.ConfigMaps("default")
|
||||
}
|
||||
|
||||
func main() {
|
||||
impl := getImplementer()
|
||||
|
||||
kvdb, err := kv.New(impl, "my-app", "bucket1")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
kvdb.Put("foo", []byte("hello kubernetes world"))
|
||||
|
||||
stored, _ := kvdb.Get("foo")
|
||||
|
||||
fmt.Println(string(stored))
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
Package kv implements a low-level key/value store backed by Kubernetes config maps.
|
||||
It supports main operations expected from key/value store such as Put, Get, Delete and List.
|
||||
Operations are protected by an internal mutex and therefore can be safely used inside a single
|
||||
node application.
|
||||
Basics
|
||||
There are only few things worth to know: key/value database is created based on bucket name so in order
|
||||
to have multiple configMaps - use different bucket names. Teardown() function will remove configMap entry
|
||||
completely destroying all entries.
|
||||
Caveats
|
||||
Since k8s-kv is based on configMaps which are in turn based on Etcd key/value store - all values have a limitation
|
||||
of 1MB so each bucket in k8s-kv is limited to that size. To overcome it - create more buckets.
|
||||
If you have multi-node application that is frequently reading/writing to the same buckets - be aware of race
|
||||
conditions as it doesn't provide any cross-node locking capabilities.
|
||||
*/
|
||||
package kv
|
|
@ -0,0 +1,311 @@
|
|||
package kv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(&internalMap{})
|
||||
}
|
||||
|
||||
type internalMap struct {
|
||||
Data map[string][]byte
|
||||
}
|
||||
|
||||
// errors
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
var b64 = base64.StdEncoding
|
||||
|
||||
// KVDB generic kv package interface
|
||||
type KVDB interface {
|
||||
Put(key string, value []byte) error
|
||||
Get(key string) (value []byte, err error)
|
||||
Delete(key string) error
|
||||
List(prefix string) (data map[string][]byte, err error)
|
||||
Teardown() error
|
||||
}
|
||||
|
||||
// KV provides access to key/value store operations such as Put, Get, Delete, List.
|
||||
// Entry in ConfigMap is created based on bucket name and total size is limited to 1MB per bucket.
|
||||
// Operations are protected by an internal mutex so it's safe to use in a single node application.
|
||||
type KV struct {
|
||||
app string
|
||||
bucket string
|
||||
implementer ConfigMapInterface
|
||||
mu *sync.RWMutex
|
||||
serializer Serializer
|
||||
}
|
||||
|
||||
// ConfigMapInterface implements a subset of Kubernetes original ConfigMapInterface to provide
|
||||
// required operations for k8s-kv. Main purpose of this interface is to enable easier testing.
|
||||
type ConfigMapInterface interface {
|
||||
Get(name string, options meta_v1.GetOptions) (*v1.ConfigMap, error)
|
||||
Create(cfgMap *v1.ConfigMap) (*v1.ConfigMap, error)
|
||||
Update(cfgMap *v1.ConfigMap) (*v1.ConfigMap, error)
|
||||
Delete(name string, options *meta_v1.DeleteOptions) error
|
||||
}
|
||||
|
||||
// New creates a new instance of KV. Requires prepared ConfigMapInterface (provided by go-client), app and bucket names.
|
||||
// App name is used as a label to make it easier to distinguish different k8s-kv instances created by separate (or the same)
|
||||
// application. Bucket name is used to give a name to config map.
|
||||
func New(implementer ConfigMapInterface, app, bucket string) (*KV, error) {
|
||||
kv := &KV{
|
||||
implementer: implementer,
|
||||
app: app,
|
||||
bucket: bucket,
|
||||
mu: &sync.RWMutex{},
|
||||
serializer: DefaultSerializer(),
|
||||
}
|
||||
|
||||
_, err := kv.getMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kv, nil
|
||||
|
||||
}
|
||||
|
||||
// Teardown deletes configMap for this bucket. All bucket's data is lost.
|
||||
func (k *KV) Teardown() error {
|
||||
return k.implementer.Delete(k.bucket, &meta_v1.DeleteOptions{})
|
||||
}
|
||||
|
||||
func (k *KV) getMap() (*v1.ConfigMap, error) {
|
||||
cfgMap, err := k.implementer.Get(k.bucket, meta_v1.GetOptions{})
|
||||
if err != nil {
|
||||
// creating
|
||||
if apierrors.IsNotFound(err) {
|
||||
return k.newConfigMapsObject()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfgMap.Data == nil {
|
||||
cfgMap.Data = make(map[string]string)
|
||||
}
|
||||
|
||||
// it's there, nothing to do
|
||||
return cfgMap, nil
|
||||
}
|
||||
|
||||
func encodeInternalMap(serializer Serializer, data map[string][]byte) (string, error) {
|
||||
var im internalMap
|
||||
im.Data = data
|
||||
bts, err := serializer.Encode(&im)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err = w.Write(bts); err != nil {
|
||||
return "", err
|
||||
}
|
||||
w.Close()
|
||||
|
||||
return b64.EncodeToString(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
func decodeInternalMap(serializer Serializer, data string) (map[string][]byte, error) {
|
||||
if data == "" {
|
||||
empty := make(map[string][]byte)
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
b, err := b64.DecodeString(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := gzip.NewReader(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decompressed, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var im internalMap
|
||||
|
||||
err = serializer.Decode(decompressed, &im)
|
||||
return im.Data, err
|
||||
}
|
||||
|
||||
const dataKey = "data"
|
||||
|
||||
func (k *KV) newConfigMapsObject() (*v1.ConfigMap, error) {
|
||||
|
||||
var lbs labels
|
||||
|
||||
lbs.init()
|
||||
|
||||
// apply labels
|
||||
lbs.set("BUCKET", k.bucket)
|
||||
lbs.set("APP", k.app)
|
||||
lbs.set("OWNER", "K8S-KV")
|
||||
|
||||
// create and return configmap object
|
||||
cfgMap := &v1.ConfigMap{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Name: k.bucket,
|
||||
Labels: lbs.toMap(),
|
||||
},
|
||||
Data: map[string]string{
|
||||
dataKey: "",
|
||||
},
|
||||
}
|
||||
|
||||
cm, err := k.implementer.Create(cfgMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
func (k *KV) saveInternalMap(cfgMap *v1.ConfigMap, im map[string][]byte) error {
|
||||
encoded, err := encodeInternalMap(k.serializer, im)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfgMap.Data[dataKey] = encoded
|
||||
|
||||
return k.saveMap(cfgMap)
|
||||
}
|
||||
|
||||
func (k *KV) getInternalMap() (*v1.ConfigMap, map[string][]byte, error) {
|
||||
cfgMap, err := k.getMap()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
im, err := decodeInternalMap(k.serializer, cfgMap.Data[dataKey])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return cfgMap, im, nil
|
||||
}
|
||||
|
||||
func (k *KV) saveMap(cfgMap *v1.ConfigMap) error {
|
||||
_, err := k.implementer.Update(cfgMap)
|
||||
return err
|
||||
}
|
||||
|
||||
// Put saves key/value pair into a bucket. Value can be any []byte value (ie: encoded JSON/GOB)
|
||||
func (k *KV) Put(key string, value []byte) error {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
|
||||
cfgMap, im, err := k.getInternalMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
im[key] = value
|
||||
|
||||
return k.saveInternalMap(cfgMap, im)
|
||||
}
|
||||
|
||||
// Get retrieves value from the key/value store bucket or returns ErrNotFound error if it was not found.
|
||||
func (k *KV) Get(key string) (value []byte, err error) {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
|
||||
_, im, err := k.getInternalMap()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
val, ok := im[key]
|
||||
if !ok {
|
||||
return []byte(""), ErrNotFound
|
||||
}
|
||||
|
||||
return val, nil
|
||||
|
||||
}
|
||||
|
||||
// Delete removes entry from the KV store bucket.
|
||||
func (k *KV) Delete(key string) error {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
|
||||
cfgMap, im, err := k.getInternalMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(im, key)
|
||||
|
||||
return k.saveInternalMap(cfgMap, im)
|
||||
}
|
||||
|
||||
// List retrieves all entries that match specific prefix
|
||||
func (k *KV) List(prefix string) (data map[string][]byte, err error) {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
|
||||
_, im, err := k.getInternalMap()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data = make(map[string][]byte)
|
||||
for key, val := range im {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
data[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// labels is a map of key value pairs to be included as metadata in a configmap object.
|
||||
type labels map[string]string
|
||||
|
||||
func (lbs *labels) init() { *lbs = labels(make(map[string]string)) }
|
||||
func (lbs labels) get(key string) string { return lbs[key] }
|
||||
func (lbs labels) set(key, val string) { lbs[key] = val }
|
||||
|
||||
func (lbs labels) keys() (ls []string) {
|
||||
for key := range lbs {
|
||||
ls = append(ls, key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (lbs labels) match(set labels) bool {
|
||||
for _, key := range set.keys() {
|
||||
if lbs.get(key) != set.get(key) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (lbs labels) toMap() map[string]string { return lbs }
|
||||
|
||||
func (lbs *labels) fromMap(kvs map[string]string) {
|
||||
for k, v := range kvs {
|
||||
lbs.set(k, v)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package kv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
)
|
||||
|
||||
type fakeImplementer struct {
|
||||
getcfgMap *v1.ConfigMap
|
||||
|
||||
createdMap *v1.ConfigMap
|
||||
updatedMap *v1.ConfigMap
|
||||
|
||||
deletedName string
|
||||
deletedOptions *meta_v1.DeleteOptions
|
||||
}
|
||||
|
||||
func (i *fakeImplementer) Get(name string, options meta_v1.GetOptions) (*v1.ConfigMap, error) {
|
||||
return i.getcfgMap, nil
|
||||
}
|
||||
|
||||
func (i *fakeImplementer) Create(cfgMap *v1.ConfigMap) (*v1.ConfigMap, error) {
|
||||
i.createdMap = cfgMap
|
||||
return i.createdMap, nil
|
||||
}
|
||||
|
||||
func (i *fakeImplementer) Update(cfgMap *v1.ConfigMap) (*v1.ConfigMap, error) {
|
||||
i.updatedMap = cfgMap
|
||||
return i.updatedMap, nil
|
||||
}
|
||||
|
||||
func (i *fakeImplementer) Delete(name string, options *meta_v1.DeleteOptions) error {
|
||||
i.deletedName = name
|
||||
i.deletedOptions = options
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGetMap(t *testing.T) {
|
||||
fi := &fakeImplementer{
|
||||
getcfgMap: &v1.ConfigMap{
|
||||
Data: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
kv, err := New(fi, "app", "b1")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get kv: %s", err)
|
||||
}
|
||||
|
||||
cfgMap, err := kv.getMap()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get map: %s", err)
|
||||
}
|
||||
|
||||
if cfgMap.Data["foo"] != "bar" {
|
||||
t.Errorf("cfgMap.Data is missing expected key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
|
||||
im := map[string][]byte{
|
||||
"foo": []byte("bar"),
|
||||
}
|
||||
fi := &fakeImplementer{
|
||||
getcfgMap: &v1.ConfigMap{
|
||||
Data: map[string]string{},
|
||||
},
|
||||
}
|
||||
kv, err := New(fi, "app", "b1")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get kv: %s", err)
|
||||
}
|
||||
|
||||
cfgMap, _ := kv.getMap()
|
||||
|
||||
kv.saveInternalMap(cfgMap, im)
|
||||
|
||||
val, err := kv.Get("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get key: %s", err)
|
||||
}
|
||||
|
||||
if string(val) != "bar" {
|
||||
t.Errorf("expected 'bar' but got: %s", string(val))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
|
||||
im := map[string][]byte{
|
||||
"a": []byte("a-val"),
|
||||
"b": []byte("b-val"),
|
||||
"c": []byte("c-val"),
|
||||
"d": []byte("d-val"),
|
||||
}
|
||||
|
||||
fi := &fakeImplementer{
|
||||
getcfgMap: &v1.ConfigMap{
|
||||
Data: map[string]string{},
|
||||
},
|
||||
}
|
||||
kv, err := New(fi, "app", "b1")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get kv: %s", err)
|
||||
}
|
||||
|
||||
cfgMap, _ := kv.getMap()
|
||||
|
||||
kv.saveInternalMap(cfgMap, im)
|
||||
|
||||
err = kv.Put("b", []byte("updated"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get key: %s", err)
|
||||
}
|
||||
|
||||
updatedIm, err := decodeInternalMap(kv.serializer, fi.updatedMap.Data[dataKey])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode internal map: %s", err)
|
||||
}
|
||||
|
||||
if string(updatedIm["b"]) != "updated" {
|
||||
t.Errorf("b value was not updated")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEncodeInternal(t *testing.T) {
|
||||
serializer := DefaultSerializer()
|
||||
|
||||
im := make(map[string][]byte)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
im[fmt.Sprintf("foo-%d", i)] = []byte(fmt.Sprintf("some important data here %d", i))
|
||||
}
|
||||
|
||||
encoded, err := encodeInternalMap(serializer, im)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode map: %s", err)
|
||||
}
|
||||
|
||||
decoded, err := decodeInternalMap(serializer, encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode map: %s", err)
|
||||
}
|
||||
|
||||
if string(decoded["foo-1"]) != "some important data here 1" {
|
||||
t.Errorf("expected to find 'some important data here 1' but got: %s", string(decoded["foo-1"]))
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package kv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var bufferPool = sync.Pool{New: allocBuffer}
|
||||
|
||||
func allocBuffer() interface{} {
|
||||
return &bytes.Buffer{}
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufferPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func releaseBuffer(v *bytes.Buffer) {
|
||||
v.Reset()
|
||||
v.Grow(0)
|
||||
bufferPool.Put(v)
|
||||
}
|
||||
|
||||
// Serializer - generic serializer interface
|
||||
type Serializer interface {
|
||||
Encode(source interface{}) ([]byte, error)
|
||||
Decode(data []byte, target interface{}) error
|
||||
}
|
||||
|
||||
// DefaultSerializer - returns default serializer
|
||||
func DefaultSerializer() Serializer {
|
||||
return &GobSerializer{}
|
||||
}
|
||||
|
||||
// GobSerializer - gob based serializer
|
||||
type GobSerializer struct{}
|
||||
|
||||
// Encode - encodes source into bytes using Gob encoder
|
||||
func (s *GobSerializer) Encode(source interface{}) ([]byte, error) {
|
||||
buf := getBuffer()
|
||||
defer releaseBuffer(buf)
|
||||
enc := gob.NewEncoder(buf)
|
||||
err := enc.Encode(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Decode - decodes given bytes into target struct
|
||||
func (s *GobSerializer) Decode(data []byte, target interface{}) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
dec := gob.NewDecoder(buf)
|
||||
return dec.Decode(target)
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/rusenask/k8s-kv/kv"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
const clusterConfig = ".kubeconfig"
|
||||
const testingNamespace = "default"
|
||||
|
||||
func getImplementer(t *testing.T) (implementer core_v1.ConfigMapInterface) {
|
||||
cfg, err := clientcmd.BuildConfigFromFlags("", clusterConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get config: %s", err)
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create client: %s", err)
|
||||
}
|
||||
|
||||
return client.ConfigMaps(testingNamespace)
|
||||
}
|
||||
|
||||
func TestPut(t *testing.T) {
|
||||
|
||||
impl := getImplementer(t)
|
||||
kv, err := kv.New(impl, "test", "testput")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create kv: %s", err)
|
||||
}
|
||||
defer kv.Teardown()
|
||||
|
||||
err = kv.Put("key", []byte("val"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPutDirectoryKeys(t *testing.T) {
|
||||
impl := getImplementer(t)
|
||||
kv, err := kv.New(impl, "test", "testputdirectorykeys")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create kv: %s", err)
|
||||
}
|
||||
defer kv.Teardown()
|
||||
|
||||
err = kv.Put("/somedir/key-here", []byte("val"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put: %s", err)
|
||||
}
|
||||
|
||||
val, err := kv.Get("/somedir/key-here")
|
||||
if err != nil {
|
||||
t.Errorf("failed to get key: %s", err)
|
||||
}
|
||||
|
||||
if string(val) != "val" {
|
||||
t.Errorf("unexpected return: %s", string(val))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
|
||||
impl := getImplementer(t)
|
||||
kv, err := kv.New(impl, "test", "testget")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create kv: %s", err)
|
||||
}
|
||||
defer kv.Teardown()
|
||||
|
||||
err = kv.Put("foo", []byte("bar"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put: %s", err)
|
||||
}
|
||||
|
||||
// getting it back
|
||||
val, err := kv.Get("foo")
|
||||
if err != nil {
|
||||
t.Errorf("failed to get: %s", err)
|
||||
}
|
||||
|
||||
if string(val) != "bar" {
|
||||
t.Errorf("expected 'bar' but got: '%s'", string(val))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
|
||||
impl := getImplementer(t)
|
||||
kvdb, err := kv.New(impl, "test", "testdelete")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create kv: %s", err)
|
||||
}
|
||||
defer kvdb.Teardown()
|
||||
|
||||
err = kvdb.Put("foo", []byte("bar"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put: %s", err)
|
||||
}
|
||||
|
||||
// getting it back
|
||||
val, err := kvdb.Get("foo")
|
||||
if err != nil {
|
||||
t.Errorf("failed to get: %s", err)
|
||||
}
|
||||
|
||||
if string(val) != "bar" {
|
||||
t.Errorf("expected 'bar' but got: '%s'", string(val))
|
||||
}
|
||||
|
||||
// deleting it
|
||||
err = kvdb.Delete("foo")
|
||||
if err != nil {
|
||||
t.Errorf("got error while deleting: %s", err)
|
||||
}
|
||||
|
||||
_, err = kvdb.Get("foo")
|
||||
if err != kv.ErrNotFound {
|
||||
t.Errorf("expected to get an error on deleted key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
count := 3
|
||||
|
||||
impl := getImplementer(t)
|
||||
kv, err := kv.New(impl, "test", "testlist")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create kv: %s", err)
|
||||
}
|
||||
defer kv.Teardown()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
err = kv.Put(fmt.Sprint(i), []byte(fmt.Sprintf("bar-%d", i)))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
items, err := kv.List("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list items, error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != count {
|
||||
t.Errorf("expected %d items, got: %d", count, len(items))
|
||||
}
|
||||
|
||||
if string(items["0"]) != "bar-0" {
|
||||
t.Errorf("unexpected value on '0': %s", items["0"])
|
||||
}
|
||||
if string(items["1"]) != "bar-1" {
|
||||
t.Errorf("unexpected value on '1': %s", items["1"])
|
||||
}
|
||||
if string(items["2"]) != "bar-2" {
|
||||
t.Errorf("unexpected value on '2': %s", items["2"])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestListPrefix(t *testing.T) {
|
||||
impl := getImplementer(t)
|
||||
kv, err := kv.New(impl, "test", "testlistprefix")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create kv: %s", err)
|
||||
}
|
||||
defer kv.Teardown()
|
||||
|
||||
err = kv.Put("aaa", []byte("aaa"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put key, error: %s", err)
|
||||
}
|
||||
err = kv.Put("aaaaa", []byte("aaa"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put key, error: %s", err)
|
||||
}
|
||||
err = kv.Put("aaaaaaa", []byte("aaa"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put key, error: %s", err)
|
||||
}
|
||||
|
||||
err = kv.Put("bbb", []byte("bbb"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put key, error: %s", err)
|
||||
}
|
||||
err = kv.Put("bbbbb", []byte("bbb"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put key, error: %s", err)
|
||||
}
|
||||
err = kv.Put("bbbbbbb", []byte("bbb"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to put key, error: %s", err)
|
||||
}
|
||||
|
||||
items, err := kv.List("aaa")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list items, error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 3 {
|
||||
t.Errorf("expected %d items, got: %d", 3, len(items))
|
||||
}
|
||||
|
||||
if string(items["aaa"]) != "aaa" {
|
||||
t.Errorf("unexpected value on 'aaa': %s", items["aaa"])
|
||||
}
|
||||
if string(items["aaaaa"]) != "aaa" {
|
||||
t.Errorf("unexpected value on 'aaaaa': %s", items["aaaaa"])
|
||||
}
|
||||
if string(items["aaaaaaa"]) != "aaa" {
|
||||
t.Errorf("unexpected value on 'aaaaaaa': %s", items["aaaaaaa"])
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package logrus_test
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Example_basic() {
|
||||
var log = logrus.New()
|
||||
log.Formatter = new(logrus.JSONFormatter)
|
||||
log.Formatter = new(logrus.TextFormatter) //default
|
||||
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
||||
log.Level = logrus.DebugLevel
|
||||
log.Out = os.Stdout
|
||||
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||
// if err == nil {
|
||||
// log.Out = file
|
||||
// } else {
|
||||
// log.Info("Failed to log to file, using default stderr")
|
||||
// }
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
entry := err.(*logrus.Entry)
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"err_animal": entry.Data["animal"],
|
||||
"err_size": entry.Data["size"],
|
||||
"err_level": entry.Level,
|
||||
"err_message": entry.Message,
|
||||
"number": 100,
|
||||
}).Error("The ice breaks!") // or use Fatal() to force the process to exit with a nonzero code
|
||||
}
|
||||
}()
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 8,
|
||||
}).Debug("Started observing beach")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"temperature": -4,
|
||||
}).Debug("Temperature changes")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "orca",
|
||||
"size": 9009,
|
||||
}).Panic("It's over 9000!")
|
||||
|
||||
// Output:
|
||||
// level=debug msg="Started observing beach" animal=walrus number=8
|
||||
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
// level=debug msg="Temperature changes" temperature=-4
|
||||
// level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
// level=error msg="The ice breaks!" err_animal=orca err_level=panic err_message="It's over 9000!" err_size=9009 number=100 omg=true
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package logrus_test
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Example_hook() {
|
||||
var log = logrus.New()
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
|
||||
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
||||
log.Out = os.Stdout
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Error("The ice breaks!")
|
||||
|
||||
// Output:
|
||||
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
// level=error msg="The ice breaks!" number=100 omg=true
|
||||
}
|
5
vendor/golang.org/x/crypto/chacha20poly1305/internal/chacha20/chacha_generic.go
generated
vendored
5
vendor/golang.org/x/crypto/chacha20poly1305/internal/chacha20/chacha_generic.go
generated
vendored
|
@ -167,9 +167,8 @@ func core(out *[64]byte, in *[16]byte, k *[32]byte) {
|
|||
}
|
||||
|
||||
// XORKeyStream crypts bytes from in to out using the given key and counters.
|
||||
// In and out may be the same slice but otherwise should not overlap. Counter
|
||||
// contains the raw ChaCha20 counter bytes (i.e. block counter followed by
|
||||
// nonce).
|
||||
// In and out must overlap entirely or not at all. Counter contains the raw
|
||||
// ChaCha20 counter bytes (i.e. block counter followed by nonce).
|
||||
func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
|
||||
var block [64]byte
|
||||
var counterCopy [16]byte
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package auth authenticates a message using a secret key.
|
||||
|
||||
The Sum function, viewed as a function of the message for a uniform random
|
||||
key, is designed to meet the standard notion of unforgeability. This means
|
||||
that an attacker cannot find authenticators for any messages not authenticated
|
||||
by the sender, even if the attacker has adaptively influenced the messages
|
||||
authenticated by the sender. For a formal definition see, e.g., Section 2.4
|
||||
of Bellare, Kilian, and Rogaway, "The security of the cipher block chaining
|
||||
message authentication code," Journal of Computer and System Sciences 61 (2000),
|
||||
362–399; http://www-cse.ucsd.edu/~mihir/papers/cbc.html.
|
||||
|
||||
auth does not make any promises regarding "strong" unforgeability; perhaps
|
||||
one valid authenticator can be converted into another valid authenticator for
|
||||
the same message. NaCl also does not make any promises regarding "truncated
|
||||
unforgeability."
|
||||
|
||||
This package is interoperable with NaCl: https://nacl.cr.yp.to/auth.html.
|
||||
*/
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
)
|
||||
|
||||
const (
|
||||
// Size is the size, in bytes, of an authenticated digest.
|
||||
Size = 32
|
||||
// KeySize is the size, in bytes, of an authentication key.
|
||||
KeySize = 32
|
||||
)
|
||||
|
||||
// Sum generates an authenticator for m using a secret key and returns the
|
||||
// 32-byte digest.
|
||||
func Sum(m []byte, key *[KeySize]byte) *[Size]byte {
|
||||
mac := hmac.New(sha512.New, key[:])
|
||||
mac.Write(m)
|
||||
out := new([KeySize]byte)
|
||||
copy(out[:], mac.Sum(nil)[:Size])
|
||||
return out
|
||||
}
|
||||
|
||||
// Verify checks that digest is a valid authenticator of message m under the
|
||||
// given secret key. Verify does not leak timing information.
|
||||
func Verify(digest []byte, m []byte, key *[32]byte) bool {
|
||||
if len(digest) != Size {
|
||||
return false
|
||||
}
|
||||
mac := hmac.New(sha512.New, key[:])
|
||||
mac.Write(m)
|
||||
expectedMAC := mac.Sum(nil) // first 256 bits of 512-bit sum
|
||||
return hmac.Equal(digest, expectedMAC[:Size])
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
rand "crypto/rand"
|
||||
mrand "math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test cases are from RFC 4231, and match those present in the tests directory
|
||||
// of the download here: https://nacl.cr.yp.to/install.html
|
||||
var testCases = []struct {
|
||||
key [32]byte
|
||||
msg []byte
|
||||
out [32]byte
|
||||
}{
|
||||
{
|
||||
key: [32]byte{
|
||||
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
|
||||
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
|
||||
0x0b, 0x0b, 0x0b, 0x0b,
|
||||
},
|
||||
msg: []byte("Hi There"),
|
||||
out: [32]byte{
|
||||
0x87, 0xaa, 0x7c, 0xde, 0xa5, 0xef, 0x61, 0x9d,
|
||||
0x4f, 0xf0, 0xb4, 0x24, 0x1a, 0x1d, 0x6c, 0xb0,
|
||||
0x23, 0x79, 0xf4, 0xe2, 0xce, 0x4e, 0xc2, 0x78,
|
||||
0x7a, 0xd0, 0xb3, 0x05, 0x45, 0xe1, 0x7c, 0xde,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: [32]byte{'J', 'e', 'f', 'e'},
|
||||
msg: []byte("what do ya want for nothing?"),
|
||||
out: [32]byte{
|
||||
0x16, 0x4b, 0x7a, 0x7b, 0xfc, 0xf8, 0x19, 0xe2,
|
||||
0xe3, 0x95, 0xfb, 0xe7, 0x3b, 0x56, 0xe0, 0xa3,
|
||||
0x87, 0xbd, 0x64, 0x22, 0x2e, 0x83, 0x1f, 0xd6,
|
||||
0x10, 0x27, 0x0c, 0xd7, 0xea, 0x25, 0x05, 0x54,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: [32]byte{
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa,
|
||||
},
|
||||
msg: []byte{ // 50 bytes of 0xdd
|
||||
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
|
||||
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
|
||||
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
|
||||
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
|
||||
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
|
||||
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
|
||||
0xdd, 0xdd,
|
||||
},
|
||||
out: [32]byte{
|
||||
0xfa, 0x73, 0xb0, 0x08, 0x9d, 0x56, 0xa2, 0x84,
|
||||
0xef, 0xb0, 0xf0, 0x75, 0x6c, 0x89, 0x0b, 0xe9,
|
||||
0xb1, 0xb5, 0xdb, 0xdd, 0x8e, 0xe8, 0x1a, 0x36,
|
||||
0x55, 0xf8, 0x3e, 0x33, 0xb2, 0x27, 0x9d, 0x39,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: [32]byte{
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x19,
|
||||
},
|
||||
msg: []byte{
|
||||
0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
|
||||
0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
|
||||
0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
|
||||
0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
|
||||
0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
|
||||
0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
|
||||
0xcd, 0xcd,
|
||||
},
|
||||
out: [32]byte{
|
||||
0xb0, 0xba, 0x46, 0x56, 0x37, 0x45, 0x8c, 0x69,
|
||||
0x90, 0xe5, 0xa8, 0xc5, 0xf6, 0x1d, 0x4a, 0xf7,
|
||||
0xe5, 0x76, 0xd9, 0x7f, 0xf9, 0x4b, 0x87, 0x2d,
|
||||
0xe7, 0x6f, 0x80, 0x50, 0x36, 0x1e, 0xe3, 0xdb,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestSum(t *testing.T) {
|
||||
for i, test := range testCases {
|
||||
tag := Sum(test.msg, &test.key)
|
||||
if !bytes.Equal(tag[:], test.out[:]) {
|
||||
t.Errorf("#%d: Sum: got\n%x\nwant\n%x", i, tag, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
wrongMsg := []byte("unknown msg")
|
||||
|
||||
for i, test := range testCases {
|
||||
if !Verify(test.out[:], test.msg, &test.key) {
|
||||
t.Errorf("#%d: Verify(%x, %q, %x) failed", i, test.out, test.msg, test.key)
|
||||
}
|
||||
if Verify(test.out[:], wrongMsg, &test.key) {
|
||||
t.Errorf("#%d: Verify(%x, %q, %x) unexpectedly passed", i, test.out, wrongMsg, test.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStress(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("exhaustiveness test")
|
||||
}
|
||||
|
||||
var key [32]byte
|
||||
msg := make([]byte, 10000)
|
||||
prng := mrand.New(mrand.NewSource(0))
|
||||
|
||||
// copied from tests/auth5.c in nacl
|
||||
for i := 0; i < 10000; i++ {
|
||||
if _, err := rand.Read(key[:]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := rand.Read(msg[:i]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tag := Sum(msg[:i], &key)
|
||||
if !Verify(tag[:], msg[:i], &key) {
|
||||
t.Errorf("#%d: unexpected failure from Verify", i)
|
||||
}
|
||||
if i > 0 {
|
||||
msgIndex := prng.Intn(i)
|
||||
oldMsgByte := msg[msgIndex]
|
||||
msg[msgIndex] += byte(1 + prng.Intn(255))
|
||||
if Verify(tag[:], msg[:i], &key) {
|
||||
t.Errorf("#%d: unexpected success from Verify after corrupting message", i)
|
||||
}
|
||||
msg[msgIndex] = oldMsgByte
|
||||
|
||||
tag[prng.Intn(len(tag))] += byte(1 + prng.Intn(255))
|
||||
if Verify(tag[:], msg[:i], &key) {
|
||||
t.Errorf("#%d: unexpected success from Verify after corrupting authenticator", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAuth(b *testing.B) {
|
||||
var key [32]byte
|
||||
if _, err := rand.Read(key[:]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
buf := make([]byte, 1024)
|
||||
if _, err := rand.Read(buf[:]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.SetBytes(int64(len(buf)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
tag := Sum(buf, &key)
|
||||
if Verify(tag[:], buf, &key) == false {
|
||||
b.Fatal("unexpected failure from Verify")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/nacl/auth"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
// Load your secret key from a safe place and reuse it across multiple
|
||||
// Sum calls. (Obviously don't use this example key for anything
|
||||
// real.) If you want to convert a passphrase to a key, use a suitable
|
||||
// package like bcrypt or scrypt.
|
||||
secretKeyBytes, err := hex.DecodeString("6368616e676520746869732070617373776f726420746f206120736563726574")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var secretKey [32]byte
|
||||
copy(secretKey[:], secretKeyBytes)
|
||||
|
||||
mac := auth.Sum([]byte("hello world"), &secretKey)
|
||||
fmt.Printf("%x\n", *mac)
|
||||
result := auth.Verify(mac[:], []byte("hello world"), &secretKey)
|
||||
fmt.Println(result)
|
||||
badResult := auth.Verify(mac[:], []byte("different message"), &secretKey)
|
||||
fmt.Println(badResult)
|
||||
// Output: eca5a521f3d77b63f567fb0cb6f5f2d200641bc8dada42f60c5f881260c30317
|
||||
// true
|
||||
// false
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package box authenticates and encrypts messages using public-key cryptography.
|
||||
Package box authenticates and encrypts small messages using public-key cryptography.
|
||||
|
||||
Box uses Curve25519, XSalsa20 and Poly1305 to encrypt and authenticate
|
||||
messages. The length of messages is not hidden.
|
||||
|
@ -13,6 +13,23 @@ example, by using nonce 1 for the first message, nonce 2 for the second
|
|||
message, etc. Nonces are long enough that randomly generated nonces have
|
||||
negligible risk of collision.
|
||||
|
||||
Messages should be small because:
|
||||
|
||||
1. The whole message needs to be held in memory to be processed.
|
||||
|
||||
2. Using large messages pressures implementations on small machines to decrypt
|
||||
and process plaintext before authenticating it. This is very dangerous, and
|
||||
this API does not allow it, but a protocol that uses excessive message sizes
|
||||
might present some implementations with no other choice.
|
||||
|
||||
3. Fixed overheads will be sufficiently amortised by messages as small as 8KB.
|
||||
|
||||
4. Performance may be improved by working with messages that fit into data caches.
|
||||
|
||||
Thus large amounts of data should be chunked so that each message is small.
|
||||
(Each message still needs a unique nonce.) If in doubt, 16KB is a reasonable
|
||||
chunk size.
|
||||
|
||||
This package is interoperable with NaCl: https://nacl.cr.yp.to/box.html.
|
||||
*/
|
||||
package box // import "golang.org/x/crypto/nacl/box"
|
||||
|
@ -56,7 +73,7 @@ func Precompute(sharedKey, peersPublicKey, privateKey *[32]byte) {
|
|||
}
|
||||
|
||||
// Seal appends an encrypted and authenticated copy of message to out, which
|
||||
// will be Overhead bytes longer than the original and must not overlap. The
|
||||
// will be Overhead bytes longer than the original and must not overlap it. The
|
||||
// nonce must be unique for each distinct message for a given pair of keys.
|
||||
func Seal(out, message []byte, nonce *[24]byte, peersPublicKey, privateKey *[32]byte) []byte {
|
||||
var sharedKey [32]byte
|
||||
|
|
|
@ -13,6 +13,23 @@ example, by using nonce 1 for the first message, nonce 2 for the second
|
|||
message, etc. Nonces are long enough that randomly generated nonces have
|
||||
negligible risk of collision.
|
||||
|
||||
Messages should be small because:
|
||||
|
||||
1. The whole message needs to be held in memory to be processed.
|
||||
|
||||
2. Using large messages pressures implementations on small machines to decrypt
|
||||
and process plaintext before authenticating it. This is very dangerous, and
|
||||
this API does not allow it, but a protocol that uses excessive message sizes
|
||||
might present some implementations with no other choice.
|
||||
|
||||
3. Fixed overheads will be sufficiently amortised by messages as small as 8KB.
|
||||
|
||||
4. Performance may be improved by working with messages that fit into data caches.
|
||||
|
||||
Thus large amounts of data should be chunked so that each message is small.
|
||||
(Each message still needs a unique nonce.) If in doubt, 16KB is a reasonable
|
||||
chunk size.
|
||||
|
||||
This package is interoperable with NaCl: https://nacl.cr.yp.to/secretbox.html.
|
||||
*/
|
||||
package secretbox // import "golang.org/x/crypto/nacl/secretbox"
|
||||
|
|
|
@ -12,7 +12,10 @@ import (
|
|||
)
|
||||
|
||||
func TestKeyExpiry(t *testing.T) {
|
||||
kring, _ := ReadKeyRing(readerFromHex(expiringKeyHex))
|
||||
kring, err := ReadKeyRing(readerFromHex(expiringKeyHex))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entity := kring[0]
|
||||
|
||||
const timeFormat = "2006-01-02"
|
||||
|
@ -104,7 +107,10 @@ func TestGoodCrossSignature(t *testing.T) {
|
|||
|
||||
// TestExternallyRevokableKey attempts to load and parse a key with a third party revocation permission.
|
||||
func TestExternallyRevocableKey(t *testing.T) {
|
||||
kring, _ := ReadKeyRing(readerFromHex(subkeyUsageHex))
|
||||
kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// The 0xA42704B92866382A key can be revoked by 0xBE3893CB843D0FE70C
|
||||
// according to this signature that appears within the key:
|
||||
|
@ -125,7 +131,10 @@ func TestExternallyRevocableKey(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestKeyRevocation(t *testing.T) {
|
||||
kring, _ := ReadKeyRing(readerFromHex(revokedKeyHex))
|
||||
kring, err := ReadKeyRing(readerFromHex(revokedKeyHex))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// revokedKeyHex contains these keys:
|
||||
// pub 1024R/9A34F7C0 2014-03-25 [revoked: 2014-03-25]
|
||||
|
@ -145,7 +154,10 @@ func TestKeyRevocation(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSubkeyRevocation(t *testing.T) {
|
||||
kring, _ := ReadKeyRing(readerFromHex(revokedSubkeyHex))
|
||||
kring, err := ReadKeyRing(readerFromHex(revokedSubkeyHex))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// revokedSubkeyHex contains these keys:
|
||||
// pub 1024R/4EF7E4BECCDE97F0 2014-03-25
|
||||
|
@ -178,7 +190,10 @@ func TestSubkeyRevocation(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestKeyUsage(t *testing.T) {
|
||||
kring, _ := ReadKeyRing(readerFromHex(subkeyUsageHex))
|
||||
kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// subkeyUsageHex contains these keys:
|
||||
// pub 1024R/2866382A created: 2014-04-01 expires: never usage: SC
|
||||
|
|
|
@ -13,7 +13,7 @@ package salsa
|
|||
func salsa2020XORKeyStream(out, in *byte, n uint64, nonce, key *byte)
|
||||
|
||||
// XORKeyStream crypts bytes from in to out using the given key and counters.
|
||||
// In and out may be the same slice but otherwise should not overlap. Counter
|
||||
// In and out must overlap entirely or not at all. Counter
|
||||
// contains the raw salsa20 counter bytes (both nonce and block counter).
|
||||
func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
|
||||
if len(in) == 0 {
|
||||
|
|
|
@ -203,7 +203,7 @@ func core(out *[64]byte, in *[16]byte, k *[32]byte, c *[16]byte) {
|
|||
}
|
||||
|
||||
// XORKeyStream crypts bytes from in to out using the given key and counters.
|
||||
// In and out may be the same slice but otherwise should not overlap. Counter
|
||||
// In and out must overlap entirely or not at all. Counter
|
||||
// contains the raw salsa20 counter bytes (both nonce and block counter).
|
||||
func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
|
||||
var block [64]byte
|
||||
|
|
|
@ -27,8 +27,8 @@ import (
|
|||
"golang.org/x/crypto/salsa20/salsa"
|
||||
)
|
||||
|
||||
// XORKeyStream crypts bytes from in to out using the given key and nonce. In
|
||||
// and out may be the same slice but otherwise should not overlap. Nonce must
|
||||
// XORKeyStream crypts bytes from in to out using the given key and nonce.
|
||||
// In and out must overlap entirely or not at all. Nonce must
|
||||
// be either 8 or 24 bytes long.
|
||||
func XORKeyStream(out, in []byte, nonce []byte, key *[32]byte) {
|
||||
if len(out) < len(in) {
|
||||
|
|
|
@ -42,9 +42,8 @@ type state struct {
|
|||
storage [maxRate]byte
|
||||
|
||||
// Specific to SHA-3 and SHAKE.
|
||||
fixedOutput bool // whether this is a fixed-output-length instance
|
||||
outputLen int // the default output size in bytes
|
||||
state spongeDirection // whether the sponge is absorbing or squeezing
|
||||
outputLen int // the default output size in bytes
|
||||
state spongeDirection // whether the sponge is absorbing or squeezing
|
||||
}
|
||||
|
||||
// BlockSize returns the rate of sponge underlying this hash function.
|
||||
|
|
|
@ -19,8 +19,8 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// startAgent executes ssh-agent, and returns a Agent interface to it.
|
||||
func startAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
|
||||
// startOpenSSHAgent executes ssh-agent, and returns a Agent interface to it.
|
||||
func startOpenSSHAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
|
||||
if testing.Short() {
|
||||
// ssh-agent is not always available, and the key
|
||||
// types supported vary by platform.
|
||||
|
@ -79,16 +79,32 @@ func startAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
|
|||
}
|
||||
}
|
||||
|
||||
func testAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
// startKeyringAgent uses Keyring to simulate a ssh-agent Server and returns a client.
|
||||
func startKeyringAgent(t *testing.T) (client Agent, cleanup func()) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
go ServeAgent(NewKeyring(), c2)
|
||||
|
||||
return NewClient(c1), func() {
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func testOpenSSHAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
agent, _, cleanup := startOpenSSHAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
testAgentInterface(t, agent, key, cert, lifetimeSecs)
|
||||
}
|
||||
|
||||
func testKeyring(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
a := NewKeyring()
|
||||
testAgentInterface(t, a, key, cert, lifetimeSecs)
|
||||
func testKeyringAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
agent, cleanup := startKeyringAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
testAgentInterface(t, agent, key, cert, lifetimeSecs)
|
||||
}
|
||||
|
||||
func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
|
@ -159,8 +175,8 @@ func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Ce
|
|||
|
||||
func TestAgent(t *testing.T) {
|
||||
for _, keyType := range []string{"rsa", "dsa", "ecdsa", "ed25519"} {
|
||||
testAgent(t, testPrivateKeys[keyType], nil, 0)
|
||||
testKeyring(t, testPrivateKeys[keyType], nil, 1)
|
||||
testOpenSSHAgent(t, testPrivateKeys[keyType], nil, 0)
|
||||
testKeyringAgent(t, testPrivateKeys[keyType], nil, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,8 +188,8 @@ func TestCert(t *testing.T) {
|
|||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
testAgent(t, testPrivateKeys["rsa"], cert, 0)
|
||||
testKeyring(t, testPrivateKeys["rsa"], cert, 1)
|
||||
testOpenSSHAgent(t, testPrivateKeys["rsa"], cert, 0)
|
||||
testKeyringAgent(t, testPrivateKeys["rsa"], cert, 0)
|
||||
}
|
||||
|
||||
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
|
||||
|
@ -203,7 +219,7 @@ func netPipe() (net.Conn, net.Conn, error) {
|
|||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
agent, _, cleanup := startOpenSSHAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
a, b, err := netPipe()
|
||||
|
@ -247,8 +263,14 @@ func TestAuth(t *testing.T) {
|
|||
conn.Close()
|
||||
}
|
||||
|
||||
func TestLockClient(t *testing.T) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
func TestLockOpenSSHAgent(t *testing.T) {
|
||||
agent, _, cleanup := startOpenSSHAgent(t)
|
||||
defer cleanup()
|
||||
testLockAgent(agent, t)
|
||||
}
|
||||
|
||||
func TestLockKeyringAgent(t *testing.T) {
|
||||
agent, cleanup := startKeyringAgent(t)
|
||||
defer cleanup()
|
||||
testLockAgent(agent, t)
|
||||
}
|
||||
|
@ -308,10 +330,19 @@ func testLockAgent(agent Agent, t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAgentLifetime(t *testing.T) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
func testOpenSSHAgentLifetime(t *testing.T) {
|
||||
agent, _, cleanup := startOpenSSHAgent(t)
|
||||
defer cleanup()
|
||||
testAgentLifetime(t, agent)
|
||||
}
|
||||
|
||||
func testKeyringAgentLifetime(t *testing.T) {
|
||||
agent, cleanup := startKeyringAgent(t)
|
||||
defer cleanup()
|
||||
testAgentLifetime(t, agent)
|
||||
}
|
||||
|
||||
func testAgentLifetime(t *testing.T, agent Agent) {
|
||||
for _, keyType := range []string{"rsa", "dsa", "ecdsa"} {
|
||||
// Add private keys to the agent.
|
||||
err := agent.Add(AddedKey{
|
||||
|
|
|
@ -106,7 +106,7 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
|
|||
return nil, s.agent.Lock(req.Passphrase)
|
||||
|
||||
case agentUnlock:
|
||||
var req agentLockMsg
|
||||
var req agentUnlockMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ func TestSetupForwardAgent(t *testing.T) {
|
|||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
_, socket, cleanup := startAgent(t)
|
||||
_, socket, cleanup := startOpenSSHAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
serverConf := ssh.ServerConfig{
|
||||
|
|
|
@ -367,6 +367,17 @@ func (r *dsaPublicKey) Type() string {
|
|||
return "ssh-dss"
|
||||
}
|
||||
|
||||
func checkDSAParams(param *dsa.Parameters) error {
|
||||
// SSH specifies FIPS 186-2, which only provided a single size
|
||||
// (1024 bits) DSA key. FIPS 186-3 allows for larger key
|
||||
// sizes, which would confuse SSH.
|
||||
if l := param.P.BitLen(); l != 1024 {
|
||||
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseDSA parses an DSA key according to RFC 4253, section 6.6.
|
||||
func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||
var w struct {
|
||||
|
@ -377,13 +388,18 @@ func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
param := dsa.Parameters{
|
||||
P: w.P,
|
||||
Q: w.Q,
|
||||
G: w.G,
|
||||
}
|
||||
if err := checkDSAParams(¶m); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
key := &dsaPublicKey{
|
||||
Parameters: dsa.Parameters{
|
||||
P: w.P,
|
||||
Q: w.Q,
|
||||
G: w.G,
|
||||
},
|
||||
Y: w.Y,
|
||||
Parameters: param,
|
||||
Y: w.Y,
|
||||
}
|
||||
return key, w.Rest, nil
|
||||
}
|
||||
|
@ -630,19 +646,28 @@ func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|||
}
|
||||
|
||||
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
||||
// *ecdsa.PrivateKey or any other crypto.Signer and returns a corresponding
|
||||
// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
|
||||
// *ecdsa.PrivateKey or any other crypto.Signer and returns a
|
||||
// corresponding Signer instance. ECDSA keys must use P-256, P-384 or
|
||||
// P-521. DSA keys must use parameter size L1024N160.
|
||||
func NewSignerFromKey(key interface{}) (Signer, error) {
|
||||
switch key := key.(type) {
|
||||
case crypto.Signer:
|
||||
return NewSignerFromSigner(key)
|
||||
case *dsa.PrivateKey:
|
||||
return &dsaPrivateKey{key}, nil
|
||||
return newDSAPrivateKey(key)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
||||
}
|
||||
}
|
||||
|
||||
func newDSAPrivateKey(key *dsa.PrivateKey) (Signer, error) {
|
||||
if err := checkDSAParams(&key.PublicKey.Parameters); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dsaPrivateKey{key}, nil
|
||||
}
|
||||
|
||||
type wrappedSigner struct {
|
||||
signer crypto.Signer
|
||||
pubKey PublicKey
|
||||
|
|
|
@ -67,7 +67,7 @@ type ServerConfig struct {
|
|||
PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
|
||||
|
||||
// PublicKeyCallback, if non-nil, is called when a client
|
||||
// offers a public key for authentication. It must return true
|
||||
// offers a public key for authentication. It must return a nil error
|
||||
// if the given public key can be used to authenticate the
|
||||
// given user. For example, see CertChecker.Authenticate. A
|
||||
// call to this function does not guarantee that the key
|
||||
|
|
|
@ -19,6 +19,8 @@ package terminal // import "golang.org/x/crypto/ssh/terminal"
|
|||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// State contains the state of a terminal.
|
||||
|
@ -50,6 +52,8 @@ func MakeRaw(fd int) (*State, error) {
|
|||
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
|
||||
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||
newState.Cflag |= syscall.CS8
|
||||
newState.Cc[unix.VMIN] = 1
|
||||
newState.Cc[unix.VTIME] = 0
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ func NewCipher(cipherFunc func([]byte) (cipher.Block, error), key []byte) (c *Ci
|
|||
}
|
||||
|
||||
// Encrypt encrypts a sector of plaintext and puts the result into ciphertext.
|
||||
// Plaintext and ciphertext may be the same slice but should not overlap.
|
||||
// Plaintext and ciphertext must overlap entirely or not at all.
|
||||
// Sectors must be a multiple of 16 bytes and less than 2²⁴ bytes.
|
||||
func (c *Cipher) Encrypt(ciphertext, plaintext []byte, sectorNum uint64) {
|
||||
if len(ciphertext) < len(plaintext) {
|
||||
|
@ -86,7 +86,7 @@ func (c *Cipher) Encrypt(ciphertext, plaintext []byte, sectorNum uint64) {
|
|||
}
|
||||
|
||||
// Decrypt decrypts a sector of ciphertext and puts the result into plaintext.
|
||||
// Plaintext and ciphertext may be the same slice but should not overlap.
|
||||
// Plaintext and ciphertext must overlap entirely or not at all.
|
||||
// Sectors must be a multiple of 16 bytes and less than 2²⁴ bytes.
|
||||
func (c *Cipher) Decrypt(plaintext, ciphertext []byte, sectorNum uint64) {
|
||||
if len(plaintext) < len(ciphertext) {
|
||||
|
|
|
@ -215,6 +215,9 @@ type AuthProvider struct {
|
|||
// audiences: bookstore_android.apps.googleusercontent.com,
|
||||
// bookstore_web.apps.googleusercontent.com
|
||||
Audiences string `protobuf:"bytes,4,opt,name=audiences" json:"audiences,omitempty"`
|
||||
// Redirect URL if JWT token is required but no present or is expired.
|
||||
// Implement authorizationUrl of securityDefinitions in OpenAPI spec.
|
||||
AuthorizationUrl string `protobuf:"bytes,5,opt,name=authorization_url,json=authorizationUrl" json:"authorization_url,omitempty"`
|
||||
}
|
||||
|
||||
func (m *AuthProvider) Reset() { *m = AuthProvider{} }
|
||||
|
@ -250,6 +253,13 @@ func (m *AuthProvider) GetAudiences() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *AuthProvider) GetAuthorizationUrl() string {
|
||||
if m != nil {
|
||||
return m.AuthorizationUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// OAuth scopes are a way to define data and permissions on data. For example,
|
||||
// there are scopes defined for "Read-only access to Google Calendar" and
|
||||
// "Access to Cloud Platform". Users can consent to a scope for an application,
|
||||
|
@ -349,33 +359,35 @@ func init() {
|
|||
func init() { proto.RegisterFile("google/api/auth.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 437 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x52, 0xcd, 0x6e, 0xd3, 0x40,
|
||||
0x10, 0x96, 0x9d, 0xa6, 0x8d, 0x27, 0x55, 0x0a, 0x2b, 0x51, 0x99, 0x52, 0x20, 0xf2, 0x29, 0x5c,
|
||||
0x1c, 0xa9, 0x45, 0x08, 0x09, 0x09, 0xd4, 0x22, 0x84, 0x7a, 0x22, 0x32, 0x42, 0x48, 0x5c, 0xac,
|
||||
0x65, 0x3d, 0x38, 0x4b, 0xdd, 0x1d, 0xb3, 0x3f, 0xcd, 0x8d, 0x87, 0xe1, 0xc9, 0x78, 0x94, 0xca,
|
||||
0x6b, 0x37, 0x71, 0xd2, 0xe3, 0x7c, 0x3f, 0x33, 0xf3, 0xcd, 0x2e, 0x3c, 0x29, 0x89, 0xca, 0x0a,
|
||||
0xe7, 0xbc, 0x96, 0x73, 0xee, 0xec, 0x32, 0xad, 0x35, 0x59, 0x62, 0xd0, 0xc2, 0x29, 0xaf, 0xe5,
|
||||
0xc9, 0x69, 0x5f, 0xa2, 0x14, 0x59, 0x6e, 0x25, 0x29, 0xd3, 0x2a, 0x93, 0xbf, 0x30, 0xb9, 0x70,
|
||||
0x76, 0x89, 0xca, 0x4a, 0xe1, 0x09, 0xf6, 0x1a, 0x86, 0xda, 0x55, 0x68, 0xe2, 0xc1, 0x74, 0x30,
|
||||
0x1b, 0x9f, 0xbd, 0x48, 0x37, 0xbd, 0xd2, 0x6d, 0x69, 0xe6, 0x2a, 0xcc, 0x5a, 0x31, 0x7b, 0x03,
|
||||
0x51, 0xad, 0xe9, 0x56, 0x16, 0xa8, 0x4d, 0xbc, 0xe7, 0x9d, 0xf1, 0xae, 0x73, 0xd1, 0x09, 0xb2,
|
||||
0x8d, 0x34, 0xf9, 0x1f, 0x00, 0x7b, 0xd8, 0x95, 0x9d, 0xc0, 0xc8, 0x60, 0x85, 0xc2, 0x92, 0x8e,
|
||||
0x83, 0x69, 0x30, 0x8b, 0xb2, 0x75, 0xcd, 0xce, 0x61, 0x48, 0x4d, 0xd6, 0x38, 0x9c, 0x06, 0xb3,
|
||||
0xf1, 0xd9, 0xf3, 0xfe, 0x98, 0x2f, 0x4d, 0xaf, 0x0c, 0xff, 0x38, 0xa9, 0xf1, 0x06, 0x95, 0x35,
|
||||
0x59, 0xab, 0x65, 0x6f, 0x21, 0xe6, 0x55, 0x45, 0xab, 0x7c, 0x25, 0xed, 0x92, 0x9c, 0xcd, 0x85,
|
||||
0xc6, 0xa2, 0x19, 0xca, 0xab, 0x78, 0x38, 0x0d, 0x66, 0xa3, 0xec, 0xd8, 0xf3, 0xdf, 0x5b, 0xfa,
|
||||
0xe3, 0x9a, 0x65, 0x1f, 0xe0, 0x50, 0xf7, 0x1a, 0xc6, 0x07, 0x3e, 0xdc, 0xb3, 0xdd, 0x70, 0xbd,
|
||||
0xa1, 0xd9, 0x96, 0x21, 0x21, 0x38, 0xec, 0xa7, 0x67, 0x13, 0x08, 0x65, 0xd1, 0xa5, 0x0a, 0x65,
|
||||
0xc1, 0x8e, 0x61, 0x5f, 0x1a, 0xe3, 0x50, 0xfb, 0x40, 0x51, 0xd6, 0x55, 0xec, 0x29, 0x8c, 0x7e,
|
||||
0xaf, 0xae, 0x4d, 0xee, 0xb4, 0x8c, 0x07, 0x9e, 0x39, 0x68, 0xea, 0x6f, 0x5a, 0xb2, 0x53, 0x88,
|
||||
0xb8, 0x2b, 0x24, 0x2a, 0x81, 0xcd, 0xb5, 0x1b, 0x6e, 0x03, 0x24, 0xef, 0xe1, 0xf1, 0x83, 0x3b,
|
||||
0xb0, 0x57, 0xf0, 0x48, 0x70, 0x45, 0x4a, 0x0a, 0x5e, 0xe5, 0x46, 0x50, 0x8d, 0xa6, 0xdb, 0xe1,
|
||||
0x68, 0x8d, 0x7f, 0xf5, 0x70, 0xb2, 0x80, 0xa3, 0x1d, 0x3b, 0x7b, 0x09, 0xe3, 0xfb, 0x37, 0xcb,
|
||||
0xd7, 0xcb, 0xc3, 0x3d, 0x74, 0x55, 0x6c, 0x6f, 0x14, 0xee, 0x6c, 0x74, 0x79, 0x0d, 0x13, 0x41,
|
||||
0x37, 0xbd, 0x93, 0x5d, 0x46, 0xdd, 0x49, 0x2c, 0x2d, 0x82, 0x1f, 0x9f, 0x3a, 0xa2, 0xa4, 0x8a,
|
||||
0xab, 0x32, 0x25, 0x5d, 0xce, 0x4b, 0x54, 0xfe, 0x83, 0xce, 0x5b, 0x8a, 0xd7, 0xd2, 0xf8, 0x1f,
|
||||
0x6c, 0x50, 0xdf, 0x4a, 0x81, 0x82, 0xd4, 0x2f, 0x59, 0xbe, 0xdb, 0xaa, 0xfe, 0x85, 0x7b, 0x9f,
|
||||
0x2f, 0x16, 0x57, 0x3f, 0xf7, 0xbd, 0xf1, 0xfc, 0x2e, 0x00, 0x00, 0xff, 0xff, 0xb9, 0x6d, 0xc6,
|
||||
0x5e, 0x1c, 0x03, 0x00, 0x00,
|
||||
// 465 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x52, 0x5f, 0x6b, 0x13, 0x4f,
|
||||
0x14, 0x65, 0x93, 0xa6, 0xcd, 0xde, 0x94, 0xb4, 0x1d, 0xf8, 0x95, 0xfd, 0xd5, 0xaa, 0x21, 0x4f,
|
||||
0x11, 0x61, 0x03, 0xad, 0x88, 0x20, 0x28, 0xad, 0x88, 0xf4, 0xc9, 0x30, 0x52, 0x04, 0x5f, 0x96,
|
||||
0x71, 0x76, 0xdc, 0x8c, 0x9d, 0xce, 0x5d, 0xe7, 0x4f, 0x03, 0x3e, 0xf8, 0x49, 0x7c, 0xf2, 0x93,
|
||||
0xf9, 0x51, 0x64, 0x67, 0xb7, 0xc9, 0x6e, 0xfa, 0x78, 0xef, 0x39, 0xe7, 0xde, 0x7b, 0xce, 0x0c,
|
||||
0xfc, 0x57, 0x20, 0x16, 0x4a, 0xcc, 0x59, 0x29, 0xe7, 0xcc, 0xbb, 0x65, 0x5a, 0x1a, 0x74, 0x48,
|
||||
0xa0, 0x6e, 0xa7, 0xac, 0x94, 0x27, 0xa7, 0x6d, 0x8a, 0xd6, 0xe8, 0x98, 0x93, 0xa8, 0x6d, 0xcd,
|
||||
0x9c, 0xfe, 0x82, 0xf1, 0x85, 0x77, 0x4b, 0xa1, 0x9d, 0xe4, 0x01, 0x20, 0x2f, 0x60, 0x60, 0xbc,
|
||||
0x12, 0x36, 0xe9, 0x4f, 0xfa, 0xb3, 0xd1, 0xd9, 0x93, 0x74, 0x33, 0x2b, 0xed, 0x52, 0xa9, 0x57,
|
||||
0x82, 0xd6, 0x64, 0xf2, 0x12, 0xe2, 0xd2, 0xe0, 0x9d, 0xcc, 0x85, 0xb1, 0xc9, 0x4e, 0x50, 0x26,
|
||||
0xdb, 0xca, 0x45, 0x43, 0xa0, 0x1b, 0xea, 0xf4, 0x6f, 0x04, 0xe4, 0xe1, 0x54, 0x72, 0x02, 0x43,
|
||||
0x2b, 0x94, 0xe0, 0x0e, 0x4d, 0x12, 0x4d, 0xa2, 0x59, 0x4c, 0xd7, 0x35, 0x39, 0x87, 0x01, 0x56,
|
||||
0x5e, 0x93, 0xde, 0x24, 0x9a, 0x8d, 0xce, 0x1e, 0xb7, 0xd7, 0x7c, 0xac, 0x66, 0x51, 0xf1, 0xc3,
|
||||
0x4b, 0x23, 0x6e, 0x85, 0x76, 0x96, 0xd6, 0x5c, 0xf2, 0x0a, 0x12, 0xa6, 0x14, 0xae, 0xb2, 0x95,
|
||||
0x74, 0x4b, 0xf4, 0x2e, 0xe3, 0x46, 0xe4, 0xd5, 0x52, 0xa6, 0x92, 0xc1, 0x24, 0x9a, 0x0d, 0xe9,
|
||||
0x71, 0xc0, 0x3f, 0xd7, 0xf0, 0xbb, 0x35, 0x4a, 0xde, 0xc2, 0xbe, 0x69, 0x0d, 0x4c, 0xf6, 0x82,
|
||||
0xb9, 0x47, 0xdb, 0xe6, 0x5a, 0x4b, 0x69, 0x47, 0x30, 0xfd, 0x1d, 0xc1, 0x7e, 0xdb, 0x3e, 0x19,
|
||||
0x43, 0x4f, 0xe6, 0x8d, 0xad, 0x9e, 0xcc, 0xc9, 0x31, 0xec, 0x4a, 0x6b, 0xbd, 0x30, 0xc1, 0x51,
|
||||
0x4c, 0x9b, 0x8a, 0xfc, 0x0f, 0xc3, 0xef, 0xab, 0x1b, 0x9b, 0x79, 0x23, 0x93, 0x7e, 0x40, 0xf6,
|
||||
0xaa, 0xfa, 0xda, 0x48, 0x72, 0x0a, 0x31, 0xf3, 0xb9, 0x14, 0x9a, 0x8b, 0x2a, 0xee, 0x0a, 0xdb,
|
||||
0x34, 0xc8, 0x73, 0x38, 0xaa, 0x4c, 0xa3, 0x91, 0x3f, 0x43, 0xa4, 0x99, 0x37, 0xb5, 0xcb, 0x98,
|
||||
0x1e, 0x76, 0x80, 0x6b, 0xa3, 0xa6, 0x6f, 0xe0, 0xe8, 0x41, 0x6a, 0xe4, 0x19, 0x1c, 0x72, 0xa6,
|
||||
0x51, 0x4b, 0xce, 0x54, 0x66, 0x39, 0x96, 0xc2, 0x36, 0x07, 0x1f, 0xac, 0xfb, 0x9f, 0x42, 0x7b,
|
||||
0xba, 0x80, 0x83, 0x2d, 0x39, 0x79, 0x0a, 0xa3, 0xfb, 0x17, 0xce, 0xd6, 0x4e, 0xe1, 0xbe, 0x75,
|
||||
0x95, 0x77, 0xcf, 0xef, 0x6d, 0x9d, 0x7f, 0x79, 0x03, 0x63, 0x8e, 0xb7, 0xad, 0x80, 0x2f, 0xe3,
|
||||
0x26, 0x3f, 0x87, 0x8b, 0xe8, 0xcb, 0xfb, 0x06, 0x28, 0x50, 0x31, 0x5d, 0xa4, 0x68, 0x8a, 0x79,
|
||||
0x21, 0x74, 0xf8, 0xce, 0xf3, 0x1a, 0x62, 0xa5, 0xb4, 0xe1, 0xbf, 0x5b, 0x61, 0xee, 0x24, 0x17,
|
||||
0x1c, 0xf5, 0x37, 0x59, 0xbc, 0xee, 0x54, 0x7f, 0x7a, 0x3b, 0x1f, 0x2e, 0x16, 0x57, 0x5f, 0x77,
|
||||
0x83, 0xf0, 0xfc, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe5, 0xa3, 0x9d, 0xc6, 0x4a, 0x03, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
|
167
vendor/google.golang.org/genproto/googleapis/api/servicecontrol/v1/service_controller.pb.go
generated
vendored
167
vendor/google.golang.org/genproto/googleapis/api/servicecontrol/v1/service_controller.pb.go
generated
vendored
|
@ -75,6 +75,8 @@ type CheckResponse struct {
|
|||
CheckErrors []*CheckError `protobuf:"bytes,2,rep,name=check_errors,json=checkErrors" json:"check_errors,omitempty"`
|
||||
// The actual config id used to process the request.
|
||||
ServiceConfigId string `protobuf:"bytes,5,opt,name=service_config_id,json=serviceConfigId" json:"service_config_id,omitempty"`
|
||||
// Feedback data returned from the server during processing a Check request.
|
||||
CheckInfo *CheckResponse_CheckInfo `protobuf:"bytes,6,opt,name=check_info,json=checkInfo" json:"check_info,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CheckResponse) Reset() { *m = CheckResponse{} }
|
||||
|
@ -103,6 +105,49 @@ func (m *CheckResponse) GetServiceConfigId() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *CheckResponse) GetCheckInfo() *CheckResponse_CheckInfo {
|
||||
if m != nil {
|
||||
return m.CheckInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CheckResponse_CheckInfo struct {
|
||||
// Consumer info of this check.
|
||||
ConsumerInfo *CheckResponse_ConsumerInfo `protobuf:"bytes,2,opt,name=consumer_info,json=consumerInfo" json:"consumer_info,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CheckResponse_CheckInfo) Reset() { *m = CheckResponse_CheckInfo{} }
|
||||
func (m *CheckResponse_CheckInfo) String() string { return proto.CompactTextString(m) }
|
||||
func (*CheckResponse_CheckInfo) ProtoMessage() {}
|
||||
func (*CheckResponse_CheckInfo) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{1, 0} }
|
||||
|
||||
func (m *CheckResponse_CheckInfo) GetConsumerInfo() *CheckResponse_ConsumerInfo {
|
||||
if m != nil {
|
||||
return m.ConsumerInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// `ConsumerInfo` provides information about the consumer project.
|
||||
type CheckResponse_ConsumerInfo struct {
|
||||
// The Google cloud project number, e.g. 1234567890. A value of 0 indicates
|
||||
// no project number is found.
|
||||
ProjectNumber int64 `protobuf:"varint,1,opt,name=project_number,json=projectNumber" json:"project_number,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CheckResponse_ConsumerInfo) Reset() { *m = CheckResponse_ConsumerInfo{} }
|
||||
func (m *CheckResponse_ConsumerInfo) String() string { return proto.CompactTextString(m) }
|
||||
func (*CheckResponse_ConsumerInfo) ProtoMessage() {}
|
||||
func (*CheckResponse_ConsumerInfo) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{1, 1} }
|
||||
|
||||
func (m *CheckResponse_ConsumerInfo) GetProjectNumber() int64 {
|
||||
if m != nil {
|
||||
return m.ProjectNumber
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Request message for the Report method.
|
||||
type ReportRequest struct {
|
||||
// The service name as specified in its service configuration. For example,
|
||||
|
@ -168,8 +213,9 @@ type ReportResponse struct {
|
|||
// `Operations` in the request succeeded. Each
|
||||
// `Operation` that failed processing has a corresponding item
|
||||
// in this list.
|
||||
// 3. A failed RPC status indicates a complete failure where none of the
|
||||
// `Operations` in the request succeeded.
|
||||
// 3. A failed RPC status indicates a general non-deterministic failure.
|
||||
// When this happens, it's impossible to know which of the
|
||||
// 'Operations' in the request succeeded or failed.
|
||||
ReportErrors []*ReportResponse_ReportError `protobuf:"bytes,1,rep,name=report_errors,json=reportErrors" json:"report_errors,omitempty"`
|
||||
// The actual config id used to process the request.
|
||||
ServiceConfigId string `protobuf:"bytes,2,opt,name=service_config_id,json=serviceConfigId" json:"service_config_id,omitempty"`
|
||||
|
@ -224,6 +270,8 @@ func (m *ReportResponse_ReportError) GetStatus() *google_rpc.Status {
|
|||
func init() {
|
||||
proto.RegisterType((*CheckRequest)(nil), "google.api.servicecontrol.v1.CheckRequest")
|
||||
proto.RegisterType((*CheckResponse)(nil), "google.api.servicecontrol.v1.CheckResponse")
|
||||
proto.RegisterType((*CheckResponse_CheckInfo)(nil), "google.api.servicecontrol.v1.CheckResponse.CheckInfo")
|
||||
proto.RegisterType((*CheckResponse_ConsumerInfo)(nil), "google.api.servicecontrol.v1.CheckResponse.ConsumerInfo")
|
||||
proto.RegisterType((*ReportRequest)(nil), "google.api.servicecontrol.v1.ReportRequest")
|
||||
proto.RegisterType((*ReportResponse)(nil), "google.api.servicecontrol.v1.ReportResponse")
|
||||
proto.RegisterType((*ReportResponse_ReportError)(nil), "google.api.servicecontrol.v1.ReportResponse.ReportError")
|
||||
|
@ -245,21 +293,25 @@ type ServiceControllerClient interface {
|
|||
// operation is executed.
|
||||
//
|
||||
// If feasible, the client should cache the check results and reuse them for
|
||||
// up to 60s. In case of server errors, the client may rely on the cached
|
||||
// 60 seconds. In case of server errors, the client can rely on the cached
|
||||
// results for longer time.
|
||||
//
|
||||
// NOTE: the `CheckRequest` has the size limit of 64KB.
|
||||
//
|
||||
// This method requires the `servicemanagement.services.check` permission
|
||||
// on the specified service. For more information, see
|
||||
// [Google Cloud IAM](https://cloud.google.com/iam).
|
||||
Check(ctx context.Context, in *CheckRequest, opts ...grpc.CallOption) (*CheckResponse, error)
|
||||
// Reports operations to Google Service Control. It should be called
|
||||
// after the operation is completed.
|
||||
// Reports operation results to Google Service Control, such as logs and
|
||||
// metrics. It should be called after an operation is completed.
|
||||
//
|
||||
// If feasible, the client should aggregate reporting data for up to 5s to
|
||||
// reduce API traffic. Limiting aggregation to 5s is to reduce data loss
|
||||
// during client crashes. Clients should carefully choose the aggregation
|
||||
// window to avoid data loss risk more than 0.01% for business and
|
||||
// compliance reasons.
|
||||
// If feasible, the client should aggregate reporting data for up to 5
|
||||
// seconds to reduce API traffic. Limiting aggregation to 5 seconds is to
|
||||
// reduce data loss during client crashes. Clients should carefully choose
|
||||
// the aggregation time window to avoid data loss risk more than 0.01%
|
||||
// for business and compliance reasons.
|
||||
//
|
||||
// NOTE: the `ReportRequest` has the size limit of 1MB.
|
||||
//
|
||||
// This method requires the `servicemanagement.services.report` permission
|
||||
// on the specified service. For more information, see
|
||||
|
@ -301,21 +353,25 @@ type ServiceControllerServer interface {
|
|||
// operation is executed.
|
||||
//
|
||||
// If feasible, the client should cache the check results and reuse them for
|
||||
// up to 60s. In case of server errors, the client may rely on the cached
|
||||
// 60 seconds. In case of server errors, the client can rely on the cached
|
||||
// results for longer time.
|
||||
//
|
||||
// NOTE: the `CheckRequest` has the size limit of 64KB.
|
||||
//
|
||||
// This method requires the `servicemanagement.services.check` permission
|
||||
// on the specified service. For more information, see
|
||||
// [Google Cloud IAM](https://cloud.google.com/iam).
|
||||
Check(context.Context, *CheckRequest) (*CheckResponse, error)
|
||||
// Reports operations to Google Service Control. It should be called
|
||||
// after the operation is completed.
|
||||
// Reports operation results to Google Service Control, such as logs and
|
||||
// metrics. It should be called after an operation is completed.
|
||||
//
|
||||
// If feasible, the client should aggregate reporting data for up to 5s to
|
||||
// reduce API traffic. Limiting aggregation to 5s is to reduce data loss
|
||||
// during client crashes. Clients should carefully choose the aggregation
|
||||
// window to avoid data loss risk more than 0.01% for business and
|
||||
// compliance reasons.
|
||||
// If feasible, the client should aggregate reporting data for up to 5
|
||||
// seconds to reduce API traffic. Limiting aggregation to 5 seconds is to
|
||||
// reduce data loss during client crashes. Clients should carefully choose
|
||||
// the aggregation time window to avoid data loss risk more than 0.01%
|
||||
// for business and compliance reasons.
|
||||
//
|
||||
// NOTE: the `ReportRequest` has the size limit of 1MB.
|
||||
//
|
||||
// This method requires the `servicemanagement.services.report` permission
|
||||
// on the specified service. For more information, see
|
||||
|
@ -385,39 +441,44 @@ func init() {
|
|||
}
|
||||
|
||||
var fileDescriptor5 = []byte{
|
||||
// 537 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0xcf, 0x6e, 0xd3, 0x40,
|
||||
0x10, 0xc6, 0xb5, 0xee, 0x1f, 0xa9, 0xe3, 0x04, 0xd4, 0x3d, 0x40, 0x64, 0xf5, 0x90, 0xfa, 0x40,
|
||||
0x23, 0x37, 0xd8, 0x6a, 0x10, 0x12, 0x0a, 0x27, 0x1a, 0x55, 0x55, 0x41, 0x82, 0xca, 0xb9, 0x21,
|
||||
0x50, 0xb4, 0x38, 0x8b, 0xb1, 0x48, 0xbc, 0xcb, 0xae, 0x9b, 0x0b, 0xe2, 0xc2, 0x03, 0x70, 0x28,
|
||||
0x6f, 0x80, 0x90, 0x38, 0xf0, 0x04, 0x3c, 0x07, 0xaf, 0xc0, 0x43, 0xc0, 0x0d, 0x79, 0x77, 0xed,
|
||||
0x3a, 0xc2, 0x58, 0xee, 0x2d, 0x9e, 0x9d, 0xf9, 0xe6, 0xb7, 0xdf, 0x4c, 0x16, 0xee, 0xc7, 0x8c,
|
||||
0xc5, 0x0b, 0x1a, 0x10, 0x9e, 0x04, 0x92, 0x8a, 0x55, 0x12, 0xd1, 0x88, 0xa5, 0x99, 0x60, 0x8b,
|
||||
0x60, 0x75, 0x54, 0x44, 0x66, 0x26, 0xb4, 0xa0, 0xc2, 0xe7, 0x82, 0x65, 0x0c, 0xef, 0xe9, 0x32,
|
||||
0x9f, 0xf0, 0xc4, 0x5f, 0x2f, 0xf3, 0x57, 0x47, 0xce, 0x5e, 0x45, 0x94, 0xa4, 0x29, 0xcb, 0x48,
|
||||
0x96, 0xb0, 0x54, 0xea, 0x5a, 0xc7, 0x6f, 0x6c, 0x19, 0xbd, 0xa1, 0xd1, 0xdb, 0x19, 0x15, 0x82,
|
||||
0x99, 0x5e, 0xce, 0xb0, 0x31, 0x9f, 0x71, 0x2a, 0x94, 0xbc, 0xc9, 0xbe, 0x6d, 0xb2, 0x05, 0x8f,
|
||||
0x02, 0x99, 0x91, 0xec, 0xc2, 0xb4, 0x75, 0xbf, 0x22, 0xe8, 0x4c, 0x72, 0xf1, 0x90, 0xbe, 0xbb,
|
||||
0xa0, 0x32, 0xc3, 0xfb, 0xd0, 0x29, 0xee, 0x97, 0x92, 0x25, 0xed, 0xa1, 0x3e, 0x1a, 0xec, 0x84,
|
||||
0xb6, 0x89, 0x3d, 0x25, 0x4b, 0x8a, 0x4f, 0x60, 0xa7, 0xd4, 0xef, 0x59, 0x7d, 0x34, 0xb0, 0x47,
|
||||
0x07, 0x7e, 0xd3, 0xd5, 0xfd, 0x67, 0x45, 0x7a, 0x78, 0x55, 0x89, 0x3d, 0xd8, 0xad, 0x38, 0xf9,
|
||||
0x3a, 0x89, 0x67, 0xc9, 0xbc, 0xb7, 0xa9, 0xda, 0xdd, 0x34, 0x07, 0x13, 0x15, 0x3f, 0x9b, 0xbb,
|
||||
0xdf, 0x11, 0x74, 0x0d, 0xa6, 0xe4, 0x2c, 0x95, 0x34, 0xe7, 0x2c, 0xa5, 0xf2, 0x42, 0xc3, 0x59,
|
||||
0xc6, 0xce, 0xe6, 0xf8, 0x09, 0x74, 0x2a, 0xbe, 0xc9, 0x9e, 0xd5, 0xdf, 0x18, 0xd8, 0xa3, 0x41,
|
||||
0x33, 0xaa, 0xea, 0x72, 0x92, 0x17, 0x84, 0x76, 0x54, 0xfe, 0x96, 0xf5, 0xb4, 0x5b, 0xf5, 0xb4,
|
||||
0xdf, 0x10, 0x74, 0x43, 0xca, 0x99, 0xc8, 0xae, 0xe1, 0xea, 0x29, 0x40, 0x09, 0x5f, 0xb0, 0xb6,
|
||||
0xb6, 0xb5, 0x52, 0x5a, 0x4f, 0xba, 0x51, 0x4f, 0xfa, 0x07, 0xc1, 0x8d, 0x82, 0xd4, 0x18, 0xfb,
|
||||
0x12, 0xba, 0x42, 0x45, 0x0a, 0xdb, 0x90, 0x42, 0x79, 0xd0, 0x8c, 0xb2, 0x2e, 0x62, 0x3e, 0xb5,
|
||||
0x8d, 0x1d, 0x71, 0xf5, 0xf1, 0x1f, 0x3a, 0xab, 0x96, 0xce, 0x79, 0x01, 0x76, 0x45, 0xa8, 0xcd,
|
||||
0xc8, 0x3d, 0xd8, 0xd6, 0xeb, 0x6d, 0xf6, 0x12, 0x17, 0xd4, 0x82, 0x47, 0xfe, 0x54, 0x9d, 0x84,
|
||||
0x26, 0x63, 0xf4, 0xc3, 0x82, 0xdd, 0x69, 0xd9, 0xd1, 0xfc, 0x93, 0xf1, 0x27, 0x04, 0x5b, 0x6a,
|
||||
0x07, 0xb0, 0xd7, 0x62, 0x51, 0xcc, 0x7c, 0x9d, 0xc3, 0x56, 0xb9, 0xda, 0x1c, 0x77, 0xf8, 0xf1,
|
||||
0xe7, 0xaf, 0xcf, 0xd6, 0x1d, 0x77, 0xbf, 0xf2, 0x98, 0xc8, 0xe0, 0x7d, 0x75, 0x41, 0x3e, 0x8c,
|
||||
0xd5, 0xee, 0x8d, 0x91, 0x87, 0x2f, 0x11, 0x6c, 0x6b, 0x17, 0xf0, 0x61, 0xbb, 0x19, 0x68, 0xa4,
|
||||
0xe1, 0x75, 0x06, 0xe6, 0xde, 0x55, 0x4c, 0x07, 0xae, 0xdb, 0xc4, 0xa4, 0x07, 0x39, 0x46, 0xde,
|
||||
0xf1, 0x25, 0x82, 0x7e, 0xc4, 0x96, 0x8d, 0x2d, 0x8e, 0x6f, 0xfd, 0xe3, 0xee, 0x79, 0xfe, 0xe6,
|
||||
0x9c, 0xa3, 0xe7, 0x8f, 0x4d, 0x5d, 0xcc, 0x16, 0x24, 0x8d, 0x7d, 0x26, 0xe2, 0x20, 0xa6, 0xa9,
|
||||
0x7a, 0x91, 0x02, 0x7d, 0x44, 0x78, 0x22, 0xeb, 0xdf, 0xb6, 0x87, 0xeb, 0x91, 0xdf, 0x08, 0x7d,
|
||||
0xb1, 0x36, 0x4f, 0x1f, 0x4d, 0x27, 0xaf, 0xb6, 0x95, 0xc0, 0xbd, 0xbf, 0x01, 0x00, 0x00, 0xff,
|
||||
0xff, 0x6c, 0x58, 0x92, 0x07, 0xbe, 0x05, 0x00, 0x00,
|
||||
// 619 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xc1, 0x6e, 0xd3, 0x4c,
|
||||
0x10, 0xd6, 0x3a, 0x6d, 0xa4, 0x4c, 0x9c, 0xfe, 0xea, 0x1e, 0x7e, 0x22, 0xab, 0x87, 0xd4, 0x12,
|
||||
0x34, 0x4a, 0x8b, 0xad, 0x16, 0x55, 0x42, 0xe1, 0x44, 0xa3, 0xaa, 0x0a, 0x48, 0xa5, 0x72, 0x38,
|
||||
0x21, 0xaa, 0xc8, 0xdd, 0x6c, 0x8c, 0x4b, 0xb2, 0x6b, 0xd6, 0x4e, 0x2e, 0x88, 0x0b, 0x0f, 0xc0,
|
||||
0xa1, 0xbc, 0x01, 0xaa, 0xc4, 0x33, 0xf0, 0x1c, 0xbc, 0x02, 0x0f, 0x01, 0x37, 0x94, 0xdd, 0xb5,
|
||||
0xeb, 0x08, 0x63, 0x92, 0x9b, 0xf7, 0xdb, 0x99, 0xf9, 0xbe, 0x9d, 0xf9, 0x3c, 0x70, 0x1c, 0x70,
|
||||
0x1e, 0x4c, 0xa8, 0xeb, 0x47, 0xa1, 0x1b, 0x53, 0x31, 0x0f, 0x09, 0x25, 0x9c, 0x25, 0x82, 0x4f,
|
||||
0xdc, 0xf9, 0x61, 0x8a, 0x0c, 0x35, 0x34, 0xa1, 0xc2, 0x89, 0x04, 0x4f, 0x38, 0xde, 0x51, 0x69,
|
||||
0x8e, 0x1f, 0x85, 0xce, 0x72, 0x9a, 0x33, 0x3f, 0xb4, 0x76, 0x72, 0x45, 0x7d, 0xc6, 0x78, 0xe2,
|
||||
0x27, 0x21, 0x67, 0xb1, 0xca, 0xb5, 0x9c, 0x52, 0x4a, 0xf2, 0x86, 0x92, 0xb7, 0x43, 0x2a, 0x04,
|
||||
0xd7, 0x5c, 0xd6, 0x41, 0x69, 0x3c, 0x8f, 0xa8, 0x90, 0xe5, 0x75, 0xf4, 0x3d, 0x1d, 0x2d, 0x22,
|
||||
0xe2, 0xc6, 0x89, 0x9f, 0xcc, 0x34, 0xad, 0x7d, 0x8b, 0xc0, 0xec, 0x2d, 0x8a, 0x7b, 0xf4, 0xdd,
|
||||
0x8c, 0xc6, 0x09, 0xde, 0x05, 0x33, 0x7d, 0x1f, 0xf3, 0xa7, 0xb4, 0x89, 0x5a, 0xa8, 0x5d, 0xf3,
|
||||
0xea, 0x1a, 0x3b, 0xf7, 0xa7, 0x14, 0x9f, 0x42, 0x2d, 0xab, 0xdf, 0x34, 0x5a, 0xa8, 0x5d, 0x3f,
|
||||
0xda, 0x73, 0xca, 0x9e, 0xee, 0xbc, 0x48, 0xc3, 0xbd, 0xbb, 0x4c, 0xdc, 0x81, 0xed, 0x5c, 0x27,
|
||||
0xc7, 0x61, 0x30, 0x0c, 0x47, 0xcd, 0x0d, 0x49, 0xf7, 0x9f, 0xbe, 0xe8, 0x49, 0xbc, 0x3f, 0xb2,
|
||||
0x6f, 0x2b, 0xd0, 0xd0, 0x32, 0xe3, 0x88, 0xb3, 0x98, 0x2e, 0x74, 0x66, 0xa5, 0x16, 0x89, 0x5a,
|
||||
0x67, 0x86, 0xf5, 0x47, 0xf8, 0x39, 0x98, 0xb9, 0xbe, 0xc5, 0x4d, 0xa3, 0x55, 0x69, 0xd7, 0x8f,
|
||||
0xda, 0xe5, 0x52, 0x25, 0xcb, 0xe9, 0x22, 0xc1, 0xab, 0x93, 0xec, 0x3b, 0x2e, 0x56, 0xbb, 0x59,
|
||||
0xa8, 0x16, 0xbf, 0x04, 0x50, 0xc4, 0x21, 0x1b, 0xf3, 0x66, 0x55, 0x76, 0xe8, 0x78, 0x05, 0xda,
|
||||
0xf4, 0x71, 0xea, 0xd4, 0x67, 0x63, 0xee, 0xd5, 0x48, 0xfa, 0x69, 0x5d, 0x43, 0x2d, 0xc3, 0xf1,
|
||||
0x25, 0x34, 0x08, 0x67, 0xf1, 0x6c, 0x4a, 0x85, 0x62, 0x51, 0x73, 0x78, 0xbc, 0x16, 0x8b, 0x2e,
|
||||
0x20, 0x89, 0x4c, 0x92, 0x3b, 0x59, 0xc7, 0x60, 0xe6, 0x6f, 0xf1, 0x7d, 0xd8, 0x8a, 0x04, 0xbf,
|
||||
0xa6, 0x24, 0x19, 0xb2, 0xd9, 0xf4, 0x8a, 0x0a, 0xd9, 0xef, 0x8a, 0xd7, 0xd0, 0xe8, 0xb9, 0x04,
|
||||
0xed, 0xaf, 0x08, 0x1a, 0x1e, 0x8d, 0xb8, 0x48, 0xd6, 0xb0, 0xd3, 0x19, 0x40, 0x36, 0xb5, 0x74,
|
||||
0x48, 0x2b, 0xfb, 0x29, 0x97, 0x5a, 0x3c, 0xa2, 0x4a, 0xb1, 0xa1, 0x7e, 0x21, 0xd8, 0x4a, 0x95,
|
||||
0x6a, 0x47, 0x5d, 0x42, 0x43, 0x48, 0x24, 0xf5, 0x0b, 0x92, 0x52, 0xfe, 0xd1, 0xd2, 0xe5, 0x22,
|
||||
0xfa, 0xa8, 0xfc, 0x63, 0x8a, 0xbb, 0xc3, 0x5f, 0xd4, 0x19, 0x85, 0xea, 0xac, 0xd7, 0x50, 0xcf,
|
||||
0x15, 0x5a, 0xc5, 0xeb, 0x1d, 0xa8, 0xaa, 0xff, 0x5a, 0x1b, 0x01, 0xa7, 0xaa, 0x45, 0x44, 0x9c,
|
||||
0x81, 0xbc, 0xf1, 0x74, 0xc4, 0xd1, 0x37, 0x03, 0xb6, 0x07, 0x19, 0xa3, 0x5e, 0x61, 0xf8, 0x13,
|
||||
0x82, 0x4d, 0xe9, 0x0f, 0xdc, 0x59, 0xc9, 0x44, 0x72, 0xbe, 0xd6, 0xfe, 0x1a, 0x86, 0xb3, 0x0f,
|
||||
0x3e, 0x7e, 0xff, 0xf1, 0xd9, 0x78, 0x60, 0xef, 0xe6, 0xb6, 0x68, 0xec, 0xbe, 0xcf, 0x1b, 0xe4,
|
||||
0x43, 0x57, 0x1a, 0xbe, 0x8b, 0x3a, 0xf8, 0x06, 0x41, 0x55, 0x75, 0x01, 0xef, 0xaf, 0x36, 0x03,
|
||||
0x25, 0xe9, 0x60, 0x9d, 0x81, 0xd9, 0x0f, 0xa5, 0xa6, 0x3d, 0xdb, 0x2e, 0xd3, 0xa4, 0x06, 0xd9,
|
||||
0x45, 0x9d, 0x93, 0x1b, 0x04, 0x2d, 0xc2, 0xa7, 0xa5, 0x14, 0x27, 0xff, 0xff, 0xd1, 0xdd, 0x8b,
|
||||
0xc5, 0xb2, 0xbd, 0x40, 0xaf, 0x9e, 0xe9, 0xbc, 0x80, 0x4f, 0x7c, 0x16, 0x38, 0x5c, 0x04, 0x6e,
|
||||
0x40, 0x99, 0x5c, 0xc5, 0xae, 0xba, 0xf2, 0xa3, 0x30, 0x2e, 0x5e, 0xea, 0x4f, 0x96, 0x91, 0x9f,
|
||||
0x08, 0x7d, 0x31, 0x36, 0xce, 0x9e, 0x0e, 0x7a, 0x57, 0x55, 0x59, 0xe0, 0xd1, 0xef, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0x5e, 0x28, 0x7b, 0xe6, 0xb7, 0x06, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -215,7 +215,7 @@ func (m *ConverseConfig) GetConverseState() *ConverseState {
|
|||
|
||||
// Specifies how to process the `audio_in` data that will be provided in
|
||||
// subsequent requests. For recommended settings, see the Google Assistant SDK
|
||||
// [best practices](https://developers.google.com/assistant/best-practices).
|
||||
// [best practices](https://developers.google.com/assistant/sdk/develop/grpc/best-practices/audio).
|
||||
type AudioInConfig struct {
|
||||
// *Required* Encoding of audio data sent in all `audio_in` messages.
|
||||
Encoding AudioInConfig_Encoding `protobuf:"varint,1,opt,name=encoding,enum=google.assistant.embedded.v1alpha1.AudioInConfig_Encoding" json:"encoding,omitempty"`
|
||||
|
|
|
@ -1091,76 +1091,78 @@ var _Bigtable_serviceDesc = grpc.ServiceDesc{
|
|||
func init() { proto.RegisterFile("google/bigtable/v2/bigtable.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 1135 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x4f, 0x6f, 0x1b, 0x45,
|
||||
0x14, 0x67, 0xec, 0xda, 0xf1, 0xbe, 0x24, 0x4d, 0x32, 0x84, 0xc6, 0x35, 0x09, 0xb8, 0x4b, 0x0b,
|
||||
0x8e, 0x4b, 0xd7, 0x55, 0x50, 0x0f, 0x75, 0x95, 0x02, 0x0e, 0x49, 0x83, 0xc0, 0x55, 0x35, 0x96,
|
||||
0x40, 0x42, 0x48, 0xd6, 0x78, 0x3d, 0x76, 0x96, 0xec, 0xbf, 0xee, 0x8c, 0x63, 0x5c, 0xc4, 0x85,
|
||||
0x03, 0x1f, 0x00, 0xce, 0x88, 0x13, 0x82, 0x0b, 0x1c, 0xb9, 0x72, 0xe0, 0x23, 0x70, 0xe0, 0x0b,
|
||||
0xf4, 0x13, 0xf0, 0x09, 0xd0, 0xcc, 0xce, 0xda, 0x4e, 0x62, 0xb7, 0x9b, 0xaa, 0xb7, 0x9d, 0xf7,
|
||||
0xde, 0xef, 0xcd, 0xef, 0xfd, 0x1d, 0x1b, 0xae, 0xf5, 0x83, 0xa0, 0xef, 0xb2, 0x5a, 0xc7, 0xe9,
|
||||
0x0b, 0xda, 0x71, 0x59, 0xed, 0x64, 0x67, 0xfc, 0x6d, 0x85, 0x51, 0x20, 0x02, 0x8c, 0x63, 0x13,
|
||||
0x6b, 0x2c, 0x3e, 0xd9, 0x29, 0x6d, 0x6a, 0x18, 0x0d, 0x9d, 0x1a, 0xf5, 0xfd, 0x40, 0x50, 0xe1,
|
||||
0x04, 0x3e, 0x8f, 0x11, 0xa5, 0xad, 0x19, 0x4e, 0xbb, 0x54, 0x50, 0xad, 0x7e, 0x43, 0xab, 0xd5,
|
||||
0xa9, 0x33, 0xe8, 0xd5, 0x86, 0x11, 0x0d, 0x43, 0x16, 0x25, 0xf0, 0x0d, 0xad, 0x8f, 0x42, 0xbb,
|
||||
0xc6, 0x05, 0x15, 0x03, 0xad, 0x30, 0xff, 0x44, 0xb0, 0x42, 0x18, 0xed, 0x92, 0x60, 0xc8, 0x09,
|
||||
0x7b, 0x3c, 0x60, 0x5c, 0xe0, 0x2d, 0x00, 0x75, 0x47, 0xdb, 0xa7, 0x1e, 0x2b, 0xa2, 0x32, 0xaa,
|
||||
0x18, 0xc4, 0x50, 0x92, 0x87, 0xd4, 0x63, 0xd8, 0x82, 0x4b, 0x51, 0x30, 0xe4, 0xc5, 0x4c, 0x19,
|
||||
0x55, 0x16, 0x77, 0x4a, 0xd6, 0xf9, 0x58, 0x2c, 0x12, 0x0c, 0x5b, 0x4c, 0x10, 0x65, 0x87, 0xef,
|
||||
0x40, 0xbe, 0xe7, 0xb8, 0x82, 0x45, 0xc5, 0xac, 0x42, 0x6c, 0xcd, 0x41, 0x1c, 0x28, 0x23, 0xa2,
|
||||
0x8d, 0x25, 0x0b, 0x09, 0x6f, 0xbb, 0x8e, 0xe7, 0x88, 0xe2, 0xa5, 0x32, 0xaa, 0x64, 0x89, 0x21,
|
||||
0x25, 0x9f, 0x4a, 0x81, 0xf9, 0x5f, 0x16, 0x56, 0x27, 0xc4, 0x79, 0x18, 0xf8, 0x9c, 0xe1, 0x03,
|
||||
0xc8, 0xdb, 0x47, 0x03, 0xff, 0x98, 0x17, 0x51, 0x39, 0x5b, 0x59, 0xdc, 0xb1, 0x66, 0x5e, 0x75,
|
||||
0x06, 0x65, 0xed, 0x31, 0xd7, 0xdd, 0x93, 0x30, 0xa2, 0xd1, 0xb8, 0x06, 0xeb, 0x2e, 0xe5, 0xa2,
|
||||
0xcd, 0x6d, 0xea, 0xfb, 0xac, 0xdb, 0x8e, 0x82, 0x61, 0xfb, 0x98, 0x8d, 0x54, 0xc8, 0x4b, 0x64,
|
||||
0x4d, 0xea, 0x5a, 0xb1, 0x8a, 0x04, 0xc3, 0x4f, 0xd8, 0xa8, 0xf4, 0x34, 0x03, 0xc6, 0xd8, 0x0d,
|
||||
0xde, 0x80, 0x85, 0x04, 0x81, 0x14, 0x22, 0x1f, 0x29, 0x33, 0xbc, 0x0b, 0x8b, 0x3d, 0xea, 0x39,
|
||||
0xee, 0x28, 0x4e, 0x6d, 0x9c, 0xc1, 0xcd, 0x84, 0x64, 0x52, 0x3c, 0xab, 0x25, 0x22, 0xc7, 0xef,
|
||||
0x7f, 0x46, 0xdd, 0x01, 0x23, 0x10, 0x03, 0x54, 0xe6, 0xef, 0x82, 0xf1, 0x78, 0x40, 0x5d, 0xa7,
|
||||
0xe7, 0x8c, 0x93, 0xf9, 0xfa, 0x39, 0x70, 0x63, 0x24, 0x18, 0x8f, 0xb1, 0x13, 0x6b, 0xbc, 0x0d,
|
||||
0xab, 0xc2, 0xf1, 0x18, 0x17, 0xd4, 0x0b, 0xdb, 0x9e, 0x63, 0x47, 0x01, 0xd7, 0x39, 0x5d, 0x19,
|
||||
0xcb, 0x9b, 0x4a, 0x8c, 0xaf, 0x40, 0xde, 0xa5, 0x1d, 0xe6, 0xf2, 0x62, 0xae, 0x9c, 0xad, 0x18,
|
||||
0x44, 0x9f, 0xf0, 0x3a, 0xe4, 0x4e, 0xa4, 0xdb, 0x62, 0x5e, 0xc5, 0x14, 0x1f, 0x64, 0x99, 0xd4,
|
||||
0x47, 0x9b, 0x3b, 0x4f, 0x58, 0x71, 0xa1, 0x8c, 0x2a, 0x39, 0x62, 0x28, 0x49, 0xcb, 0x79, 0x22,
|
||||
0xd5, 0x46, 0xc4, 0x38, 0x13, 0x32, 0x85, 0xc5, 0x42, 0x19, 0x55, 0x0a, 0x87, 0xaf, 0x90, 0x82,
|
||||
0x12, 0x91, 0x60, 0x88, 0xdf, 0x04, 0xb0, 0x03, 0xcf, 0x73, 0x62, 0xbd, 0xa1, 0xf5, 0x46, 0x2c,
|
||||
0x23, 0xc1, 0xb0, 0xb1, 0xa4, 0xba, 0xa0, 0x1d, 0xf7, 0xac, 0x79, 0x07, 0xd6, 0x5b, 0xd4, 0x0b,
|
||||
0x5d, 0x16, 0xa7, 0x3d, 0x65, 0xc7, 0x9a, 0x2d, 0x78, 0xed, 0x0c, 0x4c, 0xf7, 0xcb, 0xdc, 0x42,
|
||||
0x5d, 0x83, 0xa5, 0xa0, 0xd7, 0x93, 0xbc, 0x3b, 0x32, 0x9d, 0xaa, 0x52, 0x59, 0xb2, 0x18, 0xcb,
|
||||
0x54, 0x86, 0xcd, 0xef, 0x11, 0xac, 0x36, 0x07, 0x82, 0x0a, 0xe9, 0x35, 0xe5, 0xe8, 0x4c, 0xdd,
|
||||
0x97, 0x39, 0x75, 0x5f, 0x1d, 0x0c, 0x6f, 0xa0, 0x27, 0xbe, 0x98, 0x55, 0xbd, 0xbb, 0x39, 0xab,
|
||||
0x77, 0x9b, 0xda, 0x88, 0x4c, 0xcc, 0xcd, 0x57, 0x61, 0x6d, 0x8a, 0x47, 0x1c, 0x99, 0xf9, 0x2f,
|
||||
0x9a, 0x92, 0xa6, 0x9d, 0xec, 0x7d, 0x58, 0x60, 0xbe, 0x88, 0x1c, 0x15, 0xb0, 0xe4, 0x70, 0x73,
|
||||
0x2e, 0x87, 0x69, 0xb7, 0xd6, 0xbe, 0x2f, 0xa2, 0x11, 0x49, 0xb0, 0xa5, 0x2f, 0x21, 0xa7, 0x24,
|
||||
0xf3, 0xd3, 0x7b, 0x2a, 0xdc, 0xcc, 0xc5, 0xc2, 0xfd, 0x15, 0x01, 0x9e, 0xa6, 0x30, 0x1e, 0xfd,
|
||||
0x31, 0xf7, 0x78, 0xf6, 0xdf, 0x7d, 0x1e, 0x77, 0x3d, 0xfd, 0x67, 0xc8, 0x7f, 0x9c, 0x90, 0x5f,
|
||||
0x87, 0x9c, 0xe3, 0x77, 0xd9, 0xd7, 0x8a, 0x7a, 0x96, 0xc4, 0x07, 0x5c, 0x85, 0x7c, 0xdc, 0x8b,
|
||||
0x7a, 0x78, 0x71, 0x72, 0x4b, 0x14, 0xda, 0x56, 0x4b, 0x69, 0x88, 0xb6, 0x30, 0x7f, 0xcb, 0x40,
|
||||
0x71, 0xef, 0x88, 0xd9, 0xc7, 0x1f, 0xfa, 0xdd, 0x97, 0xd6, 0x29, 0x87, 0xb0, 0x1a, 0x46, 0xac,
|
||||
0xeb, 0xd8, 0x54, 0xb0, 0xb6, 0xde, 0xab, 0xf9, 0x34, 0x7b, 0x75, 0x65, 0x0c, 0x8b, 0x05, 0x78,
|
||||
0x0f, 0x2e, 0x8b, 0x68, 0xc0, 0xda, 0x93, 0x4a, 0x5c, 0x4a, 0x51, 0x89, 0x65, 0x89, 0x49, 0x4e,
|
||||
0x1c, 0xef, 0xc3, 0x4a, 0x8f, 0xba, 0x7c, 0xda, 0x4b, 0x2e, 0x85, 0x97, 0xcb, 0x0a, 0x34, 0x76,
|
||||
0x63, 0x1e, 0xc2, 0xd5, 0x19, 0x99, 0xd2, 0xa5, 0xbd, 0x09, 0x6b, 0x93, 0x90, 0x3d, 0x2a, 0xec,
|
||||
0x23, 0xd6, 0x55, 0x19, 0x2b, 0x90, 0x49, 0x2e, 0x9a, 0xb1, 0xdc, 0xfc, 0x01, 0xc1, 0x55, 0xb9,
|
||||
0xe1, 0x9b, 0x41, 0xd7, 0xe9, 0x8d, 0x3e, 0x8f, 0x9c, 0x97, 0x92, 0xf5, 0x5d, 0xc8, 0x45, 0x03,
|
||||
0x97, 0x25, 0xb3, 0xf9, 0xce, 0xbc, 0x77, 0x65, 0xfa, 0xd6, 0x81, 0xcb, 0x48, 0x8c, 0x32, 0x1f,
|
||||
0x40, 0x69, 0x16, 0x27, 0x1d, 0xdf, 0x36, 0x64, 0xe5, 0xf6, 0x43, 0xaa, 0x8a, 0x1b, 0x73, 0xaa,
|
||||
0x48, 0xa4, 0xcd, 0xce, 0xef, 0x05, 0x28, 0x34, 0xb4, 0x02, 0xff, 0x84, 0xa0, 0x90, 0x3c, 0x66,
|
||||
0xf8, 0xad, 0x67, 0x3f, 0x75, 0x2a, 0xfc, 0xd2, 0xf5, 0x34, 0xef, 0xa1, 0xf9, 0xd1, 0x77, 0xff,
|
||||
0x3c, 0xfd, 0x31, 0x73, 0xdf, 0xbc, 0x2b, 0x7f, 0x64, 0x7c, 0x33, 0xc9, 0xd7, 0x6e, 0x18, 0x05,
|
||||
0x5f, 0x31, 0x5b, 0xf0, 0x5a, 0xb5, 0xe6, 0xf8, 0x5c, 0x50, 0xdf, 0x66, 0xf2, 0x5b, 0x59, 0xf0,
|
||||
0x5a, 0xf5, 0xdb, 0x7a, 0xa4, 0x5d, 0xd5, 0x51, 0xf5, 0x36, 0xc2, 0x7f, 0x20, 0x58, 0x3e, 0xb5,
|
||||
0x77, 0x71, 0x65, 0xd6, 0xfd, 0xb3, 0x36, 0x7a, 0x69, 0x3b, 0x85, 0xa5, 0xa6, 0x7b, 0xa0, 0xe8,
|
||||
0x7e, 0x80, 0xef, 0x5f, 0x98, 0x2e, 0x9f, 0xf6, 0x77, 0x1b, 0xe1, 0x9f, 0x11, 0x18, 0xe3, 0xf6,
|
||||
0xc3, 0xd7, 0x9f, 0xb9, 0x40, 0x12, 0xa2, 0x37, 0x9e, 0x63, 0xa5, 0x49, 0xee, 0x2b, 0x92, 0xef,
|
||||
0x9b, 0xf5, 0x0b, 0x93, 0xf4, 0x12, 0x5f, 0x75, 0x54, 0xc5, 0xbf, 0x20, 0x80, 0xc9, 0x0e, 0xc3,
|
||||
0x37, 0x52, 0xed, 0xe7, 0xd2, 0xdb, 0xe9, 0x56, 0x61, 0x92, 0x49, 0xf3, 0xde, 0x8b, 0x93, 0xd4,
|
||||
0xa5, 0xff, 0x0b, 0xc1, 0xda, 0xb9, 0x81, 0xc6, 0x33, 0x57, 0xf2, 0xbc, 0x0d, 0x59, 0xba, 0x95,
|
||||
0xd2, 0x5a, 0x93, 0x6f, 0x2a, 0xf2, 0x0f, 0xcc, 0xc6, 0x85, 0xc9, 0xdb, 0x67, 0x7d, 0xca, 0x4c,
|
||||
0xff, 0x8d, 0x00, 0x9f, 0x9f, 0x59, 0x7c, 0x2b, 0xcd, 0xe4, 0x4f, 0x62, 0xb0, 0xd2, 0x9a, 0xeb,
|
||||
0x20, 0x1e, 0xaa, 0x20, 0x0e, 0xcd, 0xbd, 0x17, 0x1a, 0xbd, 0xd3, 0x4e, 0xeb, 0xa8, 0xda, 0x60,
|
||||
0x70, 0xc5, 0x0e, 0xbc, 0x19, 0x24, 0x1a, 0xcb, 0xc9, 0x1a, 0x79, 0x24, 0x7f, 0x38, 0x3e, 0x42,
|
||||
0x5f, 0xd4, 0xb5, 0x51, 0x3f, 0x70, 0xa9, 0xdf, 0xb7, 0x82, 0xa8, 0x5f, 0xeb, 0x33, 0x5f, 0xfd,
|
||||
0xac, 0xac, 0xc5, 0x2a, 0x1a, 0x3a, 0x7c, 0xfa, 0x0f, 0xc8, 0xbd, 0xe4, 0xbb, 0x93, 0x57, 0x66,
|
||||
0xef, 0xfd, 0x1f, 0x00, 0x00, 0xff, 0xff, 0x38, 0x8d, 0xf4, 0x91, 0xfb, 0x0c, 0x00, 0x00,
|
||||
// 1154 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xdd, 0x6e, 0x1b, 0x45,
|
||||
0x14, 0x66, 0xec, 0xd8, 0xf1, 0x9e, 0x24, 0x4d, 0x32, 0x84, 0x66, 0x6b, 0x12, 0x70, 0x97, 0x16,
|
||||
0x1c, 0x97, 0xae, 0x2b, 0xa3, 0x5e, 0xd4, 0x55, 0x0a, 0xd8, 0xe4, 0x07, 0x81, 0xab, 0x6a, 0x2c,
|
||||
0x15, 0x09, 0x21, 0x59, 0xe3, 0xf5, 0xd8, 0x59, 0xb2, 0x7f, 0xdd, 0x9d, 0x8d, 0x71, 0x11, 0x12,
|
||||
0xe2, 0x82, 0x07, 0x80, 0x6b, 0xc4, 0x15, 0x02, 0x21, 0xc1, 0x25, 0xb7, 0x5c, 0xf0, 0x08, 0x5c,
|
||||
0xf0, 0x02, 0x7d, 0x02, 0x9e, 0xa0, 0xda, 0xd9, 0x59, 0xdb, 0x49, 0xec, 0x76, 0x53, 0xf5, 0x6e,
|
||||
0xe7, 0x9c, 0xf3, 0x9d, 0xf9, 0xce, 0xef, 0xd8, 0x70, 0x75, 0xe0, 0xba, 0x03, 0x8b, 0x55, 0xbb,
|
||||
0xe6, 0x80, 0xd3, 0xae, 0xc5, 0xaa, 0x27, 0xb5, 0xf1, 0xb7, 0xee, 0xf9, 0x2e, 0x77, 0x31, 0x8e,
|
||||
0x4d, 0xf4, 0xb1, 0xf8, 0xa4, 0x56, 0xdc, 0x92, 0x30, 0xea, 0x99, 0x55, 0xea, 0x38, 0x2e, 0xa7,
|
||||
0xdc, 0x74, 0x9d, 0x20, 0x46, 0x14, 0xb7, 0x67, 0x38, 0xed, 0x51, 0x4e, 0xa5, 0xfa, 0x0d, 0xa9,
|
||||
0x16, 0xa7, 0x6e, 0xd8, 0xaf, 0x0e, 0x7d, 0xea, 0x79, 0xcc, 0x4f, 0xe0, 0x9b, 0x52, 0xef, 0x7b,
|
||||
0x46, 0x35, 0xe0, 0x94, 0x87, 0x52, 0xa1, 0xfd, 0x85, 0x60, 0x95, 0x30, 0xda, 0x23, 0xee, 0x30,
|
||||
0x20, 0xec, 0x51, 0xc8, 0x02, 0x8e, 0xb7, 0x01, 0xc4, 0x1d, 0x1d, 0x87, 0xda, 0x4c, 0x45, 0x25,
|
||||
0x54, 0x56, 0x88, 0x22, 0x24, 0xf7, 0xa9, 0xcd, 0xb0, 0x0e, 0x0b, 0xbe, 0x3b, 0x0c, 0xd4, 0x4c,
|
||||
0x09, 0x95, 0x97, 0x6a, 0x45, 0xfd, 0x7c, 0x2c, 0x3a, 0x71, 0x87, 0x6d, 0xc6, 0x89, 0xb0, 0xc3,
|
||||
0xb7, 0x21, 0xdf, 0x37, 0x2d, 0xce, 0x7c, 0x35, 0x2b, 0x10, 0xdb, 0x73, 0x10, 0xfb, 0xc2, 0x88,
|
||||
0x48, 0xe3, 0x88, 0x45, 0x04, 0xef, 0x58, 0xa6, 0x6d, 0x72, 0x75, 0xa1, 0x84, 0xca, 0x59, 0xa2,
|
||||
0x44, 0x92, 0x4f, 0x23, 0x81, 0xf6, 0x7f, 0x16, 0xd6, 0x26, 0xc4, 0x03, 0xcf, 0x75, 0x02, 0x86,
|
||||
0xf7, 0x21, 0x6f, 0x1c, 0x85, 0xce, 0x71, 0xa0, 0xa2, 0x52, 0xb6, 0xbc, 0x54, 0xd3, 0x67, 0x5e,
|
||||
0x75, 0x06, 0xa5, 0x37, 0x99, 0x65, 0x35, 0x23, 0x18, 0x91, 0x68, 0x5c, 0x85, 0x0d, 0x8b, 0x06,
|
||||
0xbc, 0x13, 0x18, 0xd4, 0x71, 0x58, 0xaf, 0xe3, 0xbb, 0xc3, 0xce, 0x31, 0x1b, 0x89, 0x90, 0x97,
|
||||
0xc9, 0x7a, 0xa4, 0x6b, 0xc7, 0x2a, 0xe2, 0x0e, 0x3f, 0x61, 0xa3, 0xe2, 0x93, 0x0c, 0x28, 0x63,
|
||||
0x37, 0x78, 0x13, 0x16, 0x13, 0x04, 0x12, 0x88, 0xbc, 0x2f, 0xcc, 0xf0, 0x2e, 0x2c, 0xf5, 0xa9,
|
||||
0x6d, 0x5a, 0xa3, 0x38, 0xb5, 0x71, 0x06, 0xb7, 0x12, 0x92, 0x49, 0xf1, 0xf4, 0x36, 0xf7, 0x4d,
|
||||
0x67, 0xf0, 0x90, 0x5a, 0x21, 0x23, 0x10, 0x03, 0x44, 0xe6, 0xef, 0x80, 0xf2, 0x28, 0xa4, 0x96,
|
||||
0xd9, 0x37, 0xc7, 0xc9, 0x7c, 0xfd, 0x1c, 0xb8, 0x31, 0xe2, 0x2c, 0x88, 0xb1, 0x13, 0x6b, 0xbc,
|
||||
0x03, 0x6b, 0xdc, 0xb4, 0x59, 0xc0, 0xa9, 0xed, 0x75, 0x6c, 0xd3, 0xf0, 0xdd, 0x40, 0xe6, 0x74,
|
||||
0x75, 0x2c, 0x6f, 0x09, 0x31, 0xbe, 0x0c, 0x79, 0x8b, 0x76, 0x99, 0x15, 0xa8, 0xb9, 0x52, 0xb6,
|
||||
0xac, 0x10, 0x79, 0xc2, 0x1b, 0x90, 0x3b, 0x89, 0xdc, 0xaa, 0x79, 0x11, 0x53, 0x7c, 0x88, 0xca,
|
||||
0x24, 0x3e, 0x3a, 0x81, 0xf9, 0x98, 0xa9, 0x8b, 0x25, 0x54, 0xce, 0x11, 0x45, 0x48, 0xda, 0xe6,
|
||||
0xe3, 0x48, 0xad, 0xf8, 0x2c, 0x60, 0x3c, 0x4a, 0xa1, 0x5a, 0x28, 0xa1, 0x72, 0xe1, 0xf0, 0x15,
|
||||
0x52, 0x10, 0x22, 0xe2, 0x0e, 0xf1, 0x9b, 0x00, 0x86, 0x6b, 0xdb, 0x66, 0xac, 0x57, 0xa4, 0x5e,
|
||||
0x89, 0x65, 0xc4, 0x1d, 0x36, 0x96, 0x45, 0x17, 0x74, 0xe2, 0x9e, 0xd5, 0x6e, 0xc3, 0x46, 0x9b,
|
||||
0xda, 0x9e, 0xc5, 0xe2, 0xb4, 0xa7, 0xec, 0x58, 0xad, 0x0d, 0xaf, 0x9d, 0x81, 0xc9, 0x7e, 0x99,
|
||||
0x5b, 0xa8, 0xab, 0xb0, 0xec, 0xf6, 0xfb, 0x11, 0xef, 0x6e, 0x94, 0x4e, 0x51, 0xa9, 0x2c, 0x59,
|
||||
0x8a, 0x65, 0x22, 0xc3, 0xda, 0xf7, 0x08, 0xd6, 0x5a, 0x21, 0xa7, 0x3c, 0xf2, 0x9a, 0x72, 0x74,
|
||||
0xa6, 0xee, 0xcb, 0x9c, 0xba, 0xaf, 0x0e, 0x8a, 0x1d, 0xca, 0x89, 0x57, 0xb3, 0xa2, 0x77, 0xb7,
|
||||
0x66, 0xf5, 0x6e, 0x4b, 0x1a, 0x91, 0x89, 0xb9, 0xf6, 0x2a, 0xac, 0x4f, 0xf1, 0x88, 0x23, 0xd3,
|
||||
0xfe, 0x43, 0x53, 0xd2, 0xb4, 0x93, 0xbd, 0x07, 0x8b, 0xcc, 0xe1, 0xbe, 0x29, 0x02, 0x8e, 0x38,
|
||||
0xdc, 0x98, 0xcb, 0x61, 0xda, 0xad, 0xbe, 0xe7, 0x70, 0x7f, 0x44, 0x12, 0x6c, 0xf1, 0x0b, 0xc8,
|
||||
0x09, 0xc9, 0xfc, 0xf4, 0x9e, 0x0a, 0x37, 0x73, 0xb1, 0x70, 0x7f, 0x45, 0x80, 0xa7, 0x29, 0x8c,
|
||||
0x47, 0x7f, 0xcc, 0x3d, 0x9e, 0xfd, 0x77, 0x9f, 0xc7, 0x5d, 0x4e, 0xff, 0x19, 0xf2, 0x1f, 0x27,
|
||||
0xe4, 0x37, 0x20, 0x67, 0x3a, 0x3d, 0xf6, 0x95, 0xa0, 0x9e, 0x25, 0xf1, 0x01, 0x57, 0x20, 0x1f,
|
||||
0xf7, 0xa2, 0x1c, 0x5e, 0x9c, 0xdc, 0xe2, 0x7b, 0x86, 0xde, 0x16, 0x1a, 0x22, 0x2d, 0xb4, 0xdf,
|
||||
0x32, 0xa0, 0x36, 0x8f, 0x98, 0x71, 0xfc, 0xa1, 0xd3, 0x7b, 0x69, 0x9d, 0x72, 0x08, 0x6b, 0x9e,
|
||||
0xcf, 0x7a, 0xa6, 0x41, 0x39, 0xeb, 0xc8, 0xbd, 0x9a, 0x4f, 0xb3, 0x57, 0x57, 0xc7, 0xb0, 0x58,
|
||||
0x80, 0x9b, 0x70, 0x89, 0xfb, 0x21, 0xeb, 0x4c, 0x2a, 0xb1, 0x90, 0xa2, 0x12, 0x2b, 0x11, 0x26,
|
||||
0x39, 0x05, 0x78, 0x0f, 0x56, 0xfb, 0xd4, 0x0a, 0xa6, 0xbd, 0xe4, 0x52, 0x78, 0xb9, 0x24, 0x40,
|
||||
0x63, 0x37, 0xda, 0x21, 0x5c, 0x99, 0x91, 0x29, 0x59, 0xda, 0x1b, 0xb0, 0x3e, 0x09, 0xd9, 0xa6,
|
||||
0xdc, 0x38, 0x62, 0x3d, 0x91, 0xb1, 0x02, 0x99, 0xe4, 0xa2, 0x15, 0xcb, 0xb5, 0x1f, 0x10, 0x5c,
|
||||
0x89, 0x36, 0x7c, 0xcb, 0xed, 0x99, 0xfd, 0xd1, 0x67, 0xbe, 0xf9, 0x52, 0xb2, 0xbe, 0x0b, 0x39,
|
||||
0x3f, 0xb4, 0x58, 0x32, 0x9b, 0xef, 0xcc, 0x7b, 0x57, 0xa6, 0x6f, 0x0d, 0x2d, 0x46, 0x62, 0x94,
|
||||
0x76, 0x00, 0xc5, 0x59, 0x9c, 0x64, 0x7c, 0x3b, 0x90, 0x8d, 0xb6, 0x1f, 0x12, 0x55, 0xdc, 0x9c,
|
||||
0x53, 0x45, 0x12, 0xd9, 0xd4, 0xfe, 0x28, 0x40, 0xa1, 0x21, 0x15, 0xf8, 0x27, 0x04, 0x85, 0xe4,
|
||||
0x31, 0xc3, 0x6f, 0x3d, 0xfb, 0xa9, 0x13, 0xe1, 0x17, 0xaf, 0xa5, 0x79, 0x0f, 0xb5, 0x8f, 0xbe,
|
||||
0xfb, 0xf7, 0xc9, 0x8f, 0x99, 0x7b, 0xda, 0x9d, 0xe8, 0x47, 0xc6, 0xd7, 0x93, 0x7c, 0xed, 0x7a,
|
||||
0xbe, 0xfb, 0x25, 0x33, 0x78, 0x50, 0xad, 0x54, 0x4d, 0x27, 0xe0, 0xd4, 0x31, 0x58, 0xf4, 0x2d,
|
||||
0x2c, 0x82, 0x6a, 0xe5, 0x9b, 0xba, 0x2f, 0x5d, 0xd5, 0x51, 0xe5, 0x16, 0xc2, 0x7f, 0x22, 0x58,
|
||||
0x39, 0xb5, 0x77, 0x71, 0x79, 0xd6, 0xfd, 0xb3, 0x36, 0x7a, 0x71, 0x27, 0x85, 0xa5, 0xa4, 0xbb,
|
||||
0x2f, 0xe8, 0x7e, 0x80, 0xef, 0x5d, 0x98, 0x6e, 0x30, 0xed, 0xef, 0x16, 0xc2, 0x3f, 0x23, 0x50,
|
||||
0xc6, 0xed, 0x87, 0xaf, 0x3d, 0x73, 0x81, 0x24, 0x44, 0xaf, 0x3f, 0xc7, 0x4a, 0x92, 0xdc, 0x13,
|
||||
0x24, 0xdf, 0xd7, 0xea, 0x17, 0x26, 0x69, 0x27, 0xbe, 0xea, 0xa8, 0x82, 0x7f, 0x41, 0x00, 0x93,
|
||||
0x1d, 0x86, 0xaf, 0xa7, 0xda, 0xcf, 0xc5, 0xb7, 0xd3, 0xad, 0xc2, 0x24, 0x93, 0xda, 0xdd, 0x17,
|
||||
0x27, 0x29, 0x4b, 0xff, 0x37, 0x82, 0xf5, 0x73, 0x03, 0x8d, 0x67, 0xae, 0xe4, 0x79, 0x1b, 0xb2,
|
||||
0x78, 0x33, 0xa5, 0xb5, 0x24, 0xdf, 0x12, 0xe4, 0x0f, 0xb4, 0xc6, 0x85, 0xc9, 0x1b, 0x67, 0x7d,
|
||||
0x46, 0x99, 0xfe, 0x07, 0x01, 0x3e, 0x3f, 0xb3, 0xf8, 0x66, 0x9a, 0xc9, 0x9f, 0xc4, 0xa0, 0xa7,
|
||||
0x35, 0x97, 0x41, 0xdc, 0x17, 0x41, 0x1c, 0x6a, 0xcd, 0x17, 0x1a, 0xbd, 0xd3, 0x4e, 0xeb, 0xa8,
|
||||
0xd2, 0xf8, 0x16, 0xc1, 0x65, 0xc3, 0xb5, 0x67, 0xb0, 0x68, 0xac, 0x24, 0x7b, 0xe4, 0x41, 0xf4,
|
||||
0xcb, 0xf1, 0x01, 0xfa, 0xbc, 0x2e, 0x8d, 0x06, 0xae, 0x45, 0x9d, 0x81, 0xee, 0xfa, 0x83, 0xea,
|
||||
0x80, 0x39, 0xe2, 0x77, 0x65, 0x35, 0x56, 0x51, 0xcf, 0x0c, 0xa6, 0xff, 0x81, 0xdc, 0x4d, 0xbe,
|
||||
0x7f, 0xcf, 0xa8, 0x07, 0x31, 0xb8, 0x69, 0xb9, 0x61, 0x4f, 0x4f, 0x5c, 0xeb, 0x0f, 0x6b, 0xdd,
|
||||
0xbc, 0xf0, 0xf0, 0xde, 0xd3, 0x00, 0x00, 0x00, 0xff, 0xff, 0x74, 0x6b, 0x7a, 0xa3, 0x17, 0x0d,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -2024,94 +2024,95 @@ func init() {
|
|||
func init() { proto.RegisterFile("google/bigtable/v2/data.proto", fileDescriptor1) }
|
||||
|
||||
var fileDescriptor1 = []byte{
|
||||
// 1412 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xdb, 0x6e, 0xdb, 0x46,
|
||||
0x13, 0x16, 0x2d, 0xeb, 0x34, 0x94, 0x25, 0x65, 0xe3, 0x38, 0x8a, 0xfe, 0xf8, 0x8f, 0xc1, 0x14,
|
||||
0xa9, 0xe2, 0xb6, 0x72, 0xab, 0x04, 0xe9, 0x21, 0x45, 0x11, 0xcb, 0x69, 0xaa, 0x36, 0xe7, 0x8d,
|
||||
0x91, 0x02, 0x01, 0x0a, 0x76, 0x2d, 0xad, 0x54, 0xc2, 0x4b, 0x2e, 0x4b, 0x52, 0x56, 0xf4, 0x22,
|
||||
0xbd, 0x6f, 0x5f, 0xa3, 0x77, 0x7d, 0x89, 0xf6, 0x31, 0xfa, 0x00, 0xbd, 0x28, 0xf6, 0xc0, 0x93,
|
||||
0xa2, 0xd8, 0x46, 0x91, 0x3b, 0x72, 0xe6, 0xfb, 0xbe, 0x99, 0x9d, 0x9d, 0x1d, 0x2e, 0x61, 0x7b,
|
||||
0xca, 0xf9, 0x94, 0xd1, 0xbd, 0x23, 0x67, 0x1a, 0x91, 0x23, 0x46, 0xf7, 0x4e, 0xfa, 0x7b, 0x63,
|
||||
0x12, 0x91, 0x9e, 0x1f, 0xf0, 0x88, 0x23, 0xa4, 0xdc, 0xbd, 0xd8, 0xdd, 0x3b, 0xe9, 0x5b, 0x4f,
|
||||
0xa1, 0x88, 0xf9, 0x1c, 0xb5, 0xa0, 0x78, 0x4c, 0x17, 0x6d, 0x63, 0xc7, 0xe8, 0xd6, 0xb1, 0x78,
|
||||
0x44, 0x77, 0xa0, 0x3a, 0x21, 0xae, 0xc3, 0x1c, 0x1a, 0xb6, 0xd7, 0x76, 0x8a, 0x5d, 0xb3, 0xdf,
|
||||
0xe9, 0xbd, 0xc9, 0xef, 0x3d, 0x10, 0x98, 0x05, 0x4e, 0xb0, 0x16, 0x86, 0xb2, 0xb2, 0x21, 0x04,
|
||||
0xeb, 0x1e, 0x71, 0xa9, 0x14, 0xad, 0x61, 0xf9, 0x8c, 0x6e, 0x43, 0x65, 0xc4, 0xd9, 0xcc, 0xf5,
|
||||
0x4e, 0x15, 0x3d, 0x90, 0x10, 0x1c, 0x43, 0xad, 0x97, 0x50, 0x56, 0x26, 0x74, 0x15, 0x6a, 0x3f,
|
||||
0xcf, 0x08, 0x73, 0x26, 0x0e, 0x0d, 0x74, 0xb6, 0xa9, 0x01, 0xf5, 0xa0, 0x34, 0xa2, 0x8c, 0xc5,
|
||||
0xda, 0xed, 0x95, 0xda, 0x94, 0x31, 0xac, 0x60, 0x96, 0x0d, 0xeb, 0xe2, 0x15, 0xdd, 0x84, 0x56,
|
||||
0xe4, 0xb8, 0x34, 0x8c, 0x88, 0xeb, 0xdb, 0xae, 0x33, 0x0a, 0x78, 0x28, 0xc5, 0x8b, 0xb8, 0x99,
|
||||
0xd8, 0x1f, 0x4b, 0x33, 0xda, 0x84, 0xd2, 0x09, 0x61, 0x33, 0xda, 0x5e, 0x93, 0xc1, 0xd5, 0x0b,
|
||||
0xda, 0x82, 0x32, 0x23, 0x47, 0x94, 0x85, 0xed, 0xe2, 0x4e, 0xb1, 0x5b, 0xc3, 0xfa, 0xcd, 0xfa,
|
||||
0xc3, 0x80, 0x2a, 0xe6, 0x73, 0x4c, 0xbc, 0x29, 0x45, 0xbb, 0xd0, 0x0a, 0x23, 0x12, 0x44, 0xf6,
|
||||
0x31, 0x5d, 0xd8, 0x23, 0xc6, 0x43, 0x3a, 0x56, 0x4b, 0x18, 0x16, 0x70, 0x43, 0x7a, 0x1e, 0xd2,
|
||||
0xc5, 0x81, 0xb4, 0xa3, 0x1b, 0xd0, 0x48, 0xb1, 0xdc, 0xa7, 0x9e, 0x8a, 0x37, 0x2c, 0xe0, 0x7a,
|
||||
0x8c, 0x7c, 0xea, 0x53, 0x0f, 0x59, 0x50, 0xa7, 0xde, 0x38, 0x45, 0x15, 0x25, 0xca, 0xc0, 0x40,
|
||||
0xbd, 0x71, 0x8c, 0xb9, 0x01, 0x8d, 0x18, 0xa3, 0xa3, 0xae, 0x6b, 0x54, 0x5d, 0xa1, 0x54, 0xcc,
|
||||
0x81, 0x09, 0xb5, 0x24, 0xe6, 0xa0, 0x06, 0x15, 0x4d, 0xb2, 0x7e, 0x84, 0x32, 0xe6, 0xf3, 0x17,
|
||||
0x34, 0x42, 0x57, 0xa0, 0x1a, 0xf0, 0xb9, 0x30, 0x8a, 0xfa, 0x14, 0xbb, 0x75, 0x5c, 0x09, 0xf8,
|
||||
0xfc, 0x21, 0x5d, 0x84, 0xe8, 0x2e, 0x80, 0x70, 0x05, 0x62, 0xa5, 0x71, 0xfd, 0xaf, 0xae, 0xaa,
|
||||
0x7f, 0x5c, 0x0e, 0x5c, 0x0b, 0xf4, 0x53, 0x68, 0xfd, 0xb6, 0x06, 0xa6, 0xde, 0x73, 0x59, 0xa9,
|
||||
0x6b, 0x60, 0xca, 0x7e, 0x5a, 0xd8, 0x99, 0x06, 0x02, 0x65, 0x7a, 0x22, 0xda, 0xe8, 0x0e, 0x6c,
|
||||
0xa9, 0x54, 0x93, 0xbd, 0x8f, 0x97, 0x16, 0x97, 0x69, 0x53, 0xfa, 0x9f, 0xc7, 0x6e, 0x5d, 0xd6,
|
||||
0x3e, 0x6c, 0x2e, 0xf3, 0x32, 0x65, 0x2b, 0x60, 0x94, 0x67, 0xc9, 0xf2, 0xf5, 0x61, 0x53, 0x54,
|
||||
0xe2, 0x8d, 0x48, 0x71, 0x11, 0x11, 0xf5, 0xc6, 0xcb, 0x71, 0x7a, 0x80, 0xf2, 0x1c, 0x19, 0xa5,
|
||||
0xa4, 0x19, 0xad, 0x2c, 0x43, 0xc4, 0x18, 0x5c, 0x80, 0xe6, 0x52, 0x5e, 0x83, 0x26, 0x6c, 0xe4,
|
||||
0x24, 0xac, 0xd7, 0xd0, 0x38, 0x8c, 0x9b, 0x51, 0x95, 0xe9, 0x76, 0x5c, 0x85, 0xb7, 0x34, 0xaf,
|
||||
0x5a, 0xeb, 0xe1, 0x52, 0x07, 0x7f, 0xac, 0xd6, 0xf3, 0x06, 0x67, 0x4d, 0x72, 0x44, 0xde, 0x4b,
|
||||
0x0c, 0xeb, 0x2f, 0x03, 0xe0, 0xa5, 0xe8, 0x73, 0x15, 0xb6, 0x07, 0xaa, 0x4c, 0xb6, 0xec, 0xfd,
|
||||
0xe5, 0x4e, 0x56, 0x3d, 0x2e, 0xe1, 0xba, 0x18, 0x49, 0xdf, 0x2b, 0x7c, 0xae, 0x9b, 0x1b, 0x29,
|
||||
0x5a, 0x16, 0x7b, 0x17, 0x44, 0x71, 0xf2, 0xca, 0x71, 0x4f, 0x8b, 0x2e, 0xce, 0xea, 0xea, 0xbe,
|
||||
0xce, 0xa8, 0x66, 0xfb, 0x3a, 0xd1, 0x1c, 0x6c, 0x80, 0x99, 0x89, 0x2f, 0xda, 0x3c, 0xa1, 0x59,
|
||||
0xff, 0x98, 0x50, 0xc3, 0x7c, 0xfe, 0xc0, 0x61, 0x11, 0x0d, 0xd0, 0x5d, 0x28, 0x8d, 0x7e, 0x22,
|
||||
0x8e, 0x27, 0x17, 0x63, 0xf6, 0xaf, 0xbf, 0xa5, 0x7f, 0x15, 0xba, 0x77, 0x20, 0xa0, 0xc3, 0x02,
|
||||
0x56, 0x1c, 0xf4, 0x1d, 0x80, 0xe3, 0x45, 0x34, 0x60, 0x94, 0x9c, 0xa8, 0xf1, 0x60, 0xf6, 0xbb,
|
||||
0xa7, 0x2b, 0x7c, 0x9b, 0xe0, 0x87, 0x05, 0x9c, 0x61, 0xa3, 0x6f, 0xa0, 0x36, 0xe2, 0xde, 0xd8,
|
||||
0x89, 0x1c, 0xae, 0x9a, 0xd3, 0xec, 0xbf, 0x7f, 0x46, 0x32, 0x31, 0x7c, 0x58, 0xc0, 0x29, 0x17,
|
||||
0x6d, 0xc2, 0x7a, 0xe8, 0x78, 0xc7, 0xed, 0xd6, 0x8e, 0xd1, 0xad, 0x0e, 0x0b, 0x58, 0xbe, 0xa1,
|
||||
0x2e, 0x34, 0x7d, 0x12, 0x86, 0x36, 0x61, 0xcc, 0x9e, 0x48, 0x7e, 0xfb, 0x82, 0x06, 0x6c, 0x08,
|
||||
0xc7, 0x3e, 0x63, 0xba, 0x22, 0xbb, 0xd0, 0x3a, 0x62, 0x7c, 0x74, 0x9c, 0x85, 0x22, 0x0d, 0x6d,
|
||||
0x48, 0x4f, 0x8a, 0xfd, 0x04, 0x36, 0xf5, 0x74, 0xb0, 0x03, 0x3a, 0xa5, 0xaf, 0x63, 0xfc, 0xba,
|
||||
0xde, 0xeb, 0x0b, 0x6a, 0x56, 0x60, 0xe1, 0xd3, 0x94, 0x0f, 0x41, 0x18, 0xed, 0x90, 0xb8, 0x3e,
|
||||
0xa3, 0x31, 0xbe, 0xb1, 0x63, 0x74, 0x8d, 0x61, 0x01, 0x37, 0x03, 0x3e, 0x7f, 0x21, 0x3d, 0x1a,
|
||||
0xfd, 0x39, 0xb4, 0x33, 0x63, 0x21, 0x1f, 0x44, 0x9c, 0xad, 0xda, 0xb0, 0x80, 0x2f, 0xa5, 0x53,
|
||||
0x22, 0x1b, 0xe8, 0x00, 0xb6, 0xd5, 0xc7, 0x24, 0x73, 0x26, 0x73, 0xfc, 0xb2, 0x4e, 0xb2, 0xa3,
|
||||
0x60, 0xc9, 0xf1, 0xcc, 0x8a, 0x3c, 0x87, 0x8b, 0x5a, 0x44, 0x8e, 0xb9, 0x98, 0x5a, 0x91, 0xfb,
|
||||
0x73, 0xed, 0x94, 0x0f, 0x99, 0x40, 0x8b, 0x02, 0x8c, 0xd2, 0x57, 0x2d, 0xf9, 0x0a, 0xb6, 0xd2,
|
||||
0x83, 0x98, 0x53, 0xad, 0x4a, 0x55, 0x6b, 0x95, 0x6a, 0x7e, 0x0c, 0x88, 0x61, 0x17, 0xe5, 0x2c,
|
||||
0x5a, 0xbb, 0x07, 0x48, 0x9d, 0x8d, 0xdc, 0x42, 0x6b, 0xf1, 0x39, 0x95, 0xbe, 0xec, 0xf2, 0x9e,
|
||||
0x24, 0xf8, 0x6c, 0x1e, 0x4d, 0x99, 0xc7, 0xff, 0x57, 0xe5, 0x91, 0xce, 0x84, 0x54, 0x2f, 0x13,
|
||||
0xff, 0x2b, 0xf8, 0x9f, 0xfc, 0xcc, 0xda, 0xbe, 0x28, 0x36, 0x9f, 0xdb, 0x7c, 0x32, 0x09, 0x69,
|
||||
0x14, 0x0b, 0xc3, 0x8e, 0xd1, 0x2d, 0x0d, 0x0b, 0xf8, 0xb2, 0x04, 0x3d, 0xa3, 0x01, 0xe6, 0xf3,
|
||||
0xa7, 0x12, 0xa1, 0xf9, 0x5f, 0x42, 0x27, 0xcf, 0x67, 0x8e, 0xeb, 0x24, 0x74, 0x53, 0xd3, 0xb7,
|
||||
0x32, 0xf4, 0x47, 0x02, 0xa0, 0xd9, 0x03, 0xd8, 0x4e, 0xd9, 0x7a, 0xdb, 0x72, 0x02, 0x75, 0x2d,
|
||||
0x70, 0x25, 0x16, 0x50, 0x9b, 0x95, 0xd5, 0xf8, 0x0c, 0x2e, 0x87, 0x51, 0xe0, 0xf8, 0x7a, 0xc6,
|
||||
0x44, 0x01, 0xf1, 0xc2, 0x09, 0x0f, 0x5c, 0x1a, 0xb4, 0x37, 0xf4, 0x21, 0xb8, 0x24, 0x01, 0xb2,
|
||||
0x12, 0x87, 0xa9, 0x5b, 0x30, 0x89, 0xef, 0xb3, 0x85, 0x2d, 0x2f, 0x02, 0x39, 0xe6, 0xc5, 0xb8,
|
||||
0x53, 0x25, 0xe0, 0x91, 0xf0, 0x67, 0x98, 0x9d, 0x7b, 0x50, 0x92, 0x83, 0x05, 0x7d, 0x0a, 0x15,
|
||||
0x95, 0xa9, 0xfa, 0xd6, 0x9a, 0xfd, 0xed, 0x53, 0x27, 0x00, 0x8e, 0xd1, 0x9d, 0xaf, 0x01, 0xd2,
|
||||
0xc1, 0xf2, 0xdf, 0x65, 0xfe, 0x34, 0xa0, 0x96, 0x4c, 0x15, 0x34, 0x84, 0x96, 0x1f, 0xd0, 0xb1,
|
||||
0x33, 0x22, 0x51, 0xd2, 0x1a, 0x6a, 0x4a, 0x9e, 0xa1, 0xd7, 0x4c, 0x68, 0x49, 0x5b, 0x98, 0x51,
|
||||
0x30, 0x4b, 0x44, 0xd6, 0xce, 0x23, 0x02, 0x82, 0xa1, 0xf9, 0xf7, 0xa0, 0x3e, 0x21, 0x2c, 0x4c,
|
||||
0x04, 0x8a, 0xe7, 0x11, 0x30, 0x25, 0x45, 0xbd, 0x0c, 0xaa, 0x50, 0x56, 0x5c, 0xeb, 0xef, 0x12,
|
||||
0x54, 0x1f, 0xcf, 0x22, 0x22, 0x97, 0xb8, 0x0f, 0x55, 0xd1, 0x9e, 0xa2, 0x1d, 0xf4, 0xd2, 0xde,
|
||||
0x5b, 0x25, 0x1a, 0xe3, 0x7b, 0x2f, 0x68, 0x24, 0x6e, 0x8f, 0xc3, 0x02, 0xae, 0x84, 0xea, 0x11,
|
||||
0xfd, 0x00, 0x68, 0x4c, 0x19, 0x15, 0x25, 0x0a, 0xb8, 0xab, 0xdb, 0x4e, 0x2f, 0xf1, 0xa3, 0x53,
|
||||
0xc5, 0xee, 0x4b, 0xda, 0x83, 0x80, 0xbb, 0xaa, 0x0d, 0xc5, 0x89, 0x1a, 0x2f, 0xd9, 0x96, 0xe5,
|
||||
0xd5, 0xa8, 0xd3, 0x05, 0x38, 0xaf, 0xbc, 0xba, 0x9c, 0xe7, 0xe5, 0xf5, 0x85, 0xfd, 0x10, 0x9a,
|
||||
0x59, 0xf9, 0x80, 0xcf, 0xe5, 0xec, 0x36, 0xfb, 0xbb, 0xe7, 0xd4, 0xc6, 0x7c, 0x2e, 0x3e, 0x21,
|
||||
0xe3, 0xac, 0xa1, 0xf3, 0x8b, 0x01, 0x15, 0x5d, 0xaa, 0xb3, 0x2f, 0x76, 0x37, 0xa1, 0xb5, 0x3c,
|
||||
0xa7, 0xf5, 0x4d, 0xbb, 0xb9, 0x34, 0x98, 0x57, 0x5e, 0xda, 0x8b, 0x67, 0x5c, 0xda, 0xd7, 0x33,
|
||||
0x97, 0xf6, 0xce, 0xaf, 0x06, 0xb4, 0x96, 0xcb, 0xfe, 0x4e, 0x33, 0xdc, 0x07, 0x10, 0x99, 0xa8,
|
||||
0x79, 0xaa, 0xb7, 0xe9, 0x1c, 0x03, 0x1d, 0xd7, 0x04, 0x4b, 0x3e, 0x76, 0x6e, 0x65, 0x53, 0xd4,
|
||||
0xdb, 0x74, 0x56, 0x8a, 0x9d, 0x26, 0x6c, 0xe4, 0xf6, 0x64, 0x00, 0x50, 0x75, 0xf5, 0x6e, 0x59,
|
||||
0xbf, 0x1b, 0x70, 0x11, 0x53, 0x32, 0x7e, 0xcc, 0xc7, 0xce, 0x64, 0xf1, 0x7d, 0xe0, 0x44, 0x14,
|
||||
0xcf, 0x18, 0x7d, 0xa7, 0x0b, 0xbf, 0x0e, 0x75, 0xe2, 0xfb, 0xc9, 0x2d, 0x2b, 0xb9, 0x5e, 0x9b,
|
||||
0xca, 0x2a, 0xa7, 0x25, 0xfa, 0x00, 0x5a, 0x8e, 0x37, 0x0a, 0xa8, 0x4b, 0xbd, 0xc8, 0x26, 0x2e,
|
||||
0x9f, 0x79, 0x91, 0xdc, 0x9f, 0xa2, 0xf8, 0xf4, 0x27, 0x9e, 0x7d, 0xe9, 0x18, 0x94, 0x61, 0x3d,
|
||||
0x98, 0x31, 0x3a, 0x20, 0xb0, 0x35, 0xe2, 0xee, 0x8a, 0x1a, 0x0e, 0x6a, 0xf7, 0x49, 0x44, 0x9e,
|
||||
0x89, 0xff, 0xdc, 0x67, 0xc6, 0xab, 0x2f, 0x34, 0x60, 0xca, 0x19, 0xf1, 0xa6, 0x3d, 0x1e, 0x4c,
|
||||
0xf7, 0xa6, 0xd4, 0x93, 0x7f, 0xc1, 0x7b, 0xca, 0x45, 0x7c, 0x27, 0xcc, 0xfe, 0x27, 0xdf, 0x8d,
|
||||
0x9f, 0x8f, 0xca, 0x12, 0x76, 0xeb, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd8, 0xbb, 0x74, 0x4d,
|
||||
0x4d, 0x0f, 0x00, 0x00,
|
||||
// 1430 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xdd, 0x6e, 0x1b, 0x45,
|
||||
0x14, 0xf6, 0xc6, 0x89, 0x7f, 0xce, 0x3a, 0xb6, 0x3b, 0x4d, 0x53, 0xd7, 0x34, 0x34, 0xda, 0xa2,
|
||||
0xe2, 0x06, 0x70, 0xc0, 0xad, 0xca, 0x4f, 0x11, 0x6a, 0x9c, 0xd2, 0x1a, 0xfa, 0x3f, 0x8d, 0x8a,
|
||||
0x54, 0x09, 0x2d, 0x13, 0x7b, 0x6c, 0x56, 0x99, 0xdd, 0x59, 0x76, 0xd7, 0x71, 0x2d, 0xf1, 0x1c,
|
||||
0xdc, 0xc3, 0x25, 0xaf, 0xc0, 0x1d, 0x2f, 0x01, 0x8f, 0xc1, 0x03, 0x70, 0x81, 0xe6, 0x67, 0xff,
|
||||
0x5c, 0x37, 0x89, 0x50, 0xef, 0x76, 0xcf, 0xf9, 0xbe, 0xef, 0x9c, 0x39, 0x73, 0xe6, 0xec, 0x2c,
|
||||
0x6c, 0x4d, 0x38, 0x9f, 0x30, 0xba, 0x7b, 0xe8, 0x4c, 0x22, 0x72, 0xc8, 0xe8, 0xee, 0x71, 0x6f,
|
||||
0x77, 0x44, 0x22, 0xd2, 0xf5, 0x03, 0x1e, 0x71, 0x84, 0x94, 0xbb, 0x1b, 0xbb, 0xbb, 0xc7, 0x3d,
|
||||
0xeb, 0x09, 0x14, 0x31, 0x9f, 0xa1, 0x26, 0x14, 0x8f, 0xe8, 0xbc, 0x65, 0x6c, 0x1b, 0x9d, 0x1a,
|
||||
0x16, 0x8f, 0xe8, 0x16, 0x54, 0xc6, 0xc4, 0x75, 0x98, 0x43, 0xc3, 0xd6, 0xca, 0x76, 0xb1, 0x63,
|
||||
0xf6, 0xda, 0xdd, 0xd7, 0xf9, 0xdd, 0x7b, 0x02, 0x33, 0xc7, 0x09, 0xd6, 0xc2, 0x50, 0x52, 0x36,
|
||||
0x84, 0x60, 0xd5, 0x23, 0x2e, 0x95, 0xa2, 0x55, 0x2c, 0x9f, 0xd1, 0x4d, 0x28, 0x0f, 0x39, 0x9b,
|
||||
0xba, 0xde, 0x89, 0xa2, 0xfb, 0x12, 0x82, 0x63, 0xa8, 0xf5, 0x02, 0x4a, 0xca, 0x84, 0x2e, 0x43,
|
||||
0xf5, 0xa7, 0x29, 0x61, 0xce, 0xd8, 0xa1, 0x81, 0xce, 0x36, 0x35, 0xa0, 0x2e, 0xac, 0x0d, 0x29,
|
||||
0x63, 0xb1, 0x76, 0x6b, 0xa9, 0x36, 0x65, 0x0c, 0x2b, 0x98, 0x65, 0xc3, 0xaa, 0x78, 0x45, 0xd7,
|
||||
0xa1, 0x19, 0x39, 0x2e, 0x0d, 0x23, 0xe2, 0xfa, 0xb6, 0xeb, 0x0c, 0x03, 0x1e, 0x4a, 0xf1, 0x22,
|
||||
0x6e, 0x24, 0xf6, 0x47, 0xd2, 0x8c, 0x36, 0x60, 0xed, 0x98, 0xb0, 0x29, 0x6d, 0xad, 0xc8, 0xe0,
|
||||
0xea, 0x05, 0x6d, 0x42, 0x89, 0x91, 0x43, 0xca, 0xc2, 0x56, 0x71, 0xbb, 0xd8, 0xa9, 0x62, 0xfd,
|
||||
0x66, 0xfd, 0x69, 0x40, 0x05, 0xf3, 0x19, 0x26, 0xde, 0x84, 0xa2, 0x1d, 0x68, 0x86, 0x11, 0x09,
|
||||
0x22, 0xfb, 0x88, 0xce, 0xed, 0x21, 0xe3, 0x21, 0x1d, 0xa9, 0x25, 0x0c, 0x0a, 0xb8, 0x2e, 0x3d,
|
||||
0x0f, 0xe8, 0x7c, 0x5f, 0xda, 0xd1, 0x35, 0xa8, 0xa7, 0x58, 0xee, 0x53, 0x4f, 0xc5, 0x1b, 0x14,
|
||||
0x70, 0x2d, 0x46, 0x3e, 0xf1, 0xa9, 0x87, 0x2c, 0xa8, 0x51, 0x6f, 0x94, 0xa2, 0x8a, 0x12, 0x65,
|
||||
0x60, 0xa0, 0xde, 0x28, 0xc6, 0x5c, 0x83, 0x7a, 0x8c, 0xd1, 0x51, 0x57, 0x35, 0xaa, 0xa6, 0x50,
|
||||
0x2a, 0x66, 0xdf, 0x84, 0x6a, 0x12, 0xb3, 0x5f, 0x85, 0xb2, 0x26, 0x59, 0x3f, 0x40, 0x09, 0xf3,
|
||||
0xd9, 0x73, 0x1a, 0xa1, 0x4b, 0x50, 0x09, 0xf8, 0x4c, 0x18, 0x45, 0x7d, 0x8a, 0x9d, 0x1a, 0x2e,
|
||||
0x07, 0x7c, 0xf6, 0x80, 0xce, 0x43, 0x74, 0x1b, 0x40, 0xb8, 0x02, 0xb1, 0xd2, 0xb8, 0xfe, 0x97,
|
||||
0x97, 0xd5, 0x3f, 0x2e, 0x07, 0xae, 0x06, 0xfa, 0x29, 0xb4, 0x7e, 0x5b, 0x01, 0x53, 0xef, 0xb9,
|
||||
0xac, 0xd4, 0x15, 0x30, 0x65, 0x3f, 0xcd, 0xed, 0x4c, 0x03, 0x81, 0x32, 0x3d, 0x16, 0x6d, 0x74,
|
||||
0x0b, 0x36, 0x55, 0xaa, 0xc9, 0xde, 0xc7, 0x4b, 0x8b, 0xcb, 0xb4, 0x21, 0xfd, 0xcf, 0x62, 0xb7,
|
||||
0x2e, 0x6b, 0x0f, 0x36, 0x16, 0x79, 0x99, 0xb2, 0x15, 0x30, 0xca, 0xb3, 0x64, 0xf9, 0x7a, 0xb0,
|
||||
0x21, 0x2a, 0xf1, 0x5a, 0xa4, 0xb8, 0x88, 0x88, 0x7a, 0xa3, 0xc5, 0x38, 0x5d, 0x40, 0x79, 0x8e,
|
||||
0x8c, 0xb2, 0xa6, 0x19, 0xcd, 0x2c, 0x43, 0xc4, 0xe8, 0x9f, 0x83, 0xc6, 0x42, 0x5e, 0xfd, 0x06,
|
||||
0xac, 0xe7, 0x24, 0xac, 0x57, 0x50, 0x3f, 0x88, 0x9b, 0x51, 0x95, 0xe9, 0x66, 0x5c, 0x85, 0x37,
|
||||
0x34, 0xaf, 0x5a, 0xeb, 0xc1, 0x42, 0x07, 0x7f, 0xac, 0xd6, 0xf3, 0x1a, 0x67, 0x45, 0x72, 0x44,
|
||||
0xde, 0x0b, 0x0c, 0xeb, 0x6f, 0x03, 0xe0, 0x85, 0xe8, 0x73, 0x15, 0xb6, 0x0b, 0xaa, 0x4c, 0xb6,
|
||||
0xec, 0xfd, 0xc5, 0x4e, 0x56, 0x3d, 0x2e, 0xe1, 0xba, 0x18, 0x49, 0xdf, 0x2b, 0x7c, 0xae, 0x9b,
|
||||
0xeb, 0x29, 0x5a, 0x16, 0x7b, 0x07, 0x44, 0x71, 0xf2, 0xca, 0x71, 0x4f, 0x8b, 0x2e, 0xce, 0xea,
|
||||
0xea, 0xbe, 0xce, 0xa8, 0x66, 0xfb, 0x3a, 0xd1, 0xec, 0xaf, 0x83, 0x99, 0x89, 0x2f, 0xda, 0x3c,
|
||||
0xa1, 0x59, 0xff, 0x9a, 0x50, 0xc5, 0x7c, 0x76, 0xcf, 0x61, 0x11, 0x0d, 0xd0, 0x6d, 0x58, 0x1b,
|
||||
0xfe, 0x48, 0x1c, 0x4f, 0x2e, 0xc6, 0xec, 0x5d, 0x7d, 0x43, 0xff, 0x2a, 0x74, 0x77, 0x5f, 0x40,
|
||||
0x07, 0x05, 0xac, 0x38, 0xe8, 0x5b, 0x00, 0xc7, 0x8b, 0x68, 0xc0, 0x28, 0x39, 0x56, 0xe3, 0xc1,
|
||||
0xec, 0x75, 0x4e, 0x56, 0xf8, 0x26, 0xc1, 0x0f, 0x0a, 0x38, 0xc3, 0x46, 0xf7, 0xa1, 0x3a, 0xe4,
|
||||
0xde, 0xc8, 0x89, 0x1c, 0xae, 0x9a, 0xd3, 0xec, 0xbd, 0x7f, 0x4a, 0x32, 0x31, 0x7c, 0x50, 0xc0,
|
||||
0x29, 0x17, 0x6d, 0xc0, 0x6a, 0xe8, 0x78, 0x47, 0xad, 0xe6, 0xb6, 0xd1, 0xa9, 0x0c, 0x0a, 0x58,
|
||||
0xbe, 0xa1, 0x0e, 0x34, 0x7c, 0x12, 0x86, 0x36, 0x61, 0xcc, 0x1e, 0x4b, 0x7e, 0xeb, 0x9c, 0x06,
|
||||
0xac, 0x0b, 0xc7, 0x1e, 0x63, 0xba, 0x22, 0x3b, 0xd0, 0x3c, 0x64, 0x7c, 0x78, 0x94, 0x85, 0x22,
|
||||
0x0d, 0xad, 0x4b, 0x4f, 0x8a, 0xfd, 0x04, 0x36, 0xf4, 0x74, 0xb0, 0x03, 0x3a, 0xa1, 0xaf, 0x62,
|
||||
0xfc, 0xaa, 0xde, 0xeb, 0x73, 0x6a, 0x56, 0x60, 0xe1, 0xd3, 0x94, 0x0f, 0x41, 0x18, 0xed, 0x90,
|
||||
0xb8, 0x3e, 0xa3, 0x31, 0xbe, 0xbe, 0x6d, 0x74, 0x8c, 0x41, 0x01, 0x37, 0x02, 0x3e, 0x7b, 0x2e,
|
||||
0x3d, 0x1a, 0xfd, 0x39, 0xb4, 0x32, 0x63, 0x21, 0x1f, 0x44, 0x9c, 0xad, 0xea, 0xa0, 0x80, 0x2f,
|
||||
0xa4, 0x53, 0x22, 0x1b, 0x68, 0x1f, 0xb6, 0xd4, 0xc7, 0x24, 0x73, 0x26, 0x73, 0xfc, 0x92, 0x4e,
|
||||
0xb2, 0xad, 0x60, 0xc9, 0xf1, 0xcc, 0x8a, 0x3c, 0x83, 0xf3, 0x5a, 0x44, 0x8e, 0xb9, 0x98, 0x5a,
|
||||
0x96, 0xfb, 0x73, 0xe5, 0x84, 0x0f, 0x99, 0x40, 0x8b, 0x02, 0x0c, 0xd3, 0x57, 0x2d, 0xf9, 0x12,
|
||||
0x36, 0xd3, 0x83, 0x98, 0x53, 0xad, 0x48, 0x55, 0x6b, 0x99, 0x6a, 0x7e, 0x0c, 0x88, 0x61, 0x17,
|
||||
0xe5, 0x2c, 0x5a, 0xbb, 0x0b, 0x48, 0x9d, 0x8d, 0xdc, 0x42, 0xab, 0xf1, 0x39, 0x95, 0xbe, 0xec,
|
||||
0xf2, 0x1e, 0x27, 0xf8, 0x6c, 0x1e, 0x0d, 0x99, 0xc7, 0xbb, 0xcb, 0xf2, 0x48, 0x67, 0x42, 0xaa,
|
||||
0x97, 0x89, 0xff, 0x15, 0xbc, 0x23, 0x3f, 0xb3, 0xb6, 0x2f, 0x8a, 0xcd, 0x67, 0x36, 0x1f, 0x8f,
|
||||
0x43, 0x1a, 0xc5, 0xc2, 0xb0, 0x6d, 0x74, 0xd6, 0x06, 0x05, 0x7c, 0x51, 0x82, 0x9e, 0xd2, 0x00,
|
||||
0xf3, 0xd9, 0x13, 0x89, 0xd0, 0xfc, 0x2f, 0xa1, 0x9d, 0xe7, 0x33, 0xc7, 0x75, 0x12, 0xba, 0xa9,
|
||||
0xe9, 0x9b, 0x19, 0xfa, 0x43, 0x01, 0xd0, 0xec, 0x3e, 0x6c, 0xa5, 0x6c, 0xbd, 0x6d, 0x39, 0x81,
|
||||
0x9a, 0x16, 0xb8, 0x14, 0x0b, 0xa8, 0xcd, 0xca, 0x6a, 0x7c, 0x06, 0x17, 0xc3, 0x28, 0x70, 0x7c,
|
||||
0x3d, 0x63, 0xa2, 0x80, 0x78, 0xe1, 0x98, 0x07, 0x2e, 0x0d, 0x5a, 0xeb, 0xfa, 0x10, 0x5c, 0x90,
|
||||
0x00, 0x59, 0x89, 0x83, 0xd4, 0x2d, 0x98, 0xc4, 0xf7, 0xd9, 0xdc, 0x96, 0x17, 0x81, 0x1c, 0xf3,
|
||||
0x7c, 0xdc, 0xa9, 0x12, 0xf0, 0x50, 0xf8, 0x33, 0xcc, 0xf6, 0x1d, 0x58, 0x93, 0x83, 0x05, 0x7d,
|
||||
0x0a, 0x65, 0x95, 0xa9, 0xfa, 0xd6, 0x9a, 0xbd, 0xad, 0x13, 0x27, 0x00, 0x8e, 0xd1, 0xed, 0xaf,
|
||||
0x01, 0xd2, 0xc1, 0xf2, 0xff, 0x65, 0xfe, 0x32, 0xa0, 0x9a, 0x4c, 0x15, 0x34, 0x80, 0xa6, 0x1f,
|
||||
0xd0, 0x91, 0x33, 0x24, 0x51, 0xd2, 0x1a, 0x6a, 0x4a, 0x9e, 0xa2, 0xd7, 0x48, 0x68, 0x49, 0x5b,
|
||||
0x98, 0x51, 0x30, 0x4d, 0x44, 0x56, 0xce, 0x22, 0x02, 0x82, 0xa1, 0xf9, 0x77, 0xa0, 0x36, 0x26,
|
||||
0x2c, 0x4c, 0x04, 0x8a, 0x67, 0x11, 0x30, 0x25, 0x45, 0xbd, 0xf4, 0x2b, 0x50, 0x52, 0x5c, 0xeb,
|
||||
0x9f, 0x35, 0xa8, 0x3c, 0x9a, 0x46, 0x44, 0x2e, 0x71, 0x0f, 0x2a, 0xa2, 0x3d, 0x45, 0x3b, 0xe8,
|
||||
0xa5, 0xbd, 0xb7, 0x4c, 0x34, 0xc6, 0x77, 0x9f, 0xd3, 0x48, 0xdc, 0x1e, 0x07, 0x05, 0x5c, 0x0e,
|
||||
0xd5, 0x23, 0xfa, 0x1e, 0xd0, 0x88, 0x32, 0x2a, 0x4a, 0x14, 0x70, 0x57, 0xb7, 0x9d, 0x5e, 0xe2,
|
||||
0x47, 0x27, 0x8a, 0xdd, 0x95, 0xb4, 0x7b, 0x01, 0x77, 0x55, 0x1b, 0x8a, 0x13, 0x35, 0x5a, 0xb0,
|
||||
0x2d, 0xca, 0xab, 0x51, 0xa7, 0x0b, 0x70, 0x56, 0x79, 0x75, 0x39, 0xcf, 0xcb, 0xeb, 0x0b, 0xfb,
|
||||
0x01, 0x34, 0xb2, 0xf2, 0x01, 0x9f, 0xc9, 0xd9, 0x6d, 0xf6, 0x76, 0xce, 0xa8, 0x8d, 0xf9, 0x4c,
|
||||
0x7c, 0x42, 0x46, 0x59, 0x43, 0xfb, 0x17, 0x03, 0xca, 0xba, 0x54, 0xa7, 0x5f, 0xec, 0xae, 0x43,
|
||||
0x73, 0x71, 0x4e, 0xeb, 0x9b, 0x76, 0x63, 0x61, 0x30, 0x2f, 0xbd, 0xb4, 0x17, 0x4f, 0xb9, 0xb4,
|
||||
0xaf, 0x66, 0x2e, 0xed, 0xed, 0x5f, 0x0d, 0x68, 0x2e, 0x96, 0xfd, 0xad, 0x66, 0xb8, 0x07, 0x20,
|
||||
0x32, 0x51, 0xf3, 0x54, 0x6f, 0xd3, 0x19, 0x06, 0x3a, 0xae, 0x0a, 0x96, 0x7c, 0x6c, 0xdf, 0xc8,
|
||||
0xa6, 0xa8, 0xb7, 0xe9, 0xb4, 0x14, 0xdb, 0x0d, 0x58, 0xcf, 0xed, 0x49, 0x1f, 0xa0, 0xe2, 0xea,
|
||||
0xdd, 0xb2, 0xfe, 0x30, 0xe0, 0x3c, 0xa6, 0x64, 0xf4, 0x88, 0x8f, 0x9c, 0xf1, 0xfc, 0xbb, 0xc0,
|
||||
0x89, 0x28, 0x9e, 0x32, 0xfa, 0x56, 0x17, 0x7e, 0x15, 0x6a, 0xc4, 0xf7, 0x93, 0x5b, 0x56, 0x72,
|
||||
0xbd, 0x36, 0x95, 0x55, 0x4e, 0x4b, 0xf4, 0x01, 0x34, 0x1d, 0x6f, 0x18, 0x50, 0x97, 0x7a, 0x91,
|
||||
0x4d, 0x5c, 0x3e, 0xf5, 0x22, 0xb9, 0x3f, 0x45, 0xf1, 0xe9, 0x4f, 0x3c, 0x7b, 0xd2, 0xd1, 0x2f,
|
||||
0xc1, 0x6a, 0x30, 0x65, 0xb4, 0xff, 0x33, 0x6c, 0x0e, 0xb9, 0xbb, 0xa4, 0x86, 0xfd, 0xea, 0x5d,
|
||||
0x12, 0x91, 0xa7, 0xe2, 0x3f, 0xf7, 0xa9, 0xf1, 0xf2, 0x0b, 0x0d, 0x98, 0x70, 0x46, 0xbc, 0x49,
|
||||
0x97, 0x07, 0x93, 0xdd, 0x09, 0xf5, 0xe4, 0x5f, 0xf0, 0xae, 0x72, 0x11, 0xdf, 0x09, 0xb3, 0xff,
|
||||
0xc9, 0xb7, 0xe3, 0xe7, 0xdf, 0x57, 0x5a, 0xf7, 0x15, 0x79, 0x9f, 0xf1, 0xe9, 0xa8, 0xdb, 0x8f,
|
||||
0x63, 0xbc, 0xe8, 0x1d, 0x96, 0xa4, 0xc2, 0x8d, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x32, 0x40,
|
||||
0x30, 0x5e, 0x68, 0x0f, 0x00, 0x00,
|
||||
}
|
||||
|
|
525
vendor/google.golang.org/genproto/googleapis/cloud/language/v1beta2/language_service.pb.go
generated
vendored
525
vendor/google.golang.org/genproto/googleapis/cloud/language/v1beta2/language_service.pb.go
generated
vendored
|
@ -17,6 +17,7 @@ It has these top-level messages:
|
|||
DependencyEdge
|
||||
EntityMention
|
||||
TextSpan
|
||||
ClassificationCategory
|
||||
AnalyzeSentimentRequest
|
||||
AnalyzeSentimentResponse
|
||||
AnalyzeEntitySentimentRequest
|
||||
|
@ -25,6 +26,8 @@ It has these top-level messages:
|
|||
AnalyzeEntitiesResponse
|
||||
AnalyzeSyntaxRequest
|
||||
AnalyzeSyntaxResponse
|
||||
ClassifyTextRequest
|
||||
ClassifyTextResponse
|
||||
AnnotateTextRequest
|
||||
AnnotateTextResponse
|
||||
*/
|
||||
|
@ -1069,7 +1072,7 @@ type Document struct {
|
|||
// The language of the document (if not specified, the language is
|
||||
// automatically detected). Both ISO and BCP-47 language codes are
|
||||
// accepted.<br>
|
||||
// [Language Support](https://cloud.google.com/natural-language/docs/languages)
|
||||
// [Language Support](/natural-language/docs/languages)
|
||||
// lists currently supported languages for each API method.
|
||||
// If the language (either specified by the caller or automatically detected)
|
||||
// is not supported by the called API method, an `INVALID_ARGUMENT` error
|
||||
|
@ -1595,6 +1598,34 @@ func (m *TextSpan) GetBeginOffset() int32 {
|
|||
return 0
|
||||
}
|
||||
|
||||
// Represents a category returned from the text classifier.
|
||||
type ClassificationCategory struct {
|
||||
// The name of the category representing the document.
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
// The classifier's confidence of the category. Number represents how certain
|
||||
// the classifier is that this category represents the given text.
|
||||
Confidence float32 `protobuf:"fixed32,2,opt,name=confidence" json:"confidence,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ClassificationCategory) Reset() { *m = ClassificationCategory{} }
|
||||
func (m *ClassificationCategory) String() string { return proto.CompactTextString(m) }
|
||||
func (*ClassificationCategory) ProtoMessage() {}
|
||||
func (*ClassificationCategory) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
|
||||
|
||||
func (m *ClassificationCategory) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ClassificationCategory) GetConfidence() float32 {
|
||||
if m != nil {
|
||||
return m.Confidence
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// The sentiment analysis request message.
|
||||
type AnalyzeSentimentRequest struct {
|
||||
// Input document.
|
||||
|
@ -1607,7 +1638,7 @@ type AnalyzeSentimentRequest struct {
|
|||
func (m *AnalyzeSentimentRequest) Reset() { *m = AnalyzeSentimentRequest{} }
|
||||
func (m *AnalyzeSentimentRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnalyzeSentimentRequest) ProtoMessage() {}
|
||||
func (*AnalyzeSentimentRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
|
||||
func (*AnalyzeSentimentRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
|
||||
|
||||
func (m *AnalyzeSentimentRequest) GetDocument() *Document {
|
||||
if m != nil {
|
||||
|
@ -1638,7 +1669,7 @@ type AnalyzeSentimentResponse struct {
|
|||
func (m *AnalyzeSentimentResponse) Reset() { *m = AnalyzeSentimentResponse{} }
|
||||
func (m *AnalyzeSentimentResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnalyzeSentimentResponse) ProtoMessage() {}
|
||||
func (*AnalyzeSentimentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
|
||||
func (*AnalyzeSentimentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
|
||||
|
||||
func (m *AnalyzeSentimentResponse) GetDocumentSentiment() *Sentiment {
|
||||
if m != nil {
|
||||
|
@ -1672,7 +1703,7 @@ type AnalyzeEntitySentimentRequest struct {
|
|||
func (m *AnalyzeEntitySentimentRequest) Reset() { *m = AnalyzeEntitySentimentRequest{} }
|
||||
func (m *AnalyzeEntitySentimentRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnalyzeEntitySentimentRequest) ProtoMessage() {}
|
||||
func (*AnalyzeEntitySentimentRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
|
||||
func (*AnalyzeEntitySentimentRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} }
|
||||
|
||||
func (m *AnalyzeEntitySentimentRequest) GetDocument() *Document {
|
||||
if m != nil {
|
||||
|
@ -1701,7 +1732,7 @@ type AnalyzeEntitySentimentResponse struct {
|
|||
func (m *AnalyzeEntitySentimentResponse) Reset() { *m = AnalyzeEntitySentimentResponse{} }
|
||||
func (m *AnalyzeEntitySentimentResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnalyzeEntitySentimentResponse) ProtoMessage() {}
|
||||
func (*AnalyzeEntitySentimentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} }
|
||||
func (*AnalyzeEntitySentimentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} }
|
||||
|
||||
func (m *AnalyzeEntitySentimentResponse) GetEntities() []*Entity {
|
||||
if m != nil {
|
||||
|
@ -1728,7 +1759,7 @@ type AnalyzeEntitiesRequest struct {
|
|||
func (m *AnalyzeEntitiesRequest) Reset() { *m = AnalyzeEntitiesRequest{} }
|
||||
func (m *AnalyzeEntitiesRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnalyzeEntitiesRequest) ProtoMessage() {}
|
||||
func (*AnalyzeEntitiesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} }
|
||||
func (*AnalyzeEntitiesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
|
||||
|
||||
func (m *AnalyzeEntitiesRequest) GetDocument() *Document {
|
||||
if m != nil {
|
||||
|
@ -1757,7 +1788,7 @@ type AnalyzeEntitiesResponse struct {
|
|||
func (m *AnalyzeEntitiesResponse) Reset() { *m = AnalyzeEntitiesResponse{} }
|
||||
func (m *AnalyzeEntitiesResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnalyzeEntitiesResponse) ProtoMessage() {}
|
||||
func (*AnalyzeEntitiesResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
|
||||
func (*AnalyzeEntitiesResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
|
||||
|
||||
func (m *AnalyzeEntitiesResponse) GetEntities() []*Entity {
|
||||
if m != nil {
|
||||
|
@ -1784,7 +1815,7 @@ type AnalyzeSyntaxRequest struct {
|
|||
func (m *AnalyzeSyntaxRequest) Reset() { *m = AnalyzeSyntaxRequest{} }
|
||||
func (m *AnalyzeSyntaxRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnalyzeSyntaxRequest) ProtoMessage() {}
|
||||
func (*AnalyzeSyntaxRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
|
||||
func (*AnalyzeSyntaxRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} }
|
||||
|
||||
func (m *AnalyzeSyntaxRequest) GetDocument() *Document {
|
||||
if m != nil {
|
||||
|
@ -1815,7 +1846,7 @@ type AnalyzeSyntaxResponse struct {
|
|||
func (m *AnalyzeSyntaxResponse) Reset() { *m = AnalyzeSyntaxResponse{} }
|
||||
func (m *AnalyzeSyntaxResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnalyzeSyntaxResponse) ProtoMessage() {}
|
||||
func (*AnalyzeSyntaxResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} }
|
||||
func (*AnalyzeSyntaxResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} }
|
||||
|
||||
func (m *AnalyzeSyntaxResponse) GetSentences() []*Sentence {
|
||||
if m != nil {
|
||||
|
@ -1838,6 +1869,42 @@ func (m *AnalyzeSyntaxResponse) GetLanguage() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// The document classification request message.
|
||||
type ClassifyTextRequest struct {
|
||||
// Input document.
|
||||
Document *Document `protobuf:"bytes,1,opt,name=document" json:"document,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ClassifyTextRequest) Reset() { *m = ClassifyTextRequest{} }
|
||||
func (m *ClassifyTextRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*ClassifyTextRequest) ProtoMessage() {}
|
||||
func (*ClassifyTextRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} }
|
||||
|
||||
func (m *ClassifyTextRequest) GetDocument() *Document {
|
||||
if m != nil {
|
||||
return m.Document
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The document classification response message.
|
||||
type ClassifyTextResponse struct {
|
||||
// Categories representing the input document.
|
||||
Categories []*ClassificationCategory `protobuf:"bytes,1,rep,name=categories" json:"categories,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ClassifyTextResponse) Reset() { *m = ClassifyTextResponse{} }
|
||||
func (m *ClassifyTextResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*ClassifyTextResponse) ProtoMessage() {}
|
||||
func (*ClassifyTextResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} }
|
||||
|
||||
func (m *ClassifyTextResponse) GetCategories() []*ClassificationCategory {
|
||||
if m != nil {
|
||||
return m.Categories
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The request message for the text annotation API, which can perform multiple
|
||||
// analysis types (sentiment, entities, and syntax) in one call.
|
||||
type AnnotateTextRequest struct {
|
||||
|
@ -1852,7 +1919,7 @@ type AnnotateTextRequest struct {
|
|||
func (m *AnnotateTextRequest) Reset() { *m = AnnotateTextRequest{} }
|
||||
func (m *AnnotateTextRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnnotateTextRequest) ProtoMessage() {}
|
||||
func (*AnnotateTextRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} }
|
||||
func (*AnnotateTextRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} }
|
||||
|
||||
func (m *AnnotateTextRequest) GetDocument() *Document {
|
||||
if m != nil {
|
||||
|
@ -1886,13 +1953,15 @@ type AnnotateTextRequest_Features struct {
|
|||
ExtractDocumentSentiment bool `protobuf:"varint,3,opt,name=extract_document_sentiment,json=extractDocumentSentiment" json:"extract_document_sentiment,omitempty"`
|
||||
// Extract entities and their associated sentiment.
|
||||
ExtractEntitySentiment bool `protobuf:"varint,4,opt,name=extract_entity_sentiment,json=extractEntitySentiment" json:"extract_entity_sentiment,omitempty"`
|
||||
// Classify the full document into categories.
|
||||
ClassifyText bool `protobuf:"varint,6,opt,name=classify_text,json=classifyText" json:"classify_text,omitempty"`
|
||||
}
|
||||
|
||||
func (m *AnnotateTextRequest_Features) Reset() { *m = AnnotateTextRequest_Features{} }
|
||||
func (m *AnnotateTextRequest_Features) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnnotateTextRequest_Features) ProtoMessage() {}
|
||||
func (*AnnotateTextRequest_Features) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor0, []int{17, 0}
|
||||
return fileDescriptor0, []int{20, 0}
|
||||
}
|
||||
|
||||
func (m *AnnotateTextRequest_Features) GetExtractSyntax() bool {
|
||||
|
@ -1923,6 +1992,13 @@ func (m *AnnotateTextRequest_Features) GetExtractEntitySentiment() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (m *AnnotateTextRequest_Features) GetClassifyText() bool {
|
||||
if m != nil {
|
||||
return m.ClassifyText
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// The text annotations response message.
|
||||
type AnnotateTextResponse struct {
|
||||
// Sentences in the input document. Populated if the user enables
|
||||
|
@ -1943,12 +2019,14 @@ type AnnotateTextResponse struct {
|
|||
// in the request or, if not specified, the automatically-detected language.
|
||||
// See [Document.language][google.cloud.language.v1beta2.Document.language] field for more details.
|
||||
Language string `protobuf:"bytes,5,opt,name=language" json:"language,omitempty"`
|
||||
// Categories identified in the input document.
|
||||
Categories []*ClassificationCategory `protobuf:"bytes,6,rep,name=categories" json:"categories,omitempty"`
|
||||
}
|
||||
|
||||
func (m *AnnotateTextResponse) Reset() { *m = AnnotateTextResponse{} }
|
||||
func (m *AnnotateTextResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*AnnotateTextResponse) ProtoMessage() {}
|
||||
func (*AnnotateTextResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} }
|
||||
func (*AnnotateTextResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} }
|
||||
|
||||
func (m *AnnotateTextResponse) GetSentences() []*Sentence {
|
||||
if m != nil {
|
||||
|
@ -1985,6 +2063,13 @@ func (m *AnnotateTextResponse) GetLanguage() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *AnnotateTextResponse) GetCategories() []*ClassificationCategory {
|
||||
if m != nil {
|
||||
return m.Categories
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Document)(nil), "google.cloud.language.v1beta2.Document")
|
||||
proto.RegisterType((*Sentence)(nil), "google.cloud.language.v1beta2.Sentence")
|
||||
|
@ -1995,6 +2080,7 @@ func init() {
|
|||
proto.RegisterType((*DependencyEdge)(nil), "google.cloud.language.v1beta2.DependencyEdge")
|
||||
proto.RegisterType((*EntityMention)(nil), "google.cloud.language.v1beta2.EntityMention")
|
||||
proto.RegisterType((*TextSpan)(nil), "google.cloud.language.v1beta2.TextSpan")
|
||||
proto.RegisterType((*ClassificationCategory)(nil), "google.cloud.language.v1beta2.ClassificationCategory")
|
||||
proto.RegisterType((*AnalyzeSentimentRequest)(nil), "google.cloud.language.v1beta2.AnalyzeSentimentRequest")
|
||||
proto.RegisterType((*AnalyzeSentimentResponse)(nil), "google.cloud.language.v1beta2.AnalyzeSentimentResponse")
|
||||
proto.RegisterType((*AnalyzeEntitySentimentRequest)(nil), "google.cloud.language.v1beta2.AnalyzeEntitySentimentRequest")
|
||||
|
@ -2003,6 +2089,8 @@ func init() {
|
|||
proto.RegisterType((*AnalyzeEntitiesResponse)(nil), "google.cloud.language.v1beta2.AnalyzeEntitiesResponse")
|
||||
proto.RegisterType((*AnalyzeSyntaxRequest)(nil), "google.cloud.language.v1beta2.AnalyzeSyntaxRequest")
|
||||
proto.RegisterType((*AnalyzeSyntaxResponse)(nil), "google.cloud.language.v1beta2.AnalyzeSyntaxResponse")
|
||||
proto.RegisterType((*ClassifyTextRequest)(nil), "google.cloud.language.v1beta2.ClassifyTextRequest")
|
||||
proto.RegisterType((*ClassifyTextResponse)(nil), "google.cloud.language.v1beta2.ClassifyTextResponse")
|
||||
proto.RegisterType((*AnnotateTextRequest)(nil), "google.cloud.language.v1beta2.AnnotateTextRequest")
|
||||
proto.RegisterType((*AnnotateTextRequest_Features)(nil), "google.cloud.language.v1beta2.AnnotateTextRequest.Features")
|
||||
proto.RegisterType((*AnnotateTextResponse)(nil), "google.cloud.language.v1beta2.AnnotateTextResponse")
|
||||
|
@ -2049,8 +2137,10 @@ type LanguageServiceClient interface {
|
|||
// tokenization along with part of speech tags, dependency trees, and other
|
||||
// properties.
|
||||
AnalyzeSyntax(ctx context.Context, in *AnalyzeSyntaxRequest, opts ...grpc.CallOption) (*AnalyzeSyntaxResponse, error)
|
||||
// A convenience method that provides all syntax, sentiment, and entity
|
||||
// features in one call.
|
||||
// Classifies a document into categories.
|
||||
ClassifyText(ctx context.Context, in *ClassifyTextRequest, opts ...grpc.CallOption) (*ClassifyTextResponse, error)
|
||||
// A convenience method that provides all syntax, sentiment, entity, and
|
||||
// classification features in one call.
|
||||
AnnotateText(ctx context.Context, in *AnnotateTextRequest, opts ...grpc.CallOption) (*AnnotateTextResponse, error)
|
||||
}
|
||||
|
||||
|
@ -2098,6 +2188,15 @@ func (c *languageServiceClient) AnalyzeSyntax(ctx context.Context, in *AnalyzeSy
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *languageServiceClient) ClassifyText(ctx context.Context, in *ClassifyTextRequest, opts ...grpc.CallOption) (*ClassifyTextResponse, error) {
|
||||
out := new(ClassifyTextResponse)
|
||||
err := grpc.Invoke(ctx, "/google.cloud.language.v1beta2.LanguageService/ClassifyText", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *languageServiceClient) AnnotateText(ctx context.Context, in *AnnotateTextRequest, opts ...grpc.CallOption) (*AnnotateTextResponse, error) {
|
||||
out := new(AnnotateTextResponse)
|
||||
err := grpc.Invoke(ctx, "/google.cloud.language.v1beta2.LanguageService/AnnotateText", in, out, c.cc, opts...)
|
||||
|
@ -2123,8 +2222,10 @@ type LanguageServiceServer interface {
|
|||
// tokenization along with part of speech tags, dependency trees, and other
|
||||
// properties.
|
||||
AnalyzeSyntax(context.Context, *AnalyzeSyntaxRequest) (*AnalyzeSyntaxResponse, error)
|
||||
// A convenience method that provides all syntax, sentiment, and entity
|
||||
// features in one call.
|
||||
// Classifies a document into categories.
|
||||
ClassifyText(context.Context, *ClassifyTextRequest) (*ClassifyTextResponse, error)
|
||||
// A convenience method that provides all syntax, sentiment, entity, and
|
||||
// classification features in one call.
|
||||
AnnotateText(context.Context, *AnnotateTextRequest) (*AnnotateTextResponse, error)
|
||||
}
|
||||
|
||||
|
@ -2204,6 +2305,24 @@ func _LanguageService_AnalyzeSyntax_Handler(srv interface{}, ctx context.Context
|
|||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LanguageService_ClassifyText_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ClassifyTextRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LanguageServiceServer).ClassifyText(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/google.cloud.language.v1beta2.LanguageService/ClassifyText",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LanguageServiceServer).ClassifyText(ctx, req.(*ClassifyTextRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LanguageService_AnnotateText_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AnnotateTextRequest)
|
||||
if err := dec(in); err != nil {
|
||||
|
@ -2242,6 +2361,10 @@ var _LanguageService_serviceDesc = grpc.ServiceDesc{
|
|||
MethodName: "AnalyzeSyntax",
|
||||
Handler: _LanguageService_AnalyzeSyntax_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ClassifyText",
|
||||
Handler: _LanguageService_ClassifyText_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AnnotateText",
|
||||
Handler: _LanguageService_AnnotateText_Handler,
|
||||
|
@ -2256,185 +2379,193 @@ func init() {
|
|||
}
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 2873 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x5a, 0x4d, 0x73, 0xdb, 0xc6,
|
||||
0xf9, 0x37, 0xf8, 0x26, 0x72, 0x29, 0xc9, 0x6b, 0xc4, 0x89, 0xf9, 0xd7, 0x3f, 0x2f, 0x0e, 0x12,
|
||||
0xd7, 0x8a, 0x9d, 0x50, 0xb1, 0xec, 0x38, 0xae, 0xed, 0xbc, 0x40, 0xc0, 0x92, 0x82, 0x4c, 0x02,
|
||||
0xc8, 0x02, 0xa0, 0xe5, 0x5c, 0x38, 0x30, 0xb9, 0x62, 0x38, 0x91, 0x00, 0x96, 0x80, 0x3c, 0x56,
|
||||
0x2f, 0x99, 0xc9, 0x4c, 0x8f, 0x99, 0x1e, 0x72, 0xe8, 0x07, 0xe8, 0xa1, 0xa7, 0x4e, 0x3a, 0xd3,
|
||||
0x99, 0x4e, 0xfb, 0x19, 0x7a, 0x4c, 0xa7, 0xa7, 0x1e, 0x7b, 0xec, 0xa1, 0x87, 0x1e, 0x7a, 0xec,
|
||||
0x3c, 0xbb, 0x0b, 0xbe, 0xc8, 0x8e, 0x25, 0x3a, 0x99, 0x4e, 0x7a, 0xdb, 0x7d, 0xf0, 0xfc, 0x9e,
|
||||
0x7d, 0xde, 0x9f, 0x05, 0x48, 0x74, 0x63, 0x10, 0xc7, 0x83, 0x7d, 0xb6, 0xd1, 0xdb, 0x8f, 0x0f,
|
||||
0xfb, 0x1b, 0xfb, 0x61, 0x34, 0x38, 0x0c, 0x07, 0x6c, 0xe3, 0xd1, 0xb5, 0x87, 0x2c, 0x0d, 0x37,
|
||||
0x27, 0x84, 0x6e, 0xc2, 0xc6, 0x8f, 0x86, 0x3d, 0x56, 0x1f, 0x8d, 0xe3, 0x34, 0x56, 0x5f, 0x11,
|
||||
0xa8, 0x3a, 0x47, 0xd5, 0x33, 0xa6, 0xba, 0x44, 0xad, 0xbd, 0x2c, 0x85, 0x86, 0xa3, 0xe1, 0x46,
|
||||
0x18, 0x45, 0x71, 0x1a, 0xa6, 0xc3, 0x38, 0x4a, 0x04, 0x78, 0xed, 0x0d, 0xf9, 0x74, 0x3f, 0x8e,
|
||||
0x06, 0xe3, 0xc3, 0x28, 0x1a, 0x46, 0x83, 0x8d, 0x78, 0xc4, 0xc6, 0x73, 0x4c, 0xaf, 0x49, 0x26,
|
||||
0xbe, 0x7b, 0x78, 0xb8, 0xb7, 0x91, 0x0e, 0x0f, 0x58, 0x92, 0x86, 0x07, 0x23, 0xc9, 0x70, 0x41,
|
||||
0x32, 0x8c, 0x47, 0xbd, 0x8d, 0x24, 0x0d, 0xd3, 0x43, 0x89, 0xd4, 0xfe, 0xa9, 0xa0, 0xb2, 0x19,
|
||||
0xf7, 0x0e, 0x0f, 0x58, 0x94, 0xaa, 0x1f, 0xa3, 0x42, 0x7a, 0x34, 0x62, 0x35, 0xe5, 0xa2, 0xb2,
|
||||
0xbe, 0xba, 0xf9, 0x76, 0xfd, 0x99, 0x7a, 0xd7, 0x33, 0x58, 0xdd, 0x3f, 0x1a, 0x31, 0xca, 0x91,
|
||||
0xea, 0x1a, 0x5a, 0xea, 0xc5, 0x51, 0xca, 0xa2, 0xb4, 0x96, 0xbb, 0xa8, 0xac, 0x57, 0xb6, 0xcf,
|
||||
0xd0, 0x8c, 0xa0, 0xae, 0xa3, 0xb3, 0x83, 0x5e, 0xd2, 0x95, 0xdb, 0xee, 0xe1, 0x78, 0x58, 0xcb,
|
||||
0x4b, 0x9e, 0x95, 0x41, 0x2f, 0x31, 0x04, 0x3d, 0x18, 0x0f, 0xd5, 0x35, 0x54, 0xce, 0x4e, 0xab,
|
||||
0x15, 0x80, 0x85, 0x4e, 0xf6, 0xda, 0x4d, 0x54, 0x80, 0xf3, 0xd4, 0xf3, 0x08, 0xfb, 0x0f, 0x5c,
|
||||
0xd2, 0x0d, 0x6c, 0xcf, 0x25, 0x86, 0xd5, 0xb0, 0x88, 0x89, 0xcf, 0xa8, 0xab, 0x08, 0xb9, 0x2d,
|
||||
0xdd, 0xb2, 0xbb, 0x3e, 0xd9, 0xf5, 0xb1, 0xa2, 0x96, 0x51, 0x61, 0xdb, 0x6f, 0xb7, 0x70, 0x6e,
|
||||
0xab, 0x8c, 0x4a, 0x49, 0x7c, 0x38, 0xee, 0x31, 0xed, 0x97, 0x0a, 0x2a, 0x7b, 0x0c, 0x0e, 0xeb,
|
||||
0x31, 0xf5, 0x0e, 0x2a, 0xa4, 0xec, 0x71, 0xca, 0x4d, 0xae, 0x6e, 0x5e, 0x3e, 0xc1, 0x64, 0x9f,
|
||||
0x3d, 0x4e, 0xbd, 0x51, 0x18, 0x51, 0x0e, 0x52, 0x1b, 0xa8, 0x92, 0xb0, 0x08, 0x7c, 0x2d, 0xed,
|
||||
0xad, 0x6e, 0xae, 0x9f, 0x20, 0xc1, 0xcb, 0xf8, 0xe9, 0x14, 0xaa, 0x7d, 0x5d, 0x40, 0x25, 0x12,
|
||||
0xa5, 0xc3, 0xf4, 0x48, 0x55, 0x51, 0x21, 0x0a, 0x0f, 0x44, 0x08, 0x2a, 0x94, 0xaf, 0xd5, 0x0f,
|
||||
0x65, 0x58, 0x72, 0x3c, 0x2c, 0x57, 0x4e, 0x38, 0x41, 0x08, 0x9a, 0x0d, 0x8a, 0x83, 0xca, 0x07,
|
||||
0x2c, 0x0d, 0xfb, 0x61, 0x1a, 0xd6, 0xf2, 0x17, 0xf3, 0xeb, 0xd5, 0xcd, 0xeb, 0xa7, 0x93, 0xd1,
|
||||
0x96, 0x28, 0x12, 0xa5, 0xe3, 0x23, 0x3a, 0x11, 0x02, 0xf1, 0x49, 0xc2, 0xfd, 0x21, 0x38, 0x90,
|
||||
0xc7, 0x27, 0x47, 0x27, 0x7b, 0x75, 0x1b, 0x0e, 0x8b, 0x78, 0x72, 0xd6, 0x8a, 0xfc, 0xb0, 0xb7,
|
||||
0x4f, 0x75, 0x58, 0x5b, 0x80, 0xe8, 0x04, 0x3d, 0xef, 0xdd, 0xd2, 0x73, 0x7b, 0x77, 0xed, 0x0e,
|
||||
0x5a, 0x99, 0x33, 0x44, 0xc5, 0x28, 0xff, 0x39, 0x3b, 0x92, 0x2e, 0x86, 0xa5, 0x7a, 0x1e, 0x15,
|
||||
0x1f, 0x85, 0xfb, 0x87, 0xc2, 0xc5, 0x15, 0x2a, 0x36, 0xb7, 0x73, 0xb7, 0x14, 0xed, 0x48, 0xa6,
|
||||
0x5b, 0x15, 0x2d, 0x05, 0xf6, 0x3d, 0xdb, 0xb9, 0x6f, 0xe3, 0x33, 0x2a, 0x42, 0x25, 0x97, 0x50,
|
||||
0xcf, 0xb1, 0xb1, 0xa2, 0x2e, 0xa3, 0x72, 0xcb, 0x31, 0x74, 0xdf, 0x72, 0x6c, 0x9c, 0x53, 0x31,
|
||||
0x5a, 0x76, 0x68, 0x53, 0xb7, 0xad, 0x4f, 0x05, 0x25, 0xaf, 0x56, 0x50, 0x91, 0x74, 0x88, 0xed,
|
||||
0xe3, 0x82, 0x7a, 0x16, 0x55, 0xef, 0x3b, 0xf4, 0x5e, 0xd7, 0x69, 0x74, 0x75, 0xea, 0xe3, 0xa2,
|
||||
0x7a, 0x0e, 0xad, 0x18, 0x8e, 0xed, 0x05, 0x6d, 0x42, 0xbb, 0x4d, 0xc7, 0x31, 0x71, 0x09, 0xd8,
|
||||
0x1d, 0x7f, 0x9b, 0x50, 0xbc, 0xa4, 0xfd, 0x22, 0x87, 0x8a, 0x7e, 0xfc, 0x39, 0x8b, 0xbe, 0x5f,
|
||||
0x92, 0x7e, 0x82, 0x56, 0x47, 0xe1, 0x38, 0xed, 0xc6, 0x7b, 0xdd, 0x64, 0xc4, 0x58, 0xef, 0x33,
|
||||
0x99, 0xa9, 0x57, 0x4f, 0x10, 0xe3, 0x86, 0xe3, 0xd4, 0xd9, 0xf3, 0x38, 0x84, 0x2e, 0x8f, 0x66,
|
||||
0x76, 0x6a, 0x07, 0x9d, 0xed, 0xb3, 0x11, 0x8b, 0xfa, 0x2c, 0xea, 0x1d, 0x75, 0x59, 0x7f, 0xc0,
|
||||
0x78, 0x25, 0x57, 0x37, 0xdf, 0x39, 0xa9, 0x65, 0x4c, 0x50, 0xa4, 0x3f, 0x60, 0x74, 0xb5, 0x3f,
|
||||
0xb7, 0x87, 0x30, 0xec, 0xb3, 0x83, 0x83, 0x50, 0x16, 0xbd, 0xd8, 0x68, 0x1f, 0xa1, 0xca, 0x24,
|
||||
0xae, 0xea, 0xcb, 0xa8, 0x72, 0x10, 0x0e, 0xa2, 0x61, 0x7a, 0xd8, 0x17, 0xd1, 0xca, 0xd1, 0x29,
|
||||
0x01, 0x04, 0x24, 0xbd, 0x78, 0x2c, 0xd4, 0xc9, 0x51, 0xb1, 0xd1, 0xfe, 0x74, 0x0e, 0x2d, 0xcf,
|
||||
0x5a, 0xa3, 0xea, 0x28, 0x9f, 0x86, 0x03, 0xd9, 0xe6, 0x36, 0x16, 0xf0, 0x43, 0xdd, 0x0f, 0x07,
|
||||
0x14, 0xb0, 0xea, 0x0e, 0x2a, 0x85, 0xc9, 0x88, 0xf5, 0x52, 0x59, 0x95, 0x9b, 0x8b, 0x48, 0xd1,
|
||||
0x39, 0x92, 0x4a, 0x09, 0xaa, 0x89, 0x0a, 0xbd, 0x30, 0x11, 0x4a, 0xaf, 0x6e, 0xbe, 0xbb, 0x88,
|
||||
0x24, 0x23, 0x4c, 0x18, 0xe5, 0x68, 0x90, 0xb2, 0x17, 0x8f, 0x0f, 0xb8, 0xef, 0x16, 0x94, 0xd2,
|
||||
0x88, 0xc7, 0x07, 0x94, 0xa3, 0xc1, 0xae, 0x01, 0x84, 0x64, 0x5c, 0x2b, 0x2e, 0x6e, 0x57, 0x93,
|
||||
0x23, 0xa9, 0x94, 0x00, 0x1a, 0x1d, 0xc4, 0x71, 0x9f, 0xd7, 0xee, 0x82, 0x1a, 0xb5, 0xe3, 0xb8,
|
||||
0x4f, 0x39, 0x1a, 0x34, 0x8a, 0x0e, 0x0f, 0x1e, 0xb2, 0x71, 0x6d, 0x69, 0x71, 0x8d, 0x6c, 0x8e,
|
||||
0xa4, 0x52, 0x02, 0xc8, 0x1a, 0xb1, 0x71, 0x12, 0x47, 0xb5, 0xf2, 0xe2, 0xb2, 0x5c, 0x8e, 0xa4,
|
||||
0x52, 0x02, 0x97, 0x35, 0x86, 0x49, 0x5c, 0xab, 0x3c, 0x87, 0x2c, 0x8e, 0xa4, 0x52, 0x82, 0xfa,
|
||||
0x00, 0x55, 0xc7, 0xac, 0x37, 0x1c, 0x8d, 0xe3, 0xde, 0x30, 0x3d, 0xaa, 0x21, 0x2e, 0xf0, 0xfd,
|
||||
0x45, 0x04, 0xd2, 0x29, 0x9c, 0xce, 0xca, 0x52, 0x9b, 0xa8, 0x98, 0xb2, 0x28, 0x61, 0xb5, 0x2a,
|
||||
0x17, 0x7a, 0x6d, 0xa1, 0x6c, 0x07, 0x20, 0x15, 0x78, 0x10, 0xf4, 0x28, 0x1e, 0xf6, 0x58, 0x6d,
|
||||
0x79, 0x71, 0x41, 0x1d, 0x00, 0x52, 0x81, 0xd7, 0xbe, 0x52, 0x50, 0xde, 0x0f, 0x07, 0xf3, 0x2d,
|
||||
0x75, 0x09, 0xe5, 0x75, 0x73, 0x07, 0x2b, 0x62, 0xe1, 0xe2, 0x9c, 0x58, 0x74, 0x70, 0x1e, 0x66,
|
||||
0xb8, 0xe1, 0xd8, 0x3b, 0xb8, 0x00, 0x24, 0x93, 0x40, 0xe3, 0x2c, 0xa3, 0x82, 0xed, 0x04, 0x36,
|
||||
0x2e, 0x01, 0xc9, 0x0e, 0xda, 0x78, 0x09, 0x48, 0x2e, 0x75, 0x6c, 0x5c, 0x06, 0x92, 0x4b, 0x7d,
|
||||
0x5c, 0x81, 0x5e, 0xea, 0x06, 0xb6, 0xe1, 0x63, 0x04, 0x4f, 0x3b, 0x84, 0x6e, 0xe1, 0xaa, 0x5a,
|
||||
0x44, 0xca, 0x2e, 0x5e, 0x86, 0x67, 0x7a, 0xa3, 0x61, 0xed, 0xe2, 0x15, 0xcd, 0x41, 0x25, 0x51,
|
||||
0x90, 0xaa, 0x8a, 0x56, 0x75, 0xb8, 0x4d, 0xf8, 0xdd, 0xa9, 0x62, 0x70, 0xa3, 0x20, 0xb4, 0x41,
|
||||
0x0c, 0xdf, 0xea, 0x10, 0xac, 0x40, 0x87, 0xb7, 0xda, 0x33, 0x94, 0x1c, 0xb4, 0x75, 0x97, 0x3a,
|
||||
0x4d, 0x4a, 0x3c, 0x0f, 0x08, 0x79, 0xed, 0xdf, 0x0a, 0x2a, 0x40, 0x61, 0x02, 0xaf, 0xa1, 0x7b,
|
||||
0x64, 0x5e, 0x9a, 0x6e, 0x18, 0x81, 0xa7, 0x4b, 0x69, 0x2b, 0xa8, 0xa2, 0x9b, 0xa0, 0x99, 0xa5,
|
||||
0xb7, 0x70, 0x4e, 0x0c, 0x84, 0xb6, 0xdb, 0x22, 0x6d, 0x62, 0x73, 0x8e, 0x3c, 0xcc, 0x1a, 0x53,
|
||||
0x70, 0x17, 0x60, 0xd6, 0x34, 0x89, 0x6d, 0xf1, 0x5d, 0x91, 0x6b, 0x62, 0x7b, 0x3e, 0x0d, 0x80,
|
||||
0x59, 0x6f, 0xe1, 0xd2, 0x74, 0x16, 0x75, 0x08, 0x5e, 0x82, 0xb3, 0x6c, 0xa7, 0x6d, 0xd9, 0x62,
|
||||
0x5f, 0x06, 0x7f, 0x3b, 0x5b, 0x2d, 0xeb, 0x93, 0x80, 0xe0, 0x0a, 0x1c, 0xec, 0xea, 0xd4, 0x17,
|
||||
0xb2, 0x10, 0x1c, 0xec, 0x52, 0xe2, 0x3a, 0x9e, 0x05, 0x63, 0x4b, 0x6f, 0xe1, 0x2a, 0x38, 0x83,
|
||||
0x92, 0x46, 0x8b, 0xec, 0x5a, 0x1d, 0xd2, 0x05, 0x33, 0xf0, 0x32, 0xb0, 0x51, 0xd2, 0xe2, 0x02,
|
||||
0x05, 0x69, 0x05, 0xce, 0xec, 0x64, 0x67, 0xae, 0x6a, 0xdf, 0x28, 0xa8, 0x00, 0xdd, 0x04, 0x94,
|
||||
0x6b, 0x38, 0xb4, 0x3d, 0x63, 0xfa, 0x32, 0x2a, 0xeb, 0x26, 0x28, 0xa4, 0xb7, 0xa4, 0xe1, 0xc1,
|
||||
0xae, 0xd5, 0xb2, 0x74, 0xfa, 0x00, 0xe7, 0xe0, 0xb0, 0x19, 0xc3, 0x3f, 0x25, 0x14, 0xe7, 0xb9,
|
||||
0x08, 0xcb, 0xd6, 0x5b, 0x5d, 0x62, 0x9b, 0x96, 0xdd, 0xc4, 0x05, 0xf0, 0x45, 0x93, 0xd0, 0xc0,
|
||||
0x36, 0x71, 0x11, 0xd6, 0x94, 0xe8, 0x2d, 0xcb, 0x13, 0x76, 0x5b, 0x54, 0xee, 0x96, 0x20, 0xb4,
|
||||
0xde, 0xb6, 0x43, 0x7d, 0x5c, 0x86, 0xb0, 0xb7, 0x1c, 0xbb, 0x29, 0x72, 0xc1, 0xa1, 0x26, 0xa1,
|
||||
0x18, 0x01, 0xb7, 0xbc, 0x32, 0x1a, 0xb8, 0xaa, 0x11, 0x54, 0x12, 0x6d, 0x0b, 0x74, 0x68, 0x12,
|
||||
0xdb, 0x24, 0x74, 0x5e, 0xe9, 0x06, 0x69, 0x5b, 0xb6, 0x65, 0xcb, 0x68, 0xb5, 0x75, 0xcf, 0x08,
|
||||
0x5a, 0xb0, 0xcd, 0x81, 0x0a, 0x36, 0x09, 0x7c, 0x50, 0x56, 0xfb, 0x02, 0x15, 0xa0, 0x67, 0x81,
|
||||
0xd2, 0x6d, 0xc7, 0x31, 0x67, 0x44, 0x9c, 0x47, 0xd8, 0x70, 0x6c, 0x53, 0x3a, 0xb6, 0x0b, 0x4f,
|
||||
0xb1, 0x02, 0xc1, 0xe1, 0x69, 0xa4, 0xcb, 0x24, 0x82, 0xbd, 0x6d, 0x5a, 0xd2, 0x91, 0x79, 0xf0,
|
||||
0xb4, 0x65, 0xfb, 0x84, 0x52, 0xa7, 0x99, 0x45, 0xbf, 0x8a, 0x96, 0x76, 0x02, 0x91, 0x63, 0x45,
|
||||
0x48, 0x3a, 0x2f, 0xd8, 0xda, 0x81, 0xf4, 0x06, 0x42, 0x49, 0xfb, 0x18, 0x95, 0x44, 0xb3, 0x03,
|
||||
0x3b, 0xec, 0xa0, 0xbd, 0x75, 0xdc, 0x0e, 0xcf, 0xb2, 0x9b, 0x41, 0x4b, 0xa7, 0x58, 0xe1, 0xf7,
|
||||
0x97, 0x56, 0x40, 0x79, 0xca, 0x95, 0x51, 0xc1, 0x0c, 0xf4, 0x16, 0xce, 0x6b, 0x3e, 0x2a, 0x89,
|
||||
0x16, 0x07, 0x12, 0xc4, 0xfd, 0x66, 0x46, 0x42, 0x05, 0x15, 0x1b, 0x16, 0xf5, 0x7c, 0x01, 0xf7,
|
||||
0x08, 0xd8, 0x84, 0x73, 0x40, 0xf6, 0xb7, 0x2d, 0x6a, 0xe2, 0x3c, 0x18, 0x3a, 0x4d, 0x18, 0x79,
|
||||
0x3f, 0x2a, 0x68, 0xb7, 0x50, 0x49, 0x34, 0x3b, 0x2e, 0x95, 0x3a, 0xee, 0x9c, 0x5e, 0xa0, 0x09,
|
||||
0xa7, 0x09, 0x97, 0xd8, 0x8e, 0xdf, 0x95, 0xfb, 0x9c, 0xb6, 0x83, 0xaa, 0x33, 0x5d, 0x4d, 0xbd,
|
||||
0x80, 0x5e, 0xa0, 0xc4, 0xb0, 0x5c, 0xea, 0x18, 0x96, 0xff, 0x60, 0xbe, 0xa6, 0xb2, 0x07, 0x3c,
|
||||
0xb5, 0xc0, 0x7e, 0xc7, 0xee, 0xce, 0xd0, 0x72, 0x5a, 0x82, 0x8a, 0xbc, 0x99, 0x81, 0x5f, 0x7d,
|
||||
0x62, 0xcf, 0xd5, 0xe4, 0x8b, 0xe8, 0xdc, 0x6c, 0x80, 0xf8, 0x63, 0x61, 0x65, 0x23, 0xf0, 0x03,
|
||||
0x4a, 0x84, 0x93, 0x5c, 0xdd, 0xf3, 0x71, 0x1e, 0x82, 0xe0, 0x52, 0xe2, 0x89, 0x0b, 0xdd, 0x0a,
|
||||
0xaa, 0x4c, 0x7a, 0x01, 0x2e, 0x8a, 0x97, 0x8f, 0x20, 0xdb, 0x97, 0xb4, 0x2d, 0x54, 0xe4, 0x8d,
|
||||
0x0f, 0x0e, 0xed, 0x38, 0x96, 0x41, 0xe6, 0x0d, 0xd7, 0x8d, 0x69, 0x13, 0x30, 0xf4, 0xac, 0x27,
|
||||
0xe4, 0xf8, 0x11, 0x7a, 0xd6, 0x4b, 0xfe, 0xb5, 0x84, 0x56, 0xe7, 0x6f, 0x4d, 0xea, 0x3a, 0xc2,
|
||||
0x9f, 0xb1, 0xb0, 0xdf, 0x4d, 0xe1, 0x6e, 0xd8, 0x1d, 0x46, 0x7d, 0xf6, 0x98, 0x5f, 0x65, 0x8a,
|
||||
0x74, 0x15, 0xe8, 0xfc, 0xca, 0x68, 0x01, 0x55, 0xb5, 0x50, 0x71, 0x3f, 0x7c, 0xc8, 0xf6, 0xe5,
|
||||
0x1d, 0xe5, 0xfa, 0x42, 0xb7, 0xb3, 0x7a, 0x0b, 0xa0, 0x54, 0x48, 0xd0, 0xfe, 0x51, 0x42, 0x45,
|
||||
0x4e, 0x78, 0xe2, 0x26, 0xac, 0x6f, 0x6d, 0x51, 0xd2, 0xc1, 0x0a, 0x6f, 0xa9, 0x50, 0xc4, 0x22,
|
||||
0x2b, 0x74, 0xb3, 0x63, 0xb4, 0x44, 0xff, 0xd2, 0xcd, 0x4e, 0xdb, 0x31, 0x71, 0x01, 0xdc, 0xa8,
|
||||
0xc3, 0xaa, 0xc8, 0x19, 0x5c, 0xd7, 0x81, 0xe2, 0x05, 0xa2, 0xef, 0x53, 0xbc, 0xc4, 0x3b, 0x7e,
|
||||
0xb0, 0x2b, 0x3a, 0x95, 0x1e, 0xec, 0x82, 0x13, 0x70, 0x45, 0x2d, 0xa1, 0x9c, 0x61, 0x60, 0x04,
|
||||
0x10, 0x83, 0x8b, 0xaf, 0x4e, 0x26, 0x02, 0x6f, 0xe3, 0x06, 0xd4, 0x01, 0x5e, 0xe1, 0x5e, 0x84,
|
||||
0x25, 0x87, 0xad, 0x8a, 0x59, 0xe1, 0xe2, 0xb3, 0xd9, 0xd0, 0xc0, 0xc0, 0x60, 0x5a, 0x9e, 0xe1,
|
||||
0x04, 0xd4, 0x23, 0xf8, 0x1c, 0x4f, 0x7c, 0x67, 0x6b, 0x07, 0xab, 0xb0, 0x22, 0xbb, 0x6e, 0x0b,
|
||||
0xbf, 0xc0, 0x1b, 0xac, 0x43, 0xbc, 0xfb, 0x96, 0xbf, 0x8d, 0xcf, 0x03, 0xdd, 0x02, 0x8e, 0x17,
|
||||
0x61, 0xd5, 0xd6, 0xe9, 0x3d, 0xfc, 0x12, 0x48, 0x6b, 0xdf, 0x27, 0xf8, 0x82, 0x58, 0x74, 0x70,
|
||||
0x8d, 0x4f, 0x20, 0xd2, 0xc4, 0xff, 0x07, 0x8a, 0xda, 0x36, 0x5e, 0x03, 0x21, 0xb6, 0x2b, 0x6d,
|
||||
0xfe, 0x7f, 0xd0, 0xd0, 0xe6, 0x1a, 0xbe, 0x0c, 0x0a, 0xd8, 0x13, 0x0d, 0x5f, 0xc9, 0x46, 0xd7,
|
||||
0xab, 0xbc, 0x8f, 0xf0, 0x82, 0xc5, 0xaf, 0xc1, 0x78, 0x72, 0xf1, 0x45, 0xd9, 0x9e, 0x75, 0x5f,
|
||||
0xdf, 0xb5, 0x3c, 0xfc, 0xba, 0x48, 0x09, 0xea, 0x83, 0x44, 0x8d, 0x8f, 0x35, 0xee, 0x88, 0x37,
|
||||
0x78, 0x5e, 0x82, 0x86, 0x6f, 0x8a, 0x95, 0xe7, 0xe1, 0x4b, 0x9c, 0xd7, 0xf1, 0x7c, 0xd0, 0xe9,
|
||||
0x27, 0x32, 0x5d, 0x39, 0xf7, 0xe5, 0xc9, 0xc6, 0xde, 0xc1, 0xeb, 0xa2, 0xf2, 0x08, 0x78, 0xe6,
|
||||
0x2d, 0x31, 0x3b, 0x49, 0x03, 0x5f, 0x91, 0x2b, 0x17, 0x5f, 0xe5, 0xa7, 0x50, 0xc7, 0x6e, 0xe1,
|
||||
0xb7, 0xb3, 0x81, 0xfa, 0x0e, 0x58, 0xe8, 0x7a, 0xb8, 0x0e, 0x16, 0x7e, 0x12, 0xe8, 0x36, 0xd7,
|
||||
0x67, 0x03, 0x38, 0xa9, 0x01, 0xcb, 0x77, 0xe1, 0x01, 0x5f, 0x52, 0xd2, 0xc2, 0xd7, 0xf8, 0x03,
|
||||
0x93, 0x3a, 0x2e, 0xde, 0x04, 0x11, 0x70, 0xc0, 0x75, 0xd0, 0x81, 0x92, 0xb6, 0xad, 0xdb, 0x3e,
|
||||
0xbe, 0x21, 0x2a, 0x17, 0xec, 0xb4, 0xcd, 0xa0, 0x8d, 0xdf, 0x83, 0xd3, 0xa9, 0xe3, 0xf8, 0xf8,
|
||||
0x26, 0xac, 0x3c, 0x70, 0xce, 0xfb, 0x7c, 0x15, 0x34, 0x1a, 0xf8, 0x16, 0xac, 0xf8, 0x89, 0x3f,
|
||||
0xe5, 0x4d, 0xc7, 0x71, 0x2d, 0x03, 0xdf, 0xe6, 0x83, 0x1d, 0x88, 0x77, 0xe6, 0x06, 0xd1, 0x5d,
|
||||
0x60, 0xd9, 0xe5, 0x66, 0x7f, 0xc0, 0xdb, 0x55, 0xc0, 0x67, 0xfd, 0x87, 0x1c, 0x69, 0xf9, 0x2d,
|
||||
0x82, 0x3f, 0x12, 0xf3, 0xa8, 0xe3, 0x6e, 0x03, 0xfa, 0x63, 0x99, 0x72, 0x50, 0x86, 0x58, 0xe7,
|
||||
0xd9, 0x19, 0xec, 0x76, 0x3a, 0x78, 0x0b, 0x96, 0x26, 0x3f, 0xd5, 0x00, 0x96, 0x86, 0x43, 0x89,
|
||||
0xd5, 0xb4, 0xb1, 0x09, 0xae, 0xb8, 0x77, 0x1f, 0x13, 0x3e, 0x61, 0x2c, 0xcf, 0xc7, 0x0d, 0x71,
|
||||
0x27, 0x69, 0x1b, 0xb8, 0xc9, 0x13, 0xc0, 0x69, 0x8b, 0xbc, 0xdc, 0x86, 0x89, 0x90, 0xed, 0x78,
|
||||
0xe0, 0x2d, 0xce, 0x19, 0xb4, 0x0d, 0xbc, 0x03, 0x6e, 0x31, 0x1c, 0x17, 0xdf, 0x03, 0x4f, 0x98,
|
||||
0x96, 0xc7, 0x87, 0x37, 0x31, 0x71, 0x4b, 0xfb, 0x2a, 0x87, 0x56, 0xe6, 0xde, 0x8b, 0xbf, 0xdf,
|
||||
0x3b, 0x20, 0x99, 0xfb, 0x82, 0x70, 0x6d, 0x91, 0x17, 0xf2, 0xd9, 0x0f, 0x09, 0x73, 0x6f, 0xe4,
|
||||
0xf9, 0xe7, 0xff, 0xde, 0xf1, 0xae, 0x7c, 0xa9, 0xc6, 0x68, 0x59, 0x7e, 0xc3, 0x79, 0xda, 0x3c,
|
||||
0x40, 0xa8, 0x64, 0x38, 0xed, 0x36, 0xbc, 0x57, 0x6b, 0x4d, 0x54, 0xce, 0x4c, 0x52, 0x6b, 0xd3,
|
||||
0x6f, 0x4c, 0xe2, 0x15, 0x7e, 0xf2, 0x85, 0xe9, 0x75, 0xb4, 0xfc, 0x90, 0x0d, 0x86, 0x51, 0x37,
|
||||
0xde, 0xdb, 0x4b, 0x98, 0x78, 0x35, 0x2b, 0xd2, 0x2a, 0xa7, 0x39, 0x9c, 0xa4, 0xfd, 0x4e, 0x41,
|
||||
0x17, 0xf4, 0x28, 0xdc, 0x3f, 0xfa, 0x39, 0x9b, 0xaa, 0xc6, 0x7e, 0x76, 0xc8, 0x92, 0x54, 0x35,
|
||||
0x50, 0xb9, 0x2f, 0xbf, 0x69, 0x9d, 0xd2, 0xcd, 0xd9, 0x27, 0x30, 0x3a, 0x01, 0xaa, 0x2e, 0x5a,
|
||||
0x61, 0x51, 0x2f, 0xee, 0x0f, 0xa3, 0x41, 0x77, 0xc6, 0xe7, 0x57, 0x4f, 0xf4, 0xb9, 0xc0, 0x70,
|
||||
0x6f, 0x2f, 0xb3, 0x99, 0x9d, 0xf6, 0x57, 0x05, 0xd5, 0x9e, 0x54, 0x39, 0x19, 0xc5, 0x30, 0xcf,
|
||||
0xee, 0x23, 0x35, 0x3b, 0xba, 0x3b, 0x8d, 0x8d, 0xb2, 0x60, 0x6c, 0xce, 0x65, 0x32, 0xa6, 0x2f,
|
||||
0xda, 0xb3, 0xdf, 0xe0, 0x72, 0xf3, 0xdf, 0xe0, 0x54, 0x22, 0xf2, 0x80, 0x45, 0x3d, 0x96, 0xc8,
|
||||
0x2f, 0x4a, 0x97, 0x4f, 0x71, 0x16, 0xf0, 0xd3, 0x29, 0x52, 0xfb, 0x83, 0x82, 0x5e, 0x91, 0x86,
|
||||
0x89, 0x94, 0xfb, 0x5f, 0x89, 0xc8, 0x17, 0xe8, 0xd5, 0xef, 0xd2, 0x5b, 0x86, 0x45, 0x47, 0x65,
|
||||
0xa0, 0xa5, 0x43, 0x96, 0xd4, 0x14, 0xee, 0xa0, 0x4b, 0xa7, 0x2a, 0x3a, 0x3a, 0x81, 0x3d, 0x2b,
|
||||
0x00, 0x70, 0xcd, 0x7e, 0x69, 0x56, 0x83, 0x21, 0x4b, 0x7e, 0xe4, 0x2e, 0x7b, 0x3c, 0x29, 0xbb,
|
||||
0xa9, 0xc2, 0xff, 0x1d, 0x5f, 0xfd, 0x56, 0x41, 0xe7, 0xb3, 0xf2, 0x39, 0x8a, 0xd2, 0xf0, 0xf1,
|
||||
0x8f, 0xdc, 0x53, 0x7f, 0x54, 0xd0, 0x8b, 0xc7, 0xf4, 0x95, 0x8e, 0x9a, 0x2b, 0x3b, 0xe5, 0x79,
|
||||
0xcb, 0x4e, 0xbd, 0x8b, 0x4a, 0xfc, 0xea, 0x98, 0xd4, 0x72, 0x5c, 0xc6, 0x9b, 0x27, 0xcd, 0x12,
|
||||
0x60, 0xa6, 0x12, 0x33, 0xe7, 0xea, 0xfc, 0x31, 0x57, 0xff, 0x2d, 0x8f, 0x5e, 0xd0, 0xc5, 0x2f,
|
||||
0x18, 0x0c, 0xda, 0xf5, 0x0f, 0xea, 0xe9, 0xfb, 0xa8, 0xbc, 0xc7, 0xc2, 0xf4, 0x70, 0xcc, 0x12,
|
||||
0xf9, 0x05, 0xf3, 0xce, 0x09, 0x42, 0x9e, 0xa2, 0x4a, 0xbd, 0x21, 0x45, 0xd0, 0x89, 0xb0, 0x27,
|
||||
0x43, 0x98, 0xff, 0x9e, 0x21, 0x5c, 0xfb, 0x8b, 0x82, 0xca, 0xd9, 0x41, 0xea, 0x25, 0xb4, 0xca,
|
||||
0x1e, 0xa7, 0xe3, 0xb0, 0x97, 0x76, 0x13, 0x1e, 0x4f, 0xee, 0x82, 0x32, 0x5d, 0x91, 0x54, 0x11,
|
||||
0x64, 0xf5, 0x2d, 0x84, 0x33, 0xb6, 0x49, 0x35, 0xe4, 0x38, 0xe3, 0x59, 0x49, 0xcf, 0x0a, 0x47,
|
||||
0xbd, 0x8b, 0xd6, 0x32, 0xd6, 0xa7, 0xf4, 0xfe, 0x3c, 0x07, 0xd5, 0x24, 0x87, 0xf9, 0x44, 0x63,
|
||||
0xbf, 0x85, 0x6a, 0x73, 0x07, 0x1d, 0xcd, 0x60, 0x0b, 0x1c, 0xfb, 0xd2, 0xec, 0x81, 0xd3, 0xe6,
|
||||
0xa6, 0x7d, 0x9b, 0x83, 0x4a, 0x9a, 0xf5, 0xe9, 0x8f, 0x29, 0x31, 0x67, 0xdb, 0x48, 0xfe, 0xf9,
|
||||
0xda, 0xc8, 0xd3, 0x87, 0x69, 0xe1, 0x87, 0x1d, 0xa6, 0xc5, 0xf9, 0xa2, 0xb9, 0x72, 0x0b, 0x2d,
|
||||
0xcf, 0xa6, 0x92, 0xb8, 0x47, 0xda, 0x04, 0x9f, 0x81, 0x55, 0xe0, 0x37, 0x6e, 0x89, 0x57, 0xab,
|
||||
0xc0, 0x6f, 0x5c, 0xbb, 0x29, 0x5e, 0xad, 0x02, 0xbf, 0x71, 0x7d, 0x13, 0xe7, 0x37, 0x7f, 0xb5,
|
||||
0x84, 0xce, 0xb6, 0xa4, 0x18, 0x4f, 0xfc, 0xe2, 0xa8, 0xfe, 0x5e, 0x41, 0xf8, 0xf8, 0x65, 0x41,
|
||||
0xbd, 0x79, 0x62, 0xa1, 0x3c, 0xf5, 0x42, 0xb4, 0xf6, 0xfe, 0xc2, 0x38, 0x91, 0x10, 0x5a, 0xfd,
|
||||
0xcb, 0x6f, 0xff, 0xfe, 0x75, 0x6e, 0x5d, 0x7b, 0x63, 0xf2, 0xd3, 0x68, 0xe6, 0x93, 0xe4, 0x76,
|
||||
0x78, 0x0c, 0x74, 0x5b, 0xb9, 0xa2, 0x7e, 0xa3, 0xa0, 0xb3, 0xc7, 0xc6, 0x83, 0xfa, 0xde, 0xe9,
|
||||
0x0e, 0x3f, 0x36, 0xff, 0xd6, 0x6e, 0x2e, 0x0a, 0x93, 0x2a, 0xbf, 0xc3, 0x55, 0xbe, 0xac, 0x69,
|
||||
0xdf, 0xad, 0x72, 0x86, 0x01, 0x8d, 0xff, 0x7c, 0x6c, 0x02, 0x4f, 0xcb, 0x44, 0xbd, 0xbb, 0x80,
|
||||
0x06, 0x4f, 0x5c, 0x79, 0xd6, 0x3e, 0x78, 0x4e, 0xb4, 0x34, 0xe3, 0x06, 0x37, 0xa3, 0xae, 0xbd,
|
||||
0x75, 0x82, 0x19, 0x47, 0x73, 0xfe, 0xff, 0x8d, 0x82, 0x56, 0xe6, 0x66, 0x8e, 0x7a, 0xfd, 0x94,
|
||||
0xa1, 0x9f, 0x9d, 0xa8, 0x6b, 0x37, 0x16, 0x03, 0x49, 0x95, 0xaf, 0x72, 0x95, 0x2f, 0x69, 0x17,
|
||||
0x9f, 0x91, 0x2c, 0x1c, 0x01, 0x9a, 0xfe, 0x5a, 0x41, 0xcb, 0xb3, 0x3d, 0x48, 0xdd, 0x5c, 0x7c,
|
||||
0x08, 0xac, 0x5d, 0x5f, 0x08, 0x23, 0xd5, 0xbc, 0xc2, 0xd5, 0x7c, 0x53, 0x7b, 0xed, 0xa9, 0x6a,
|
||||
0x4e, 0x01, 0xb7, 0x95, 0x2b, 0x5b, 0x5f, 0x2a, 0xe8, 0xf5, 0x5e, 0x7c, 0xf0, 0xec, 0x63, 0xb6,
|
||||
0xce, 0x1f, 0x2b, 0x5e, 0x77, 0x1c, 0xa7, 0xb1, 0xab, 0x7c, 0x4a, 0x24, 0x6c, 0x10, 0x03, 0xa4,
|
||||
0x1e, 0x8f, 0x07, 0x1b, 0x03, 0x16, 0xf1, 0xdf, 0xeb, 0x37, 0xc4, 0xa3, 0x70, 0x34, 0x4c, 0xbe,
|
||||
0xe3, 0x4f, 0x08, 0x77, 0x32, 0xc2, 0xc3, 0x12, 0x47, 0x5c, 0xff, 0x4f, 0x00, 0x00, 0x00, 0xff,
|
||||
0xff, 0x55, 0xc3, 0xe3, 0x00, 0xb5, 0x20, 0x00, 0x00,
|
||||
// 2996 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x5a, 0xcd, 0x6f, 0xdc, 0xd6,
|
||||
0xb5, 0x37, 0xe7, 0x4b, 0xa3, 0x3b, 0x92, 0x7c, 0x4d, 0x3b, 0xf6, 0x3c, 0xbd, 0x7c, 0x38, 0x74,
|
||||
0xfc, 0xac, 0xd8, 0x89, 0x14, 0x4b, 0x8e, 0xe3, 0x67, 0x3b, 0x1f, 0x14, 0x79, 0x67, 0x44, 0x99,
|
||||
0x43, 0x32, 0x97, 0xe4, 0x58, 0xf6, 0x66, 0x40, 0xcf, 0x50, 0x93, 0x41, 0x24, 0x72, 0xde, 0x90,
|
||||
0x32, 0xac, 0xb7, 0x09, 0x1a, 0xa0, 0xcb, 0xa0, 0x8b, 0xfc, 0x09, 0x5d, 0x14, 0x28, 0x50, 0xa4,
|
||||
0x40, 0x81, 0xa2, 0x5d, 0xf4, 0x2f, 0xe8, 0xb2, 0x40, 0xff, 0x82, 0x2e, 0xbb, 0xe8, 0xa2, 0x8b,
|
||||
0x76, 0x57, 0x9c, 0x7b, 0x2f, 0x67, 0x38, 0xb2, 0x62, 0x69, 0x1c, 0xa3, 0x48, 0x77, 0xf7, 0x9e,
|
||||
0x39, 0xbf, 0x73, 0xcf, 0xd7, 0x3d, 0xe7, 0xf0, 0x4a, 0xe8, 0x56, 0x3f, 0x8e, 0xfb, 0x7b, 0xe1,
|
||||
0x5a, 0x77, 0x2f, 0x3e, 0xe8, 0xad, 0xed, 0x05, 0x51, 0xff, 0x20, 0xe8, 0x87, 0x6b, 0x4f, 0x6f,
|
||||
0x3e, 0x09, 0xd3, 0x60, 0x7d, 0x4c, 0xe8, 0x24, 0xe1, 0xe8, 0xe9, 0xa0, 0x1b, 0xae, 0x0e, 0x47,
|
||||
0x71, 0x1a, 0xcb, 0x6f, 0x70, 0xd4, 0x2a, 0x43, 0xad, 0x66, 0x4c, 0xab, 0x02, 0xb5, 0xfc, 0xba,
|
||||
0x10, 0x1a, 0x0c, 0x07, 0x6b, 0x41, 0x14, 0xc5, 0x69, 0x90, 0x0e, 0xe2, 0x28, 0xe1, 0xe0, 0xe5,
|
||||
0x2b, 0xe2, 0xd7, 0xbd, 0x38, 0xea, 0x8f, 0x0e, 0xa2, 0x68, 0x10, 0xf5, 0xd7, 0xe2, 0x61, 0x38,
|
||||
0x9a, 0x62, 0x7a, 0x4b, 0x30, 0xb1, 0xdd, 0x93, 0x83, 0xdd, 0xb5, 0x74, 0xb0, 0x1f, 0x26, 0x69,
|
||||
0xb0, 0x3f, 0x14, 0x0c, 0x97, 0x04, 0xc3, 0x68, 0xd8, 0x5d, 0x4b, 0xd2, 0x20, 0x3d, 0x10, 0x48,
|
||||
0xe5, 0x6f, 0x12, 0xaa, 0xea, 0x71, 0xf7, 0x60, 0x3f, 0x8c, 0x52, 0xf9, 0x33, 0x54, 0x4a, 0x0f,
|
||||
0x87, 0x61, 0x5d, 0xba, 0x2c, 0xad, 0x2c, 0xad, 0xbf, 0xb7, 0xfa, 0x42, 0xbd, 0x57, 0x33, 0xd8,
|
||||
0xaa, 0x77, 0x38, 0x0c, 0x29, 0x43, 0xca, 0xcb, 0x68, 0xae, 0x1b, 0x47, 0x69, 0x18, 0xa5, 0xf5,
|
||||
0xc2, 0x65, 0x69, 0x65, 0x7e, 0xeb, 0x0c, 0xcd, 0x08, 0xf2, 0x0a, 0x3a, 0xdb, 0xef, 0x26, 0x1d,
|
||||
0xb1, 0xed, 0x1c, 0x8c, 0x06, 0xf5, 0xa2, 0xe0, 0x59, 0xec, 0x77, 0x13, 0x8d, 0xd3, 0xfd, 0xd1,
|
||||
0x40, 0x5e, 0x46, 0xd5, 0xec, 0xb4, 0x7a, 0x09, 0x58, 0xe8, 0x78, 0xaf, 0xdc, 0x46, 0x25, 0x38,
|
||||
0x4f, 0xbe, 0x80, 0xb0, 0xf7, 0xc8, 0x21, 0x1d, 0xdf, 0x72, 0x1d, 0xa2, 0x19, 0x0d, 0x83, 0xe8,
|
||||
0xf8, 0x8c, 0xbc, 0x84, 0x90, 0x63, 0xaa, 0x86, 0xd5, 0xf1, 0xc8, 0x8e, 0x87, 0x25, 0xb9, 0x8a,
|
||||
0x4a, 0x5b, 0x5e, 0xcb, 0xc4, 0x85, 0xcd, 0x2a, 0xaa, 0x24, 0xf1, 0xc1, 0xa8, 0x1b, 0x2a, 0x3f,
|
||||
0x93, 0x50, 0xd5, 0x0d, 0xe1, 0xb0, 0x6e, 0x28, 0xdf, 0x43, 0xa5, 0x34, 0x7c, 0x96, 0x32, 0x93,
|
||||
0x6b, 0xeb, 0xd7, 0x4e, 0x30, 0xd9, 0x0b, 0x9f, 0xa5, 0xee, 0x30, 0x88, 0x28, 0x03, 0xc9, 0x0d,
|
||||
0x34, 0x9f, 0x84, 0x11, 0xf8, 0x5a, 0xd8, 0x5b, 0x5b, 0x5f, 0x39, 0x41, 0x82, 0x9b, 0xf1, 0xd3,
|
||||
0x09, 0x54, 0xf9, 0xb6, 0x84, 0x2a, 0x24, 0x4a, 0x07, 0xe9, 0xa1, 0x2c, 0xa3, 0x52, 0x14, 0xec,
|
||||
0xf3, 0x10, 0xcc, 0x53, 0xb6, 0x96, 0x3f, 0x11, 0x61, 0x29, 0xb0, 0xb0, 0x5c, 0x3f, 0xe1, 0x04,
|
||||
0x2e, 0x28, 0x1f, 0x14, 0x1b, 0x55, 0xf7, 0xc3, 0x34, 0xe8, 0x05, 0x69, 0x50, 0x2f, 0x5e, 0x2e,
|
||||
0xae, 0xd4, 0xd6, 0x37, 0x4e, 0x27, 0xa3, 0x25, 0x50, 0x24, 0x4a, 0x47, 0x87, 0x74, 0x2c, 0x04,
|
||||
0xe2, 0x93, 0x04, 0x7b, 0x03, 0x70, 0x20, 0x8b, 0x4f, 0x81, 0x8e, 0xf7, 0xf2, 0x16, 0x1c, 0x16,
|
||||
0xb1, 0xe4, 0xac, 0x97, 0xd9, 0x61, 0xef, 0x9d, 0xea, 0xb0, 0x16, 0x07, 0xd1, 0x31, 0x7a, 0xda,
|
||||
0xbb, 0x95, 0x97, 0xf6, 0xee, 0xf2, 0x3d, 0xb4, 0x38, 0x65, 0x88, 0x8c, 0x51, 0xf1, 0xcb, 0xf0,
|
||||
0x50, 0xb8, 0x18, 0x96, 0xf2, 0x05, 0x54, 0x7e, 0x1a, 0xec, 0x1d, 0x70, 0x17, 0xcf, 0x53, 0xbe,
|
||||
0xb9, 0x5b, 0xb8, 0x23, 0x29, 0x87, 0x22, 0xdd, 0x6a, 0x68, 0xce, 0xb7, 0x1e, 0x58, 0xf6, 0x43,
|
||||
0x0b, 0x9f, 0x91, 0x11, 0xaa, 0x38, 0x84, 0xba, 0xb6, 0x85, 0x25, 0x79, 0x01, 0x55, 0x4d, 0x5b,
|
||||
0x53, 0x3d, 0xc3, 0xb6, 0x70, 0x41, 0xc6, 0x68, 0xc1, 0xa6, 0x4d, 0xd5, 0x32, 0x1e, 0x73, 0x4a,
|
||||
0x51, 0x9e, 0x47, 0x65, 0xd2, 0x26, 0x96, 0x87, 0x4b, 0xf2, 0x59, 0x54, 0x7b, 0x68, 0xd3, 0x07,
|
||||
0x1d, 0xbb, 0xd1, 0x51, 0xa9, 0x87, 0xcb, 0xf2, 0x39, 0xb4, 0xa8, 0xd9, 0x96, 0xeb, 0xb7, 0x08,
|
||||
0xed, 0x34, 0x6d, 0x5b, 0xc7, 0x15, 0x60, 0xb7, 0xbd, 0x2d, 0x42, 0xf1, 0x9c, 0xf2, 0xd3, 0x02,
|
||||
0x2a, 0x7b, 0xf1, 0x97, 0x61, 0xf4, 0xc3, 0x92, 0xf4, 0x73, 0xb4, 0x34, 0x0c, 0x46, 0x69, 0x27,
|
||||
0xde, 0xed, 0x24, 0xc3, 0x30, 0xec, 0x7e, 0x21, 0x32, 0xf5, 0xc6, 0x09, 0x62, 0x9c, 0x60, 0x94,
|
||||
0xda, 0xbb, 0x2e, 0x83, 0xd0, 0x85, 0x61, 0x6e, 0x27, 0xb7, 0xd1, 0xd9, 0x5e, 0x38, 0x0c, 0xa3,
|
||||
0x5e, 0x18, 0x75, 0x0f, 0x3b, 0x61, 0xaf, 0x1f, 0xb2, 0x9b, 0x5c, 0x5b, 0x7f, 0xff, 0xa4, 0x92,
|
||||
0x31, 0x46, 0x91, 0x5e, 0x3f, 0xa4, 0x4b, 0xbd, 0xa9, 0x3d, 0x84, 0x61, 0x2f, 0xdc, 0xdf, 0x0f,
|
||||
0xc4, 0xa5, 0xe7, 0x1b, 0xe5, 0x53, 0x34, 0x3f, 0x8e, 0xab, 0xfc, 0x3a, 0x9a, 0xdf, 0x0f, 0xfa,
|
||||
0xd1, 0x20, 0x3d, 0xe8, 0xf1, 0x68, 0x15, 0xe8, 0x84, 0x00, 0x02, 0x92, 0x6e, 0x3c, 0xe2, 0xea,
|
||||
0x14, 0x28, 0xdf, 0x28, 0xbf, 0x3f, 0x87, 0x16, 0xf2, 0xd6, 0xc8, 0x2a, 0x2a, 0xa6, 0x41, 0x5f,
|
||||
0x94, 0xb9, 0xb5, 0x19, 0xfc, 0xb0, 0xea, 0x05, 0x7d, 0x0a, 0x58, 0x79, 0x1b, 0x55, 0x82, 0x64,
|
||||
0x18, 0x76, 0x53, 0x71, 0x2b, 0xd7, 0x67, 0x91, 0xa2, 0x32, 0x24, 0x15, 0x12, 0x64, 0x1d, 0x95,
|
||||
0xba, 0x41, 0xc2, 0x95, 0x5e, 0x5a, 0xff, 0x60, 0x16, 0x49, 0x5a, 0x90, 0x84, 0x94, 0xa1, 0x41,
|
||||
0xca, 0x6e, 0x3c, 0xda, 0x67, 0xbe, 0x9b, 0x51, 0x4a, 0x23, 0x1e, 0xed, 0x53, 0x86, 0x06, 0xbb,
|
||||
0xfa, 0x10, 0x92, 0x51, 0xbd, 0x3c, 0xbb, 0x5d, 0x4d, 0x86, 0xa4, 0x42, 0x02, 0x68, 0xb4, 0x1f,
|
||||
0xc7, 0x3d, 0x76, 0x77, 0x67, 0xd4, 0xa8, 0x15, 0xc7, 0x3d, 0xca, 0xd0, 0xa0, 0x51, 0x74, 0xb0,
|
||||
0xff, 0x24, 0x1c, 0xd5, 0xe7, 0x66, 0xd7, 0xc8, 0x62, 0x48, 0x2a, 0x24, 0x80, 0xac, 0x61, 0x38,
|
||||
0x4a, 0xe2, 0xa8, 0x5e, 0x9d, 0x5d, 0x96, 0xc3, 0x90, 0x54, 0x48, 0x60, 0xb2, 0x46, 0xd0, 0x89,
|
||||
0xeb, 0xf3, 0x2f, 0x21, 0x8b, 0x21, 0xa9, 0x90, 0x20, 0x3f, 0x42, 0xb5, 0x51, 0xd8, 0x1d, 0x0c,
|
||||
0x47, 0x71, 0x77, 0x90, 0x1e, 0xd6, 0x11, 0x13, 0xf8, 0xd1, 0x2c, 0x02, 0xe9, 0x04, 0x4e, 0xf3,
|
||||
0xb2, 0xe4, 0x26, 0x2a, 0xa7, 0x61, 0x94, 0x84, 0xf5, 0x1a, 0x13, 0x7a, 0x73, 0xa6, 0x6c, 0x07,
|
||||
0x20, 0xe5, 0x78, 0x10, 0xf4, 0x34, 0x1e, 0x74, 0xc3, 0xfa, 0xc2, 0xec, 0x82, 0xda, 0x00, 0xa4,
|
||||
0x1c, 0xaf, 0x7c, 0x23, 0xa1, 0xa2, 0x17, 0xf4, 0xa7, 0x4b, 0xea, 0x1c, 0x2a, 0xaa, 0xfa, 0x36,
|
||||
0x96, 0xf8, 0xc2, 0xc1, 0x05, 0xbe, 0x68, 0xe3, 0x22, 0xf4, 0x70, 0xcd, 0xb6, 0xb6, 0x71, 0x09,
|
||||
0x48, 0x3a, 0x81, 0xc2, 0x59, 0x45, 0x25, 0xcb, 0xf6, 0x2d, 0x5c, 0x01, 0x92, 0xe5, 0xb7, 0xf0,
|
||||
0x1c, 0x90, 0x1c, 0x6a, 0x5b, 0xb8, 0x0a, 0x24, 0x87, 0x7a, 0x78, 0x1e, 0x6a, 0xa9, 0xe3, 0x5b,
|
||||
0x9a, 0x87, 0x11, 0xfc, 0xda, 0x26, 0x74, 0x13, 0xd7, 0xe4, 0x32, 0x92, 0x76, 0xf0, 0x02, 0xfc,
|
||||
0xa6, 0x36, 0x1a, 0xc6, 0x0e, 0x5e, 0x54, 0x6c, 0x54, 0xe1, 0x17, 0x52, 0x96, 0xd1, 0x92, 0x0a,
|
||||
0xd3, 0x84, 0xd7, 0x99, 0x28, 0x06, 0x13, 0x05, 0xa1, 0x0d, 0xa2, 0x79, 0x46, 0x9b, 0x60, 0x09,
|
||||
0x2a, 0xbc, 0xd1, 0xca, 0x51, 0x0a, 0x50, 0xd6, 0x1d, 0x6a, 0x37, 0x29, 0x71, 0x5d, 0x20, 0x14,
|
||||
0x95, 0x7f, 0x48, 0xa8, 0x04, 0x17, 0x13, 0x78, 0x35, 0xd5, 0x25, 0xd3, 0xd2, 0x54, 0x4d, 0xf3,
|
||||
0x5d, 0x55, 0x48, 0x5b, 0x44, 0xf3, 0xaa, 0x0e, 0x9a, 0x19, 0xaa, 0x89, 0x0b, 0xbc, 0x21, 0xb4,
|
||||
0x1c, 0x93, 0xb4, 0x88, 0xc5, 0x38, 0x8a, 0xd0, 0x6b, 0x74, 0xce, 0x5d, 0x82, 0x5e, 0xd3, 0x24,
|
||||
0x96, 0xc1, 0x76, 0x65, 0xa6, 0x89, 0xe5, 0x7a, 0xd4, 0x07, 0x66, 0xd5, 0xc4, 0x95, 0x49, 0x2f,
|
||||
0x6a, 0x13, 0x3c, 0x07, 0x67, 0x59, 0x76, 0xcb, 0xb0, 0xf8, 0xbe, 0x0a, 0xfe, 0xb6, 0x37, 0x4d,
|
||||
0xe3, 0x73, 0x9f, 0xe0, 0x79, 0x38, 0xd8, 0x51, 0xa9, 0xc7, 0x65, 0x21, 0x38, 0xd8, 0xa1, 0xc4,
|
||||
0xb1, 0x5d, 0x03, 0xda, 0x96, 0x6a, 0xe2, 0x1a, 0x38, 0x83, 0x92, 0x86, 0x49, 0x76, 0x8c, 0x36,
|
||||
0xe9, 0x80, 0x19, 0x78, 0x01, 0xd8, 0x28, 0x31, 0x99, 0x40, 0x4e, 0x5a, 0x84, 0x33, 0xdb, 0xd9,
|
||||
0x99, 0x4b, 0xca, 0x77, 0x12, 0x2a, 0x41, 0x35, 0x01, 0xe5, 0x1a, 0x36, 0x6d, 0xe5, 0x4c, 0x5f,
|
||||
0x40, 0x55, 0x55, 0x07, 0x85, 0x54, 0x53, 0x18, 0xee, 0xef, 0x18, 0xa6, 0xa1, 0xd2, 0x47, 0xb8,
|
||||
0x00, 0x87, 0xe5, 0x0c, 0x7f, 0x4c, 0x28, 0x2e, 0x32, 0x11, 0x86, 0xa5, 0x9a, 0x1d, 0x62, 0xe9,
|
||||
0x86, 0xd5, 0xc4, 0x25, 0xf0, 0x45, 0x93, 0x50, 0xdf, 0xd2, 0x71, 0x19, 0xd6, 0x94, 0xa8, 0xa6,
|
||||
0xe1, 0x72, 0xbb, 0x0d, 0x2a, 0x76, 0x73, 0x10, 0x5a, 0x77, 0xcb, 0xa6, 0x1e, 0xae, 0x42, 0xd8,
|
||||
0x4d, 0xdb, 0x6a, 0xf2, 0x5c, 0xb0, 0xa9, 0x4e, 0x28, 0x46, 0xc0, 0x2d, 0x46, 0x46, 0x0d, 0xd7,
|
||||
0x14, 0x82, 0x2a, 0xbc, 0x6c, 0x81, 0x0e, 0x4d, 0x62, 0xe9, 0x84, 0x4e, 0x2b, 0xdd, 0x20, 0x2d,
|
||||
0xc3, 0x32, 0x2c, 0x11, 0xad, 0x96, 0xea, 0x6a, 0xbe, 0x09, 0xdb, 0x02, 0xa8, 0x60, 0x11, 0xdf,
|
||||
0x03, 0x65, 0x95, 0xaf, 0x50, 0x09, 0x6a, 0x16, 0x28, 0xdd, 0xb2, 0x6d, 0x3d, 0x27, 0xe2, 0x02,
|
||||
0xc2, 0x9a, 0x6d, 0xe9, 0xc2, 0xb1, 0x1d, 0xf8, 0x15, 0x4b, 0x10, 0x1c, 0x96, 0x46, 0xaa, 0x48,
|
||||
0x22, 0xd8, 0x5b, 0xba, 0x21, 0x1c, 0x59, 0x04, 0x4f, 0x1b, 0x96, 0x47, 0x28, 0xb5, 0x9b, 0x59,
|
||||
0xf4, 0x6b, 0x68, 0x6e, 0xdb, 0xe7, 0x39, 0x56, 0x86, 0xa4, 0x73, 0xfd, 0xcd, 0x6d, 0x48, 0x6f,
|
||||
0x20, 0x54, 0x94, 0xcf, 0x50, 0x85, 0x17, 0x3b, 0xb0, 0xc3, 0xf2, 0x5b, 0x9b, 0x47, 0xed, 0x70,
|
||||
0x0d, 0xab, 0xe9, 0x9b, 0x2a, 0xc5, 0x12, 0x9b, 0x5f, 0x4c, 0x9f, 0xb2, 0x94, 0xab, 0xa2, 0x92,
|
||||
0xee, 0xab, 0x26, 0x2e, 0x2a, 0x1e, 0xaa, 0xf0, 0x12, 0x07, 0x12, 0xf8, 0x7c, 0x93, 0x93, 0x30,
|
||||
0x8f, 0xca, 0x0d, 0x83, 0xba, 0x1e, 0x87, 0xbb, 0x04, 0x6c, 0xc2, 0x05, 0x20, 0x7b, 0x5b, 0x06,
|
||||
0xd5, 0x71, 0x11, 0x0c, 0x9d, 0x24, 0x8c, 0x98, 0x8f, 0x4a, 0xca, 0x1d, 0x54, 0xe1, 0xc5, 0x8e,
|
||||
0x49, 0xa5, 0xb6, 0x33, 0xa5, 0x17, 0x68, 0xc2, 0x68, 0xdc, 0x25, 0x96, 0xed, 0x75, 0xc4, 0xbe,
|
||||
0xa0, 0x6c, 0xa3, 0x5a, 0xae, 0xaa, 0xc9, 0x97, 0xd0, 0x79, 0x4a, 0x34, 0xc3, 0xa1, 0xb6, 0x66,
|
||||
0x78, 0x8f, 0xa6, 0xef, 0x54, 0xf6, 0x03, 0x4b, 0x2d, 0xb0, 0xdf, 0xb6, 0x3a, 0x39, 0x5a, 0x41,
|
||||
0x49, 0x50, 0x99, 0x15, 0x33, 0xf0, 0xab, 0x47, 0xac, 0xa9, 0x3b, 0xf9, 0x1a, 0x3a, 0x97, 0x0f,
|
||||
0x10, 0xfb, 0x99, 0x5b, 0xd9, 0xf0, 0x3d, 0x9f, 0x12, 0xee, 0x24, 0x47, 0x75, 0x3d, 0x5c, 0x84,
|
||||
0x20, 0x38, 0x94, 0xb8, 0x7c, 0xa0, 0x5b, 0x44, 0xf3, 0xe3, 0x5a, 0x80, 0xcb, 0xfc, 0xe3, 0xc3,
|
||||
0xcf, 0xf6, 0x15, 0x65, 0x13, 0x95, 0x59, 0xe1, 0x83, 0x43, 0xdb, 0xb6, 0xa1, 0x91, 0x69, 0xc3,
|
||||
0x55, 0x6d, 0x52, 0x04, 0x34, 0x35, 0xab, 0x09, 0x05, 0x76, 0x84, 0x9a, 0xd5, 0x92, 0xbf, 0xcf,
|
||||
0xa1, 0xa5, 0xe9, 0xa9, 0x49, 0x5e, 0x41, 0xf8, 0x8b, 0x30, 0xe8, 0x75, 0x52, 0x98, 0x0d, 0x3b,
|
||||
0x83, 0xa8, 0x17, 0x3e, 0x63, 0xa3, 0x4c, 0x99, 0x2e, 0x01, 0x9d, 0x8d, 0x8c, 0x06, 0x50, 0x65,
|
||||
0x03, 0x95, 0xf7, 0x82, 0x27, 0xe1, 0x9e, 0x98, 0x51, 0x36, 0x66, 0x9a, 0xce, 0x56, 0x4d, 0x80,
|
||||
0x52, 0x2e, 0x41, 0xf9, 0x6b, 0x05, 0x95, 0x19, 0xe1, 0xb9, 0x49, 0x58, 0xdd, 0xdc, 0xa4, 0xa4,
|
||||
0x8d, 0x25, 0x56, 0x52, 0xe1, 0x12, 0xf3, 0xac, 0x50, 0xf5, 0xb6, 0x66, 0xf2, 0xfa, 0xa5, 0xea,
|
||||
0xed, 0x96, 0xad, 0xe3, 0x12, 0xb8, 0x51, 0x85, 0x55, 0x99, 0x31, 0x38, 0x8e, 0x0d, 0x97, 0x17,
|
||||
0x88, 0x9e, 0x47, 0xf1, 0x1c, 0xab, 0xf8, 0xfe, 0x0e, 0xaf, 0x54, 0xaa, 0xbf, 0x03, 0x4e, 0xc0,
|
||||
0xf3, 0x72, 0x05, 0x15, 0x34, 0x0d, 0x23, 0x80, 0x68, 0x4c, 0x7c, 0x6d, 0xdc, 0x11, 0x58, 0x19,
|
||||
0xd7, 0xe0, 0x1e, 0xe0, 0x45, 0xe6, 0x45, 0x58, 0x32, 0xd8, 0x12, 0xef, 0x15, 0x0e, 0x3e, 0x9b,
|
||||
0x35, 0x0d, 0x0c, 0x0c, 0xba, 0xe1, 0x6a, 0xb6, 0x4f, 0x5d, 0x82, 0xcf, 0xb1, 0xc4, 0xb7, 0x37,
|
||||
0xb7, 0xb1, 0x0c, 0x2b, 0xb2, 0xe3, 0x98, 0xf8, 0x3c, 0x2b, 0xb0, 0x36, 0x71, 0x1f, 0x1a, 0xde,
|
||||
0x16, 0xbe, 0x00, 0x74, 0x03, 0x38, 0x5e, 0x83, 0x55, 0x4b, 0xa5, 0x0f, 0xf0, 0x45, 0x90, 0xd6,
|
||||
0x7a, 0x48, 0xf0, 0x25, 0xbe, 0x68, 0xe3, 0x3a, 0xeb, 0x40, 0xa4, 0x89, 0xff, 0x0b, 0x14, 0xb5,
|
||||
0x2c, 0xbc, 0x0c, 0x42, 0x2c, 0x47, 0xd8, 0xfc, 0xdf, 0xa0, 0xa1, 0xc5, 0x34, 0x7c, 0x1d, 0x14,
|
||||
0xb0, 0xc6, 0x1a, 0xbe, 0x91, 0xb5, 0xae, 0x37, 0x59, 0x1d, 0x61, 0x17, 0x16, 0xbf, 0x05, 0xed,
|
||||
0xc9, 0xc1, 0x97, 0x45, 0x79, 0x56, 0x3d, 0x75, 0xc7, 0x70, 0xf1, 0xdb, 0x3c, 0x25, 0xa8, 0x07,
|
||||
0x12, 0x15, 0xd6, 0xd6, 0x98, 0x23, 0xae, 0xb0, 0xbc, 0x04, 0x0d, 0xdf, 0xe1, 0x2b, 0xd7, 0xc5,
|
||||
0x57, 0x19, 0xaf, 0xed, 0x7a, 0xa0, 0xd3, 0xff, 0x88, 0x74, 0x65, 0xdc, 0xd7, 0xc6, 0x1b, 0x6b,
|
||||
0x1b, 0xaf, 0xf0, 0x9b, 0x47, 0xc0, 0x33, 0xef, 0xf2, 0xde, 0x49, 0x1a, 0xf8, 0xba, 0x58, 0x39,
|
||||
0xf8, 0x06, 0x3b, 0x85, 0xda, 0x96, 0x89, 0xdf, 0xcb, 0x1a, 0xea, 0xfb, 0x60, 0xa1, 0xe3, 0xe2,
|
||||
0x55, 0xb0, 0xf0, 0x73, 0x5f, 0xb5, 0x98, 0x3e, 0x6b, 0xc0, 0x49, 0x35, 0x58, 0x7e, 0x00, 0x3f,
|
||||
0xb0, 0x25, 0x25, 0x26, 0xbe, 0xc9, 0x7e, 0xd0, 0xa9, 0xed, 0xe0, 0x75, 0x10, 0x01, 0x07, 0x6c,
|
||||
0x80, 0x0e, 0x94, 0xb4, 0x2c, 0xd5, 0xf2, 0xf0, 0x2d, 0x7e, 0x73, 0xc1, 0x4e, 0x4b, 0xf7, 0x5b,
|
||||
0xf8, 0x43, 0x38, 0x9d, 0xda, 0xb6, 0x87, 0x6f, 0xc3, 0xca, 0x05, 0xe7, 0x7c, 0xc4, 0x56, 0x7e,
|
||||
0xa3, 0x81, 0xef, 0xc0, 0x8a, 0x9d, 0xf8, 0xbf, 0xac, 0xe8, 0xd8, 0x8e, 0xa1, 0xe1, 0xbb, 0xac,
|
||||
0xb1, 0x03, 0xf1, 0xde, 0x54, 0x23, 0xba, 0x0f, 0x2c, 0x3b, 0xcc, 0xec, 0x8f, 0x59, 0xb9, 0xf2,
|
||||
0x59, 0xaf, 0xff, 0x84, 0x21, 0x0d, 0xcf, 0x24, 0xf8, 0x53, 0xde, 0x8f, 0xda, 0xce, 0x16, 0xa0,
|
||||
0x3f, 0x13, 0x29, 0x07, 0xd7, 0x10, 0xab, 0x2c, 0x3b, 0xfd, 0x9d, 0x76, 0x1b, 0x6f, 0xc2, 0x52,
|
||||
0x67, 0xa7, 0x6a, 0xc0, 0xd2, 0xb0, 0x29, 0x31, 0x9a, 0x16, 0xd6, 0xc1, 0x15, 0x0f, 0x1e, 0x62,
|
||||
0xc2, 0x3a, 0x8c, 0xe1, 0x7a, 0xb8, 0xc1, 0x67, 0x92, 0x96, 0x86, 0x9b, 0x2c, 0x01, 0xec, 0x16,
|
||||
0xcf, 0xcb, 0x2d, 0xe8, 0x08, 0xd9, 0x8e, 0x05, 0xde, 0x60, 0x9c, 0x7e, 0x4b, 0xc3, 0xdb, 0xe0,
|
||||
0x16, 0xcd, 0x76, 0xf0, 0x03, 0xf0, 0x84, 0x6e, 0xb8, 0xac, 0x79, 0x13, 0x1d, 0x9b, 0xca, 0x37,
|
||||
0x05, 0xb4, 0x38, 0xf5, 0x5d, 0xfc, 0xc3, 0xbe, 0x01, 0xc9, 0xd4, 0x0b, 0xc2, 0xcd, 0x59, 0x3e,
|
||||
0xc8, 0xf3, 0x0f, 0x09, 0x53, 0x5f, 0xe4, 0xc5, 0x97, 0x7f, 0xef, 0xf8, 0x40, 0x7c, 0x54, 0x63,
|
||||
0xb4, 0x20, 0xde, 0x70, 0x8e, 0xeb, 0x07, 0x08, 0x55, 0x34, 0xbb, 0xd5, 0x82, 0xef, 0x6a, 0xa5,
|
||||
0x89, 0xaa, 0x99, 0x49, 0x72, 0x7d, 0xf2, 0xc6, 0xc4, 0x3f, 0xe1, 0xc7, 0x2f, 0x4c, 0x6f, 0xa3,
|
||||
0x85, 0x27, 0x61, 0x7f, 0x10, 0x75, 0xe2, 0xdd, 0xdd, 0x24, 0xe4, 0x9f, 0x66, 0x65, 0x5a, 0x63,
|
||||
0x34, 0x9b, 0x91, 0x14, 0x13, 0x5d, 0xd4, 0xf6, 0x82, 0x24, 0x19, 0xec, 0x0e, 0xba, 0xec, 0x09,
|
||||
0x4d, 0x0b, 0xd2, 0xb0, 0x1f, 0x8f, 0x8e, 0x7f, 0x79, 0x79, 0x13, 0xa1, 0x6e, 0x1c, 0xed, 0x0e,
|
||||
0x7a, 0xec, 0xa9, 0x83, 0x7f, 0x6e, 0xe6, 0x28, 0xca, 0xaf, 0x25, 0x74, 0x49, 0x8d, 0x82, 0xbd,
|
||||
0xc3, 0xff, 0x0f, 0x27, 0x86, 0x86, 0xff, 0x77, 0x10, 0x26, 0xa9, 0xac, 0xa1, 0x6a, 0x4f, 0xbc,
|
||||
0x90, 0x9d, 0x32, 0x68, 0xd9, 0x83, 0x1a, 0x1d, 0x03, 0x65, 0x07, 0x2d, 0x86, 0x51, 0x37, 0xee,
|
||||
0x0d, 0xa2, 0x7e, 0x27, 0x17, 0xc1, 0x1b, 0x27, 0x46, 0x90, 0x63, 0x58, 0xec, 0x16, 0xc2, 0xdc,
|
||||
0x4e, 0xf9, 0xb3, 0x84, 0xea, 0xcf, 0xab, 0x9c, 0x0c, 0x63, 0xe8, 0x8e, 0x0f, 0x91, 0x9c, 0x1d,
|
||||
0xdd, 0x99, 0x44, 0x5a, 0x9a, 0x31, 0xd2, 0xe7, 0x32, 0x19, 0x93, 0xcf, 0xf6, 0xfc, 0x8b, 0x5e,
|
||||
0x61, 0xfa, 0x45, 0x4f, 0x26, 0x3c, 0xab, 0xc0, 0xa1, 0x89, 0x78, 0x9f, 0xba, 0x76, 0x8a, 0xb3,
|
||||
0x80, 0x9f, 0x4e, 0x90, 0xca, 0x6f, 0x25, 0xf4, 0x86, 0x30, 0x8c, 0x27, 0xf0, 0x7f, 0x4a, 0x44,
|
||||
0xbe, 0x42, 0x6f, 0x7e, 0x9f, 0xde, 0x22, 0x2c, 0x2a, 0xaa, 0x02, 0x2d, 0x1d, 0x84, 0x49, 0x5d,
|
||||
0x62, 0x0e, 0xba, 0x7a, 0xaa, 0x2b, 0x4c, 0xc7, 0xb0, 0x17, 0x05, 0x00, 0x86, 0xf6, 0x8b, 0x79,
|
||||
0x0d, 0x06, 0x61, 0xf2, 0x23, 0x77, 0xd9, 0xb3, 0xf1, 0xb5, 0x9b, 0x28, 0xfc, 0xef, 0xf1, 0xd5,
|
||||
0xaf, 0x24, 0x74, 0x21, 0xbb, 0x3e, 0x87, 0x51, 0x1a, 0x3c, 0xfb, 0x91, 0x7b, 0xea, 0x77, 0x12,
|
||||
0x7a, 0xed, 0x88, 0xbe, 0xc2, 0x51, 0x53, 0xd7, 0x4e, 0x7a, 0xd9, 0x6b, 0x27, 0xdf, 0x47, 0x15,
|
||||
0x36, 0x88, 0x26, 0xf5, 0x02, 0x93, 0xf1, 0xce, 0x49, 0x9d, 0x09, 0x98, 0xa9, 0xc0, 0x4c, 0xb9,
|
||||
0xba, 0x78, 0xc4, 0xd5, 0x8f, 0xd1, 0x79, 0x51, 0xaa, 0x0f, 0xa1, 0xf6, 0xbf, 0x4a, 0x47, 0x2b,
|
||||
0xfb, 0xe8, 0xc2, 0xb4, 0x6c, 0xe1, 0x14, 0x1f, 0xa1, 0x2e, 0x6f, 0x08, 0x93, 0xfc, 0xf9, 0xf0,
|
||||
0x04, 0xf1, 0xc7, 0xf7, 0x13, 0x9a, 0x13, 0xa4, 0xfc, 0xa4, 0x84, 0xce, 0xab, 0xfc, 0x4f, 0x3b,
|
||||
0xe1, 0xab, 0xb6, 0x45, 0x7e, 0x88, 0xaa, 0xbb, 0x61, 0x90, 0x1e, 0x8c, 0xc2, 0x44, 0x3c, 0xed,
|
||||
0xde, 0x3b, 0x41, 0xc8, 0x31, 0xaa, 0xac, 0x36, 0x84, 0x08, 0x3a, 0x16, 0xf6, 0x7c, 0x36, 0x16,
|
||||
0x7f, 0x60, 0x36, 0x2e, 0xff, 0x53, 0x42, 0xd5, 0xec, 0x20, 0xf9, 0x2a, 0x5a, 0x0a, 0x9f, 0xa5,
|
||||
0xa3, 0xa0, 0x9b, 0x76, 0x12, 0x96, 0x9a, 0xcc, 0x05, 0x55, 0xba, 0x28, 0xa8, 0x3c, 0x5f, 0xe5,
|
||||
0x77, 0x11, 0xce, 0xd8, 0xc6, 0x17, 0xbb, 0xc0, 0x18, 0xcf, 0x0a, 0x7a, 0x56, 0x03, 0xe4, 0xfb,
|
||||
0x68, 0x39, 0x63, 0x3d, 0xa6, 0x8d, 0x15, 0x19, 0xa8, 0x2e, 0x38, 0xf4, 0xe7, 0x7a, 0xd4, 0x1d,
|
||||
0x54, 0x9f, 0x3a, 0xe8, 0x30, 0x87, 0x2d, 0x31, 0xec, 0xc5, 0xfc, 0x81, 0x93, 0x3a, 0x2d, 0x5f,
|
||||
0x41, 0x8b, 0x5d, 0x91, 0x4d, 0x1d, 0x36, 0xa4, 0x55, 0x18, 0xfb, 0x42, 0x37, 0x97, 0x62, 0xca,
|
||||
0x2f, 0x8b, 0x50, 0x39, 0xf2, 0x8e, 0xff, 0x31, 0x5d, 0xc4, 0x7c, 0xd9, 0x2c, 0xbe, 0x5c, 0xd9,
|
||||
0x3c, 0x7e, 0x78, 0x28, 0xbd, 0xda, 0xe1, 0xa1, 0x7c, 0x64, 0x78, 0x98, 0xbe, 0xb0, 0x95, 0x57,
|
||||
0x74, 0x61, 0xaf, 0xdf, 0x41, 0x0b, 0xf9, 0x34, 0xe6, 0xc3, 0xbd, 0x45, 0xf0, 0x19, 0x58, 0xf9,
|
||||
0x5e, 0xe3, 0x0e, 0xff, 0xde, 0xf5, 0xbd, 0xc6, 0xcd, 0xdb, 0xfc, 0x7b, 0xd7, 0xf7, 0x1a, 0x1b,
|
||||
0xeb, 0xb8, 0xb8, 0xfe, 0x87, 0x2a, 0x3a, 0x6b, 0x8a, 0x13, 0x5d, 0xfe, 0x67, 0x60, 0xf9, 0x37,
|
||||
0x12, 0xc2, 0x47, 0x67, 0x2e, 0xf9, 0xf6, 0x89, 0x97, 0xf4, 0xd8, 0xb9, 0x72, 0xf9, 0xa3, 0x99,
|
||||
0x71, 0x3c, 0xcf, 0x94, 0xd5, 0xaf, 0xff, 0xf4, 0x97, 0x6f, 0x0b, 0x2b, 0xca, 0x95, 0xf1, 0xdf,
|
||||
0xab, 0x33, 0x57, 0x27, 0x77, 0x83, 0x23, 0xa0, 0xbb, 0xd2, 0x75, 0xf9, 0x3b, 0x09, 0x9d, 0x3d,
|
||||
0xd2, 0x65, 0xe5, 0x0f, 0x4f, 0x77, 0xf8, 0x91, 0x31, 0x62, 0xf9, 0xf6, 0xac, 0x30, 0xa1, 0xf2,
|
||||
0xfb, 0x4c, 0xe5, 0x6b, 0x8a, 0xf2, 0xfd, 0x2a, 0x67, 0x18, 0xd0, 0xf8, 0x8f, 0x47, 0x06, 0x99,
|
||||
0xdc, 0x15, 0xbd, 0x3f, 0x83, 0x06, 0xcf, 0x4d, 0x8e, 0xcb, 0x1f, 0xbf, 0x24, 0x5a, 0x98, 0x71,
|
||||
0x8b, 0x99, 0xb1, 0xaa, 0xbc, 0x7b, 0x82, 0x19, 0x87, 0x53, 0xfe, 0xff, 0x85, 0x84, 0x16, 0xa7,
|
||||
0x5a, 0xb7, 0xbc, 0x71, 0xca, 0xd0, 0xe7, 0x07, 0x93, 0xe5, 0x5b, 0xb3, 0x81, 0x84, 0xca, 0x37,
|
||||
0x98, 0xca, 0x57, 0x95, 0xcb, 0x2f, 0x48, 0x16, 0x86, 0x00, 0x4d, 0x7f, 0x2e, 0xa1, 0x85, 0x7c,
|
||||
0x3b, 0x95, 0xd7, 0x4f, 0x77, 0x03, 0xf3, 0x7d, 0x7d, 0x79, 0x63, 0x26, 0x8c, 0x50, 0xf3, 0x3a,
|
||||
0x53, 0xf3, 0x1d, 0xe5, 0xad, 0x63, 0xd4, 0xcc, 0x57, 0xdf, 0x4c, 0xcb, 0x7c, 0x01, 0x3e, 0x51,
|
||||
0xcb, 0x63, 0xda, 0xe4, 0xf2, 0xc6, 0x4c, 0x98, 0x53, 0x68, 0x19, 0xe4, 0x00, 0x77, 0xa5, 0xeb,
|
||||
0x9b, 0x5f, 0x4b, 0xe8, 0xed, 0x6e, 0xbc, 0xff, 0xe2, 0x63, 0x36, 0x2f, 0x1c, 0x29, 0x31, 0xce,
|
||||
0x28, 0x4e, 0x63, 0x47, 0x7a, 0x4c, 0x04, 0xac, 0x1f, 0x03, 0x64, 0x35, 0x1e, 0xf5, 0xd7, 0xfa,
|
||||
0x61, 0xc4, 0xfe, 0xd5, 0x63, 0x8d, 0xff, 0x14, 0x0c, 0x07, 0xc9, 0xf7, 0xfc, 0xff, 0xca, 0xbd,
|
||||
0x8c, 0xf0, 0xa4, 0xc2, 0x10, 0x1b, 0xff, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x72, 0x26, 0xce, 0x5b,
|
||||
0xf0, 0x22, 0x00, 0x00,
|
||||
}
|
||||
|
|
1157
vendor/google.golang.org/genproto/googleapis/cloud/videointelligence/v1beta2/video_intelligence.pb.go
generated
vendored
Normal file
1157
vendor/google.golang.org/genproto/googleapis/cloud/videointelligence/v1beta2/video_intelligence.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1769
vendor/google.golang.org/genproto/googleapis/container/v1/cluster_service.pb.go
generated
vendored
1769
vendor/google.golang.org/genproto/googleapis/container/v1/cluster_service.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue