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 }