Skip to content

Commit

Permalink
notifier: test mode implementation
Browse files Browse the repository at this point in the history
this commit enables the notifier to be started in a test mode.
the test mode is enabled via an env var "NOTIFIER_TEST_MODE".
in test mode the notifier will send a notification at each poll
interval.

make targets are provided to start and restart the notifier in test
mode.
the local dev configuration can be configured accordingly to point to
the supported delivery method of your choice.

Signed-off-by: ldelossa <ldelossa@redhat.com>
  • Loading branch information
ldelossa authored and ldelossa committed Aug 24, 2020
1 parent 4b35c88 commit 9bd4f4d
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 1 deletion.
25 changes: 25 additions & 0 deletions Makefile
Expand Up @@ -23,6 +23,8 @@ goimports-local:

# start a local development environment.
# each services runs in it's own container to test service->service communication.
#
# local dev configuration can be found in "./local-dev/clair/config.yaml"
.PHONY: local-dev-up
local-dev-up: vendor
$(docker-compose) up -d traefik
Expand All @@ -37,6 +39,29 @@ local-dev-up: vendor
$(docker-compose) up -d notifier
$(docker-compose) up -d swagger-ui

# starts a local dev environment for testing notifier
# the notifier will create a notification on very notifier.poll_interval value in the local dev configuration.
#
# the notifier will deliver the notification to the configured deliverer in the local dev configuration.
# the default deliverer is rabbitmq/amqp
#
# local dev configuration can be found in "./local-dev/clair/config.yaml"
.PHONY: local-dev-notifier-test
local-dev-notifier-test: vendor
$(docker-compose) up -d traefik
$(docker-compose) up -d jaeger
$(docker-compose) up -d prometheus
$(docker-compose) up -d rabbitmq
$(docker-compose) up -d activemq
$(docker-compose) up -d clair-db
$(docker) exec -it clair-db bash -c 'while ! pg_isready; do echo "waiting for postgres"; sleep 2; done'
$(docker-compose) up -d notifier-test-mode
$(docker-compose) up -d swagger-ui

.PHONY: local-dev-notifier-test-restart
local-dev-notifier-test-restart: vendor
$(docker-compose) up -d --force-recreate notifier-test-mode

vendor: vendor/modules.txt

vendor/modules.txt: go.mod
Expand Down
20 changes: 20 additions & 0 deletions docker-compose.yaml
Expand Up @@ -75,6 +75,26 @@ services:
- "traefik.http.routers.index_report.entrypoints=clair"
- "traefik.http.routers.index_report.rule=PathPrefix(`/api/v1/notification`)"

# this should only be created and deleted via the make target "local-dev-notifier-test"
notifier-test-mode:
container_name: clair-notifier
image: quay.io/claircore/golang:1.13.5
ports:
- "8079:8080"
- "8088:8089"
volumes:
- "./:/src/clair/"
environment:
CLAIR_CONF: "/src/clair/local-dev/clair/config.yaml"
CLAIR_MODE: "notifier"
NOTIFIER_TEST_MODE: "true"
command:
["bash", "-c", "cd /src/clair/cmd/clair; go run -mod vendor ."]
labels:
- "traefik.enable=true"
- "traefik.http.routers.index_report.entrypoints=clair"
- "traefik.http.routers.index_report.rule=PathPrefix(`/api/v1/notification`)"

indexer:
container_name: clair-indexer
image: quay.io/claircore/golang:1.13.5
Expand Down
2 changes: 1 addition & 1 deletion local-dev/clair/config.yaml
Expand Up @@ -18,7 +18,7 @@ notifier:
connstring: host=clair-db port=5432 user=clair dbname=clair sslmode=disable
migrations: true
delivery_interval: 5s
poll_interval: 5s
poll_interval: 15s
# webhook:
# target: "http://webhook/"
# callback: "http://clair-notifier/api/v1/notifications"
Expand Down
5 changes: 5 additions & 0 deletions matcher/mock.go
Expand Up @@ -2,6 +2,7 @@ package matcher

