using cred helper from watcher
parent
286508d60d
commit
52c5101646
|
@ -40,6 +40,7 @@ import (
|
||||||
|
|
||||||
// credentials helpers
|
// credentials helpers
|
||||||
_ "github.com/keel-hq/keel/extension/credentialshelper/aws"
|
_ "github.com/keel-hq/keel/extension/credentialshelper/aws"
|
||||||
|
secretsCredentialsHelper "github.com/keel-hq/keel/extension/credentialshelper/secrets"
|
||||||
|
|
||||||
// bots
|
// bots
|
||||||
_ "github.com/keel-hq/keel/bot/hipchat"
|
_ "github.com/keel-hq/keel/bot/hipchat"
|
||||||
|
@ -171,8 +172,14 @@ func main() {
|
||||||
|
|
||||||
// setting up providers
|
// setting up providers
|
||||||
providers := setupProviders(implementer, sender, approvalsManager, &t.GenericResourceCache)
|
providers := setupProviders(implementer, sender, approvalsManager, &t.GenericResourceCache)
|
||||||
|
|
||||||
|
// registering secrets based credentials helper
|
||||||
secretsGetter := secrets.NewGetter(implementer)
|
secretsGetter := secrets.NewGetter(implementer)
|
||||||
teardownTriggers := setupTriggers(ctx, providers, secretsGetter, approvalsManager)
|
ch := secretsCredentialsHelper.New(secretsGetter)
|
||||||
|
credentialshelper.RegisterCredentialsHelper("secrets", ch)
|
||||||
|
|
||||||
|
// trigger setup
|
||||||
|
teardownTriggers := setupTriggers(ctx, providers, approvalsManager)
|
||||||
|
|
||||||
bot.Run(implementer, approvalsManager)
|
bot.Run(implementer, approvalsManager)
|
||||||
|
|
||||||
|
@ -235,7 +242,7 @@ func setupProviders(k8sImplementer kubernetes.Implementer, sender notification.S
|
||||||
|
|
||||||
// setupTriggers - setting up triggers. New triggers should be added to this function. Each trigger
|
// 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)
|
// 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, approvalsManager approvals.Manager) (teardown func()) {
|
func setupTriggers(ctx context.Context, providers provider.Providers, approvalsManager approvals.Manager) (teardown func()) {
|
||||||
|
|
||||||
// setting up generic http webhook server
|
// setting up generic http webhook server
|
||||||
whs := http.NewTriggerServer(&http.Opts{
|
whs := http.NewTriggerServer(&http.Opts{
|
||||||
|
@ -273,7 +280,7 @@ func setupTriggers(ctx context.Context, providers provider.Providers, secretsGet
|
||||||
|
|
||||||
registryClient := registry.New()
|
registryClient := registry.New()
|
||||||
watcher := poll.NewRepositoryWatcher(providers, registryClient)
|
watcher := poll.NewRepositoryWatcher(providers, registryClient)
|
||||||
pollManager := poll.NewPollManager(providers, watcher, secretsGetter, credentialshelper.New())
|
pollManager := poll.NewPollManager(providers, watcher)
|
||||||
|
|
||||||
// start poll manager, will finish with ctx
|
// start poll manager, will finish with ctx
|
||||||
go watcher.Start(ctx)
|
go watcher.Start(ctx)
|
||||||
|
|
|
@ -5,9 +5,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/keel-hq/keel/extension/credentialshelper"
|
|
||||||
"github.com/keel-hq/keel/provider"
|
"github.com/keel-hq/keel/provider"
|
||||||
"github.com/keel-hq/keel/secrets"
|
|
||||||
"github.com/keel-hq/keel/types"
|
"github.com/keel-hq/keel/types"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
@ -31,10 +29,6 @@ func init() {
|
||||||
type DefaultManager struct {
|
type DefaultManager struct {
|
||||||
providers provider.Providers
|
providers provider.Providers
|
||||||
|
|
||||||
secretsGetter secrets.Getter
|
|
||||||
|
|
||||||
credentialsHelper credentialshelper.CredentialsHelper
|
|
||||||
|
|
||||||
// repository watcher
|
// repository watcher
|
||||||
watcher Watcher
|
watcher Watcher
|
||||||
|
|
||||||
|
@ -48,11 +42,9 @@ type DefaultManager struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPollManager - new default poller
|
// NewPollManager - new default poller
|
||||||
func NewPollManager(providers provider.Providers, watcher Watcher, secretsGetter secrets.Getter, credentialsHelper credentialshelper.CredentialsHelper) *DefaultManager {
|
func NewPollManager(providers provider.Providers, watcher Watcher) *DefaultManager {
|
||||||
return &DefaultManager{
|
return &DefaultManager{
|
||||||
providers: providers,
|
providers: providers,
|
||||||
secretsGetter: secretsGetter,
|
|
||||||
credentialsHelper: credentialsHelper,
|
|
||||||
watcher: watcher,
|
watcher: watcher,
|
||||||
mu: &sync.Mutex{},
|
mu: &sync.Mutex{},
|
||||||
scanTick: 1,
|
scanTick: 1,
|
||||||
|
@ -93,59 +85,25 @@ func (s *DefaultManager) Start(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultManager) scan(ctx context.Context) error {
|
func (s *DefaultManager) scan(ctx context.Context) error {
|
||||||
log.Info("performing scan")
|
log.Debug("trigger.poll.manager: performing scan")
|
||||||
trackedImages, err := s.providers.TrackedImages()
|
trackedImages, err := s.providers.TrackedImages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var tracked float64
|
var tracked float64
|
||||||
|
|
||||||
for _, trackedImage := range trackedImages {
|
for _, trackedImage := range trackedImages {
|
||||||
if trackedImage.Trigger != types.TriggerTypePoll {
|
if trackedImage.Trigger != types.TriggerTypePoll {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tracked++
|
tracked++
|
||||||
|
err = s.watcher.Watch(trackedImage, trackedImage.PollSchedule)
|
||||||
var imageCreds *types.Credentials
|
|
||||||
|
|
||||||
// anonymous credentials
|
|
||||||
creds := &types.Credentials{}
|
|
||||||
imageCreds, err = s.secretsGetter.Get(trackedImage)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"error": err,
|
|
||||||
"secrets": trackedImage.Secrets,
|
|
||||||
"image": trackedImage.Image.Remote(),
|
|
||||||
}).Error("trigger.poll.manager: failed to get authentication credentials")
|
|
||||||
} else {
|
|
||||||
creds = imageCreds
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: refactor to either recreate it every 10 hours (12 hours expiration) or better to retrieve creds
|
|
||||||
// just before quering the registry
|
|
||||||
if imageCreds.Username == "" && imageCreds.Password == "" {
|
|
||||||
registryCreds, err := s.credentialsHelper.GetCredentials(trackedImage.Image.Registry())
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"error": err,
|
|
||||||
"registry": trackedImage.Image.Registry(),
|
|
||||||
"image": trackedImage.Image.Remote(),
|
|
||||||
}).Error("trigger.poll.manager: failed to get registry credentials")
|
|
||||||
} else {
|
|
||||||
creds = registryCreds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.watcher.Watch(trackedImage.Image.Remote(), trackedImage.PollSchedule, creds.Username, creds.Password)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"error": err,
|
"error": err,
|
||||||
"schedule": trackedImage.PollSchedule,
|
"schedule": trackedImage.PollSchedule,
|
||||||
"image": trackedImage.Image.Remote(),
|
"image": trackedImage.Image.Remote(),
|
||||||
}).Error("trigger.poll.manager: failed to start watching repository")
|
}).Error("trigger.poll.manager: failed to start watching repository")
|
||||||
// continue processing other images
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/keel-hq/keel/util/codecs"
|
"github.com/keel-hq/keel/util/codecs"
|
||||||
"github.com/keel-hq/keel/util/image"
|
"github.com/keel-hq/keel/util/image"
|
||||||
|
|
||||||
"github.com/keel-hq/keel/extension/credentialshelper"
|
// "github.com/keel-hq/keel/extension/credentialshelper"
|
||||||
_ "github.com/keel-hq/keel/extension/credentialshelper/aws"
|
_ "github.com/keel-hq/keel/extension/credentialshelper/aws"
|
||||||
|
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -59,7 +59,7 @@ func TestCheckDeployment(t *testing.T) {
|
||||||
|
|
||||||
watcher := NewRepositoryWatcher(providers, frc)
|
watcher := NewRepositoryWatcher(providers, frc)
|
||||||
|
|
||||||
pm := NewPollManager(providers, watcher, &FakeSecretsGetter{}, credentialshelper.New())
|
pm := NewPollManager(providers, watcher)
|
||||||
|
|
||||||
imageA := "gcr.io/v2-namespace/hello-world:1.1.1"
|
imageA := "gcr.io/v2-namespace/hello-world:1.1.1"
|
||||||
imageB := "gcr.io/v2-namespace/greetings-world:1.1.1"
|
imageB := "gcr.io/v2-namespace/greetings-world:1.1.1"
|
||||||
|
@ -80,11 +80,11 @@ func TestCheckDeployment(t *testing.T) {
|
||||||
if watcher.watched[keyA].schedule != types.KeelPollDefaultSchedule {
|
if watcher.watched[keyA].schedule != types.KeelPollDefaultSchedule {
|
||||||
t.Errorf("unexpected schedule: %s", watcher.watched[keyA].schedule)
|
t.Errorf("unexpected schedule: %s", watcher.watched[keyA].schedule)
|
||||||
}
|
}
|
||||||
if watcher.watched[keyA].imageRef.Remote() != ref.Remote() {
|
if watcher.watched[keyA].trackedImage.Image.Remote() != ref.Remote() {
|
||||||
t.Errorf("unexpected remote remote: %s", watcher.watched[keyA].imageRef.Remote())
|
t.Errorf("unexpected remote remote: %s", watcher.watched[keyA].trackedImage.Image.Remote())
|
||||||
}
|
}
|
||||||
if watcher.watched[keyA].imageRef.Tag() != ref.Tag() {
|
if watcher.watched[keyA].trackedImage.Image.Tag() != ref.Tag() {
|
||||||
t.Errorf("unexpected tag: %s", watcher.watched[keyA].imageRef.Tag())
|
t.Errorf("unexpected tag: %s", watcher.watched[keyA].trackedImage.Image.Tag())
|
||||||
}
|
}
|
||||||
|
|
||||||
refB, _ := image.Parse(imageB)
|
refB, _ := image.Parse(imageB)
|
||||||
|
@ -95,11 +95,11 @@ func TestCheckDeployment(t *testing.T) {
|
||||||
if watcher.watched[keyB].schedule != types.KeelPollDefaultSchedule {
|
if watcher.watched[keyB].schedule != types.KeelPollDefaultSchedule {
|
||||||
t.Errorf("unexpected schedule: %s", watcher.watched[keyB].schedule)
|
t.Errorf("unexpected schedule: %s", watcher.watched[keyB].schedule)
|
||||||
}
|
}
|
||||||
if watcher.watched[keyB].imageRef.Remote() != refB.Remote() {
|
if watcher.watched[keyB].trackedImage.Image.Remote() != refB.Remote() {
|
||||||
t.Errorf("unexpected remote remote: %s", watcher.watched[keyB].imageRef.Remote())
|
t.Errorf("unexpected remote remote: %s", watcher.watched[keyB].trackedImage.Image.Remote())
|
||||||
}
|
}
|
||||||
if watcher.watched[keyB].imageRef.Tag() != refB.Tag() {
|
if watcher.watched[keyB].trackedImage.Image.Tag() != refB.Tag() {
|
||||||
t.Errorf("unexpected tag: %s", watcher.watched[keyB].imageRef.Tag())
|
t.Errorf("unexpected tag: %s", watcher.watched[keyB].trackedImage.Image.Tag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ func TestCheckECRDeployment(t *testing.T) {
|
||||||
|
|
||||||
watcher := NewRepositoryWatcher(providers, rc)
|
watcher := NewRepositoryWatcher(providers, rc)
|
||||||
|
|
||||||
pm := NewPollManager(providers, watcher, &FakeSecretsGetter{}, credentialshelper.New())
|
pm := NewPollManager(providers, watcher)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -157,10 +157,10 @@ func TestCheckECRDeployment(t *testing.T) {
|
||||||
if watcher.watched[keyA].schedule != types.KeelPollDefaultSchedule {
|
if watcher.watched[keyA].schedule != types.KeelPollDefaultSchedule {
|
||||||
t.Errorf("unexpected schedule: %s", watcher.watched[keyA].schedule)
|
t.Errorf("unexpected schedule: %s", watcher.watched[keyA].schedule)
|
||||||
}
|
}
|
||||||
if watcher.watched[keyA].imageRef.Remote() != imgA.Remote() {
|
if watcher.watched[keyA].trackedImage.Image.Remote() != imgA.Remote() {
|
||||||
t.Errorf("unexpected remote remote: %s", watcher.watched[keyA].imageRef.Remote())
|
t.Errorf("unexpected remote remote: %s", watcher.watched[keyA].trackedImage.Image.Remote())
|
||||||
}
|
}
|
||||||
if watcher.watched[keyA].imageRef.Tag() != imgA.Tag() {
|
if watcher.watched[keyA].trackedImage.Image.Tag() != imgA.Tag() {
|
||||||
t.Errorf("unexpected tag: %s", watcher.watched[keyA].imageRef.Tag())
|
t.Errorf("unexpected tag: %s", watcher.watched[keyA].trackedImage.Image.Tag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/keel-hq/keel/extension/credentialshelper"
|
||||||
"github.com/keel-hq/keel/provider"
|
"github.com/keel-hq/keel/provider"
|
||||||
"github.com/keel-hq/keel/registry"
|
"github.com/keel-hq/keel/registry"
|
||||||
"github.com/keel-hq/keel/types"
|
"github.com/keel-hq/keel/types"
|
||||||
|
@ -30,14 +31,12 @@ func init() {
|
||||||
|
|
||||||
// Watcher - generic watcher interface
|
// Watcher - generic watcher interface
|
||||||
type Watcher interface {
|
type Watcher interface {
|
||||||
Watch(imageName, registryUsername, registryPassword, schedule string) error
|
Watch(image *types.TrackedImage, schedule string) error
|
||||||
Unwatch(image string) error
|
Unwatch(image string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type watchDetails struct {
|
type watchDetails struct {
|
||||||
imageRef *image.Reference
|
trackedImage *types.TrackedImage
|
||||||
registryUsername string // "" for anonymous
|
|
||||||
registryPassword string // "" for anonymous
|
|
||||||
digest string // image digest
|
digest string // image digest
|
||||||
latest string // latest tag
|
latest string // latest tag
|
||||||
schedule string
|
schedule string
|
||||||
|
@ -115,7 +114,7 @@ func (w *RepositoryWatcher) Unwatch(imageName string) error {
|
||||||
|
|
||||||
// Watch - starts watching repository for changes, if it's already watching - ignores,
|
// Watch - starts watching repository for changes, if it's already watching - ignores,
|
||||||
// if details changed - updates details
|
// if details changed - updates details
|
||||||
func (w *RepositoryWatcher) Watch(imageName, schedule, registryUsername, registryPassword string) error {
|
func (w *RepositoryWatcher) Watch(image *types.TrackedImage, schedule string) error {
|
||||||
|
|
||||||
if schedule == "" {
|
if schedule == "" {
|
||||||
return fmt.Errorf("cron schedule cannot be empty")
|
return fmt.Errorf("cron schedule cannot be empty")
|
||||||
|
@ -125,32 +124,23 @@ func (w *RepositoryWatcher) Watch(imageName, schedule, registryUsername, registr
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"error": err,
|
"error": err,
|
||||||
"image": imageName,
|
"image": image.String(),
|
||||||
"schedule": schedule,
|
"schedule": schedule,
|
||||||
}).Error("trigger.poll.RepositoryWatcher.addJob: invalid cron schedule")
|
}).Error("trigger.poll.RepositoryWatcher.addJob: invalid cron schedule")
|
||||||
return fmt.Errorf("invalid cron schedule: %s", err)
|
return fmt.Errorf("invalid cron schedule: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
imageRef, err := image.Parse(imageName)
|
key := getImageIdentifier(image.Image)
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"error": err,
|
|
||||||
"image_name": imageName,
|
|
||||||
}).Error("trigger.poll.RepositoryWatcher.Watch: failed to parse image")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := getImageIdentifier(imageRef)
|
|
||||||
|
|
||||||
// checking whether it's already being watched
|
// checking whether it's already being watched
|
||||||
details, ok := w.watched[key]
|
details, ok := w.watched[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
err = w.addJob(imageRef, registryUsername, registryPassword, schedule)
|
// err = w.addJob(imageRef, registryUsername, registryPassword, schedule)
|
||||||
|
err = w.addJob(image, schedule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"error": err,
|
"error": err,
|
||||||
"image_name": imageName,
|
"image": image.String(),
|
||||||
"registry_username": registryUsername,
|
|
||||||
}).Error("trigger.poll.RepositoryWatcher.Watch: failed to add image watch job")
|
}).Error("trigger.poll.RepositoryWatcher.Watch: failed to add image watch job")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -162,52 +152,37 @@ func (w *RepositoryWatcher) Watch(imageName, schedule, registryUsername, registr
|
||||||
w.cron.UpdateJob(key, schedule)
|
w.cron.UpdateJob(key, schedule)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checking auth details, if changed - need to update
|
|
||||||
if details.registryPassword != registryPassword || details.registryUsername != registryUsername {
|
|
||||||
// recreating job
|
|
||||||
w.cron.DeleteJob(key)
|
|
||||||
err = w.addJob(imageRef, registryUsername, registryPassword, schedule)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"error": err,
|
|
||||||
"image_name": imageName,
|
|
||||||
"registry_username": registryUsername,
|
|
||||||
}).Error("trigger.poll.RepositoryWatcher.Watch: failed to add image watch job")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// nothing to do
|
// nothing to do
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *RepositoryWatcher) addJob(ref *image.Reference, registryUsername, registryPassword, schedule string) error {
|
func (w *RepositoryWatcher) addJob(ti *types.TrackedImage, schedule string) error {
|
||||||
// getting initial digest
|
// getting initial digest
|
||||||
reg := ref.Scheme() + "://" + ref.Registry()
|
reg := ti.Image.Scheme() + "://" + ti.Image.Registry()
|
||||||
|
|
||||||
|
creds := credentialshelper.GetCredentials(ti)
|
||||||
|
|
||||||
digest, err := w.registryClient.Digest(registry.Opts{
|
digest, err := w.registryClient.Digest(registry.Opts{
|
||||||
Registry: reg,
|
Registry: reg,
|
||||||
Name: ref.ShortName(),
|
Name: ti.Image.ShortName(),
|
||||||
Tag: ref.Tag(),
|
Tag: ti.Image.Tag(),
|
||||||
Username: registryUsername,
|
Username: creds.Username,
|
||||||
Password: registryPassword,
|
Password: creds.Password,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"error": err,
|
"error": err,
|
||||||
"image": ref.Remote(),
|
"image": ti.Image.String(),
|
||||||
}).Error("trigger.poll.RepositoryWatcher.addJob: failed to get image digest")
|
}).Error("trigger.poll.RepositoryWatcher.addJob: failed to get image digest")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
key := getImageIdentifier(ref)
|
key := getImageIdentifier(ti.Image)
|
||||||
details := &watchDetails{
|
details := &watchDetails{
|
||||||
imageRef: ref,
|
trackedImage: ti,
|
||||||
digest: digest, // current image digest
|
digest: digest, // current image digest
|
||||||
latest: ref.Tag(),
|
latest: ti.Image.Tag(),
|
||||||
registryUsername: registryUsername,
|
|
||||||
registryPassword: registryPassword,
|
|
||||||
schedule: schedule,
|
schedule: schedule,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,13 +192,13 @@ func (w *RepositoryWatcher) addJob(ref *image.Reference, registryUsername, regis
|
||||||
// checking tag type, for versioned (semver) tags we setup a watch all tags job
|
// checking tag type, for versioned (semver) tags we setup a watch all tags job
|
||||||
// and for non-semver types we create a single tag watcher which
|
// and for non-semver types we create a single tag watcher which
|
||||||
// checks digest
|
// checks digest
|
||||||
_, err = version.GetVersion(ref.Tag())
|
_, err = version.GetVersion(ti.Image.Tag())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// adding new job
|
// adding new job
|
||||||
job := NewWatchTagJob(w.providers, w.registryClient, details)
|
job := NewWatchTagJob(w.providers, w.registryClient, details)
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"job_name": key,
|
"job_name": key,
|
||||||
"image": ref.Remote(),
|
"image": ti.Image.String(),
|
||||||
"digest": digest,
|
"digest": digest,
|
||||||
"schedule": schedule,
|
"schedule": schedule,
|
||||||
}).Info("trigger.poll.RepositoryWatcher: new watch tag digest job added")
|
}).Info("trigger.poll.RepositoryWatcher: new watch tag digest job added")
|
||||||
|
@ -234,7 +209,7 @@ func (w *RepositoryWatcher) addJob(ref *image.Reference, registryUsername, regis
|
||||||
job := NewWatchRepositoryTagsJob(w.providers, w.registryClient, details)
|
job := NewWatchRepositoryTagsJob(w.providers, w.registryClient, details)
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"job_name": key,
|
"job_name": key,
|
||||||
"image": ref.Remote(),
|
"image": ti.Image.String(),
|
||||||
"digest": digest,
|
"digest": digest,
|
||||||
"schedule": schedule,
|
"schedule": schedule,
|
||||||
}).Info("trigger.poll.RepositoryWatcher: new watch repository tags job added")
|
}).Info("trigger.poll.RepositoryWatcher: new watch repository tags job added")
|
||||||
|
@ -261,21 +236,22 @@ func NewWatchTagJob(providers provider.Providers, registryClient registry.Client
|
||||||
|
|
||||||
// Run - main function to check schedule
|
// Run - main function to check schedule
|
||||||
func (j *WatchTagJob) Run() {
|
func (j *WatchTagJob) Run() {
|
||||||
reg := j.details.imageRef.Scheme() + "://" + j.details.imageRef.Registry()
|
creds := credentialshelper.GetCredentials(j.details.trackedImage)
|
||||||
|
reg := j.details.trackedImage.Image.Scheme() + "://" + j.details.trackedImage.Image.Registry()
|
||||||
currentDigest, err := j.registryClient.Digest(registry.Opts{
|
currentDigest, err := j.registryClient.Digest(registry.Opts{
|
||||||
Registry: reg,
|
Registry: reg,
|
||||||
Name: j.details.imageRef.ShortName(),
|
Name: j.details.trackedImage.Image.ShortName(),
|
||||||
Tag: j.details.imageRef.Tag(),
|
Tag: j.details.trackedImage.Image.Tag(),
|
||||||
Username: j.details.registryUsername,
|
Username: creds.Username,
|
||||||
Password: j.details.registryPassword,
|
Password: creds.Password,
|
||||||
})
|
})
|
||||||
|
|
||||||
registriesScannedCounter.With(prometheus.Labels{"registry": j.details.imageRef.Registry(), "image": j.details.imageRef.Name()}).Inc()
|
registriesScannedCounter.With(prometheus.Labels{"registry": j.details.trackedImage.Image.Registry(), "image": j.details.trackedImage.Image.Name()}).Inc()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"error": err,
|
"error": err,
|
||||||
"image": j.details.imageRef.Remote(),
|
"image": j.details.trackedImage.Image.String(),
|
||||||
}).Error("trigger.poll.WatchTagJob: failed to check digest")
|
}).Error("trigger.poll.WatchTagJob: failed to check digest")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -283,7 +259,7 @@ func (j *WatchTagJob) Run() {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"current_digest": j.details.digest,
|
"current_digest": j.details.digest,
|
||||||
"new_digest": currentDigest,
|
"new_digest": currentDigest,
|
||||||
"image_name": j.details.imageRef.Remote(),
|
"image": j.details.trackedImage.Image.String(),
|
||||||
}).Debug("trigger.poll.WatchTagJob: checking digest")
|
}).Debug("trigger.poll.WatchTagJob: checking digest")
|
||||||
|
|
||||||
// checking whether image digest has changed
|
// checking whether image digest has changed
|
||||||
|
@ -293,14 +269,14 @@ func (j *WatchTagJob) Run() {
|
||||||
|
|
||||||
event := types.Event{
|
event := types.Event{
|
||||||
Repository: types.Repository{
|
Repository: types.Repository{
|
||||||
Name: j.details.imageRef.Repository(),
|
Name: j.details.trackedImage.Image.Repository(),
|
||||||
Tag: j.details.imageRef.Tag(),
|
Tag: j.details.trackedImage.Image.Tag(),
|
||||||
Digest: currentDigest,
|
Digest: currentDigest,
|
||||||
},
|
},
|
||||||
TriggerName: types.TriggerTypePoll.String(),
|
TriggerName: types.TriggerTypePoll.String(),
|
||||||
}
|
}
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"repository": j.details.imageRef.Repository(),
|
"image": j.details.trackedImage.Image.String(),
|
||||||
"new_digest": currentDigest,
|
"new_digest": currentDigest,
|
||||||
}).Info("trigger.poll.WatchTagJob: digest change detected, submiting event to providers")
|
}).Info("trigger.poll.WatchTagJob: digest change detected, submiting event to providers")
|
||||||
|
|
||||||
|
@ -327,32 +303,35 @@ func NewWatchRepositoryTagsJob(providers provider.Providers, registryClient regi
|
||||||
|
|
||||||
// Run - main function to check schedule
|
// Run - main function to check schedule
|
||||||
func (j *WatchRepositoryTagsJob) Run() {
|
func (j *WatchRepositoryTagsJob) Run() {
|
||||||
reg := j.details.imageRef.Scheme() + "://" + j.details.imageRef.Registry()
|
|
||||||
|
|
||||||
|
creds := credentialshelper.GetCredentials(j.details.trackedImage)
|
||||||
|
|
||||||
|
// reg := j.details.imageRef.Scheme() + "://" + j.details.imageRef.Registry()
|
||||||
|
reg := j.details.trackedImage.Image.Scheme() + "://" + j.details.trackedImage.Image.Registry()
|
||||||
if j.details.latest == "" {
|
if j.details.latest == "" {
|
||||||
j.details.latest = j.details.imageRef.Tag()
|
j.details.latest = j.details.trackedImage.Image.Tag()
|
||||||
}
|
}
|
||||||
|
|
||||||
repository, err := j.registryClient.Get(registry.Opts{
|
repository, err := j.registryClient.Get(registry.Opts{
|
||||||
Registry: reg,
|
Registry: reg,
|
||||||
Name: j.details.imageRef.ShortName(),
|
Name: j.details.trackedImage.Image.ShortName(),
|
||||||
Tag: j.details.latest,
|
Tag: j.details.latest,
|
||||||
Username: j.details.registryUsername,
|
Username: creds.Username,
|
||||||
Password: j.details.registryPassword,
|
Password: creds.Password,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"error": err,
|
"error": err,
|
||||||
"image": j.details.imageRef.Remote(),
|
"image": j.details.trackedImage.Image.String(),
|
||||||
}).Error("trigger.poll.WatchRepositoryTagsJob: failed to get repository")
|
}).Error("trigger.poll.WatchRepositoryTagsJob: failed to get repository")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"current_tag": j.details.imageRef.Tag(),
|
"current_tag": j.details.trackedImage.Image.Tag(),
|
||||||
"repository_tags": repository.Tags,
|
"repository_tags": repository.Tags,
|
||||||
"image_name": j.details.imageRef.Remote(),
|
"image_name": j.details.trackedImage.Image.Remote(),
|
||||||
}).Debug("trigger.poll.WatchRepositoryTagsJob: checking tags")
|
}).Debug("trigger.poll.WatchRepositoryTagsJob: checking tags")
|
||||||
|
|
||||||
latestVersion, newAvailable, err := version.NewAvailable(j.details.latest, repository.Tags)
|
latestVersion, newAvailable, err := version.NewAvailable(j.details.latest, repository.Tags)
|
||||||
|
@ -360,7 +339,8 @@ func (j *WatchRepositoryTagsJob) Run() {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"error": err,
|
"error": err,
|
||||||
"repository_tags": repository.Tags,
|
"repository_tags": repository.Tags,
|
||||||
"image": j.details.imageRef.Remote(),
|
// "image": j.details.imageRef.Remote(),
|
||||||
|
"image": j.details.trackedImage.Image.String(),
|
||||||
}).Error("trigger.poll.WatchRepositoryTagsJob: failed to get latest version from tags")
|
}).Error("trigger.poll.WatchRepositoryTagsJob: failed to get latest version from tags")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -372,13 +352,13 @@ func (j *WatchRepositoryTagsJob) Run() {
|
||||||
j.details.latest = latestVersion
|
j.details.latest = latestVersion
|
||||||
event := types.Event{
|
event := types.Event{
|
||||||
Repository: types.Repository{
|
Repository: types.Repository{
|
||||||
Name: j.details.imageRef.Repository(),
|
Name: j.details.trackedImage.Image.Repository(),
|
||||||
Tag: latestVersion,
|
Tag: latestVersion,
|
||||||
},
|
},
|
||||||
TriggerName: types.TriggerTypePoll.String(),
|
TriggerName: types.TriggerTypePoll.String(),
|
||||||
}
|
}
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"repository": j.details.imageRef.Repository(),
|
"repository": j.details.trackedImage.Image.Repository(),
|
||||||
"new_tag": latestVersion,
|
"new_tag": latestVersion,
|
||||||
}).Info("trigger.poll.WatchRepositoryTagsJob: submiting event to providers")
|
}).Info("trigger.poll.WatchRepositoryTagsJob: submiting event to providers")
|
||||||
j.providers.Submit(event)
|
j.providers.Submit(event)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/keel-hq/keel/approvals"
|
"github.com/keel-hq/keel/approvals"
|
||||||
"github.com/keel-hq/keel/cache/memory"
|
"github.com/keel-hq/keel/cache/memory"
|
||||||
|
"github.com/keel-hq/keel/extension/credentialshelper"
|
||||||
"github.com/keel-hq/keel/provider"
|
"github.com/keel-hq/keel/provider"
|
||||||
"github.com/keel-hq/keel/registry"
|
"github.com/keel-hq/keel/registry"
|
||||||
"github.com/keel-hq/keel/types"
|
"github.com/keel-hq/keel/types"
|
||||||
|
@ -13,6 +14,16 @@ import (
|
||||||
"github.com/keel-hq/keel/util/image"
|
"github.com/keel-hq/keel/util/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func mustParse(img string) *types.TrackedImage {
|
||||||
|
ref, err := image.Parse(img)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &types.TrackedImage{
|
||||||
|
Image: ref,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ======== fake registry client for testing =======
|
// ======== fake registry client for testing =======
|
||||||
type fakeRegistryClient struct {
|
type fakeRegistryClient struct {
|
||||||
opts registry.Opts // opts set if anything called Digest(opts Opts)
|
opts registry.Opts // opts set if anything called Digest(opts Opts)
|
||||||
|
@ -23,6 +34,7 @@ type fakeRegistryClient struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeRegistryClient) Get(opts registry.Opts) (*registry.Repository, error) {
|
func (c *fakeRegistryClient) Get(opts registry.Opts) (*registry.Repository, error) {
|
||||||
|
c.opts = opts
|
||||||
return ®istry.Repository{
|
return ®istry.Repository{
|
||||||
Name: opts.Name,
|
Name: opts.Name,
|
||||||
Tags: c.tagsToReturn,
|
Tags: c.tagsToReturn,
|
||||||
|
@ -30,6 +42,7 @@ func (c *fakeRegistryClient) Get(opts registry.Opts) (*registry.Repository, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeRegistryClient) Digest(opts registry.Opts) (digest string, err error) {
|
func (c *fakeRegistryClient) Digest(opts registry.Opts) (digest string, err error) {
|
||||||
|
c.opts = opts
|
||||||
return c.digestToReturn, nil
|
return c.digestToReturn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +81,9 @@ func TestWatchTagJob(t *testing.T) {
|
||||||
reference, _ := image.Parse("foo/bar:1.1")
|
reference, _ := image.Parse("foo/bar:1.1")
|
||||||
|
|
||||||
details := &watchDetails{
|
details := &watchDetails{
|
||||||
imageRef: reference,
|
trackedImage: &types.TrackedImage{
|
||||||
|
Image: reference,
|
||||||
|
},
|
||||||
digest: "sha256:123123123",
|
digest: "sha256:123123123",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +128,9 @@ func TestWatchTagJobLatest(t *testing.T) {
|
||||||
reference, _ := image.Parse("foo/bar:latest")
|
reference, _ := image.Parse("foo/bar:latest")
|
||||||
|
|
||||||
details := &watchDetails{
|
details := &watchDetails{
|
||||||
imageRef: reference,
|
trackedImage: &types.TrackedImage{
|
||||||
|
Image: reference,
|
||||||
|
},
|
||||||
digest: "sha256:123123123",
|
digest: "sha256:123123123",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +175,9 @@ func TestWatchAllTagsJob(t *testing.T) {
|
||||||
reference, _ := image.Parse("foo/bar:1.1.0")
|
reference, _ := image.Parse("foo/bar:1.1.0")
|
||||||
|
|
||||||
details := &watchDetails{
|
details := &watchDetails{
|
||||||
imageRef: reference,
|
trackedImage: &types.TrackedImage{
|
||||||
|
Image: reference,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
job := NewWatchRepositoryTagsJob(providers, frc, details)
|
job := NewWatchRepositoryTagsJob(providers, frc, details)
|
||||||
|
@ -192,7 +211,9 @@ func TestWatchAllTagsJobCurrentLatest(t *testing.T) {
|
||||||
reference, _ := image.Parse("foo/bar:latest")
|
reference, _ := image.Parse("foo/bar:latest")
|
||||||
|
|
||||||
details := &watchDetails{
|
details := &watchDetails{
|
||||||
imageRef: reference,
|
trackedImage: &types.TrackedImage{
|
||||||
|
Image: reference,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
job := NewWatchRepositoryTagsJob(providers, frc, details)
|
job := NewWatchRepositoryTagsJob(providers, frc, details)
|
||||||
|
@ -257,10 +278,10 @@ func TestWatchMultipleTags(t *testing.T) {
|
||||||
|
|
||||||
watcher := NewRepositoryWatcher(providers, frc)
|
watcher := NewRepositoryWatcher(providers, frc)
|
||||||
|
|
||||||
watcher.Watch("gcr.io/v2-namespace/hello-world:1.1.1", "@every 10m", "", "")
|
watcher.Watch(mustParse("gcr.io/v2-namespace/hello-world:1.1.1"), "@every 10m")
|
||||||
watcher.Watch("gcr.io/v2-namespace/greetings-world:1.1.1", "@every 10m", "", "")
|
watcher.Watch(mustParse("gcr.io/v2-namespace/greetings-world:1.1.1"), "@every 10m")
|
||||||
watcher.Watch("gcr.io/v2-namespace/greetings-world:alpha", "@every 10m", "", "")
|
watcher.Watch(mustParse("gcr.io/v2-namespace/greetings-world:alpha"), "@every 10m")
|
||||||
watcher.Watch("gcr.io/v2-namespace/greetings-world:master", "@every 10m", "", "")
|
watcher.Watch(mustParse("gcr.io/v2-namespace/greetings-world:master"), "@every 10m")
|
||||||
|
|
||||||
if len(watcher.watched) != 4 {
|
if len(watcher.watched) != 4 {
|
||||||
t.Errorf("expected to find watching 4 entries, found: %d", len(watcher.watched))
|
t.Errorf("expected to find watching 4 entries, found: %d", len(watcher.watched))
|
||||||
|
@ -287,3 +308,64 @@ func TestWatchMultipleTags(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeCredentialsHelper struct {
|
||||||
|
|
||||||
|
// set by the caller
|
||||||
|
getImageRequest *types.TrackedImage
|
||||||
|
|
||||||
|
// credentials to return
|
||||||
|
creds *types.Credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fch *fakeCredentialsHelper) GetCredentials(image *types.TrackedImage) (*types.Credentials, error) {
|
||||||
|
fch.getImageRequest = image
|
||||||
|
return fch.creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fch *fakeCredentialsHelper) IsEnabled() bool { return true }
|
||||||
|
|
||||||
|
func TestWatchTagJobCheckCredentials(t *testing.T) {
|
||||||
|
|
||||||
|
fakeHelper := &fakeCredentialsHelper{
|
||||||
|
creds: &types.Credentials{
|
||||||
|
Username: "user-xx",
|
||||||
|
Password: "pass-xx",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialshelper.RegisterCredentialsHelper("fake", fakeHelper)
|
||||||
|
defer credentialshelper.UnregisterCredentialsHelper("fake")
|
||||||
|
|
||||||
|
fp := &fakeProvider{}
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
reference, _ := image.Parse("foo/bar:1.1")
|
||||||
|
|
||||||
|
details := &watchDetails{
|
||||||
|
trackedImage: &types.TrackedImage{
|
||||||
|
Image: reference,
|
||||||
|
},
|
||||||
|
digest: "sha256:123123123",
|
||||||
|
}
|
||||||
|
|
||||||
|
job := NewWatchTagJob(providers, frc, details)
|
||||||
|
|
||||||
|
job.Run()
|
||||||
|
|
||||||
|
// checking whether new job was submitted
|
||||||
|
|
||||||
|
if frc.opts.Password != "pass-xx" {
|
||||||
|
t.Errorf("unexpected password for registry: %s", frc.opts.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
if frc.opts.Username != "user-xx" {
|
||||||
|
t.Errorf("unexpected username for registry: %s", frc.opts.Username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue