From a43ef258d51fd00dd2a4ac205c4c79cf78582429 Mon Sep 17 00:00:00 2001 From: Per Goncalves da Silva Date: Mon, 10 Feb 2025 13:02:12 +0100 Subject: [PATCH 1/2] Decompose RegistryV1ToHelmChart function Signed-off-by: Per Goncalves da Silva --- .../rukpak/convert/registryv1.go | 61 +++++----- .../rukpak/convert/registryv1_test.go | 111 +++++++----------- 2 files changed, 77 insertions(+), 95 deletions(-) diff --git a/internal/operator-controller/rukpak/convert/registryv1.go b/internal/operator-controller/rukpak/convert/registryv1.go index 0bbc7dfaa0..87fde4d1b1 100644 --- a/internal/operator-controller/rukpak/convert/registryv1.go +++ b/internal/operator-controller/rukpak/convert/registryv1.go @@ -42,16 +42,44 @@ type Plain struct { } func RegistryV1ToHelmChart(ctx context.Context, rv1 fs.FS, installNamespace string, watchNamespaces []string) (*chart.Chart, error) { + reg, err := ParseFS(ctx, rv1) + if err != nil { + return nil, err + } + + plain, err := Convert(reg, installNamespace, watchNamespaces) + if err != nil { + return nil, err + } + + chrt := &chart.Chart{Metadata: &chart.Metadata{}} + chrt.Metadata.Annotations = reg.CSV.GetAnnotations() + for _, obj := range plain.Objects { + jsonData, err := json.Marshal(obj) + if err != nil { + return nil, err + } + hash := sha256.Sum256(jsonData) + chrt.Templates = append(chrt.Templates, &chart.File{ + Name: fmt.Sprintf("object-%x.json", hash[0:8]), + Data: jsonData, + }) + } + + return chrt, nil +} + +func ParseFS(ctx context.Context, rv1 fs.FS) (RegistryV1, error) { l := log.FromContext(ctx) reg := RegistryV1{} annotationsFileData, err := fs.ReadFile(rv1, filepath.Join("metadata", "annotations.yaml")) if err != nil { - return nil, err + return reg, err } annotationsFile := registry.AnnotationsFile{} if err := yaml.Unmarshal(annotationsFileData, &annotationsFile); err != nil { - return nil, err + return reg, err } reg.PackageName = annotationsFile.Annotations.PackageName @@ -100,14 +128,14 @@ func RegistryV1ToHelmChart(ctx context.Context, rv1 fs.FS, installNamespace stri } return nil }); err != nil { - return nil, err + return reg, err } if err := copyMetadataPropertiesToCSV(®.CSV, rv1); err != nil { - return nil, err + return reg, err } - return toChart(reg, installNamespace, watchNamespaces) + return reg, nil } // copyMetadataPropertiesToCSV copies properties from `metadata/propeties.yaml` (in the filesystem fsys) into @@ -158,29 +186,6 @@ func copyMetadataPropertiesToCSV(csv *v1alpha1.ClusterServiceVersion, fsys fs.FS return nil } -func toChart(in RegistryV1, installNamespace string, watchNamespaces []string) (*chart.Chart, error) { - plain, err := Convert(in, installNamespace, watchNamespaces) - if err != nil { - return nil, err - } - - chrt := &chart.Chart{Metadata: &chart.Metadata{}} - chrt.Metadata.Annotations = in.CSV.GetAnnotations() - for _, obj := range plain.Objects { - jsonData, err := json.Marshal(obj) - if err != nil { - return nil, err - } - hash := sha256.Sum256(jsonData) - chrt.Templates = append(chrt.Templates, &chart.File{ - Name: fmt.Sprintf("object-%x.json", hash[0:8]), - Data: jsonData, - }) - } - - return chrt, nil -} - func validateTargetNamespaces(supportedInstallModes sets.Set[string], installNamespace string, targetNamespaces []string) error { set := sets.New[string](targetNamespaces...) switch { diff --git a/internal/operator-controller/rukpak/convert/registryv1_test.go b/internal/operator-controller/rukpak/convert/registryv1_test.go index 4e36059c74..6ea3c8c725 100644 --- a/internal/operator-controller/rukpak/convert/registryv1_test.go +++ b/internal/operator-controller/rukpak/convert/registryv1_test.go @@ -1,8 +1,9 @@ -package convert +package convert_test import ( "context" "fmt" + "github.com/operator-framework/operator-controller/internal/rukpak/convert" "os" "strings" "testing" @@ -49,7 +50,7 @@ func getCsvAndService() (v1alpha1.ClusterServiceVersion, corev1.Service) { func TestRegistryV1SuiteNamespaceNotAvailable(t *testing.T) { var targetNamespaces []string - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should set the namespaces of the object correctly") t.Log("It should set the namespace to installnamespace if not available") @@ -57,14 +58,14 @@ func TestRegistryV1SuiteNamespaceNotAvailable(t *testing.T) { csv, svc := getCsvAndService() unstructuredSvc := convertToUnstructured(t, svc) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: csv, Others: []unstructured.Unstructured{unstructuredSvc}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, targetNamespaces) require.NoError(t, err) t.Log("By verifying if plain bundle has required objects") @@ -80,7 +81,7 @@ func TestRegistryV1SuiteNamespaceNotAvailable(t *testing.T) { func TestRegistryV1SuiteNamespaceAvailable(t *testing.T) { var targetNamespaces []string - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should set the namespaces of the object correctly") t.Log("It should override namespace if already available") @@ -91,14 +92,14 @@ func TestRegistryV1SuiteNamespaceAvailable(t *testing.T) { unstructuredSvc := convertToUnstructured(t, svc) unstructuredSvc.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: csv, Others: []unstructured.Unstructured{unstructuredSvc}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, targetNamespaces) require.NoError(t, err) t.Log("By verifying if plain bundle has required objects") @@ -114,7 +115,7 @@ func TestRegistryV1SuiteNamespaceAvailable(t *testing.T) { func TestRegistryV1SuiteNamespaceUnsupportedKind(t *testing.T) { var targetNamespaces []string - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should set the namespaces of the object correctly") t.Log("It should error when object is not supported") t.Log("It should error when unsupported GVK is passed") @@ -132,14 +133,14 @@ func TestRegistryV1SuiteNamespaceUnsupportedKind(t *testing.T) { unstructuredEvt := convertToUnstructured(t, event) unstructuredEvt.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Event"}) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: csv, Others: []unstructured.Unstructured{unstructuredEvt}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, targetNamespaces) require.Error(t, err) require.ErrorContains(t, err, "bundle contains unsupported resource") require.Nil(t, plainBundle) @@ -148,7 +149,7 @@ func TestRegistryV1SuiteNamespaceUnsupportedKind(t *testing.T) { func TestRegistryV1SuiteNamespaceClusterScoped(t *testing.T) { var targetNamespaces []string - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should set the namespaces of the object correctly") t.Log("It should not set ns cluster scoped object is passed") t.Log("It should not error when cluster scoped obj is passed and not set its namespace") @@ -166,14 +167,14 @@ func TestRegistryV1SuiteNamespaceClusterScoped(t *testing.T) { unstructuredpriorityclass := convertToUnstructured(t, pc) unstructuredpriorityclass.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PriorityClass"}) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: csv, Others: []unstructured.Unstructured{unstructuredpriorityclass}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, targetNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, targetNamespaces) require.NoError(t, err) t.Log("By verifying if plain bundle has required objects") @@ -242,7 +243,7 @@ func getBaseCsvAndService() (v1alpha1.ClusterServiceVersion, corev1.Service) { } func TestRegistryV1SuiteGenerateAllNamespace(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should convert into plain manifests successfully with AllNamespaces") @@ -253,14 +254,14 @@ func TestRegistryV1SuiteGenerateAllNamespace(t *testing.T) { t.Log("By creating a registry v1 bundle") watchNamespaces := []string{""} unstructuredSvc := convertToUnstructured(t, svc) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: *csv, Others: []unstructured.Unstructured{unstructuredSvc}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces) require.NoError(t, err) t.Log("By verifying if plain bundle has required objects") @@ -275,7 +276,7 @@ func TestRegistryV1SuiteGenerateAllNamespace(t *testing.T) { } func TestRegistryV1SuiteGenerateMultiNamespace(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should convert into plain manifests successfully with MultiNamespace") @@ -286,14 +287,14 @@ func TestRegistryV1SuiteGenerateMultiNamespace(t *testing.T) { t.Log("By creating a registry v1 bundle") watchNamespaces := []string{"testWatchNs1", "testWatchNs2"} unstructuredSvc := convertToUnstructured(t, svc) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: *csv, Others: []unstructured.Unstructured{unstructuredSvc}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces) require.NoError(t, err) t.Log("By verifying if plain bundle has required objects") @@ -308,7 +309,7 @@ func TestRegistryV1SuiteGenerateMultiNamespace(t *testing.T) { } func TestRegistryV1SuiteGenerateSingleNamespace(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should convert into plain manifests successfully with SingleNamespace") @@ -319,14 +320,14 @@ func TestRegistryV1SuiteGenerateSingleNamespace(t *testing.T) { t.Log("By creating a registry v1 bundle") watchNamespaces := []string{"testWatchNs1"} unstructuredSvc := convertToUnstructured(t, svc) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: *csv, Others: []unstructured.Unstructured{unstructuredSvc}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces) require.NoError(t, err) t.Log("By verifying if plain bundle has required objects") @@ -341,7 +342,7 @@ func TestRegistryV1SuiteGenerateSingleNamespace(t *testing.T) { } func TestRegistryV1SuiteGenerateOwnNamespace(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should convert into plain manifests successfully with own namespace") @@ -352,14 +353,14 @@ func TestRegistryV1SuiteGenerateOwnNamespace(t *testing.T) { t.Log("By creating a registry v1 bundle") watchNamespaces := []string{installNamespace} unstructuredSvc := convertToUnstructured(t, svc) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: *csv, Others: []unstructured.Unstructured{unstructuredSvc}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces) require.NoError(t, err) t.Log("By verifying if plain bundle has required objects") @@ -374,7 +375,7 @@ func TestRegistryV1SuiteGenerateOwnNamespace(t *testing.T) { } func TestRegistryV1SuiteGenerateErrorMultiNamespaceEmpty(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should error when multinamespace mode is supported with an empty string in target namespaces") @@ -385,20 +386,20 @@ func TestRegistryV1SuiteGenerateErrorMultiNamespaceEmpty(t *testing.T) { t.Log("By creating a registry v1 bundle") watchNamespaces := []string{"testWatchNs1", ""} unstructuredSvc := convertToUnstructured(t, svc) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: *csv, Others: []unstructured.Unstructured{unstructuredSvc}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces) require.Error(t, err) require.Nil(t, plainBundle) } func TestRegistryV1SuiteGenerateErrorSingleNamespaceDisabled(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should error when single namespace mode is disabled with more than one target namespaces") @@ -409,20 +410,20 @@ func TestRegistryV1SuiteGenerateErrorSingleNamespaceDisabled(t *testing.T) { t.Log("By creating a registry v1 bundle") watchNamespaces := []string{"testWatchNs1", "testWatchNs2"} unstructuredSvc := convertToUnstructured(t, svc) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: *csv, Others: []unstructured.Unstructured{unstructuredSvc}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces) require.Error(t, err) require.Nil(t, plainBundle) } func TestRegistryV1SuiteGenerateErrorAllNamespaceDisabled(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should error when all namespace mode is disabled with target namespace containing an empty string") @@ -438,50 +439,26 @@ func TestRegistryV1SuiteGenerateErrorAllNamespaceDisabled(t *testing.T) { t.Log("By creating a registry v1 bundle") watchNamespaces := []string{""} unstructuredSvc := convertToUnstructured(t, svc) - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: *csv, Others: []unstructured.Unstructured{unstructuredSvc}, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces) require.Error(t, err) require.Nil(t, plainBundle) } -func TestRegistryV1SuiteGeneratePropagateCsvAnnotations(t *testing.T) { - t.Log("RegistryV1 Suite Convert") - t.Log("It should generate objects successfully based on target namespaces") - - t.Log("It should propagate csv annotations to chart metadata annotation") - baseCSV, svc := getBaseCsvAndService() - csv := baseCSV.DeepCopy() - csv.Spec.InstallModes = []v1alpha1.InstallMode{{Type: v1alpha1.InstallModeTypeMultiNamespace, Supported: true}} - - t.Log("By creating a registry v1 bundle") - watchNamespaces := []string{"testWatchNs1", "testWatchNs2"} - unstructuredSvc := convertToUnstructured(t, svc) - registryv1Bundle := RegistryV1{ - PackageName: "testPkg", - CSV: *csv, - Others: []unstructured.Unstructured{unstructuredSvc}, - } - - t.Log("By converting to helm") - chrt, err := toChart(registryv1Bundle, installNamespace, watchNamespaces) - require.NoError(t, err) - require.Contains(t, chrt.Metadata.Annotations, olmProperties) -} - func TestRegistryV1SuiteReadBundleFileSystem(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should read the registry+v1 bundle filesystem correctly") t.Log("It should include metadata/properties.yaml and csv.metadata.annotations['olm.properties'] in chart metadata") fsys := os.DirFS("testdata/combine-properties-bundle") - chrt, err := RegistryV1ToHelmChart(context.Background(), fsys, "", nil) + chrt, err := convert.RegistryV1ToHelmChart(context.Background(), fsys, "", nil) require.NoError(t, err) require.NotNil(t, chrt) require.NotNil(t, chrt.Metadata) @@ -490,7 +467,7 @@ func TestRegistryV1SuiteReadBundleFileSystem(t *testing.T) { } func TestRegistryV1SuiteGenerateNoWebhooks(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should enforce limitations") @@ -506,20 +483,20 @@ func TestRegistryV1SuiteGenerateNoWebhooks(t *testing.T) { }, } watchNamespaces := []string{metav1.NamespaceAll} - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: csv, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces) require.Error(t, err) require.ErrorContains(t, err, "webhookDefinitions are not supported") require.Nil(t, plainBundle) } func TestRegistryV1SuiteGenerateNoAPISerciceDefinitions(t *testing.T) { - t.Log("RegistryV1 Suite Convert") + t.Log("convert.RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should enforce limitations") @@ -537,13 +514,13 @@ func TestRegistryV1SuiteGenerateNoAPISerciceDefinitions(t *testing.T) { }, } watchNamespaces := []string{metav1.NamespaceAll} - registryv1Bundle := RegistryV1{ + registryv1Bundle := convert.RegistryV1{ PackageName: "testPkg", CSV: csv, } t.Log("By converting to plain") - plainBundle, err := Convert(registryv1Bundle, installNamespace, watchNamespaces) + plainBundle, err := convert.Convert(registryv1Bundle, installNamespace, watchNamespaces) require.Error(t, err) require.ErrorContains(t, err, "apiServiceDefintions are not supported") require.Nil(t, plainBundle) @@ -559,7 +536,7 @@ func convertToUnstructured(t *testing.T, obj interface{}) unstructured.Unstructu func findObjectByName(name string, result []client.Object) client.Object { for _, o := range result { // Since this is a controlled env, comparing only the names is sufficient for now. - // In future, compare GVKs too by ensuring its set on the unstructuredObj. + // In the future, compare GVKs too by ensuring its set on the unstructuredObj. if o.GetName() == name { return o } From f02b793144a692f59cca22d17e2bd9cad66cb156 Mon Sep 17 00:00:00 2001 From: Per Goncalves da Silva Date: Tue, 11 Feb 2025 09:55:47 +0100 Subject: [PATCH 2/2] Add ParseFS failure unit tests Signed-off-by: Per Goncalves da Silva Signed-off-by: Per G. da Silva --- .../rukpak/convert/registryv1.go | 14 +++ .../rukpak/convert/registryv1_test.go | 115 +++++++++++++++--- 2 files changed, 114 insertions(+), 15 deletions(-) diff --git a/internal/operator-controller/rukpak/convert/registryv1.go b/internal/operator-controller/rukpak/convert/registryv1.go index 87fde4d1b1..b08c4a988f 100644 --- a/internal/operator-controller/rukpak/convert/registryv1.go +++ b/internal/operator-controller/rukpak/convert/registryv1.go @@ -69,6 +69,14 @@ func RegistryV1ToHelmChart(ctx context.Context, rv1 fs.FS, installNamespace stri return chrt, nil } +// ParseFS converts the rv1 filesystem into a RegistryV1. +// ParseFS expects the filesystem to conform to the registry+v1 format: +// metadata/annotations.yaml +// manifests/ +// - csv.yaml +// - ... +// +// manifests directory does not contain subdirectories func ParseFS(ctx context.Context, rv1 fs.FS) (RegistryV1, error) { l := log.FromContext(ctx) @@ -84,6 +92,7 @@ func ParseFS(ctx context.Context, rv1 fs.FS) (RegistryV1, error) { reg.PackageName = annotationsFile.Annotations.PackageName const manifestsDir = "manifests" + foundCSV := false if err := fs.WalkDir(rv1, manifestsDir, func(path string, e fs.DirEntry, err error) error { if err != nil { return err @@ -119,6 +128,7 @@ func ParseFS(ctx context.Context, rv1 fs.FS) (RegistryV1, error) { return err } reg.CSV = csv + foundCSV = true default: reg.Others = append(reg.Others, *info.Object.(*unstructured.Unstructured)) } @@ -131,6 +141,10 @@ func ParseFS(ctx context.Context, rv1 fs.FS) (RegistryV1, error) { return reg, err } + if !foundCSV { + return reg, fmt.Errorf("no ClusterServiceVersion found in %q", manifestsDir) + } + if err := copyMetadataPropertiesToCSV(®.CSV, rv1); err != nil { return reg, err } diff --git a/internal/operator-controller/rukpak/convert/registryv1_test.go b/internal/operator-controller/rukpak/convert/registryv1_test.go index 6ea3c8c725..42786fada3 100644 --- a/internal/operator-controller/rukpak/convert/registryv1_test.go +++ b/internal/operator-controller/rukpak/convert/registryv1_test.go @@ -3,10 +3,11 @@ package convert_test import ( "context" "fmt" - "github.com/operator-framework/operator-controller/internal/rukpak/convert" + "io/fs" "os" "strings" "testing" + "testing/fstest" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" @@ -21,12 +22,17 @@ import ( "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-registry/alpha/property" + + "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/convert" ) const ( olmNamespaces = "olm.targetNamespaces" olmProperties = "olm.properties" installNamespace = "testInstallNamespace" + + bundlePathAnnotations = "metadata/annotations.yaml" + bundlePathCSV = "manifests/csv.yaml" ) func getCsvAndService() (v1alpha1.ClusterServiceVersion, corev1.Service) { @@ -50,7 +56,7 @@ func getCsvAndService() (v1alpha1.ClusterServiceVersion, corev1.Service) { func TestRegistryV1SuiteNamespaceNotAvailable(t *testing.T) { var targetNamespaces []string - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should set the namespaces of the object correctly") t.Log("It should set the namespace to installnamespace if not available") @@ -81,7 +87,7 @@ func TestRegistryV1SuiteNamespaceNotAvailable(t *testing.T) { func TestRegistryV1SuiteNamespaceAvailable(t *testing.T) { var targetNamespaces []string - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should set the namespaces of the object correctly") t.Log("It should override namespace if already available") @@ -115,7 +121,7 @@ func TestRegistryV1SuiteNamespaceAvailable(t *testing.T) { func TestRegistryV1SuiteNamespaceUnsupportedKind(t *testing.T) { var targetNamespaces []string - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should set the namespaces of the object correctly") t.Log("It should error when object is not supported") t.Log("It should error when unsupported GVK is passed") @@ -149,7 +155,7 @@ func TestRegistryV1SuiteNamespaceUnsupportedKind(t *testing.T) { func TestRegistryV1SuiteNamespaceClusterScoped(t *testing.T) { var targetNamespaces []string - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should set the namespaces of the object correctly") t.Log("It should not set ns cluster scoped object is passed") t.Log("It should not error when cluster scoped obj is passed and not set its namespace") @@ -243,7 +249,7 @@ func getBaseCsvAndService() (v1alpha1.ClusterServiceVersion, corev1.Service) { } func TestRegistryV1SuiteGenerateAllNamespace(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should convert into plain manifests successfully with AllNamespaces") @@ -276,7 +282,7 @@ func TestRegistryV1SuiteGenerateAllNamespace(t *testing.T) { } func TestRegistryV1SuiteGenerateMultiNamespace(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should convert into plain manifests successfully with MultiNamespace") @@ -309,7 +315,7 @@ func TestRegistryV1SuiteGenerateMultiNamespace(t *testing.T) { } func TestRegistryV1SuiteGenerateSingleNamespace(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should convert into plain manifests successfully with SingleNamespace") @@ -342,7 +348,7 @@ func TestRegistryV1SuiteGenerateSingleNamespace(t *testing.T) { } func TestRegistryV1SuiteGenerateOwnNamespace(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should convert into plain manifests successfully with own namespace") @@ -375,7 +381,7 @@ func TestRegistryV1SuiteGenerateOwnNamespace(t *testing.T) { } func TestRegistryV1SuiteGenerateErrorMultiNamespaceEmpty(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should error when multinamespace mode is supported with an empty string in target namespaces") @@ -399,7 +405,7 @@ func TestRegistryV1SuiteGenerateErrorMultiNamespaceEmpty(t *testing.T) { } func TestRegistryV1SuiteGenerateErrorSingleNamespaceDisabled(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should error when single namespace mode is disabled with more than one target namespaces") @@ -423,7 +429,7 @@ func TestRegistryV1SuiteGenerateErrorSingleNamespaceDisabled(t *testing.T) { } func TestRegistryV1SuiteGenerateErrorAllNamespaceDisabled(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should error when all namespace mode is disabled with target namespace containing an empty string") @@ -452,7 +458,7 @@ func TestRegistryV1SuiteGenerateErrorAllNamespaceDisabled(t *testing.T) { } func TestRegistryV1SuiteReadBundleFileSystem(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should read the registry+v1 bundle filesystem correctly") @@ -466,8 +472,50 @@ func TestRegistryV1SuiteReadBundleFileSystem(t *testing.T) { require.JSONEq(t, `[{"type":"from-csv-annotations-key","value":"from-csv-annotations-value"},{"type":"from-file-key","value":"from-file-value"}]`, chrt.Metadata.Annotations[olmProperties]) } +func TestParseFSFails(t *testing.T) { + for _, tt := range []struct { + name string + FS fs.FS + }{ + { + name: "bundle missing ClusterServiceVersion manifest", + FS: removePaths(newBundleFS(), bundlePathCSV), + }, { + name: "bundle missing metadata/annotations.yaml", + FS: removePaths(newBundleFS(), bundlePathAnnotations), + }, { + name: "bundle missing metadata/ directory", + FS: removePaths(newBundleFS(), "metadata/"), + }, { + name: "bundle missing manifests/ directory", + FS: removePaths(newBundleFS(), "manifests/"), + }, + } { + t.Run(tt.name, func(t *testing.T) { + _, err := convert.ParseFS(context.Background(), tt.FS) + require.Error(t, err) + }) + } +} + +func TestRegistryV1SuiteReadBundleFileSystemFailsOnNoCSV(t *testing.T) { + t.Log("RegistryV1 Suite Convert") + t.Log("It should generate objects successfully based on target namespaces") + + t.Log("It should read the registry+v1 bundle filesystem correctly") + t.Log("It should include metadata/properties.yaml and csv.metadata.annotations['olm.properties'] in chart metadata") + fsys := os.DirFS("testdata/combine-properties-bundle") + + chrt, err := convert.RegistryV1ToHelmChart(context.Background(), fsys, "", nil) + require.NoError(t, err) + require.NotNil(t, chrt) + require.NotNil(t, chrt.Metadata) + require.Contains(t, chrt.Metadata.Annotations, olmProperties) + require.JSONEq(t, `[{"type":"from-csv-annotations-key","value":"from-csv-annotations-value"},{"type":"from-file-key","value":"from-file-value"}]`, chrt.Metadata.Annotations[olmProperties]) +} + func TestRegistryV1SuiteGenerateNoWebhooks(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should enforce limitations") @@ -496,7 +544,7 @@ func TestRegistryV1SuiteGenerateNoWebhooks(t *testing.T) { } func TestRegistryV1SuiteGenerateNoAPISerciceDefinitions(t *testing.T) { - t.Log("convert.RegistryV1 Suite Convert") + t.Log("RegistryV1 Suite Convert") t.Log("It should generate objects successfully based on target namespaces") t.Log("It should enforce limitations") @@ -543,3 +591,40 @@ func findObjectByName(name string, result []client.Object) client.Object { } return nil } + +func newBundleFS() fstest.MapFS { + annotationsYml := ` +annotations: + operators.operatorframework.io.bundle.mediatype.v1: registry+v1 + operators.operatorframework.io.bundle.package.v1: test +` + + csvYml := ` +apiVersion: operators.operatorframework.io/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: test.v1.0.0 + annotations: + olm.properties: '[{"type":"from-csv-annotations-key", "value":"from-csv-annotations-value"}]' +spec: + installModes: + - type: AllNamespaces + supported: true +` + + return fstest.MapFS{ + bundlePathAnnotations: &fstest.MapFile{Data: []byte(strings.Trim(annotationsYml, "\n"))}, + bundlePathCSV: &fstest.MapFile{Data: []byte(strings.Trim(csvYml, "\n"))}, + } +} + +func removePaths(mapFs fstest.MapFS, paths ...string) fstest.MapFS { + for k := range mapFs { + for _, path := range paths { + if strings.HasPrefix(k, path) { + delete(mapFs, k) + } + } + } + return mapFs +}