github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/source_registry_test.go (about)

     1  package resolver
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/blang/semver/v4"
     9  	opver "github.com/operator-framework/api/pkg/lib/version"
    10  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    11  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    12  	"github.com/operator-framework/operator-registry/pkg/api"
    13  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  )
    19  
    20  func TestNewOperatorFromBundle(t *testing.T) {
    21  	version := opver.OperatorVersion{Version: semver.MustParse("0.1.0-abc")}
    22  	csv := v1alpha1.ClusterServiceVersion{
    23  		TypeMeta: metav1.TypeMeta{
    24  			Kind:       v1alpha1.ClusterServiceVersionKind,
    25  			APIVersion: v1alpha1.GroupVersion,
    26  		},
    27  		ObjectMeta: metav1.ObjectMeta{
    28  			Name:      "testCSV",
    29  			Namespace: "testNamespace",
    30  		},
    31  		Spec: v1alpha1.ClusterServiceVersionSpec{
    32  			Replaces: "v1",
    33  			CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{
    34  				Owned:    []v1alpha1.CRDDescription{},
    35  				Required: []v1alpha1.CRDDescription{},
    36  			},
    37  			APIServiceDefinitions: v1alpha1.APIServiceDefinitions{
    38  				Owned:    []v1alpha1.APIServiceDescription{},
    39  				Required: []v1alpha1.APIServiceDescription{},
    40  			},
    41  			Version: version,
    42  		},
    43  	}
    44  
    45  	csvJSON, err := json.Marshal(csv)
    46  	require.NoError(t, err)
    47  	bundleNoAPIs := &api.Bundle{
    48  		CsvName:     "testBundle",
    49  		PackageName: "testPackage",
    50  		ChannelName: "testChannel",
    51  		Version:     version.String(),
    52  		CsvJson:     string(csvJSON),
    53  		Object:      []string{string(csvJSON)},
    54  	}
    55  
    56  	csv.Spec.CustomResourceDefinitions.Owned = []v1alpha1.CRDDescription{
    57  		{
    58  			Name:    "owneds.crd.group.com",
    59  			Version: "v1",
    60  			Kind:    "OwnedCRD",
    61  		},
    62  	}
    63  	csv.Spec.CustomResourceDefinitions.Required = []v1alpha1.CRDDescription{
    64  		{
    65  			Name:    "requireds.crd.group.com",
    66  			Version: "v1",
    67  			Kind:    "RequiredCRD",
    68  		},
    69  	}
    70  	csv.Spec.APIServiceDefinitions.Owned = []v1alpha1.APIServiceDescription{
    71  		{
    72  			Name:    "ownedapis",
    73  			Group:   "apis.group.com",
    74  			Version: "v1",
    75  			Kind:    "OwnedAPI",
    76  		},
    77  	}
    78  	csv.Spec.APIServiceDefinitions.Required = []v1alpha1.APIServiceDescription{
    79  		{
    80  			Name:    "requiredapis",
    81  			Group:   "apis.group.com",
    82  			Version: "v1",
    83  			Kind:    "RequiredAPI",
    84  		},
    85  	}
    86  
    87  	csvJSONWithAPIs, err := json.Marshal(csv)
    88  	require.NoError(t, err)
    89  
    90  	crd := v1beta1.CustomResourceDefinition{
    91  		TypeMeta: metav1.TypeMeta{
    92  			Kind:       "CustomResourceDefinition",
    93  			APIVersion: "apiextensions.k8s.io/v1beta1",
    94  		},
    95  		ObjectMeta: metav1.ObjectMeta{
    96  			Name: "owneds.crd.group.com",
    97  		},
    98  		Spec: v1beta1.CustomResourceDefinitionSpec{
    99  			Group: "crd.group.com",
   100  			Versions: []v1beta1.CustomResourceDefinitionVersion{
   101  				{
   102  					Name:    "v1",
   103  					Served:  true,
   104  					Storage: true,
   105  				},
   106  			},
   107  			Names: v1beta1.CustomResourceDefinitionNames{
   108  				Plural:   "owneds",
   109  				Singular: "owned",
   110  				Kind:     "OwnedCRD",
   111  				ListKind: "OwnedCRDList",
   112  			},
   113  		},
   114  	}
   115  	crdJSON, err := json.Marshal(crd)
   116  	require.NoError(t, err)
   117  
   118  	bundleWithAPIs := &api.Bundle{
   119  		CsvName:     "testBundle",
   120  		PackageName: "testPackage",
   121  		ChannelName: "testChannel",
   122  		Version:     version.String(),
   123  		CsvJson:     string(csvJSONWithAPIs),
   124  		Object:      []string{string(csvJSONWithAPIs), string(crdJSON)},
   125  		ProvidedApis: []*api.GroupVersionKind{
   126  			{
   127  				Group:   "crd.group.com",
   128  				Version: "v1",
   129  				Kind:    "OwnedCRD",
   130  				Plural:  "owneds",
   131  			},
   132  			{
   133  				Plural:  "ownedapis",
   134  				Group:   "apis.group.com",
   135  				Version: "v1",
   136  				Kind:    "OwnedAPI",
   137  			},
   138  		},
   139  		RequiredApis: []*api.GroupVersionKind{
   140  			{
   141  				Group:   "crd.group.com",
   142  				Version: "v1",
   143  				Kind:    "RequiredCRD",
   144  				Plural:  "requireds",
   145  			},
   146  			{
   147  				Plural:  "requiredapis",
   148  				Group:   "apis.group.com",
   149  				Version: "v1",
   150  				Kind:    "RequiredAPI",
   151  			},
   152  		},
   153  	}
   154  
   155  	bundleWithPropsAndDeps := &api.Bundle{
   156  		CsvName:     "testBundle",
   157  		PackageName: "testPackage",
   158  		ChannelName: "testChannel",
   159  		Version:     version.String(),
   160  		BundlePath:  "image",
   161  		Properties: []*api.Property{
   162  			{
   163  				Type:  "olm.gvk",
   164  				Value: "{\"group\":\"crd.group.com\",\"kind\":\"OwnedCRD\",\"version\":\"v1\"}",
   165  			},
   166  			{
   167  				Type:  "olm.gvk",
   168  				Value: "{\"group\":\"apis.group.com\",\"kind\":\"OwnedAPI\",\"version\":\"v1\"}",
   169  			},
   170  		},
   171  		Dependencies: []*api.Dependency{
   172  			{
   173  				Type:  "olm.gvk",
   174  				Value: "{\"group\":\"crd.group.com\",\"kind\":\"RequiredCRD\",\"version\":\"v1\"}",
   175  			},
   176  			{
   177  				Type:  "olm.gvk",
   178  				Value: "{\"group\":\"apis.group.com\",\"kind\":\"RequiredAPI\",\"version\":\"v1\"}",
   179  			},
   180  		},
   181  	}
   182  
   183  	bundleWithAPIsUnextracted := &api.Bundle{
   184  		CsvName:     "testBundle",
   185  		PackageName: "testPackage",
   186  		ChannelName: "testChannel",
   187  		CsvJson:     string(csvJSONWithAPIs),
   188  		Object:      []string{string(csvJSONWithAPIs), string(crdJSON)},
   189  	}
   190  
   191  	type args struct {
   192  		bundle         *api.Bundle
   193  		sourceKey      cache.SourceKey
   194  		defaultChannel string
   195  	}
   196  	tests := []struct {
   197  		name    string
   198  		args    args
   199  		want    *cache.Entry
   200  		wantErr error
   201  	}{
   202  		{
   203  			name: "BundleNoAPIs",
   204  			args: args{
   205  				bundle:    bundleNoAPIs,
   206  				sourceKey: cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   207  			},
   208  			want: &cache.Entry{
   209  				Name:         "testBundle",
   210  				Version:      &version.Version,
   211  				ProvidedAPIs: cache.EmptyAPISet(),
   212  				RequiredAPIs: cache.EmptyAPISet(),
   213  				Bundle:       bundleNoAPIs,
   214  				SourceInfo: &cache.OperatorSourceInfo{
   215  					Package: "testPackage",
   216  					Channel: "testChannel",
   217  					Catalog: cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   218  				},
   219  			},
   220  		},
   221  		{
   222  			name: "BundleWithAPIs",
   223  			args: args{
   224  				bundle:    bundleWithAPIs,
   225  				sourceKey: cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   226  			},
   227  			want: &cache.Entry{
   228  				Name:    "testBundle",
   229  				Version: &version.Version,
   230  				ProvidedAPIs: cache.APISet{
   231  					opregistry.APIKey{
   232  						Group:   "crd.group.com",
   233  						Version: "v1",
   234  						Kind:    "OwnedCRD",
   235  						Plural:  "owneds",
   236  					}: struct{}{},
   237  					opregistry.APIKey{
   238  						Group:   "apis.group.com",
   239  						Version: "v1",
   240  						Kind:    "OwnedAPI",
   241  						Plural:  "ownedapis",
   242  					}: struct{}{},
   243  				},
   244  				RequiredAPIs: cache.APISet{
   245  					opregistry.APIKey{
   246  						Group:   "crd.group.com",
   247  						Version: "v1",
   248  						Kind:    "RequiredCRD",
   249  						Plural:  "requireds",
   250  					}: struct{}{},
   251  					opregistry.APIKey{
   252  						Group:   "apis.group.com",
   253  						Version: "v1",
   254  						Kind:    "RequiredAPI",
   255  						Plural:  "requiredapis",
   256  					}: struct{}{},
   257  				},
   258  				Properties: []*api.Property{
   259  					{
   260  						Type:  "olm.gvk",
   261  						Value: "{\"group\":\"crd.group.com\",\"kind\":\"OwnedCRD\",\"version\":\"v1\"}",
   262  					},
   263  					{
   264  						Type:  "olm.gvk",
   265  						Value: "{\"group\":\"apis.group.com\",\"kind\":\"OwnedAPI\",\"version\":\"v1\"}",
   266  					},
   267  					{
   268  						Type:  "olm.gvk.required",
   269  						Value: "{\"group\":\"crd.group.com\",\"kind\":\"RequiredCRD\",\"version\":\"v1\"}",
   270  					},
   271  					{
   272  						Type:  "olm.gvk.required",
   273  						Value: "{\"group\":\"apis.group.com\",\"kind\":\"RequiredAPI\",\"version\":\"v1\"}",
   274  					},
   275  				},
   276  				Bundle: bundleWithAPIs,
   277  				SourceInfo: &cache.OperatorSourceInfo{
   278  					Package: "testPackage",
   279  					Channel: "testChannel",
   280  					Catalog: cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   281  				},
   282  			},
   283  		},
   284  		{
   285  			name: "BundleIgnoreCSV",
   286  			args: args{
   287  				bundle:    bundleWithAPIsUnextracted,
   288  				sourceKey: cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   289  			},
   290  			want: &cache.Entry{
   291  				Name:         "testBundle",
   292  				ProvidedAPIs: cache.EmptyAPISet(),
   293  				RequiredAPIs: cache.EmptyAPISet(),
   294  				Bundle:       bundleWithAPIsUnextracted,
   295  				SourceInfo: &cache.OperatorSourceInfo{
   296  					Package: "testPackage",
   297  					Channel: "testChannel",
   298  					Catalog: cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   299  				},
   300  			},
   301  		},
   302  		{
   303  			name: "BundleInDefaultChannel",
   304  			args: args{
   305  				bundle:         bundleNoAPIs,
   306  				sourceKey:      cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   307  				defaultChannel: "testChannel",
   308  			},
   309  			want: &cache.Entry{
   310  				Name:         "testBundle",
   311  				Version:      &version.Version,
   312  				ProvidedAPIs: cache.EmptyAPISet(),
   313  				RequiredAPIs: cache.EmptyAPISet(),
   314  				Bundle:       bundleNoAPIs,
   315  				SourceInfo: &cache.OperatorSourceInfo{
   316  					Package:        "testPackage",
   317  					Channel:        "testChannel",
   318  					Catalog:        cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   319  					DefaultChannel: true,
   320  				},
   321  			},
   322  		},
   323  		{
   324  			name: "BundleWithPropertiesAndDependencies",
   325  			args: args{
   326  				bundle:    bundleWithPropsAndDeps,
   327  				sourceKey: cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   328  			},
   329  			want: &cache.Entry{
   330  				Name:         "testBundle",
   331  				Version:      &version.Version,
   332  				ProvidedAPIs: cache.EmptyAPISet(),
   333  				RequiredAPIs: cache.EmptyAPISet(),
   334  				Properties: []*api.Property{
   335  					{
   336  						Type:  "olm.gvk",
   337  						Value: "{\"group\":\"crd.group.com\",\"kind\":\"OwnedCRD\",\"version\":\"v1\"}",
   338  					},
   339  					{
   340  						Type:  "olm.gvk",
   341  						Value: "{\"group\":\"apis.group.com\",\"kind\":\"OwnedAPI\",\"version\":\"v1\"}",
   342  					},
   343  					{
   344  						Type:  "olm.gvk.required",
   345  						Value: "{\"group\":\"crd.group.com\",\"kind\":\"RequiredCRD\",\"version\":\"v1\"}",
   346  					},
   347  					{
   348  						Type:  "olm.gvk.required",
   349  						Value: "{\"group\":\"apis.group.com\",\"kind\":\"RequiredAPI\",\"version\":\"v1\"}",
   350  					},
   351  				},
   352  				BundlePath: bundleWithPropsAndDeps.BundlePath,
   353  				SourceInfo: &cache.OperatorSourceInfo{
   354  					Package: "testPackage",
   355  					Channel: "testChannel",
   356  					Catalog: cache.SourceKey{Name: "source", Namespace: "testNamespace"},
   357  				},
   358  			},
   359  		},
   360  	}
   361  	for _, tt := range tests {
   362  		t.Run(tt.name, func(t *testing.T) {
   363  			got, err := newOperatorFromBundle(tt.args.bundle, "", tt.args.sourceKey, tt.args.defaultChannel)
   364  			require.Equal(t, tt.wantErr, err)
   365  			requirePropertiesEqual(t, tt.want.Properties, got.Properties)
   366  			tt.want.Properties, got.Properties = nil, nil
   367  			require.Equal(t, tt.want, got)
   368  		})
   369  	}
   370  }
   371  
   372  func TestNewOperatorFromBundleStripsPluralRequiredAndProvidedAPIKeys(t *testing.T) {
   373  	key := cache.SourceKey{Namespace: "testnamespace", Name: "testname"}
   374  	o, err := newOperatorFromBundle(&api.Bundle{
   375  		CsvName: fmt.Sprintf("%s/%s", key.Namespace, key.Name),
   376  		ProvidedApis: []*api.GroupVersionKind{{
   377  			Group:   "g",
   378  			Version: "v1",
   379  			Kind:    "K",
   380  			Plural:  "ks",
   381  		}},
   382  		RequiredApis: []*api.GroupVersionKind{{
   383  			Group:   "g2",
   384  			Version: "v2",
   385  			Kind:    "K2",
   386  			Plural:  "ks2",
   387  		}},
   388  	}, "", key, "")
   389  
   390  	assert.NoError(t, err)
   391  	assert.Equal(t, "K.v1.g", o.ProvidedAPIs.String())
   392  	assert.Equal(t, "K2.v2.g2", o.RequiredAPIs.String())
   393  }