Skip to content

Commit

Permalink
clair: add "mode" argument
Browse files Browse the repository at this point in the history
This change promotes "mode" out of the config file and makes it settable
via the command line and the environment.

Additional changes are introduced that make sure a single configuration
file can work in multiple "modes" and there's some clean-up changes.

Signed-off-by: Hank Donnay <hdonnay@redhat.com>
  • Loading branch information
hdonnay committed Mar 6, 2020
1 parent b3e0100 commit fd5993f
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 36 deletions.
20 changes: 13 additions & 7 deletions cmd/clair/httptransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ import (
"github.com/quay/clair/v4/config"
"github.com/quay/clair/v4/indexer"
"github.com/quay/clair/v4/matcher"
"github.com/quay/clair/v4/middleware/auth"
"github.com/quay/clair/v4/middleware/compress"
)

// httptransport configures an http server according to Clair's operation mode.
func httptransport(ctx context.Context, conf config.Config) (*http.Server, error) {
var srv *http.Server
var err error
switch {
case conf.Mode == config.DevMode:
srv, err = devMode(ctx, conf)
case conf.Mode == config.ComboMode:
srv, err = comboMode(ctx, conf)
case conf.Mode == config.IndexerMode:
srv, err = indexerMode(ctx, conf)
case conf.Mode == config.MatcherMode:
Expand All @@ -42,7 +44,7 @@ func httptransport(ctx context.Context, conf config.Config) (*http.Server, error
return srv, nil
}

func devMode(ctx context.Context, conf config.Config) (*http.Server, error) {
func comboMode(ctx context.Context, conf config.Config) (*http.Server, error) {
libI, err := libindex.New(ctx, &libindex.Opts{
ConnString: conf.Indexer.ConnString,
ScanLockRetry: time.Duration(conf.Indexer.ScanLockRetry) * time.Second,
Expand Down Expand Up @@ -98,17 +100,21 @@ func indexerMode(ctx context.Context, conf config.Config) (*http.Server, error)
return nil, err
}
return &http.Server{
Addr: conf.Indexer.HTTPListenAddr,
Addr: conf.HTTPListenAddr,
Handler: othttp.NewHandler(compress.Handler(indexer), "server"),
}, nil
}

func matcherMode(ctx context.Context, conf config.Config) (*http.Server, error) {
libV, err := libvuln.New(ctx, &libvuln.Opts{
vopt := libvuln.Opts{
MaxConnPool: int32(conf.Matcher.MaxConnPool),
ConnString: conf.Matcher.ConnString,
Migrations: conf.Matcher.Migrations,
})
}
if conf.Matcher.Updaters != nil {
vopt.Run = *conf.Matcher.Updaters
}
libV, err := libvuln.New(ctx, &vopt)
if err != nil {
return nil, fmt.Errorf("failed to initialize libvuln: %v", err)
}
Expand All @@ -122,7 +128,7 @@ func matcherMode(ctx context.Context, conf config.Config) (*http.Server, error)
return nil, err
}
return &http.Server{
Addr: conf.Matcher.HTTPListenAddr,
Addr: conf.HTTPListenAddr,
Handler: othttp.NewHandler(compress.Handler(matcher), "server"),
}, nil
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/clair/introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ func introspection(ctx context.Context, cfg *config.Config, healthCheck func() b
// tracing.
DefaultSampler: sdktrace.NeverSample(),
}
if cfg.Mode == config.DevMode {
// Unless we're in dev mode, then go ham.
if logLevel(cfg) == zerolog.DebugLevel {
// Unless we're debugging, then go ham.
traceCfg.DefaultSampler = sdktrace.AlwaysSample()
}
if p := cfg.Trace.Probability; p != nil {
Expand Down
37 changes: 26 additions & 11 deletions cmd/clair/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"time"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
yaml "gopkg.in/yaml.v2"

"github.com/quay/clair/v4/config"
Expand All @@ -22,37 +21,50 @@ import (
// Version is a version string, optionally injected at build time.
var Version string

const (
envConfig = `CLAIR_CONF`
envMode = `CLAIR_MODE`
)

func main() {
// parse conf cli
var confv ConfValue
flag.Var(&confv, "conf", "The file system path to Clair's config file.")
var (
confFile ConfValue
conf config.Config
runMode ConfMode
)
confFile.Set(os.Getenv(envConfig))
runMode.Set(os.Getenv(envMode))
flag.Var(&confFile, "conf", "The file system path to Clair's config file.")
flag.Var(&runMode, "mode", "The operation mode for this server.")
flag.Parse()
if confv.String() == "" {
golog.Fatalf("must provide a -conf flag")
if confFile.String() == "" {
golog.Fatalf("must provide a -conf flag or set %q in the environment", envConfig)
}

// validate config
var conf config.Config
err := yaml.NewDecoder(confv.file).Decode(&conf)
err := yaml.NewDecoder(confFile.file).Decode(&conf)
if err != nil {
golog.Fatalf("failed to decode yaml config: %v", err)
}
conf.Mode = runMode.String()
err = config.Validate(conf)
if err != nil {
golog.Fatalf("failed to validate config: %v", err)
}

// setup global log level
var out io.Writer = os.Stderr
if conf.Mode == config.DevMode {
ll := logLevel(&conf)
if ll == zerolog.DebugLevel {
out = zerolog.ConsoleWriter{
Out: os.Stderr,
}
}
log := zerolog.New(out).With().
Timestamp().
Logger().
Level(logLevel(conf))
Level(ll)

// create global application context
ctx, cancel := context.WithCancel(context.Background())
Expand All @@ -65,6 +77,10 @@ func main() {
logfunc := func(_ net.Listener) context.Context {
return ctx
}
log.Info().
Str("mode", runMode.String()).
Str("config", confFile.String()).
Msg("start")

// Make sure to configure our metrics and tracing providers before creating
// any package objects that may close over a provider.
Expand Down Expand Up @@ -144,11 +160,10 @@ func main() {
}
}

func logLevel(conf config.Config) zerolog.Level {
func logLevel(conf *config.Config) zerolog.Level {
level := strings.ToLower(conf.LogLevel)
switch level {
case "debug":
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
return zerolog.DebugLevel
case "info":
return zerolog.InfoLevel
Expand Down
53 changes: 53 additions & 0 deletions cmd/clair/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,56 @@ func (v *ConfValue) Set(s string) error {
v.file = f
return nil
}

const (
_ ConfMode = iota
ModeCombo
ModeIndexer
ModeMatcher
ModeNotifier
)

// ConfMode enumerates the arguments that are acceptable "modes".
type ConfMode int

func (v *ConfMode) String() string {
if v == nil {
return ""
}
switch *v {
case ModeCombo:
return "combo"
case ModeIndexer:
return "indexer"
case ModeMatcher:
return "matcher"
case ModeNotifier:
return "notifier"
default:
}
return "invalid"
}

// Get implements flag.Getter
func (v *ConfMode) Get() interface{} {
return *v
}

// Set implements flag.Value
func (v *ConfMode) Set(s string) error {
switch s {
case "", "dev":
fallthrough
case "combo", "combination", "pizza": // "Pizza", of course, being the best Combos flavor.
*v = ModeCombo
case "index", "indexer":
*v = ModeIndexer
case "match", "matcher":
*v = ModeMatcher
case "notify", "notifier":
*v = ModeNotifier
default:
return fmt.Errorf("unknown mode argument %q", s)
}
return nil
}
34 changes: 18 additions & 16 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,30 @@ import (
"strings"
)

// These are the mode arguments that calling code can use in the Mode member of
// a Config struct. They're called out here for documentation.
const (
// receives claircore.Manifest(s), indexes their contents, and returns claircore.IndexReport(s)
// IndexerMode receives claircore.Manifests, indexes their contents, and
// returns claircore.IndexReports.
IndexerMode = "indexer"
// populates and updates the vulnerability database and creates claircore.VulnerabilityReport(s) from claircore.IndexReport(s)
// MatcherMode populates and updates the vulnerability database and creates
// claircore.VulnerabilityReports from claircore.IndexReports.
MatcherMode = "matcher"
// runs both indexer and matcher in a single process communicating over local api
DevMode = "dev"
// ComboMode runs all services in a single process.
ComboMode = "combo"
)

type Config struct {
// indicates a Clair node's operational mode
Mode string `yaml:"mode"`
// indicates the global listen address if running in "Dev"
// Mode indicates a Clair node's operational mode.
//
// This should be set by code that's populating and validating a config.
Mode string `yaml:"-"`
// indicates the HTTP listen address
HTTPListenAddr string `yaml:"http_listen_addr"`
// IntrospectionAddr is an address to listen on for introspection http
// endpoints, e.g. metrics and profiling.
//
// If not provided, a random port will be chosen.
IntrospectionAddr string `yaml:"introspection_addr"`
// indicates log level for the process
LogLevel string `yaml:"log_level"`
Expand All @@ -42,8 +50,6 @@ type Auth struct {
}

type Indexer struct {
// indicates the listening http address if mode is 'indexer'
HTTPListenAddr string `yaml:"http_listen_addr"`
// the conn string to the datastore
ConnString string `yaml:"connstring"`
// the interval in seconds to retry a manifest scan if the lock was not acquired
Expand All @@ -55,14 +61,10 @@ type Indexer struct {
}

type Matcher struct {
// indicates the listening http address if mode is 'matcher'
HTTPListenAddr string `yaml:"http_listen_addr"`
// the conn string to the datastore
ConnString string `yaml:"connstring"`
// if sql usage, the connection pool size
MaxConnPool int `yaml:"max_conn_pool"`
// a regex pattern of updaters to run
Run string `yaml:"run"`
// the address where the indexer service can be reached
IndexerAddr string `yaml:"indexer_addr"`
// should the Matcher be responsible for setting up the database
Expand Down Expand Up @@ -107,7 +109,7 @@ type Dogstatsd struct {

func Validate(conf Config) error {
switch strings.ToLower(conf.Mode) {
case DevMode:
case ComboMode:
if conf.HTTPListenAddr == "" {
return fmt.Errorf("dev mode selected but no global HTTPListenAddr")
}
Expand All @@ -118,14 +120,14 @@ func Validate(conf Config) error {
return fmt.Errorf("matcher mode requires a database connection string")
}
case IndexerMode:
if conf.Indexer.HTTPListenAddr == "" {
if conf.HTTPListenAddr == "" {
return fmt.Errorf("indexer mode selected but no http listen address")
}
if conf.Indexer.ConnString == "" {
return fmt.Errorf("indexer mode requires a database connection string")
}
case MatcherMode:
if conf.Matcher.HTTPListenAddr == "" {
if conf.HTTPListenAddr == "" {
return fmt.Errorf("matcher mode selected but no http listen address")
}
if conf.Matcher.ConnString == "" {
Expand Down

0 comments on commit fd5993f

Please sign in to comment.