From dd247bda8b1604b15fef598a98d39ebe3f281784 Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Tue, 22 Aug 2023 07:23:52 -0600 Subject: [PATCH] add a round-tripper to ensure we label non-OLM resources This round-tripper is added to our *rest.Config when it's possible to detect that we're in a CI environment. Developers should set $CI=true to get this behavior locally. Signed-off-by: Steve Kuznetsov --- cmd/olm/main.go | 6 ++- pkg/controller/operators/catalog/operator.go | 8 ++- .../validating_round_tripper.go | 53 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 pkg/controller/operators/validatingroundtripper/validating_round_tripper.go diff --git a/cmd/olm/main.go b/cmd/olm/main.go index c0b6868f2b0..44734967879 100644 --- a/cmd/olm/main.go +++ b/cmd/olm/main.go @@ -11,6 +11,7 @@ import ( configclientset "github.com/openshift/client-go/config/clientset/versioned" configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/validatingroundtripper" "github.com/sirupsen/logrus" "github.com/spf13/pflag" corev1 "k8s.io/api/core/v1" @@ -139,6 +140,9 @@ func main() { } config := mgr.GetConfig() + // create a config that validates we're creating objects with labels + validatingConfig := validatingroundtripper.Wrap(config) + versionedConfigClient, err := configclientset.NewForConfig(config) if err != nil { logger.WithError(err).Fatal("error configuring openshift proxy client") @@ -147,7 +151,7 @@ func main() { if err != nil { logger.WithError(err).Fatal("error configuring config client") } - opClient, err := operatorclient.NewClientFromRestConfig(config) + opClient, err := operatorclient.NewClientFromRestConfig(validatingConfig) if err != nil { logger.WithError(err).Fatal("error configuring operator client") } diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go index 8ad84dea86f..352aa9fbe7b 100644 --- a/pkg/controller/operators/catalog/operator.go +++ b/pkg/controller/operators/catalog/operator.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/validatingroundtripper" errorwrap "github.com/pkg/errors" "github.com/sirupsen/logrus" "google.golang.org/grpc/connectivity" @@ -140,8 +141,11 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo return nil, err } + // create a config that validates we're creating objects with labels + validatingConfig := validatingroundtripper.Wrap(config) + // Create a new client for dynamic types (CRs) - dynamicClient, err := dynamic.NewForConfig(config) + dynamicClient, err := dynamic.NewForConfig(validatingConfig) if err != nil { return nil, err } @@ -152,7 +156,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo } // Create a new queueinformer-based operator. - opClient, err := operatorclient.NewClientFromRestConfig(config) + opClient, err := operatorclient.NewClientFromRestConfig(validatingConfig) if err != nil { return nil, err } diff --git a/pkg/controller/operators/validatingroundtripper/validating_round_tripper.go b/pkg/controller/operators/validatingroundtripper/validating_round_tripper.go new file mode 100644 index 00000000000..c9c1cbd395d --- /dev/null +++ b/pkg/controller/operators/validatingroundtripper/validating_round_tripper.go @@ -0,0 +1,53 @@ +package validatingroundtripper + +import ( + "fmt" + "net/http" + "os" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/rest" +) + +type validatingRoundTripper struct { + delegate http.RoundTripper +} + +func (rt *validatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + if req.Method == "POST" { + b, err := req.GetBody() + if err != nil { + panic(err) + } + dec := yaml.NewYAMLOrJSONDecoder(b, 10) + unstructuredObject := &unstructured.Unstructured{} + if err := dec.Decode(unstructuredObject); err != nil { + panic(fmt.Errorf("error decoding object to an unstructured object: %w", err)) + } + gvk := unstructuredObject.GroupVersionKind() + if gvk.Kind != "Event" { + if labels := unstructuredObject.GetLabels(); labels[install.OLMManagedLabelKey] != install.OLMManagedLabelValue { + panic(fmt.Errorf("%s.%s/%v %s/%s does not have labels[%s]=%s", gvk.Kind, gvk.Group, gvk.Version, unstructuredObject.GetNamespace(), unstructuredObject.GetName(), install.OLMManagedLabelKey, install.OLMManagedLabelValue)) + } + } + } + return rt.delegate.RoundTrip(req) +} + +var _ http.RoundTripper = (*validatingRoundTripper)(nil) + +// Wrap is meant to be used in developer environments and CI to make it easy to find places +// where we accidentally create Kubernetes objects without our management label. +func Wrap(cfg *rest.Config) *rest.Config { + if _, set := os.LookupEnv("CI"); !set { + return cfg + } + + cfgCopy := *cfg + cfgCopy.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return &validatingRoundTripper{delegate: rt} + }) + return &cfgCopy +}