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(cli): make regexp for excluded namespace configurable #79

Merged
merged 11 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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("^$"))
})
erikgb marked this conversation as resolved.
Show resolved Hide resolved

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")
}