Skip to content

Commit

Permalink
feat(cli): make regexp for excluded namespace configurable (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgb authored Jan 30, 2023
1 parent ee358da commit 9a50838
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 35 deletions.
11 changes: 10 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"flag"
"log"

"github.com/spf13/pflag"

"github.com/statnett/image-scanner-operator/internal/config"
"github.com/statnett/image-scanner-operator/internal/operator"
)
Expand All @@ -13,7 +15,14 @@ func main() {
cfg.Zap.Development = true

opr := operator.Operator{}
if err := opr.BindConfig(&cfg, flag.CommandLine); err != nil {
if err := opr.BindFlags(&cfg, flag.CommandLine); err != nil {
log.Fatal(err)
}

pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()

if err := opr.UnmarshalConfig(&cfg); err != nil {
log.Fatal(err)
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.19
require (
github.com/distribution/distribution v2.8.1+incompatible
github.com/go-logr/logr v1.2.3
github.com/mitchellh/mapstructure v1.5.0
github.com/onsi/ginkgo/v2 v2.7.1
github.com/onsi/gomega v1.26.0
github.com/opencontainers/go-digest v1.0.0
Expand Down Expand Up @@ -57,7 +58,6 @@ require (
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
Expand Down
18 changes: 10 additions & 8 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"regexp"
"time"

"github.com/statnett/controller-runtime-viper/pkg/zap"
Expand All @@ -9,14 +10,15 @@ import (
)

type Config struct {
MetricsLabels []string `mapstructure:"cis-metrics-labels"`
ScanInterval time.Duration `mapstructure:"scan-interval"`
ScanJobNamespace string `mapstructure:"scan-job-namespace"`
ScanJobServiceAccount string `mapstructure:"scan-job-service-account"`
ScanNamespaces []string `mapstructure:"namespaces"`
ScanWorkloadResources []string `mapstructure:"scan-workload-resources"`
TrivyImage string `mapstructure:"trivy-image"`
Zap zap.Options `mapstructure:"-"`
MetricsLabels []string `mapstructure:"cis-metrics-labels"`
ScanInterval time.Duration `mapstructure:"scan-interval"`
ScanJobNamespace string `mapstructure:"scan-job-namespace"`
ScanJobServiceAccount string `mapstructure:"scan-job-service-account"`
ScanNamespaces []string `mapstructure:"namespaces"`
ScanNamespaceExcludeRegexp *regexp.Regexp `mapstructure:"scan-namespace-exclude-regexp"`
ScanWorkloadResources []string `mapstructure:"scan-workload-resources"`
TrivyImage string `mapstructure:"trivy-image"`
Zap zap.Options `mapstructure:"-"`
}

func (c Config) TimeUntilNextScan(cis *stasv1alpha1.ContainerImageScan) time.Duration {
Expand Down
6 changes: 6 additions & 0 deletions internal/controller/stas/containerimagescan_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,18 @@ func (r *ContainerImageScanReconciler) Reconcile(ctx context.Context, req ctrl.R

// SetupWithManager sets up the controller with the Manager.
func (r *ContainerImageScanReconciler) SetupWithManager(mgr ctrl.Manager) error {
var predicates []predicate.Predicate
if r.ScanNamespaceExcludeRegexp != nil {
predicates = append(predicates, predicate.Not(namespaceMatchRegexp(r.ScanNamespaceExcludeRegexp)))
}

return ctrl.NewControllerManagedBy(mgr).
For(&stasv1alpha1.ContainerImageScan{},
builder.WithPredicates(
predicate.GenerationChangedPredicate{},
ignoreDeletionPredicate(),
)).
WithEventFilter(predicate.And(predicates...)).
Complete(r)
}

Expand Down
10 changes: 5 additions & 5 deletions internal/controller/stas/predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import (
stasv1alpha1 "github.com/statnett/image-scanner-operator/api/stas/v1alpha1"
)

var systemNamespaceRegex = regexp.MustCompile("^(kube-|openshift-).*")

var systemNamespace = predicate.NewPredicateFuncs(func(object client.Object) bool {
return systemNamespaceRegex.MatchString(object.GetNamespace())
})
func namespaceMatchRegexp(re *regexp.Regexp) predicate.Predicate {
return predicate.NewPredicateFuncs(func(object client.Object) bool {
return re.MatchString(object.GetNamespace())
})
}

func podContainerStatusImagesChanged() predicate.Predicate {
return predicate.Funcs{
Expand Down
12 changes: 8 additions & 4 deletions internal/controller/stas/workload_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,21 @@ func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error {
groupKinds[i] = k.GroupKind()
}

predicates := []predicate.Predicate{
predicate.Not(managedByImageScanner),
}
if r.ScanNamespaceExcludeRegexp != nil {
predicates = append(predicates, predicate.Not(namespaceMatchRegexp(r.ScanNamespaceExcludeRegexp)))
}

bldr := ctrl.NewControllerManagedBy(mgr).
For(&corev1.Pod{},
builder.WithPredicates(
podContainerStatusImagesChanged(),
predicate.Or(controllerInKinds(groupKinds...), noController),
ignoreDeletionPredicate(),
)).
WithEventFilter(predicate.And(
predicate.Not(systemNamespace),
predicate.Not(managedByImageScanner),
)).
WithEventFilter(predicate.And(predicates...)).
Watches(&source.Kind{Type: &stasv1alpha1.ContainerImageScan{}},
&handler.EnqueueRequestForOwner{OwnerType: &corev1.Pod{}},
builder.WithPredicates(
Expand Down
26 changes: 26 additions & 0 deletions internal/operator/decode_hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package operator

import (
"reflect"
"regexp"

"github.com/mitchellh/mapstructure"
)

// stringToRegexpHookFunc returns a DecodeHookFunc that converts strings to regexp.Regexp.
func stringToRegexpHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}

if t != reflect.TypeOf(&regexp.Regexp{}) {
return data, nil
}

return regexp.Compile(data.(string))
}
}
41 changes: 25 additions & 16 deletions internal/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"time"

"github.com/mitchellh/mapstructure"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/statnett/controller-runtime-viper/pkg/zap"
Expand Down Expand Up @@ -46,25 +47,24 @@ func init() {

type Operator struct{}

func (o Operator) BindConfig(cfg *config.Config, fs *flag.FlagSet) error {
flag.String("metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.String("health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.Bool("leader-elect", false,
func (o Operator) BindFlags(cfg *config.Config, fs *flag.FlagSet) error {
fs.String("metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
fs.String("health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
fs.Bool("leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.Bool("enable-profiling", false, "Enable profiling (pprof); available on metrics endpoint.")
flag.String("namespaces", "", "comma-separated list of namespaces to watch")
flag.String("cis-metrics-labels", "", "comma-separated list of labels in CIS resources to create metrics labels for")
flag.Duration("scan-interval", 12*time.Hour, "The minimum time between fetch scan reports from image scanner")
flag.String("scan-job-namespace", "", "The namespace to schedule scan jobs.")
flag.String("scan-job-service-account", "default", "The service account used to run scan jobs.")
flag.String("scan-workload-resources", "", "comma-separated list of workload resources to scan")
flag.String("trivy-image", "", "The image used for obtaining the trivy binary.")
flag.Bool("help", false, "print out usage and a summary of options")
fs.Bool("enable-profiling", false, "Enable profiling (pprof); available on metrics endpoint.")
fs.String("namespaces", "", "comma-separated list of namespaces to watch")
fs.String("cis-metrics-labels", "", "comma-separated list of labels in CIS resources to create metrics labels for")
fs.Duration("scan-interval", 12*time.Hour, "The minimum time between fetch scan reports from image scanner")
fs.String("scan-job-namespace", "", "The namespace to schedule scan jobs.")
fs.String("scan-job-service-account", "default", "The service account used to run scan jobs.")
fs.String("scan-workload-resources", "", "comma-separated list of workload resources to scan")
fs.String("scan-namespace-exclude-regexp", "^(kube-|openshift-).*", "regexp for namespace to exclude from scanning")
fs.String("trivy-image", "", "The image used for obtaining the trivy binary.")
fs.Bool("help", false, "print out usage and a summary of options")

cfg.Zap.BindFlags(fs)
pflag.CommandLine.AddGoFlagSet(fs)
pflag.Parse()

pfs := &pflag.FlagSet{}
pfs.AddGoFlagSet(fs)
Expand All @@ -76,13 +76,22 @@ func (o Operator) BindConfig(cfg *config.Config, fs *flag.FlagSet) error {
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))

return nil
}

func (o Operator) UnmarshalConfig(cfg *config.Config) error {
helpRequested := viper.GetBool("help")
if helpRequested {
pflag.Usage()
os.Exit(0)
}

if err := viper.Unmarshal(cfg); err != nil {
hook := mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
stringToRegexpHookFunc(),
)
if err := viper.Unmarshal(cfg, viper.DecodeHook(hook)); err != nil {
return fmt.Errorf("unable to decode config into struct: %w", err)
}

Expand Down
48 changes: 48 additions & 0 deletions internal/operator/operator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package operator

import (
"flag"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/statnett/image-scanner-operator/internal/config"
)

var _ = Describe("Operator config from flags", func() {
var (
opr Operator = Operator{}
fs *flag.FlagSet
cfg *config.Config
)

BeforeEach(func() {
fs = flag.NewFlagSet("test", flag.ExitOnError)
cfg = &config.Config{}
Expect(opr.BindFlags(cfg, fs)).To(Succeed())
})

Context("Using scan-namespace-exclude-regexp flag", func() {
It("Should have correct default", func() {
Expect(fs.Parse(nil)).To(Succeed())
Expect(opr.UnmarshalConfig(cfg)).To(Succeed())
Expect(cfg.ScanNamespaceExcludeRegexp).NotTo(BeNil())
Expect(cfg.ScanNamespaceExcludeRegexp.String()).To(Equal("^(kube-|openshift-).*"))
})

It("Should be configurable", func() {
args := []string{"--scan-namespace-exclude-regexp=^$"}
Expect(fs.Parse(args)).To(Succeed())
Expect(opr.UnmarshalConfig(cfg)).To(Succeed())
Expect(cfg.ScanNamespaceExcludeRegexp).NotTo(BeNil())
Expect(cfg.ScanNamespaceExcludeRegexp.String()).To(Equal("^$"))
})

It("Should error on invalid regexp", func() {
args := []string{"--scan-namespace-exclude-regexp=["}
Expect(fs.Parse(args)).To(Succeed())
Expect(opr.UnmarshalConfig(cfg)).To(MatchError(ContainSubstring("error parsing regexp")))
})
})

})
13 changes: 13 additions & 0 deletions internal/operator/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package operator

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestOperator(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Operator Suite")
}

0 comments on commit 9a50838

Please sign in to comment.