diff --git a/cmd/root.go b/cmd/root.go index 9041157..a350410 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -188,7 +188,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. diff --git a/pkg/api/update/update.go b/pkg/api/update/update.go index 4721e3e..ba044ab 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 1ae8125..fa863b5 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 18f39c2..a283301 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 3b52b5e..c07b181 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 3c46295..b410b1c 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 }