Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1ffe549
Add filter for other ConfigMaps within the same namespace
ncaak Jul 14, 2023
12ad75b
Add validationEngine and watcher to reconcilier
ncaak Jul 14, 2023
bfbed74
Add initializer and accessible function from struct
ncaak Jul 14, 2023
db7ff74
Fix Manager setup to account for validationEngine and watcher
ncaak Jul 14, 2023
36f192b
Fix missing channel
ncaak Aug 9, 2023
2d169d1
Decouple Prometheus registry initialization
ncaak Aug 9, 2023
5496b73
Fix missing interface change
ncaak Aug 9, 2023
878979f
Add new utils functions for Prometheus registry decoupling
ncaak Aug 9, 2023
833c15a
Fix global variable missleading
ncaak Aug 9, 2023
e38100b
Refactor validation engine package WIP
ncaak Aug 11, 2023
7c572b7
Refactor re-use existing methods on VE
ncaak Aug 11, 2023
c94677c
Refactor minor changes on initializacion
ncaak Aug 14, 2023
9642fa1
WIP Move configmap watcher to its own package
ncaak Aug 17, 2023
706daed
Refactor main script to improve readability
ncaak Aug 17, 2023
ed1d8b8
Fix redundant system trigger of the informer
ncaak Aug 17, 2023
aef2a12
Rollback VE and CMW dependencies and interface
ncaak Aug 17, 2023
08cc888
Move CMW control to VE package
ncaak Aug 17, 2023
cab232a
Update comments
ncaak Aug 17, 2023
4efd6f4
Fix linting recommendations
ncaak Aug 21, 2023
3288463
WIP Refactor metrics used on Prom registry to be used in VE struct
ncaak Aug 21, 2023
0d9235d
Update documentation
ncaak Aug 22, 2023
9fa2b71
Fix loglines prefixes
ncaak Aug 22, 2023
2a35cf6
Fix linter issues
ncaak Aug 22, 2023
3bea7df
Fix missing prefix on metrics
ncaak Aug 22, 2023
b570cdb
Fix unit tests breaking due to metrics preload
ncaak Aug 22, 2023
2a3abee
Fix nit recommendations
ncaak Aug 24, 2023
99e05d2
Add dynamic namespace setting on watcher
ncaak Aug 24, 2023
dc54e41
Fix unit tests
ncaak Aug 25, 2023
134b73c
Use Pod instead of deployment for namespace
ncaak Aug 29, 2023
f51630d
Move back Watcher to reconcilier
ncaak Aug 29, 2023
c32a514
Add accesses for reconcilier
ncaak Aug 29, 2023
dc542f1
Remove Watcher from VE and fix main script
ncaak Aug 29, 2023
df2b040
Refactor loglines and pod namespace gather function
ncaak Aug 29, 2023
5fbf0c6
Add new logic for removing unexistent checks from configuration
ncaak Aug 31, 2023
9e2dfeb
Minor refactoring
ncaak Aug 31, 2023
d5af598
Add missing documentation and fix tests
ncaak Aug 31, 2023
9eeda44
Unexport validation engine structure as now it's not handled outside …
ncaak Aug 31, 2023
994c690
Add missing operator.yaml fix to get new namespace ENV variable
ncaak Sep 11, 2023
8625560
Add delete scenario in the watcher
ncaak Sep 11, 2023
043a69f
Minor refactoring
ncaak Sep 11, 2023
4f6c8fe
Bundle regenerated with missing env variable
ncaak Sep 12, 2023
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
2 changes: 1 addition & 1 deletion bundle.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/
LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/
LABEL operators.operatorframework.io.bundle.package.v1=deployment-validation-operator
LABEL operators.operatorframework.io.bundle.channels.v1=alpha
LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.28.1
LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.31.0+git
LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1
LABEL operators.operatorframework.io.metrics.project_layout=unknown

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ metadata:
annotations:
alm-examples: '[]'
capabilities: Basic Install
createdAt: "2023-08-24T07:58:38Z"
operators.operatorframework.io/builder: operator-sdk-v1.28.1
createdAt: "2023-09-12T14:13:09Z"
operators.operatorframework.io/builder: operator-sdk-v1.31.0+git
operators.operatorframework.io/project_layout: unknown
name: deployment-validation-operator.v0.0.0
namespace: placeholder
Expand Down Expand Up @@ -82,6 +82,10 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/deployment-validation-operator/dv-operator:latest
imagePullPolicy: Always
livenessProbe:
Expand Down
2 changes: 1 addition & 1 deletion bundle/metadata/annotations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ annotations:
operators.operatorframework.io.bundle.metadata.v1: metadata/
operators.operatorframework.io.bundle.package.v1: deployment-validation-operator
operators.operatorframework.io.bundle.channels.v1: alpha
operators.operatorframework.io.metrics.builder: operator-sdk-v1.28.1
operators.operatorframework.io.metrics.builder: operator-sdk-v1.31.0+git
operators.operatorframework.io.metrics.mediatype.v1: metrics+v1
operators.operatorframework.io.metrics.project_layout: unknown
4 changes: 4 additions & 0 deletions deploy/openshift/operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: dvo-config
mountPath: /config
Expand Down
101 changes: 65 additions & 36 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import (
apis "github.com/app-sre/deployment-validation-operator/api"
dvconfig "github.com/app-sre/deployment-validation-operator/config"
"github.com/app-sre/deployment-validation-operator/internal/options"
"github.com/app-sre/deployment-validation-operator/pkg/configmap"
"github.com/app-sre/deployment-validation-operator/pkg/controller"
dvo_prom "github.com/app-sre/deployment-validation-operator/pkg/prometheus"
dvoProm "github.com/app-sre/deployment-validation-operator/pkg/prometheus"
"github.com/app-sre/deployment-validation-operator/pkg/validations"
"github.com/app-sre/deployment-validation-operator/version"
"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -86,68 +87,66 @@ func setupManager(log logr.Logger, opts options.Options) (manager.Manager, error
return nil, fmt.Errorf("getting config: %w", err)
}

