diff --git a/cmd/root.go b/cmd/root.go index 1be52a8ed..57abdb55e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -189,7 +189,7 @@ func Run(c *cobra.Command, names []string) { httpAPI := api.New(apiToken) if enableUpdateAPI { - updateHandler := update.New(func() { runUpdatesWithNotifications(filter) }, updateLock) + updateHandler := update.New(func(images []string) { runUpdatesWithNotifications(filters.FilterByImage(images, filter)) }, updateLock) httpAPI.RegisterFunc(updateHandler.Path, updateHandler.Handle) // If polling isn't enabled the scheduler is never started and // we need to trigger the startup messages manually. @@ -293,7 +293,7 @@ func writeStartupMessage(c *cobra.Command, sched time.Time, filtering string) { startupLog.Info("Scheduling first run: " + sched.Format("2006-01-02 15:04:05 -0700 MST")) startupLog.Info("Note that the first check will be performed in " + until) } else if runOnce, _ := c.PersistentFlags().GetBool("run-once"); runOnce { - startupLog.Info("Running a one time update.") + startupLog.Info("Running a one time update.") } else { startupLog.Info("Periodic runs are not enabled.") } diff --git a/pkg/api/update/update.go b/pkg/api/update/update.go index 4721e3ef2..ba044ab50 100644 --- a/pkg/api/update/update.go +++ b/pkg/api/update/update.go @@ -4,6 +4,7 @@ import ( "io" "net/http" "os" + "strings" log "github.com/sirupsen/logrus" ) @@ -13,7 +14,7 @@ var ( ) // New is a factory function creating a new Handler instance -func New(updateFn func(), updateLock chan bool) *Handler { +func New(updateFn func(images []string), updateLock chan bool) *Handler { if updateLock != nil { lock = updateLock } else { @@ -29,7 +30,7 @@ func New(updateFn func(), updateLock chan bool) *Handler { // Handler is an API handler used for triggering container update scans type Handler struct { - fn func() + fn func(images []string) Path string } @@ -43,12 +44,29 @@ func (handle *Handler) Handle(w http.ResponseWriter, r *http.Request) { return } - select { - case chanValue := <-lock: + var images []string + imageQueries, found := r.URL.Query()["image"] + if found { + for _, image := range imageQueries { + images = append(images, strings.Split(image, ",")...) + } + + } else { + images = nil + } + + if len(images) > 0 { + chanValue := <-lock defer func() { lock <- chanValue }() - handle.fn() - default: - log.Debug("Skipped. Another update already running.") + handle.fn(images) + } else { + select { + case chanValue := <-lock: + defer func() { lock <- chanValue }() + handle.fn(images) + default: + log.Debug("Skipped. Another update already running.") + } } } diff --git a/pkg/container/mocks/FilterableContainer.go b/pkg/container/mocks/FilterableContainer.go index 1ae812535..fa863b51a 100644 --- a/pkg/container/mocks/FilterableContainer.go +++ b/pkg/container/mocks/FilterableContainer.go @@ -78,3 +78,17 @@ func (_m *FilterableContainer) Scope() (string, bool) { return r0, r1 } + +// ImageName provides a mock function with given fields: +func (_m *FilterableContainer) ImageName() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} diff --git a/pkg/filters/filters.go b/pkg/filters/filters.go index 18f39c257..a283301da 100644 --- a/pkg/filters/filters.go +++ b/pkg/filters/filters.go @@ -70,6 +70,24 @@ func FilterByScope(scope string, baseFilter t.Filter) t.Filter { } } +// FilterByImage returns all containers that have a specific image +func FilterByImage(images []string, baseFilter t.Filter) t.Filter { + if images == nil { + return baseFilter + } + + return func(c t.FilterableContainer) bool { + image := strings.Split(c.ImageName(), ":")[0] + for _, targetImage := range images { + if image == targetImage { + return baseFilter(c) + } + } + + return false + } +} + // BuildFilter creates the needed filter of containers func BuildFilter(names []string, enableLabel bool, scope string) (t.Filter, string) { sb := strings.Builder{} diff --git a/pkg/filters/filters_test.go b/pkg/filters/filters_test.go index 3b52b5ee5..c07b1812f 100644 --- a/pkg/filters/filters_test.go +++ b/pkg/filters/filters_test.go @@ -110,6 +110,43 @@ func TestFilterByDisabledLabel(t *testing.T) { container.AssertExpectations(t) } +func TestFilterByImage(t *testing.T) { + filterEmpty := FilterByImage(nil, NoFilter) + filterSingle := FilterByImage([]string{"registry"}, NoFilter) + filterMultiple := FilterByImage([]string{"registry", "bla"}, NoFilter) + assert.NotNil(t, filterSingle) + assert.NotNil(t, filterMultiple) + + container := new(mocks.FilterableContainer) + container.On("ImageName").Return("registry:2") + assert.True(t, filterEmpty(container)) + assert.True(t, filterSingle(container)) + assert.True(t, filterMultiple(container)) + container.AssertExpectations(t) + + container = new(mocks.FilterableContainer) + container.On("ImageName").Return("registry:latest") + assert.True(t, filterEmpty(container)) + assert.True(t, filterSingle(container)) + assert.True(t, filterMultiple(container)) + container.AssertExpectations(t) + + container = new(mocks.FilterableContainer) + container.On("ImageName").Return("abcdef1234") + assert.True(t, filterEmpty(container)) + assert.False(t, filterSingle(container)) + assert.False(t, filterMultiple(container)) + container.AssertExpectations(t) + + container = new(mocks.FilterableContainer) + container.On("ImageName").Return("bla:latest") + assert.True(t, filterEmpty(container)) + assert.False(t, filterSingle(container)) + assert.True(t, filterMultiple(container)) + container.AssertExpectations(t) + +} + func TestBuildFilter(t *testing.T) { var names []string names = append(names, "test") diff --git a/pkg/types/filterable_container.go b/pkg/types/filterable_container.go index 3c462954a..b410b1cbc 100644 --- a/pkg/types/filterable_container.go +++ b/pkg/types/filterable_container.go @@ -7,4 +7,5 @@ type FilterableContainer interface { IsWatchtower() bool Enabled() (bool, bool) Scope() (string, bool) + ImageName() string }