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

     1  package resolver
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/blang/semver/v4"
     8  	"github.com/sirupsen/logrus/hooks/test"
     9  	"github.com/stretchr/testify/require"
    10  	"k8s.io/apimachinery/pkg/api/errors"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/apimachinery/pkg/labels"
    13  
    14  	opver "github.com/operator-framework/api/pkg/lib/version"
    15  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    16  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    17  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    18  	"github.com/operator-framework/operator-registry/pkg/api"
    19  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    20  )
    21  
    22  func TestInferProperties(t *testing.T) {
    23  	catalog := cache.SourceKey{Namespace: "namespace", Name: "name"}
    24  
    25  	for _, tc := range []struct {
    26  		Name          string
    27  		CSV           *v1alpha1.ClusterServiceVersion
    28  		Subscriptions []*v1alpha1.Subscription
    29  		Expected      []*api.Property
    30  	}{
    31  		{
    32  			Name: "no subscriptions infers no properties",
    33  			CSV: &v1alpha1.ClusterServiceVersion{
    34  				ObjectMeta: metav1.ObjectMeta{
    35  					Name: "a",
    36  				},
    37  			},
    38  		},
    39  		{
    40  			Name: "one unrelated subscription infers no properties",
    41  			CSV: &v1alpha1.ClusterServiceVersion{
    42  				ObjectMeta: metav1.ObjectMeta{
    43  					Name: "a",
    44  				},
    45  			},
    46  			Subscriptions: []*v1alpha1.Subscription{
    47  				{
    48  					Spec: &v1alpha1.SubscriptionSpec{
    49  						Package: "x",
    50  					},
    51  					Status: v1alpha1.SubscriptionStatus{
    52  						InstalledCSV: "b",
    53  					},
    54  				},
    55  			},
    56  		},
    57  		{
    58  			Name: "one subscription with empty package field infers no properties",
    59  			CSV: &v1alpha1.ClusterServiceVersion{
    60  				ObjectMeta: metav1.ObjectMeta{
    61  					Name: "a",
    62  				},
    63  			},
    64  			Subscriptions: []*v1alpha1.Subscription{
    65  				{
    66  					Spec: &v1alpha1.SubscriptionSpec{
    67  						Package: "",
    68  					},
    69  					Status: v1alpha1.SubscriptionStatus{
    70  						InstalledCSV: "a",
    71  					},
    72  				},
    73  			},
    74  		},
    75  		{
    76  			Name: "two related subscriptions infers no properties",
    77  			CSV: &v1alpha1.ClusterServiceVersion{
    78  				ObjectMeta: metav1.ObjectMeta{
    79  					Name: "a",
    80  				},
    81  			},
    82  			Subscriptions: []*v1alpha1.Subscription{
    83  				{
    84  					Spec: &v1alpha1.SubscriptionSpec{
    85  						Package: "x",
    86  					},
    87  					Status: v1alpha1.SubscriptionStatus{
    88  						InstalledCSV: "a",
    89  					},
    90  				},
    91  				{
    92  					Spec: &v1alpha1.SubscriptionSpec{
    93  						Package: "y",
    94  					},
    95  					Status: v1alpha1.SubscriptionStatus{
    96  						InstalledCSV: "a",
    97  					},
    98  				},
    99  			},
   100  		},
   101  		{
   102  			Name: "one matching subscription infers package property",
   103  			CSV: &v1alpha1.ClusterServiceVersion{
   104  				ObjectMeta: metav1.ObjectMeta{
   105  					Name: "a",
   106  				},
   107  				Spec: v1alpha1.ClusterServiceVersionSpec{
   108  					Version: opver.OperatorVersion{Version: semver.MustParse("1.2.3")},
   109  				},
   110  			},
   111  			Subscriptions: []*v1alpha1.Subscription{
   112  				{
   113  					Spec: &v1alpha1.SubscriptionSpec{
   114  						Package:                "x",
   115  						CatalogSource:          catalog.Name,
   116  						CatalogSourceNamespace: catalog.Namespace,
   117  					},
   118  					Status: v1alpha1.SubscriptionStatus{
   119  						InstalledCSV: "a",
   120  					},
   121  				},
   122  			},
   123  			Expected: []*api.Property{
   124  				{
   125  					Type:  "olm.package",
   126  					Value: `{"packageName":"x","version":"1.2.3"}`,
   127  				},
   128  			},
   129  		},
   130  		{
   131  			Name: "one matching subscription to other-namespace catalogsource infers package property",
   132  			CSV: &v1alpha1.ClusterServiceVersion{
   133  				ObjectMeta: metav1.ObjectMeta{
   134  					Name: "a",
   135  				},
   136  				Spec: v1alpha1.ClusterServiceVersionSpec{
   137  					Version: opver.OperatorVersion{Version: semver.MustParse("1.2.3")},
   138  				},
   139  			},
   140  			Subscriptions: []*v1alpha1.Subscription{
   141  				{
   142  					Spec: &v1alpha1.SubscriptionSpec{
   143  						Package:                "x",
   144  						CatalogSource:          "other-name",
   145  						CatalogSourceNamespace: "other-namespace",
   146  					},
   147  					Status: v1alpha1.SubscriptionStatus{
   148  						InstalledCSV: "a",
   149  					},
   150  				},
   151  			},
   152  			Expected: []*api.Property{
   153  				{
   154  					Type:  "olm.package",
   155  					Value: `{"packageName":"x","version":"1.2.3"}`,
   156  				},
   157  			},
   158  		},
   159  		{
   160  			Name: "one matching subscription infers package property without csv version",
   161  			CSV: &v1alpha1.ClusterServiceVersion{
   162  				ObjectMeta: metav1.ObjectMeta{
   163  					Name: "a",
   164  				},
   165  			},
   166  			Subscriptions: []*v1alpha1.Subscription{
   167  				{
   168  					Spec: &v1alpha1.SubscriptionSpec{
   169  						Package:                "x",
   170  						CatalogSource:          catalog.Name,
   171  						CatalogSourceNamespace: catalog.Namespace,
   172  					},
   173  					Status: v1alpha1.SubscriptionStatus{
   174  						InstalledCSV: "a",
   175  					},
   176  				},
   177  			},
   178  			Expected: []*api.Property{
   179  				{
   180  					Type:  "olm.package",
   181  					Value: `{"packageName":"x","version":""}`,
   182  				},
   183  			},
   184  		},
   185  	} {
   186  		t.Run(tc.Name, func(t *testing.T) {
   187  			require := require.New(t)
   188  			logger, _ := test.NewNullLogger()
   189  			s := &csvSource{
   190  				logger: logger,
   191  			}
   192  			actual, err := s.inferProperties(tc.CSV, tc.Subscriptions)
   193  			require.NoError(err)
   194  			require.Equal(tc.Expected, actual)
   195  		})
   196  	}
   197  }
   198  
   199  func TestNewEntryFromCSV(t *testing.T) {
   200  	version := opver.OperatorVersion{Version: semver.MustParse("0.1.0-abc")}
   201  	type args struct {
   202  		csv *v1alpha1.ClusterServiceVersion
   203  	}
   204  	tests := []struct {
   205  		name    string
   206  		args    args
   207  		want    *cache.Entry
   208  		wantErr error
   209  	}{
   210  		{
   211  			name: "NoProvided/NoRequired",
   212  			args: args{
   213  				csv: &v1alpha1.ClusterServiceVersion{
   214  					ObjectMeta: metav1.ObjectMeta{
   215  						Name: "operator.v1",
   216  					},
   217  					Spec: v1alpha1.ClusterServiceVersionSpec{
   218  						Version: version,
   219  					},
   220  				},
   221  			},
   222  			want: &cache.Entry{
   223  				Name:         "operator.v1",
   224  				ProvidedAPIs: cache.EmptyAPISet(),
   225  				RequiredAPIs: cache.EmptyAPISet(),
   226  				SourceInfo:   &cache.OperatorSourceInfo{},
   227  				Version:      &version.Version,
   228  			},
   229  		},
   230  		{
   231  			name: "Provided/NoRequired",
   232  			args: args{
   233  				csv: &v1alpha1.ClusterServiceVersion{
   234  					ObjectMeta: metav1.ObjectMeta{
   235  						Name: "operator.v1",
   236  					},
   237  					Spec: v1alpha1.ClusterServiceVersionSpec{
   238  						Version: version,
   239  						CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{
   240  							Owned: []v1alpha1.CRDDescription{
   241  								{
   242  									Name:    "crdkinds.g",
   243  									Version: "v1",
   244  									Kind:    "CRDKind",
   245  								},
   246  							},
   247  						},
   248  						APIServiceDefinitions: v1alpha1.APIServiceDefinitions{
   249  							Owned: []v1alpha1.APIServiceDescription{
   250  								{
   251  									Name:    "apikinds",
   252  									Group:   "g",
   253  									Version: "v1",
   254  									Kind:    "APIKind",
   255  								},
   256  							},
   257  						},
   258  					},
   259  				},
   260  			},
   261  			want: &cache.Entry{
   262  				Name: "operator.v1",
   263  				ProvidedAPIs: map[opregistry.APIKey]struct{}{
   264  					{Group: "g", Version: "v1", Kind: "APIKind", Plural: "apikinds"}: {},
   265  					{Group: "g", Version: "v1", Kind: "CRDKind", Plural: "crdkinds"}: {},
   266  				},
   267  				Properties: []*api.Property{
   268  					{
   269  						Type:  "olm.gvk",
   270  						Value: "{\"group\":\"g\",\"kind\":\"APIKind\",\"version\":\"v1\"}",
   271  					},
   272  					{
   273  						Type:  "olm.gvk",
   274  						Value: "{\"group\":\"g\",\"kind\":\"CRDKind\",\"version\":\"v1\"}",
   275  					},
   276  				},
   277  				RequiredAPIs: cache.EmptyAPISet(),
   278  				SourceInfo:   &cache.OperatorSourceInfo{},
   279  				Version:      &version.Version,
   280  			},
   281  		},
   282  		{
   283  			name: "NoProvided/Required",
   284  			args: args{
   285  				csv: &v1alpha1.ClusterServiceVersion{
   286  					ObjectMeta: metav1.ObjectMeta{
   287  						Name: "operator.v1",
   288  					},
   289  					Spec: v1alpha1.ClusterServiceVersionSpec{
   290  						Version: version,
   291  						CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{
   292  							Required: []v1alpha1.CRDDescription{
   293  								{
   294  									Name:    "crdkinds.g",
   295  									Version: "v1",
   296  									Kind:    "CRDKind",
   297  								},
   298  							},
   299  						},
   300  						APIServiceDefinitions: v1alpha1.APIServiceDefinitions{
   301  							Required: []v1alpha1.APIServiceDescription{
   302  								{
   303  									Name:    "apikinds",
   304  									Group:   "g",
   305  									Version: "v1",
   306  									Kind:    "APIKind",
   307  								},
   308  							},
   309  						},
   310  					},
   311  				},
   312  			},
   313  			want: &cache.Entry{
   314  				Name:         "operator.v1",
   315  				ProvidedAPIs: cache.EmptyAPISet(),
   316  				RequiredAPIs: map[opregistry.APIKey]struct{}{
   317  					{Group: "g", Version: "v1", Kind: "APIKind", Plural: "apikinds"}: {},
   318  					{Group: "g", Version: "v1", Kind: "CRDKind", Plural: "crdkinds"}: {},
   319  				},
   320  				Properties: []*api.Property{
   321  					{
   322  						Type:  "olm.gvk.required",
   323  						Value: "{\"group\":\"g\",\"kind\":\"APIKind\",\"version\":\"v1\"}",
   324  					},
   325  					{
   326  						Type:  "olm.gvk.required",
   327  						Value: "{\"group\":\"g\",\"kind\":\"CRDKind\",\"version\":\"v1\"}",
   328  					},
   329  				},
   330  				SourceInfo: &cache.OperatorSourceInfo{},
   331  				Version:    &version.Version,
   332  			},
   333  		},
   334  		{
   335  			name: "Provided/Required",
   336  			args: args{
   337  				csv: &v1alpha1.ClusterServiceVersion{
   338  					ObjectMeta: metav1.ObjectMeta{
   339  						Name: "operator.v1",
   340  					},
   341  					Spec: v1alpha1.ClusterServiceVersionSpec{
   342  						Version: version,
   343  						CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{
   344  							Owned: []v1alpha1.CRDDescription{
   345  								{
   346  									Name:    "crdownedkinds.g",
   347  									Version: "v1",
   348  									Kind:    "CRDOwnedKind",
   349  								},
   350  							},
   351  							Required: []v1alpha1.CRDDescription{
   352  								{
   353  									Name:    "crdreqkinds.g2",
   354  									Version: "v1",
   355  									Kind:    "CRDReqKind",
   356  								},
   357  							},
   358  						},
   359  						APIServiceDefinitions: v1alpha1.APIServiceDefinitions{
   360  							Owned: []v1alpha1.APIServiceDescription{
   361  								{
   362  									Name:    "apiownedkinds",
   363  									Group:   "g",
   364  									Version: "v1",
   365  									Kind:    "APIOwnedKind",
   366  								},
   367  							},
   368  							Required: []v1alpha1.APIServiceDescription{
   369  								{
   370  									Name:    "apireqkinds",
   371  									Group:   "g2",
   372  									Version: "v1",
   373  									Kind:    "APIReqKind",
   374  								},
   375  							},
   376  						},
   377  					},
   378  				},
   379  			},
   380  			want: &cache.Entry{
   381  				Name: "operator.v1",
   382  				ProvidedAPIs: map[opregistry.APIKey]struct{}{
   383  					{Group: "g", Version: "v1", Kind: "APIOwnedKind", Plural: "apiownedkinds"}: {},
   384  					{Group: "g", Version: "v1", Kind: "CRDOwnedKind", Plural: "crdownedkinds"}: {},
   385  				},
   386  				RequiredAPIs: map[opregistry.APIKey]struct{}{
   387  					{Group: "g2", Version: "v1", Kind: "APIReqKind", Plural: "apireqkinds"}: {},
   388  					{Group: "g2", Version: "v1", Kind: "CRDReqKind", Plural: "crdreqkinds"}: {},
   389  				},
   390  				Properties: []*api.Property{
   391  					{
   392  						Type:  "olm.gvk",
   393  						Value: "{\"group\":\"g\",\"kind\":\"APIOwnedKind\",\"version\":\"v1\"}",
   394  					},
   395  					{
   396  						Type:  "olm.gvk",
   397  						Value: "{\"group\":\"g\",\"kind\":\"CRDOwnedKind\",\"version\":\"v1\"}",
   398  					},
   399  					{
   400  						Type:  "olm.gvk.required",
   401  						Value: "{\"group\":\"g2\",\"kind\":\"APIReqKind\",\"version\":\"v1\"}",
   402  					},
   403  					{
   404  						Type:  "olm.gvk.required",
   405  						Value: "{\"group\":\"g2\",\"kind\":\"CRDReqKind\",\"version\":\"v1\"}",
   406  					},
   407  				},
   408  				SourceInfo: &cache.OperatorSourceInfo{},
   409  				Version:    &version.Version,
   410  			},
   411  		},
   412  	}
   413  	for _, tt := range tests {
   414  		t.Run(tt.name, func(t *testing.T) {
   415  			got, err := newEntryFromV1Alpha1CSV(tt.args.csv)
   416  			require.Equal(t, tt.wantErr, err)
   417  			requirePropertiesEqual(t, tt.want.Properties, got.Properties)
   418  			tt.want.Properties, got.Properties = nil, nil
   419  			require.Equal(t, tt.want, got)
   420  		})
   421  	}
   422  }
   423  
   424  type fakeCSVLister []*v1alpha1.ClusterServiceVersion
   425  
   426  func (f fakeCSVLister) List(selector labels.Selector) ([]*v1alpha1.ClusterServiceVersion, error) {
   427  	return f, nil
   428  }
   429  
   430  func (f fakeCSVLister) Get(name string) (*v1alpha1.ClusterServiceVersion, error) {
   431  	for _, csv := range f {
   432  		if csv.Name == name {
   433  			return csv, nil
   434  		}
   435  	}
   436  	return nil, errors.NewNotFound(v1alpha1.SchemeGroupVersion.WithResource("clusterserviceversions").GroupResource(), name)
   437  }
   438  
   439  type fakeSubscriptionLister []*v1alpha1.Subscription
   440  
   441  func (f fakeSubscriptionLister) List(selector labels.Selector) ([]*v1alpha1.Subscription, error) {
   442  	return f, nil
   443  }
   444  
   445  func (f fakeSubscriptionLister) Get(name string) (*v1alpha1.Subscription, error) {
   446  	for _, sub := range f {
   447  		if sub.Name == name {
   448  			return sub, nil
   449  		}
   450  	}
   451  	return nil, errors.NewNotFound(v1alpha1.SchemeGroupVersion.WithResource("subscriptions").GroupResource(), name)
   452  }
   453  
   454  type fakeOperatorGroupLister []*operatorsv1.OperatorGroup
   455  
   456  func (f fakeOperatorGroupLister) List(selector labels.Selector) ([]*operatorsv1.OperatorGroup, error) {
   457  	return f, nil
   458  }
   459  
   460  func (f fakeOperatorGroupLister) Get(name string) (*operatorsv1.OperatorGroup, error) {
   461  	for _, og := range f {
   462  		if og.Name == name {
   463  			return og, nil
   464  		}
   465  	}
   466  	return nil, errors.NewNotFound(operatorsv1.SchemeGroupVersion.WithResource("operatorgroups").GroupResource(), name)
   467  }
   468  
   469  func TestPropertiesAnnotationHonored(t *testing.T) {
   470  	og := &operatorsv1.OperatorGroup{
   471  		ObjectMeta: metav1.ObjectMeta{
   472  			Name:      "og",
   473  			Namespace: "fake-ns",
   474  		},
   475  	}
   476  	src := &csvSource{
   477  		csvLister: fakeCSVLister{
   478  			&v1alpha1.ClusterServiceVersion{
   479  				ObjectMeta: metav1.ObjectMeta{
   480  					Namespace: "fake-ns",
   481  					Name:      "csv",
   482  					Annotations: map[string]string{
   483  						"operatorframework.io/properties": `{"properties":[{"type":"test-type","value":{"test":"value"}}]}`,
   484  					},
   485  				},
   486  			},
   487  		},
   488  		subLister: fakeSubscriptionLister{&v1alpha1.Subscription{
   489  			ObjectMeta: metav1.ObjectMeta{
   490  				Namespace: "fake-ns",
   491  				Name:      "sub",
   492  			},
   493  			Status: v1alpha1.SubscriptionStatus{
   494  				InstalledCSV: "csv",
   495  			},
   496  		}},
   497  		ogLister: fakeOperatorGroupLister{og},
   498  	}
   499  	ss, err := src.Snapshot(context.Background())
   500  	require.NoError(t, err)
   501  	requirePropertiesEqual(t, []*api.Property{{Type: "test-type", Value: `{"test":"value"}`}}, ss.Entries[0].Properties)
   502  }