diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 3b7020a68..cdc33926d 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -20,7 +20,6 @@ import ( "context" "flag" "fmt" - "net/url" "os" "path/filepath" @@ -47,11 +46,8 @@ import ( "github.com/operator-framework/operator-controller/internal/controllers" "github.com/operator-framework/operator-controller/internal/httputil" "github.com/operator-framework/operator-controller/internal/labels" - registryv1handler "github.com/operator-framework/operator-controller/internal/rukpak/handler" crdupgradesafety "github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety" - "github.com/operator-framework/operator-controller/internal/rukpak/provisioner/registry" "github.com/operator-framework/operator-controller/internal/rukpak/source" - "github.com/operator-framework/operator-controller/internal/rukpak/storage" "github.com/operator-framework/operator-controller/internal/version" "github.com/operator-framework/operator-controller/pkg/features" "github.com/operator-framework/operator-controller/pkg/scheme" @@ -95,6 +91,7 @@ func main() { flag.StringVar(&systemNamespace, "system-namespace", "", "Configures the namespace that gets used to deploy system resources.") opts := zap.Options{ Development: true, + TimeEncoder: zapcore.RFC3339NanoTimeEncoder, } opts.BindFlags(flag.CommandLine) @@ -161,7 +158,12 @@ func main() { } cl := mgr.GetClient() - catalogClient := catalogclient.New(cl, cache.NewFilesystemCache(cachePath, httpClient)) + catalogsCachePath := filepath.Join(cachePath, "catalogs") + if err := os.MkdirAll(catalogsCachePath, 0700); err != nil { + setupLog.Error(err, "unable to create catalogs cache directory") + os.Exit(1) + } + catalogClient := catalogclient.New(cl, cache.NewFilesystemCache(catalogsCachePath, httpClient)) installNamespaceMapper := helmclient.ObjectToStringMapper(func(obj client.Object) (string, error) { ext := obj.(*ocv1alpha1.ClusterExtension) @@ -193,7 +195,6 @@ func main() { domain := ocv1alpha1.GroupVersion.Group cleanupUnpackCacheKey := fmt.Sprintf("%s/cleanup-unpack-cache", domain) - deleteCachedBundleKey := fmt.Sprintf("%s/delete-cached-bundle", domain) if err := clusterExtensionFinalizers.Register(cleanupUnpackCacheKey, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { ext := obj.(*ocv1alpha1.ClusterExtension) return crfinalizer.Result{}, os.RemoveAll(filepath.Join(unpacker.BaseCachePath, ext.GetName())) @@ -202,23 +203,6 @@ func main() { os.Exit(1) } - localStorageRoot := filepath.Join(cachePath, "bundles") - if err := os.MkdirAll(localStorageRoot, 0755); err != nil { - setupLog.Error(err, "unable to create local storage root directory", "root", localStorageRoot) - os.Exit(1) - } - localStorage := &storage.LocalDirectory{ - RootDirectory: localStorageRoot, - URL: url.URL{}, - } - if err := clusterExtensionFinalizers.Register(deleteCachedBundleKey, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { - ext := obj.(*ocv1alpha1.ClusterExtension) - return crfinalizer.Result{}, localStorage.Delete(ctx, ext.GetName()) - })); err != nil { - setupLog.Error(err, "unable to register finalizer", "finalizerKey", deleteCachedBundleKey) - os.Exit(1) - } - aeClient, err := apiextensionsv1client.NewForConfig(mgr.GetConfig()) if err != nil { setupLog.Error(err, "unable to create apiextensions client") @@ -234,9 +218,7 @@ func main() { BundleProvider: catalogClient, ActionClientGetter: acg, Unpacker: unpacker, - Storage: localStorage, InstalledBundleGetter: &controllers.DefaultInstalledBundleGetter{ActionClientGetter: acg}, - Handler: registryv1handler.HandlerFunc(registry.HandleBundleDeployment), Finalizers: clusterExtensionFinalizers, CaCertDir: caCertDir, Preflights: preflights, diff --git a/go.mod b/go.mod index ddbead6ff..d0d176668 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/google/go-containerregistry v0.20.0 github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20240505154900-ff385a972813 github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20240505154900-ff385a972813 - github.com/nlepage/go-tarfs v1.2.1 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 github.com/operator-framework/api v0.26.0 diff --git a/go.sum b/go.sum index e10b38985..4b0bbfd8b 100644 --- a/go.sum +++ b/go.sum @@ -560,8 +560,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nlepage/go-tarfs v1.2.1 h1:o37+JPA+ajllGKSPfy5+YpsNHDjZnAoyfvf5GsUa+Ks= -github.com/nlepage/go-tarfs v1.2.1/go.mod h1:rno18mpMy9aEH1IiJVftFsqPyIpwqSUiAOpJYjlV2NA= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= diff --git a/internal/controllers/clusterextension_controller.go b/internal/controllers/clusterextension_controller.go index 3d4bc44a1..dcb1e402e 100644 --- a/internal/controllers/clusterextension_controller.go +++ b/internal/controllers/clusterextension_controller.go @@ -36,6 +36,7 @@ import ( "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -71,10 +72,9 @@ import ( "github.com/operator-framework/operator-controller/internal/httputil" "github.com/operator-framework/operator-controller/internal/labels" "github.com/operator-framework/operator-controller/internal/rukpak/bundledeployment" - registryv1handler "github.com/operator-framework/operator-controller/internal/rukpak/handler" + "github.com/operator-framework/operator-controller/internal/rukpak/convert" crdupgradesafety "github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety" rukpaksource "github.com/operator-framework/operator-controller/internal/rukpak/source" - "github.com/operator-framework/operator-controller/internal/rukpak/storage" "github.com/operator-framework/operator-controller/internal/rukpak/util" ) @@ -88,8 +88,6 @@ type ClusterExtensionReconciler struct { BundleProvider BundleProvider Unpacker rukpaksource.Unpacker ActionClientGetter helmclient.ActionClientGetter - Storage storage.Storage - Handler registryv1handler.Handler dynamicWatchMutex sync.RWMutex dynamicWatchGVKs sets.Set[schema.GroupVersionKind] controller crcontroller.Controller @@ -132,6 +130,8 @@ type Preflight interface { // This has been taken from rukpak, and an issue was created before to discuss it: https://github.com/operator-framework/rukpak/issues/800. func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := log.FromContext(ctx).WithName("operator-controller") + ctx = log.IntoContext(ctx, l) + l.V(1).Info("reconcile starting") defer l.V(1).Info("reconcile ending") @@ -212,6 +212,9 @@ func checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool { */ //nolint:unparam func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (ctrl.Result, error) { + l := log.FromContext(ctx) + + l.V(1).Info("handling finalizers") finalizeResult, err := r.Finalizers.Finalize(ctx, ext) if err != nil { // TODO: For now, this error handling follows the pattern of other error handling. @@ -232,6 +235,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp } // run resolution + l.V(1).Info("resolving bundle") bundle, err := r.resolve(ctx, *ext) if err != nil { // Note: We don't distinguish between resolution-specific errors and generic errors @@ -242,6 +246,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp return ctrl.Result{}, err } + l.V(1).Info("validating bundle") if err := r.validateBundle(bundle); err != nil { ext.Status.ResolvedBundle = nil ext.Status.InstalledBundle = nil @@ -269,6 +274,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp // Note: The BundleDeployment here is not a k8s API, its a simple Go struct which // necessary embedded values. bd := r.generateBundleDeploymentForUnpack(ctx, bundle.Image, ext) + l.V(1).Info("unpacking resolved bundle") unpackResult, err := r.Unpacker.Unpack(ctx, bd) if err != nil { setStatusUnpackFailed(ext, err.Error()) @@ -282,12 +288,6 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp return ctrl.Result{}, nil case rukpaksource.StateUnpacked: - // TODO: Add finalizer to clean the stored bundles, after https://github.com/operator-framework/rukpak/pull/897 - // merges. - if err := r.Storage.Store(ctx, ext.GetName(), unpackResult.Bundle); err != nil { - setStatusUnpackFailed(ext, err.Error()) - return ctrl.Result{}, err - } setStatusUnpacked(ext, fmt.Sprintf("unpack successful: %v", unpackResult.Message)) default: setStatusUnpackFailed(ext, "unexpected unpack status") @@ -295,18 +295,15 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp return ctrl.Result{}, fmt.Errorf("unexpected unpack status: %v", unpackResult.Message) } - bundleFS, err := r.Storage.Load(ctx, ext.GetName()) - if err != nil { - setInstalledStatusConditionFailed(ext, err.Error()) - return ctrl.Result{}, err - } - - chrt, values, err := r.Handler.Handle(ctx, bundleFS, bd) + l.V(1).Info("converting bundle to helm chart") + chrt, err := convert.RegistryV1ToHelmChart(ctx, unpackResult.Bundle, ext.Spec.InstallNamespace, []string{corev1.NamespaceAll}) if err != nil { setInstalledStatusConditionFailed(ext, err.Error()) return ctrl.Result{}, err } + values := chartutil.Values{} + l.V(1).Info("getting helm client") ac, err := r.ActionClientGetter.ActionClientFor(ctx, ext) if err != nil { ext.Status.InstalledBundle = nil @@ -324,12 +321,14 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp }, } + l.V(1).Info("getting current state of helm release") rel, desiredRel, state, err := r.getReleaseState(ac, ext, chrt, values, post) if err != nil { setInstalledStatusConditionFailed(ext, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonErrorGettingReleaseState, err)) return ctrl.Result{}, err } + l.V(1).Info("running preflight checks") for _, preflight := range r.Preflights { if ext.Spec.Preflight != nil && ext.Spec.Preflight.CRDUpgradeSafety != nil { if _, ok := preflight.(*crdupgradesafety.Preflight); ok && ext.Spec.Preflight.CRDUpgradeSafety.Disabled { @@ -354,6 +353,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp } } + l.V(1).Info("reconciling helm release changes") switch state { case stateNeedsInstall: rel, err = ac.Install(ext.GetName(), ext.Spec.InstallNamespace, chrt, values, func(install *action.Install) error { @@ -384,6 +384,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp return ctrl.Result{}, fmt.Errorf("unexpected release state %q", state) } + l.V(1).Info("configuring watches for release objects") relObjects, err := util.ManifestObjects(strings.NewReader(rel.Manifest), fmt.Sprintf("%s-release-manifest", rel.Name)) if err != nil { setInstalledStatusConditionFailed(ext, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonCreateDynamicWatchFailed, err)) diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index 6dc8206e2..c3dd97d5f 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -18,9 +18,7 @@ package controllers_test import ( "context" - "io/fs" "log" - "net/http" "os" "path/filepath" "testing" @@ -40,7 +38,6 @@ import ( "github.com/operator-framework/operator-controller/internal/controllers" bd "github.com/operator-framework/operator-controller/internal/rukpak/bundledeployment" "github.com/operator-framework/operator-controller/internal/rukpak/source" - "github.com/operator-framework/operator-controller/internal/rukpak/storage" "github.com/operator-framework/operator-controller/internal/testutil" "github.com/operator-framework/operator-controller/pkg/scheme" ) @@ -61,39 +58,6 @@ func (m *MockUnpacker) Cleanup(ctx context.Context, bundle *bd.BundleDeployment) panic("implement me") } -// MockStorage is a mock of Storage interface -type MockStorage struct { - mock.Mock -} - -func (m *MockStorage) Load(ctx context.Context, owner string) (fs.FS, error) { - args := m.Called(ctx, owner) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(fs.FS), args.Error(1) -} - -func (m *MockStorage) Delete(ctx context.Context, owner string) error { - //TODO implement me - panic("implement me") -} - -func (m *MockStorage) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - //TODO implement me - panic("implement me") -} - -func (m *MockStorage) URLFor(ctx context.Context, owner string) (string, error) { - //TODO implement me - panic("implement me") -} - -func (m *MockStorage) Store(ctx context.Context, owner string, bundle fs.FS) error { - args := m.Called(ctx, owner, bundle) - return args.Error(0) -} - func newClient(t *testing.T) client.Client { cl, err := client.New(config, client.Options{Scheme: scheme.Scheme}) require.NoError(t, err) @@ -125,7 +89,6 @@ func newClientAndReconciler(t *testing.T, bundle *ocv1alpha1.BundleMetadata) (cl BundleProvider: &fakeCatalogClient, ActionClientGetter: helmClientGetter, Unpacker: unpacker, - Storage: store, InstalledBundleGetter: mockInstalledBundleGetter, Finalizers: crfinalizer.NewFinalizers(), } @@ -136,7 +99,6 @@ var ( config *rest.Config helmClientGetter helmclient.ActionClientGetter unpacker source.Unpacker // Interface, will be initialized as a mock in TestMain - store storage.Storage ) func TestMain(m *testing.M) { @@ -160,7 +122,6 @@ func TestMain(m *testing.M) { utilruntime.Must(err) unpacker = new(MockUnpacker) - store = new(MockStorage) code := m.Run() utilruntime.Must(testEnv.Stop()) diff --git a/internal/rukpak/convert/registryv1.go b/internal/rukpak/convert/registryv1.go index 134133d95..a705358fd 100644 --- a/internal/rukpak/convert/registryv1.go +++ b/internal/rukpak/convert/registryv1.go @@ -1,26 +1,25 @@ package convert import ( - "bytes" - "errors" + "context" + "crypto/sha256" + "encoding/json" "fmt" - "io" "io/fs" "path/filepath" "strings" - "testing/fstest" - "time" + "helm.sh/helm/v3/pkg/chart" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - apimachyaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/cli-runtime/pkg/resource" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/yaml" "github.com/operator-framework/api/pkg/operators/v1alpha1" @@ -33,7 +32,6 @@ import ( type RegistryV1 struct { PackageName string CSV v1alpha1.ClusterServiceVersion - CRDs []apiextensionsv1.CustomResourceDefinition Others []unstructured.Unstructured } @@ -41,7 +39,9 @@ type Plain struct { Objects []client.Object } -func RegistryV1ToPlain(rv1 fs.FS, installNamespace string, watchNamespaces []string) (fs.FS, error) { +func RegistryV1ToHelmChart(ctx context.Context, rv1 fs.FS, installNamespace string, watchNamespaces []string) (*chart.Chart, error) { + l := log.FromContext(ctx) + reg := RegistryV1{} fileData, err := fs.ReadFile(rv1, filepath.Join("metadata", "annotations.yaml")) if err != nil { @@ -53,54 +53,52 @@ func RegistryV1ToPlain(rv1 fs.FS, installNamespace string, watchNamespaces []str } reg.PackageName = annotationsFile.Annotations.PackageName - var objects []*unstructured.Unstructured const manifestsDir = "manifests" - - entries, err := fs.ReadDir(rv1, manifestsDir) - if err != nil { - return nil, err - } - for _, e := range entries { + if err := fs.WalkDir(rv1, manifestsDir, func(path string, e fs.DirEntry, err error) error { + if err != nil { + return err + } 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())) + if path == manifestsDir { + return nil + } + return fmt.Errorf("subdirectories are not allowed within the %q directory of the bundle image filesystem: found %q", manifestsDir, path) } - fileData, err := fs.ReadFile(rv1, filepath.Join(manifestsDir, e.Name())) + manifestFile, err := rv1.Open(path) if err != nil { - return nil, err + return 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) + defer func() { + if err := manifestFile.Close(); err != nil { + l.Error(err, "error closing file", "path", path) } - objects = append(objects, &obj) - } - } + }() - for _, obj := range objects { - obj := obj - switch obj.GetObjectKind().GroupVersionKind().Kind { - case "ClusterServiceVersion": - csv := v1alpha1.ClusterServiceVersion{} - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &csv); err != nil { - return nil, err + result := resource.NewLocalBuilder().Unstructured().Flatten().Stream(manifestFile, path).Do() + if err := result.Err(); err != nil { + return err + } + if err := result.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err } - reg.CSV = csv - case "CustomResourceDefinition": - crd := apiextensionsv1.CustomResourceDefinition{} - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &crd); err != nil { - return nil, err + switch info.Object.GetObjectKind().GroupVersionKind().Kind { + case "ClusterServiceVersion": + csv := v1alpha1.ClusterServiceVersion{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(info.Object.(*unstructured.Unstructured).Object, &csv); err != nil { + return err + } + reg.CSV = csv + default: + reg.Others = append(reg.Others, *info.Object.(*unstructured.Unstructured)) } - reg.CRDs = append(reg.CRDs, crd) - default: - reg.Others = append(reg.Others, *obj) + return nil + }); err != nil { + return fmt.Errorf("error parsing objects in %q: %v", path, err) } + return nil + }); err != nil { + return nil, err } plain, err := Convert(reg, installNamespace, watchNamespaces) @@ -108,37 +106,20 @@ func RegistryV1ToPlain(rv1 fs.FS, installNamespace string, watchNamespaces []str return nil, err } - var manifest bytes.Buffer + chrt := &chart.Chart{Metadata: &chart.Metadata{}} for _, obj := range plain.Objects { - yamlData, err := yaml.Marshal(obj) + jsonData, err := json.Marshal(obj) if err != nil { return nil, err } - if _, err := fmt.Fprintf(&manifest, "---\n%s\n", string(yamlData)); err != nil { - return nil, err - } - } - - now := time.Now() - plainFS := fstest.MapFS{ - ".": &fstest.MapFile{ - Data: nil, - Mode: fs.ModeDir | 0755, - ModTime: now, - }, - "manifests": &fstest.MapFile{ - Data: nil, - Mode: fs.ModeDir | 0755, - ModTime: now, - }, - "manifests/manifest.yaml": &fstest.MapFile{ - Data: manifest.Bytes(), - Mode: 0644, - ModTime: now, - }, + hash := sha256.Sum256(jsonData) + chrt.Templates = append(chrt.Templates, &chart.File{ + Name: fmt.Sprintf("object-%x.json", hash[0:8]), + Data: jsonData, + }) } - return plainFS, nil + return chrt, nil } func validateTargetNamespaces(supportedInstallModes sets.Set[string], installNamespace string, targetNamespaces []string) error { @@ -311,10 +292,6 @@ func Convert(in RegistryV1, installNamespace string, targetNamespaces []string) obj := obj objs = append(objs, &obj) } - for _, obj := range in.CRDs { - obj := obj - objs = append(objs, &obj) - } for _, obj := range in.Others { obj := obj supported, namespaced := registrybundle.IsSupported(obj.GetKind()) diff --git a/internal/rukpak/handler/interfaces.go b/internal/rukpak/handler/interfaces.go deleted file mode 100644 index 823f0fcb7..000000000 --- a/internal/rukpak/handler/interfaces.go +++ /dev/null @@ -1,21 +0,0 @@ -package handler - -import ( - "context" - "io/fs" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - - bd "github.com/operator-framework/operator-controller/internal/rukpak/bundledeployment" -) - -type Handler interface { - Handle(context.Context, fs.FS, *bd.BundleDeployment) (*chart.Chart, chartutil.Values, error) -} - -type HandlerFunc func(context.Context, fs.FS, *bd.BundleDeployment) (*chart.Chart, chartutil.Values, error) - -func (f HandlerFunc) Handle(ctx context.Context, fsys fs.FS, bd *bd.BundleDeployment) (*chart.Chart, chartutil.Values, error) { - return f(ctx, fsys, bd) -} diff --git a/internal/rukpak/provisioner/plain/plain.go b/internal/rukpak/provisioner/plain/plain.go deleted file mode 100644 index fd293832e..000000000 --- a/internal/rukpak/provisioner/plain/plain.go +++ /dev/null @@ -1,102 +0,0 @@ -package plain - -import ( - "context" - "crypto/sha256" - "errors" - "fmt" - "io/fs" - "path/filepath" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/yaml" - - "github.com/operator-framework/operator-controller/internal/rukpak/bundledeployment" - "github.com/operator-framework/operator-controller/internal/rukpak/util" -) - -const ( - // ProvisionerID is the unique plain provisioner ID - ProvisionerID = "core-rukpak-io-plain" - - manifestsDir = "manifests" -) - -func HandleBundleDeployment(_ context.Context, fsys fs.FS, _ *bundledeployment.BundleDeployment) (*chart.Chart, chartutil.Values, error) { - if err := ValidateBundle(fsys); err != nil { - return nil, nil, err - } - - chrt, err := chartFromBundle(fsys) - if err != nil { - return nil, nil, err - } - return chrt, nil, nil -} - -func ValidateBundle(fsys fs.FS) error { - objects, err := getBundleObjects(fsys) - if err != nil { - return fmt.Errorf("get objects from bundle manifests: %v", err) - } - if len(objects) == 0 { - return errors.New("invalid bundle: found zero objects: plain+v0 bundles are required to contain at least one object") - } - return nil -} - -func getBundleObjects(bundleFS fs.FS) ([]client.Object, error) { - entries, err := fs.ReadDir(bundleFS, manifestsDir) - if err != nil { - return nil, err - } - - var bundleObjects []client.Object - 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())) - } - - manifestObjects, err := getObjects(bundleFS, e) - if err != nil { - return nil, err - } - bundleObjects = append(bundleObjects, manifestObjects...) - } - return bundleObjects, nil -} - -func getObjects(bundle fs.FS, manifest fs.DirEntry) ([]client.Object, error) { - manifestPath := filepath.Join(manifestsDir, manifest.Name()) - manifestReader, err := bundle.Open(manifestPath) - if err != nil { - return nil, err - } - defer manifestReader.Close() - return util.ManifestObjects(manifestReader, manifestPath) -} - -func chartFromBundle(fsys fs.FS) (*chart.Chart, error) { - objects, err := getBundleObjects(fsys) - if err != nil { - return nil, fmt.Errorf("read bundle objects from bundle: %v", err) - } - - chrt := &chart.Chart{ - Metadata: &chart.Metadata{}, - } - for _, obj := range objects { - yamlData, err := yaml.Marshal(obj) - if err != nil { - return nil, err - } - hash := sha256.Sum256(yamlData) - chrt.Templates = append(chrt.Templates, &chart.File{ - Name: fmt.Sprintf("object-%x.yaml", hash[0:8]), - Data: yamlData, - }) - } - return chrt, nil -} diff --git a/internal/rukpak/provisioner/registry/registry.go b/internal/rukpak/provisioner/registry/registry.go deleted file mode 100644 index c77d18889..000000000 --- a/internal/rukpak/provisioner/registry/registry.go +++ /dev/null @@ -1,28 +0,0 @@ -package registry - -import ( - "context" - "fmt" - "io/fs" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/operator-framework/operator-controller/internal/rukpak/bundledeployment" - "github.com/operator-framework/operator-controller/internal/rukpak/convert" - "github.com/operator-framework/operator-controller/internal/rukpak/provisioner/plain" -) - -const ( - // ProvisionerID is the unique registry provisioner ID - ProvisionerID = "core-rukpak-io-registry" -) - -func HandleBundleDeployment(ctx context.Context, fsys fs.FS, bd *bundledeployment.BundleDeployment) (*chart.Chart, chartutil.Values, error) { - plainFS, err := convert.RegistryV1ToPlain(fsys, bd.Spec.InstallNamespace, []string{metav1.NamespaceAll}) - if err != nil { - return nil, nil, fmt.Errorf("convert registry+v1 bundle to plain+v0 bundle: %v", err) - } - return plain.HandleBundleDeployment(ctx, plainFS, bd) -} diff --git a/internal/rukpak/storage/localdir.go b/internal/rukpak/storage/localdir.go deleted file mode 100644 index 71b53fb50..000000000 --- a/internal/rukpak/storage/localdir.go +++ /dev/null @@ -1,75 +0,0 @@ -package storage - -import ( - "bytes" - "compress/gzip" - "context" - "errors" - "fmt" - "io" - "io/fs" - "net/url" - "os" - "path/filepath" - - "github.com/nlepage/go-tarfs" - - "github.com/operator-framework/operator-controller/internal/rukpak/util" -) - -var _ Storage = &LocalDirectory{} - -type LocalDirectory struct { - RootDirectory string - URL url.URL -} - -func (s *LocalDirectory) Load(_ context.Context, owner string) (fs.FS, error) { - bundleFile, err := os.Open(s.bundlePath(owner)) - if err != nil { - return nil, err - } - defer bundleFile.Close() - tarReader, err := gzip.NewReader(bundleFile) - if err != nil { - return nil, err - } - return tarfs.New(tarReader) -} - -func (s *LocalDirectory) Store(_ context.Context, owner string, bundle fs.FS) error { - buf := &bytes.Buffer{} - if err := util.FSToTarGZ(buf, bundle); err != nil { - return fmt.Errorf("convert bundle %q to tar.gz: %v", owner, err) - } - - bundleFile, err := os.Create(s.bundlePath(owner)) - if err != nil { - return err - } - defer bundleFile.Close() - - if _, err := io.Copy(bundleFile, buf); err != nil { - return err - } - return nil -} - -func (s *LocalDirectory) Delete(_ context.Context, owner string) error { - return ignoreNotExist(os.Remove(s.bundlePath(owner))) -} - -func (s *LocalDirectory) bundlePath(bundleName string) string { - return filepath.Join(s.RootDirectory, localDirectoryBundleFile(bundleName)) -} - -func localDirectoryBundleFile(bundleName string) string { - return fmt.Sprintf("%s.tgz", bundleName) -} - -func ignoreNotExist(err error) error { - if errors.Is(err, os.ErrNotExist) { - return nil - } - return err -} diff --git a/internal/rukpak/storage/localdir_test.go b/internal/rukpak/storage/localdir_test.go deleted file mode 100644 index d4d283441..000000000 --- a/internal/rukpak/storage/localdir_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package storage - -import ( - "context" - "errors" - "fmt" - "io/fs" - "os" - "path/filepath" - "reflect" - "testing/fstest" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "k8s.io/apimachinery/pkg/util/rand" -) - -var _ = Describe("LocalDirectory", func() { - var ( - ctx context.Context - owner string - store LocalDirectory - testFS fs.FS - ) - - BeforeEach(func() { - ctx = context.Background() - owner = fmt.Sprintf("test-bundle-%s", rand.String(5)) - store = LocalDirectory{RootDirectory: GinkgoT().TempDir()} - testFS = generateFS() - }) - When("a bundleDeployment is not stored", func() { - Describe("Store", func() { - It("should store a bundle FS", func() { - Expect(store.Store(ctx, owner, testFS)).To(Succeed()) - _, err := os.Stat(filepath.Join(store.RootDirectory, fmt.Sprintf("%s.tgz", owner))) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Describe("Load", func() { - It("should fail due to file not existing", func() { - _, err := store.Load(ctx, owner) - Expect(err).To(WithTransform(func(err error) bool { return errors.Is(err, os.ErrNotExist) }, BeTrue())) - }) - }) - - Describe("Delete", func() { - It("should succeed despite file not existing", func() { - Expect(store.Delete(ctx, owner)).To(Succeed()) - }) - }) - }) - When("a bundleDeployment is stored", func() { - BeforeEach(func() { - Expect(store.Store(ctx, owner, testFS)).To(Succeed()) - }) - Describe("Store", func() { - It("should re-store a bundleDeployment FS", func() { - Expect(store.Store(ctx, owner, testFS)).To(Succeed()) - }) - }) - - Describe("Load", func() { - It("should load the bundleDeployment", func() { - loadedTestFS, err := store.Load(ctx, owner) - Expect(err).NotTo(HaveOccurred()) - Expect(fsEqual(testFS, loadedTestFS)).To(BeTrue()) - }) - }) - - Describe("Delete", func() { - It("should delete the bundleDeployment", func() { - Expect(store.Delete(ctx, owner)).To(Succeed()) - _, err := os.Stat(filepath.Join(store.RootDirectory, fmt.Sprintf("%s.tgz", owner))) - Expect(err).To(WithTransform(func(err error) bool { return errors.Is(err, os.ErrNotExist) }, BeTrue())) - }) - }) - }) -}) - -func generateFS() fs.FS { - gen := fstest.MapFS{} - - numFiles := rand.IntnRange(10, 20) - for i := 0; i < numFiles; i++ { - pathLength := rand.IntnRange(30, 60) - filePath := "" - for j := 0; j < pathLength; j += rand.IntnRange(5, 10) { - filePath = filepath.Join(filePath, rand.String(rand.IntnRange(5, 10))) - } - gen[filePath] = &fstest.MapFile{ - Data: []byte(rand.String(rand.IntnRange(1, 400))), - Mode: fs.FileMode(rand.IntnRange(0600, 0777)), - // Need to do some rounding and location shenanigans here to align with nuances of the tar implementation. - ModTime: time.Now().Round(time.Second).Add(time.Duration(-rand.IntnRange(0, 100000)) * time.Second).In(&time.Location{}), - } - } - return &gen -} - -func fsEqual(a, b fs.FS) (bool, error) { - aMap := fstest.MapFS{} - bMap := fstest.MapFS{} - - walkFunc := func(f fs.FS, m fstest.MapFS) fs.WalkDirFunc { - return func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - data, err := fs.ReadFile(f, path) - if err != nil { - return err - } - info, err := d.Info() - if err != nil { - return err - } - m[path] = &fstest.MapFile{ - Data: data, - Mode: d.Type(), - ModTime: info.ModTime().UTC(), - } - return nil - } - } - if err := fs.WalkDir(a, ".", walkFunc(a, aMap)); err != nil { - return false, err - } - if err := fs.WalkDir(b, ".", walkFunc(b, bMap)); err != nil { - return false, err - } - return reflect.DeepEqual(aMap, bMap), nil -} diff --git a/internal/rukpak/storage/storage.go b/internal/rukpak/storage/storage.go deleted file mode 100644 index 6cd6d4aa5..000000000 --- a/internal/rukpak/storage/storage.go +++ /dev/null @@ -1,20 +0,0 @@ -package storage - -import ( - "context" - "io/fs" -) - -type Storage interface { - Loader - Storer -} - -type Loader interface { - Load(ctx context.Context, owner string) (fs.FS, error) -} - -type Storer interface { - Store(ctx context.Context, owner string, bundle fs.FS) error - Delete(ctx context.Context, owner string) error -} diff --git a/internal/rukpak/storage/storage_suite_test.go b/internal/rukpak/storage/storage_suite_test.go deleted file mode 100644 index 8c583497e..000000000 --- a/internal/rukpak/storage/storage_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package storage_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestStorage(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Storage Suite") -}