Skip to content

Commit

Permalink
add protobuf object handling to validating round tripper
Browse files Browse the repository at this point in the history
Signed-off-by: Per Goncalves da Silva <[email protected]>
  • Loading branch information
Per Goncalves da Silva committed Jan 10, 2025
1 parent f5058f7 commit 8735be1
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 15 deletions.
2 changes: 1 addition & 1 deletion cmd/olm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func main() {
config := mgr.GetConfig()

// create a config that validates we're creating objects with labels
validatingConfig := validatingroundtripper.Wrap(config)
validatingConfig := validatingroundtripper.Wrap(config, mgr.GetScheme())

versionedConfigClient, err := configclientset.NewForConfig(config)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/operators/catalog/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
}

// create a config that validates we're creating objects with labels
validatingConfig := validatingroundtripper.Wrap(config)
validatingConfig := validatingroundtripper.Wrap(config, scheme)

// Create a new client for dynamic types (CRs)
dynamicClient, err := dynamic.NewForConfig(validatingConfig)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package validatingroundtripper

import (
"bytes"
"fmt"
"io"
"net/http"
"os"

"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"

"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
Expand All @@ -13,23 +18,74 @@ import (

type validatingRoundTripper struct {
delegate http.RoundTripper
codecs serializer.CodecFactory
}

func (rt *validatingRoundTripper) decodeYAMLOrJSON(data []byte) (*unstructured.Unstructured, error) {
bodyBuffer := bytes.NewBuffer(data)
dec := yaml.NewYAMLOrJSONDecoder(bodyBuffer, 10)
unstructuredObject := &unstructured.Unstructured{}
if err := dec.Decode(unstructuredObject); err != nil {
return nil, fmt.Errorf("error decoding yaml/json object to an unstructured object: %w: %+v", err, string(data))
}
return unstructuredObject, nil
}

func (rt *validatingRoundTripper) decodeProtobuf(data []byte) (*unstructured.Unstructured, error) {
// Decode Protobuf
decoder := rt.codecs.UniversalDeserializer()
obj, _, err := decoder.Decode(data, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to decode protobuf data: %w", err)
}

// Convert to Unstructured for further processing
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, fmt.Errorf("failed to convert object to unstructured: %w", err)
}

return &unstructured.Unstructured{Object: unstructuredObj}, nil
}

func (rt *validatingRoundTripper) decodeRequestBody(req *http.Request) *unstructured.Unstructured {
b, err := req.GetBody()
if err != nil {
panic(fmt.Errorf("failed to get request body: %w", err))
}
defer b.Close()

data, err := io.ReadAll(b)
if err != nil {
panic(fmt.Errorf("failed to read request body: %w", err))
}

var unstructuredObject *unstructured.Unstructured
switch req.Header.Get("Content-Type") {
case "application/vnd.kubernetes.protobuf":
unstructuredObject, err = rt.decodeProtobuf(data)
default:
unstructuredObject, err = rt.decodeYAMLOrJSON(data)
}

if err != nil {
panic(fmt.Errorf("failed to decode request body: %w", err))
}

return unstructuredObject
}

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))
}
unstructuredObject := rt.decodeRequestBody(req)
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))
labels := unstructuredObject.GetLabels()
if 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))
}
}
}
Expand All @@ -40,14 +96,17 @@ 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 {
func Wrap(cfg *rest.Config, scheme *runtime.Scheme) *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 &validatingRoundTripper{
delegate: rt,
codecs: serializer.NewCodecFactory(scheme),
}
})
return &cfgCopy
}

0 comments on commit 8735be1

Please sign in to comment.