Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(http): optional query parameter to update only containers of a specified image #1289

Merged
merged 12 commits into from
Jun 14, 2022
4 changes: 2 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.")
}
Expand Down
32 changes: 25 additions & 7 deletions pkg/api/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"io"
"net/http"
"os"
"strings"

log "github.com/sirupsen/logrus"
)
Expand All @@ -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 {
Expand All @@ -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
}

Expand All @@ -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.")
}
}

}
14 changes: 14 additions & 0 deletions pkg/container/mocks/FilterableContainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
18 changes: 18 additions & 0 deletions pkg/filters/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
37 changes: 37 additions & 0 deletions pkg/filters/filters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions pkg/types/filterable_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ type FilterableContainer interface {
IsWatchtower() bool
Enabled() (bool, bool)
Scope() (string, bool)
ImageName() string
}