log.Info("Initialize Scheme")
log.Info("Initialize Manager")

scheme, err := initializeScheme()
mgr, err := initManager(log, opts, cfg)
if err != nil {
return nil, fmt.Errorf("initializing scheme: %w", err)
return nil, fmt.Errorf("initializing manager: %w", err)
}

log.Info("Initialize Manager")
log.Info("Registering Components")

mgrOpts, err := getManagerOptions(scheme, opts)
if err != nil {
return nil, fmt.Errorf("getting manager options: %w", err)
}
log.Info("Initialize Prometheus Registry")

mgr, err := manager.New(cfg, mgrOpts)
reg := prometheus.NewRegistry()
metrics, err := dvoProm.PreloadMetrics(reg)
if err != nil {
return nil, fmt.Errorf("initializing manager: %w", err)
return nil, fmt.Errorf("preloading kube-linter metrics: %w", err)
}

if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil {
return nil, fmt.Errorf("adding healthz check: %w", err)
}
log.Info(fmt.Sprintf("Initialize Prometheus metrics endpoint on %q", opts.MetricsEndpoint()))

if err := mgr.AddReadyzCheck("check", healthz.Ping); err != nil {
return nil, fmt.Errorf("adding readyz check: %w", err)
srv, err := dvoProm.NewServer(reg, opts.MetricsPath, fmt.Sprintf(":%d", opts.MetricsPort))
if err != nil {
return nil, fmt.Errorf("initializing metrics server: %w", err)
}

log.Info("Registering Components")

discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())
if err != nil {
return nil, fmt.Errorf("initializing discovery client: %w", err)
if err := mgr.Add(srv); err != nil {
return nil, fmt.Errorf("adding metrics server to manager: %w", err)
}

gr, err := controller.NewGenericReconciler(mgr.GetClient(), discoveryClient)
log.Info("Initialize ConfigMap watcher")

cmWatcher, err := configmap.NewWatcher(cfg)
if err != nil {
return nil, fmt.Errorf("initializing generic reconciler: %w", err)
return nil, fmt.Errorf("initializing configmap watcher: %w", err)
}

if err = gr.AddToManager(mgr); err != nil {
return nil, fmt.Errorf("adding generic reconciler to manager: %w", err)
if err := mgr.Add(cmWatcher); err != nil {
return nil, fmt.Errorf("adding configmap watcher to manager: %w", err)
}

log.Info("Initializing Prometheus Registry")
log.Info("Initialize Validation Engine")

reg := prometheus.NewRegistry()
err = validations.InitEngine(opts.ConfigFile, metrics)
if err != nil {
return nil, fmt.Errorf("initializing validation engine: %w", err)
}

log.Info(fmt.Sprintf("Initializing Prometheus metrics endpoint on %q", opts.MetricsEndpoint()))
log.Info("Initialize Reconciler")

srv, err := dvo_prom.NewServer(reg, opts.MetricsPath, fmt.Sprintf(":%d", opts.MetricsPort))
discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())
if err != nil {
return nil, fmt.Errorf("initializing metrics server: %w", err)
return nil, fmt.Errorf("initializing discovery client: %w", err)
}

if err := mgr.Add(srv); err != nil {
return nil, fmt.Errorf("adding metrics server to manager: %w", err)
gr, err := controller.NewGenericReconciler(mgr.GetClient(), discoveryClient, cmWatcher)
if err != nil {
return nil, fmt.Errorf("initializing generic reconciler: %w", err)
}

log.Info("Initializing Validation Engine")

if err := validations.InitializeValidationEngine(opts.ConfigFile, reg); err != nil {
return nil, fmt.Errorf("initializing validation engine: %w", err)
if err = gr.AddToManager(mgr); err != nil {
return nil, fmt.Errorf("adding generic reconciler to manager: %w", err)
}

return mgr, nil
Expand Down Expand Up @@ -235,3 +234,33 @@ func kubeClientQPS() (float32, error) {
qps = float32(val)
return qps, err
}

func initManager(log logr.Logger, opts options.Options, cfg *rest.Config) (manager.Manager, error) {
log.Info("Initialize Scheme")
scheme, err := initializeScheme()
if err != nil {
return nil, fmt.Errorf("initializing scheme: %w", err)
}

log.Info("Getting Manager Options")
mgrOpts, err := getManagerOptions(scheme, opts)
if err != nil {
return nil, fmt.Errorf("getting manager options: %w", err)
}

mgr, err := manager.New(cfg, mgrOpts)
if err != nil {
return nil, fmt.Errorf("getting new manager: %w", err)
}

log.Info("Adding Healthz and Readyz checks")
if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil {
return nil, fmt.Errorf("adding healthz check: %w", err)
}

if err := mgr.AddReadyzCheck("check", healthz.Ping); err != nil {
return nil, fmt.Errorf("adding readyz check: %w", err)
}

return mgr, nil
}
185 changes: 185 additions & 0 deletions pkg/configmap/configmap_watcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package configmap

import (
"context"
"fmt"
"os"
"reflect"
"time"

"golang.stackrox.io/kube-linter/pkg/config"
"gopkg.in/yaml.v3"

"github.com/app-sre/deployment-validation-operator/pkg/validations"
"github.com/go-logr/logr"
apicorev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/log"
)

// this structure mirrors Kube-Linter configuration structure
// it is used as a bridge to unmarshall ConfigMap data
// doc: https://pkg.go.dev/golang.stackrox.io/kube-linter/pkg/config#Config
type KubeLinterChecks struct {
Checks struct {
AddAllBuiltIn bool `yaml:"addAllBuiltIn,omitempty"`
DoNotAutoAddDefaults bool `yaml:"doNotAutoAddDefaults,omitempty"`
Exclude []string `yaml:"exclude,omitempty"`
Include []string `yaml:"include,omitempty"`
IgnorePaths []string `yaml:"ignorePaths,omitempty"`
} `yaml:"checks"`
}

type Watcher struct {
clientset kubernetes.Interface
checks KubeLinterChecks
ch chan config.Config
logger logr.Logger
namespace string
}

var configMapName = "deployment-validation-operator-config"
var configMapDataAccess = "deployment-validation-operator-config.yaml"

// NewWatcher creates a new Watcher instance for observing changes to a ConfigMap.
//
// Parameters:
// - cfg: A pointer to a rest.Config representing the Kubernetes client configuration.
//
// Returns:
// - A pointer to a Watcher instance for monitoring changes to DVO ConfigMap resource.
// - An error if there's an issue while initializing the Kubernetes clientset.
func NewWatcher(cfg *rest.Config) (*Watcher, error) {
clientset, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, fmt.Errorf("initializing clientset: %w", err)
}

// the Informer will use this to monitor the namespace for the ConfigMap.
namespace, err := getPodNamespace()
if err != nil {
return nil, fmt.Errorf("getting namespace: %w", err)
}

return &Watcher{
clientset: clientset,
logger: log.Log.WithName("ConfigMapWatcher"),
ch: make(chan config.Config),
namespace: namespace,
}, nil
}

// GetStaticKubelinterConfig returns the ConfigMap's checks configuration
func (cmw *Watcher) GetStaticKubelinterConfig(ctx context.Context) (config.Config, error) {
cm, err := cmw.clientset.CoreV1().
ConfigMaps(cmw.namespace).Get(ctx, configMapName, v1.GetOptions{})
if err != nil {
return config.Config{}, fmt.Errorf("getting initial configuration: %w", err)
}

return cmw.getKubeLinterConfig(cm.Data[configMapDataAccess])
}

// Start will update the channel structure with new configuration data from ConfigMap update event
func (cmw Watcher) Start(ctx context.Context) error {
factory := informers.NewSharedInformerFactoryWithOptions(
cmw.clientset, time.Second*30, informers.WithNamespace(cmw.namespace),
)
informer := factory.Core().V1().ConfigMaps().Informer()

informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ // nolint:errcheck
AddFunc: func(obj interface{}) {
newCm := obj.(*apicorev1.ConfigMap)

if configMapName != newCm.GetName() {
return
}

cmw.logger.Info(
"a ConfigMap has been created under watched namespace",
"name", newCm.GetName(),
"namespace", newCm.GetNamespace(),
)

cfg, err := cmw.getKubeLinterConfig(newCm.Data[configMapDataAccess])
if err != nil {
cmw.logger.Error(err, "ConfigMap data format")
return
}

cmw.ch <- cfg
},
UpdateFunc: func(oldObj, newObj interface{}) {
newCm := newObj.(*apicorev1.ConfigMap)

// This is sometimes triggered even if no change was due to the ConfigMap
if configMapName != newCm.GetName() || reflect.DeepEqual(oldObj, newObj) {
return
}

cmw.logger.Info(
"a ConfigMap has been updated under watched namespace",
"name", newCm.GetName(),
"namespace", newCm.GetNamespace(),
)

cfg, err := cmw.getKubeLinterConfig(newCm.Data[configMapDataAccess])
if err != nil {
cmw.logger.Error(err, "ConfigMap data format")
return
}

cmw.ch <- cfg
},
DeleteFunc: func(oldObj interface{}) {
cm := oldObj.(*apicorev1.ConfigMap)

cmw.logger.Info(
"a ConfigMap has been deleted under watched namespace",
"name", cm.GetName(),
"namespace", cm.GetNamespace(),
)

cmw.ch <- config.Config{
Checks: validations.GetDefaultChecks(),
}
},
})

factory.Start(ctx.Done())

return nil
}

// ConfigChanged receives push notifications when the configuration is updated
func (cmw *Watcher) ConfigChanged() <-chan config.Config {
return cmw.ch
}

// getKubeLinterConfig returns a valid Kube-linter Config structure
// based on the checks received by the string
func (cmw *Watcher) getKubeLinterConfig(data string) (config.Config, error) {
var cfg config.Config

err := yaml.Unmarshal([]byte(data), &cmw.checks)
if err != nil {
return cfg, fmt.Errorf("unmarshalling configmap data: %w", err)
}

cfg.Checks = config.ChecksConfig(cmw.checks.Checks)

return cfg, nil
}

func getPodNamespace() (string, error) {
namespace, exists := os.LookupEnv("POD_NAMESPACE")
if !exists {
return "", fmt.Errorf("could not find DVO pod")
}

return namespace, nil
}
Loading