diff --git a/cmd/core/main.go b/cmd/core/main.go index b4f2dbe0..70da8a3e 100644 --- a/cmd/core/main.go +++ b/cmd/core/main.go @@ -48,6 +48,7 @@ import ( "github.com/operator-framework/rukpak/internal/storage" "github.com/operator-framework/rukpak/internal/uploadmgr" "github.com/operator-framework/rukpak/internal/util" + "github.com/operator-framework/rukpak/internal/validators" "github.com/operator-framework/rukpak/internal/version" ) @@ -205,12 +206,19 @@ func main() { os.Exit(1) } + plainValidator, err := validators.NewPlainValidator() + if err != nil { + setupLog.Error(err, "unable to setup bundle validator") + os.Exit(1) + } + if err = (&plaincontrollers.BundleReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Storage: bundleStorage, Finalizers: bundleFinalizers, Unpacker: unpacker, + Validator: plainValidator, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", rukpakv1alpha1.BundleKind) os.Exit(1) diff --git a/combo-bd.yaml b/combo-bd.yaml new file mode 100644 index 00000000..2f9376e7 --- /dev/null +++ b/combo-bd.yaml @@ -0,0 +1,17 @@ +# TODO(asmacdo) dev only, remove before merge +apiVersion: core.rukpak.io/v1alpha1 +kind: BundleDeployment +metadata: + name: combo +spec: + provisionerClassName: core.rukpak.io/plain + template: + metadata: + labels: + app: combo + spec: + provisionerClassName: core.rukpak.io/plain + source: + image: + ref: quay.io/operator-framework/combo-bundle:v0.0.2 + type: image diff --git a/internal/provisioner/plain/controllers/bundle_controller.go b/internal/provisioner/plain/controllers/bundle_controller.go index b8d67098..fce72d4d 100644 --- a/internal/provisioner/plain/controllers/bundle_controller.go +++ b/internal/provisioner/plain/controllers/bundle_controller.go @@ -17,20 +17,13 @@ limitations under the License. package controllers import ( - "bytes" "context" - "errors" "fmt" - "io" - "io/fs" - "path/filepath" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" apimacherrors "k8s.io/apimachinery/pkg/util/errors" - apimachyaml "k8s.io/apimachinery/pkg/util/yaml" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -44,6 +37,7 @@ import ( "github.com/operator-framework/rukpak/internal/storage" updater "github.com/operator-framework/rukpak/internal/updater/bundle" "github.com/operator-framework/rukpak/internal/util" + "github.com/operator-framework/rukpak/internal/validators" ) // BundleReconciler reconciles a Bundle object @@ -53,6 +47,7 @@ type BundleReconciler struct { Storage storage.Storage Finalizers finalizer.Finalizers Unpacker source.Unpacker + Validator validators.Validator } //+kubebuilder:rbac:groups=core.rukpak.io,resources=bundles,verbs=list;watch;update;patch @@ -146,15 +141,9 @@ func (r *BundleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr updateStatusUnpacking(&u, unpackResult) return ctrl.Result{}, nil case source.StateUnpacked: - objects, err := getObjects(unpackResult.Bundle) - if err != nil { - return ctrl.Result{}, updateStatusUnpackFailing(&u, fmt.Errorf("get objects from bundle manifests: %v", err)) - } - if len(objects) == 0 { - return ctrl.Result{}, updateStatusUnpackFailing(&u, errors.New("invalid bundle: found zero objects: "+ - "plain+v0 bundles are required to contain at least one object")) + if err = r.Validator.Validate(unpackResult.Bundle); err != nil { + return ctrl.Result{}, updateStatusUnpackFailing(&u, fmt.Errorf("Bundle Validation Errors: %v", err)) } - if err := r.Storage.Store(ctx, bundle, unpackResult.Bundle); err != nil { return ctrl.Result{}, updateStatusUnpackFailing(&u, fmt.Errorf("persist bundle objects: %v", err)) } @@ -228,39 +217,6 @@ func updateStatusUnpackFailing(u *updater.Updater, err error) error { return err } -func getObjects(bundleFS fs.FS) ([]client.Object, error) { - var objects []client.Object - const manifestsDir = "manifests" - - entries, err := fs.ReadDir(bundleFS, manifestsDir) - if err != nil { - return nil, err - } - for _, e := range entries { - if e.IsDir() { - return nil, fmt.Errorf("subdirectories are not allowed within the %q directory of the bundle image filesystem: found %q", manifestsDir, filepath.Join(manifestsDir, e.Name())) - } - fileData, err := fs.ReadFile(bundleFS, filepath.Join(manifestsDir, e.Name())) - if err != nil { - return nil, err - } - - dec := apimachyaml.NewYAMLOrJSONDecoder(bytes.NewReader(fileData), 1024) - for { - obj := unstructured.Unstructured{} - err := dec.Decode(&obj) - if errors.Is(err, io.EOF) { - break - } - if err != nil { - return nil, fmt.Errorf("read %q: %v", e.Name(), err) - } - objects = append(objects, &obj) - } - } - return objects, nil -} - // SetupWithManager sets up the controller with the Manager. func (r *BundleReconciler) SetupWithManager(mgr ctrl.Manager) error { l := mgr.GetLogger().WithName("controller.bundle") diff --git a/internal/provisioner/plain/controllers/bundledeployment_controller.go b/internal/provisioner/plain/controllers/bundledeployment_controller.go index efd0318e..843126f1 100644 --- a/internal/provisioner/plain/controllers/bundledeployment_controller.go +++ b/internal/provisioner/plain/controllers/bundledeployment_controller.go @@ -51,6 +51,7 @@ import ( "github.com/operator-framework/rukpak/internal/storage" updater "github.com/operator-framework/rukpak/internal/updater/bundle-deployment" "github.com/operator-framework/rukpak/internal/util" + "github.com/operator-framework/rukpak/internal/validators" ) // BundleDeploymentReconciler reconciles a BundleDeployment object @@ -370,7 +371,7 @@ func (r *BundleDeploymentReconciler) loadBundle(ctx context.Context, bundle *ruk return nil, fmt.Errorf("load bundle: %v", err) } - objects, err := getObjects(bundleFS) + objects, err := validators.GetObjects(bundleFS) if err != nil { return nil, fmt.Errorf("read bundle objects from bundle: %v", err) } diff --git a/internal/validators/base.go b/internal/validators/base.go new file mode 100644 index 00000000..998eb021 --- /dev/null +++ b/internal/validators/base.go @@ -0,0 +1,53 @@ +package validators + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/fs" + "path/filepath" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + apimachyaml "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Validator interface { + Validate(fs.FS) error +} + +// TODO(asmacdo) I moved this *and* made it public, is this a problem? +func GetObjects(bundleFS fs.FS) ([]client.Object, error) { + var objects []client.Object + const manifestsDir = "manifests" + + entries, err := fs.ReadDir(bundleFS, manifestsDir) + if err != nil { + return nil, err + } + for _, e := range entries { + if e.IsDir() { + return nil, fmt.Errorf("subdirectories are not allowed within the %q directory of the bundle image filesystem: found %q", manifestsDir, filepath.Join(manifestsDir, e.Name())) + } + fileData, err := fs.ReadFile(bundleFS, filepath.Join(manifestsDir, e.Name())) + if err != nil { + return nil, err + } + + dec := apimachyaml.NewYAMLOrJSONDecoder(bytes.NewReader(fileData), 1024) + for { + obj := unstructured.Unstructured{} + err := dec.Decode(&obj) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + // TODO(asmacdo) is this clear enough? + return nil, fmt.Errorf("read %q: %v", e.Name(), err) + } + objects = append(objects, &obj) + } + } + return objects, nil +} diff --git a/internal/validators/plain.go b/internal/validators/plain.go new file mode 100644 index 00000000..291d1c2c --- /dev/null +++ b/internal/validators/plain.go @@ -0,0 +1,41 @@ +package validators + +import ( + "errors" + "fmt" + "io/fs" +) + +func NewPlainValidator() (Validator, error) { + + return &plainValidator{}, nil +} + +type plainValidator struct { +} + +func (r *plainValidator) Validate(bundle fs.FS) error { + var validationErrors []error + objects, err := GetObjects(bundle) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("get objects from bundle manifests: %v", err)) + } + if len(objects) == 0 { + validationErrors = append(validationErrors, errors.New("invalid bundle: found zero objects: "+ + "plain+v0 bundles are required to contain at least one object")) + } + + // TODO(asmacdo) dev only, remove before merge + validationErrors = append(validationErrors, errors.New("")) + validationErrors = append(validationErrors, errors.New("hello")) + validationErrors = append(validationErrors, errors.New("world")) + validationErrors = append(validationErrors, errors.New("annnndd")) + validationErrors = append(validationErrors, errors.New("four Errors!")) + validationErrors = append(validationErrors, errors.New("moved this shit around!")) + + err = errors.New("") + for _, each := range validationErrors { + err = fmt.Errorf("%v\n%v", err, each) + } + return err +}