import (
"context"
"sync"

"github.com/google/uuid"
"github.com/quay/claircore"
Expand All @@ -18,6 +19,10 @@ type Mock struct {
LatestUpdateOperations_ func(context.Context) (map[string][]driver.UpdateOperation, error)
UpdateDiff_ func(context.Context, uuid.UUID, uuid.UUID) (*driver.UpdateDiff, error)
Scan_ func(context.Context, *claircore.IndexReport) (*claircore.VulnerabilityReport, error)
// TestUOs provide memory for the mock.
// usage of this field can be dictated by the test case's needs.
sync.Mutex
TestUOs map[string][]driver.UpdateOperation
}

// DeleteUpdateOperations marks the provided refs as seen and processed.
Expand Down
19 changes: 19 additions & 0 deletions notifier/service/service.go
Expand Up @@ -3,6 +3,7 @@ package service
import (
"context"
"fmt"
"os"
"time"

"github.com/google/uuid"
Expand Down Expand Up @@ -101,6 +102,12 @@ func New(ctx context.Context, opts Opts) (*service, error) {
return nil, fmt.Errorf("failed to initialize key manager: %v", err)
}

// check for test mode
if tm := os.Getenv("NOTIFIER_TEST_MODE"); tm != "" {
log.Info().Str("interval", opts.PollInterval.String()).Msg("NOTIFIER TEST MODE ENABLED. NOTIFIER WILL CREATE TEST NOTIFICATIONS ON A SET INTERVAL")
testModeInit(ctx, &opts)
}

// kick off the poller
log.Info().Str("interval", opts.PollInterval.String()).Msg("intializing poller")
poller := notifier.NewPoller(opts.PollInterval, store, opts.Matcher)
Expand Down Expand Up @@ -144,6 +151,18 @@ func New(ctx context.Context, opts Opts) (*service, error) {
}, nil
}

// testModeInit will inject a mock Indexer and Matcher into opts
// to be used in testing mode.
func testModeInit(ctx context.Context, opts *Opts) error {
mm := &matcher.Mock{}
im := &indexer.Mock{}
matcherForTestMode(mm)
indexerForTestMode(im)
opts.Matcher = mm
opts.Indexer = im
return nil
}

func storeInit(ctx context.Context, opts Opts) (*postgres.Store, *postgres.KeyStore, *sqlx.DB, error) {
log := zerolog.Ctx(ctx).With().
Str("component", "notifier/service/storeInit").
Expand Down
114 changes: 114 additions & 0 deletions notifier/service/testmode.go
@@ -0,0 +1,114 @@
package service

import (
"context"
"crypto/rand"
"crypto/sha256"
"time"

"github.com/google/uuid"
"github.com/quay/clair/v4/indexer"
"github.com/quay/clair/v4/matcher"
"github.com/quay/claircore"
"github.com/quay/claircore/libvuln/driver"
)

// indexerForTestMode configures a mock Indexer service for notifier test mode.
//
// in notifier test mode a notifier.Processor will request "indexer.AffectedManifest" with a
// set of vulnerabilities at which point we will return a mock affected vulnerability.
func indexerForTestMode(mock *indexer.Mock) {
affectedManifests := func(ctx context.Context, vulns []claircore.Vulnerability) (claircore.AffectedManifests, error) {
if len(vulns) == 0 {
return claircore.NewAffectedManifests(), nil
}

data := make([]byte, sha256.Size)
_, err := rand.Read(data)
if err != nil {
return claircore.AffectedManifests{}, err
}
digest, err := claircore.NewDigest("sha256", data)
if err != nil {
return claircore.AffectedManifests{}, err
}
am := claircore.AffectedManifests{
Vulnerabilities: map[string]*claircore.Vulnerability{
vulns[0].ID: &(vulns[0]),
},
VulnerableManifests: map[string][]string{
digest.String(): []string{vulns[0].ID},
},
}
return am, nil
}
mock.AffectedManifests_ = affectedManifests
}

// MatcherForTestMode configures a mock Matcher service for notifier test mode.
//
// in notifier test mode a notifier.Poller will request "matcher.LatestUpdateOperations" at which point
// a new UO pair will be smithed.
//
// next a notifier.Processor will request "matcher.UpdateOperations" and look for "test-updater" UOs in which
// the smithed pair will be returned.
//
// finally a notifier.Processor will request to "matcher.UpdateDiff" will be created where a mock added vulnerability
// will be returned.
func matcherForTestMode(mock *matcher.Mock) {
latestUpdateOperations := func(context.Context) (map[string][]driver.UpdateOperation, error) {
latest := driver.UpdateOperation{
Ref: uuid.New(),
Updater: "test-updater",
Fingerprint: "test-fingerprint",
}
older := driver.UpdateOperation{
Ref: uuid.New(),
Updater: "test-updater",
Fingerprint: "test-fingerprint",
}
mock.Lock()
defer mock.Unlock()
mock.TestUOs = map[string][]driver.UpdateOperation{
"test-updater": []driver.UpdateOperation{latest, older},
}
m := map[string][]driver.UpdateOperation{
latest.Updater: []driver.UpdateOperation{
latest,
},
}
return m, nil
}
updateOperations := func(context.Context, ...string) (map[string][]driver.UpdateOperation, error) {
mock.Lock()
defer mock.Unlock()
m := map[string][]driver.UpdateOperation{}
for k, v := range mock.TestUOs {
m[k] = v
}
return m, nil
}
updateDiff := func(context.Context, uuid.UUID, uuid.UUID) (*driver.UpdateDiff, error) {
v := claircore.Vulnerability{
ID: "0",
Updater: "test-updater",
Name: "test-vulnerability",
Description: "this vulnerability indicates you are running the notifier in test mode.",
Issued: time.Now(),
NormalizedSeverity: claircore.Unknown,
FixedInVersion: "",
}
mock.Lock()
diff := driver.UpdateDiff{
Cur: mock.TestUOs["test-updater"][0],
Prev: mock.TestUOs["test-updater"][1],
Added: []claircore.Vulnerability{v},
}
mock.Unlock()
return &diff, nil
}
mock.LatestUpdateOperations_ = latestUpdateOperations
mock.UpdateOperations_ = updateOperations
mock.UpdateDiff_ = updateDiff
return
}

0 comments on commit 9bd4f4d

Please sign in to comment.