github.com/operator-framework/operator-lifecycle-manager@v0.30.0/test/e2e/installplan_e2e_test.go (about) 1 package e2e 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "path/filepath" 9 "strconv" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/blang/semver/v4" 15 . "github.com/onsi/ginkgo/v2" 16 . "github.com/onsi/gomega" 17 . "github.com/onsi/gomega/gstruct" 18 "github.com/stretchr/testify/require" 19 appsv1 "k8s.io/api/apps/v1" 20 authorizationv1 "k8s.io/api/authorization/v1" 21 corev1 "k8s.io/api/core/v1" 22 rbacv1 "k8s.io/api/rbac/v1" 23 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 24 "k8s.io/apimachinery/pkg/api/equality" 25 apierrors "k8s.io/apimachinery/pkg/api/errors" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 "k8s.io/apimachinery/pkg/runtime" 29 k8sjson "k8s.io/apimachinery/pkg/runtime/serializer/json" 30 "k8s.io/apimachinery/pkg/util/diff" 31 "k8s.io/apimachinery/pkg/util/wait" 32 "k8s.io/client-go/discovery" 33 "k8s.io/client-go/util/retry" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 36 opver "github.com/operator-framework/api/pkg/lib/version" 37 operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" 38 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 39 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" 40 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog" 41 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" 42 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/apis/rbac" 43 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" 44 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 45 "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" 46 "github.com/operator-framework/operator-lifecycle-manager/test/e2e/util" 47 ) 48 49 const ( 50 deprecatedCRDDir = "deprecated-crd" 51 ) 52 53 var _ = Describe("Install Plan", func() { 54 var ( 55 c operatorclient.ClientInterface 56 crc versioned.Interface 57 generatedNamespace corev1.Namespace 58 ) 59 60 BeforeEach(func() { 61 namespaceName := genName("install-plan-e2e-") 62 og := operatorsv1.OperatorGroup{ 63 ObjectMeta: metav1.ObjectMeta{ 64 Name: fmt.Sprintf("%s-operatorgroup", namespaceName), 65 Namespace: namespaceName, 66 }, 67 } 68 generatedNamespace = SetupGeneratedTestNamespaceWithOperatorGroup(namespaceName, og) 69 c = ctx.Ctx().KubeClient() 70 crc = ctx.Ctx().OperatorClient() 71 }) 72 73 AfterEach(func() { 74 TeardownNamespace(generatedNamespace.GetName()) 75 }) 76 77 When("an InstallPlan step contains a deprecated resource version", func() { 78 var ( 79 csv operatorsv1alpha1.ClusterServiceVersion 80 plan operatorsv1alpha1.InstallPlan 81 deprecated client.Object 82 manifest string 83 counter float64 84 ) 85 86 BeforeEach(func() { 87 dc, err := discovery.NewDiscoveryClientForConfig(ctx.Ctx().RESTConfig()) 88 Expect(err).ToNot(HaveOccurred()) 89 90 v, err := dc.ServerVersion() 91 Expect(err).ToNot(HaveOccurred()) 92 93 if minor, err := strconv.Atoi(v.Minor); err == nil && minor < 16 { 94 Skip("test is dependent on CRD v1 introduced at 1.16") 95 } 96 }) 97 98 BeforeEach(func() { 99 counter = 0 100 for _, metric := range getMetricsFromPod(ctx.Ctx().KubeClient(), getPodWithLabel(ctx.Ctx().KubeClient(), "app=catalog-operator")) { 101 if metric.Family == "installplan_warnings_total" { 102 counter = metric.Value 103 } 104 } 105 deprecatedCRD, err := util.DecodeFile(filepath.Join(testdataDir, deprecatedCRDDir, "deprecated.crd.yaml"), &apiextensionsv1.CustomResourceDefinition{}) 106 Expect(err).NotTo(HaveOccurred()) 107 108 Expect(ctx.Ctx().Client().Create(context.Background(), deprecatedCRD)).To(Succeed()) 109 110 csv = newCSV(genName("test-csv-"), generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil) 111 Expect(ctx.Ctx().Client().Create(context.Background(), &csv)).To(Succeed()) 112 113 deprecated, err = util.DecodeFile(filepath.Join(testdataDir, deprecatedCRDDir, "deprecated.cr.yaml"), &unstructured.Unstructured{}, util.WithNamespace(generatedNamespace.GetName())) 114 Expect(err).NotTo(HaveOccurred()) 115 116 scheme := runtime.NewScheme() 117 { 118 var b bytes.Buffer 119 Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(deprecated, &b)).To(Succeed()) 120 manifest = b.String() 121 } 122 123 plan = operatorsv1alpha1.InstallPlan{ 124 ObjectMeta: metav1.ObjectMeta{ 125 Namespace: generatedNamespace.GetName(), 126 Name: genName("test-plan-"), 127 }, 128 Spec: operatorsv1alpha1.InstallPlanSpec{ 129 Approval: operatorsv1alpha1.ApprovalAutomatic, 130 Approved: true, 131 ClusterServiceVersionNames: []string{}, 132 }, 133 } 134 Expect(ctx.Ctx().Client().Create(context.Background(), &plan)).To(Succeed()) 135 plan.Status = operatorsv1alpha1.InstallPlanStatus{ 136 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 137 CatalogSources: []string{}, 138 Plan: []*operatorsv1alpha1.Step{ 139 { 140 Resolving: csv.GetName(), 141 Status: operatorsv1alpha1.StepStatusUnknown, 142 Resource: operatorsv1alpha1.StepResource{ 143 Name: deprecated.GetName(), 144 Version: "v1", 145 Kind: "Deprecated", 146 Manifest: manifest, 147 }, 148 }, 149 }, 150 } 151 Expect(ctx.Ctx().Client().Status().Update(context.Background(), &plan)).To(Succeed()) 152 Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { 153 return &plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&plan), &plan) 154 }).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete)) 155 }) 156 157 AfterEach(func() { 158 Eventually(func() error { 159 return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &csv)) 160 }).Should(Succeed()) 161 Eventually(func() error { 162 deprecatedCRD := &apiextensionsv1.CustomResourceDefinition{ 163 ObjectMeta: metav1.ObjectMeta{ 164 Name: "deprecateds.operators.io.operator-framework", 165 }, 166 } 167 return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), deprecatedCRD)) 168 }).Should(Succeed()) 169 }) 170 171 It("creates an Event surfacing the deprecation warning", func() { 172 Eventually(func() ([]corev1.Event, error) { 173 var events corev1.EventList 174 if err := ctx.Ctx().Client().List(context.Background(), &events, client.InNamespace(generatedNamespace.GetName())); err != nil { 175 return nil, err 176 } 177 var result []corev1.Event 178 for _, item := range events.Items { 179 result = append(result, corev1.Event{ 180 InvolvedObject: corev1.ObjectReference{ 181 APIVersion: item.InvolvedObject.APIVersion, 182 Kind: item.InvolvedObject.Kind, 183 Namespace: item.InvolvedObject.Namespace, 184 Name: item.InvolvedObject.Name, 185 FieldPath: item.InvolvedObject.FieldPath, 186 }, 187 Reason: item.Reason, 188 Message: item.Message, 189 }) 190 } 191 return result, nil 192 }).Should(ContainElement(corev1.Event{ 193 InvolvedObject: corev1.ObjectReference{ 194 APIVersion: operatorsv1alpha1.InstallPlanAPIVersion, 195 Kind: operatorsv1alpha1.InstallPlanKind, 196 Namespace: generatedNamespace.GetName(), 197 Name: plan.GetName(), 198 FieldPath: "status.plan[0]", 199 }, 200 Reason: "AppliedWithWarnings", 201 Message: fmt.Sprintf("1 warning(s) generated during installation of operator \"%s\" (Deprecated \"%s\"): operators.io.operator-framework/v1 Deprecated is deprecated", csv.GetName(), deprecated.GetName()), 202 })) 203 }) 204 205 It("increments a metric counting the warning", func() { 206 Eventually(func() []Metric { 207 return getMetricsFromPod(ctx.Ctx().KubeClient(), getPodWithLabel(ctx.Ctx().KubeClient(), "app=catalog-operator")) 208 }).Should(ContainElement(LikeMetric( 209 WithFamily("installplan_warnings_total"), 210 WithValueGreaterThan(counter), 211 ))) 212 }) 213 }) 214 215 When("a CustomResourceDefinition step resolved from a bundle is applied", func() { 216 var ( 217 crd apiextensionsv1.CustomResourceDefinition 218 manifest string 219 ) 220 221 BeforeEach(func() { 222 csv := newCSV("test-csv", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil) 223 Expect(ctx.Ctx().Client().Create(context.Background(), &csv)).To(Succeed()) 224 225 crd = apiextensionsv1.CustomResourceDefinition{ 226 ObjectMeta: metav1.ObjectMeta{ 227 Name: "tests.example.com", 228 }, 229 TypeMeta: metav1.TypeMeta{ 230 Kind: "CustomResourceDefinition", 231 APIVersion: apiextensionsv1.SchemeGroupVersion.String(), 232 }, 233 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 234 Group: "example.com", 235 Scope: apiextensionsv1.ClusterScoped, 236 Names: apiextensionsv1.CustomResourceDefinitionNames{ 237 Plural: "tests", 238 Singular: "test", 239 Kind: "Test", 240 ListKind: "TestList", 241 }, 242 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{ 243 Name: "v1", 244 Served: true, 245 Storage: true, 246 Schema: &apiextensionsv1.CustomResourceValidation{ 247 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 248 Type: "object", 249 }, 250 }, 251 }}, 252 }, 253 } 254 255 scheme := runtime.NewScheme() 256 Expect(corev1.AddToScheme(scheme)).To(Succeed()) 257 { 258 var b bytes.Buffer 259 Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&crd, &b)).To(Succeed()) 260 manifest = b.String() 261 } 262 263 plan := operatorsv1alpha1.InstallPlan{ 264 ObjectMeta: metav1.ObjectMeta{ 265 Namespace: generatedNamespace.GetName(), 266 Name: "test-plan", 267 }, 268 Spec: operatorsv1alpha1.InstallPlanSpec{ 269 Approval: operatorsv1alpha1.ApprovalAutomatic, 270 Approved: true, 271 ClusterServiceVersionNames: []string{}, 272 }, 273 } 274 Expect(ctx.Ctx().Client().Create(context.Background(), &plan)).To(Succeed()) 275 plan.Status = operatorsv1alpha1.InstallPlanStatus{ 276 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 277 CatalogSources: []string{}, 278 Plan: []*operatorsv1alpha1.Step{ 279 { 280 Resolving: "test-csv", 281 Status: operatorsv1alpha1.StepStatusUnknown, 282 Resource: operatorsv1alpha1.StepResource{ 283 Name: crd.GetName(), 284 Version: apiextensionsv1.SchemeGroupVersion.String(), 285 Kind: "CustomResourceDefinition", 286 Manifest: manifest, 287 }, 288 }, 289 }, 290 } 291 Expect(ctx.Ctx().Client().Status().Update(context.Background(), &plan)).To(Succeed()) 292 Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { 293 return &plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&plan), &plan) 294 }).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete)) 295 }) 296 297 AfterEach(func() { 298 Eventually(func() error { 299 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{})) 300 }).Should(Succeed()) 301 }) 302 303 It("is annotated with a reference to its associated ClusterServiceVersion", func() { 304 Eventually(func() (map[string]string, error) { 305 if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&crd), &crd); err != nil { 306 return nil, err 307 } 308 return crd.GetAnnotations(), nil 309 }).Should(HaveKeyWithValue( 310 HavePrefix("operatorframework.io/installed-alongside-"), 311 fmt.Sprintf("%s/test-csv", generatedNamespace.GetName()), 312 )) 313 }) 314 315 When("a second plan includes the same CustomResourceDefinition", func() { 316 var ( 317 csv operatorsv1alpha1.ClusterServiceVersion 318 plan operatorsv1alpha1.InstallPlan 319 ) 320 321 BeforeEach(func() { 322 csv = newCSV("test-csv-two", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil) 323 Expect(ctx.Ctx().Client().Create(context.Background(), &csv)).To(Succeed()) 324 325 plan = operatorsv1alpha1.InstallPlan{ 326 ObjectMeta: metav1.ObjectMeta{ 327 Namespace: generatedNamespace.GetName(), 328 Name: "test-plan-two", 329 }, 330 Spec: operatorsv1alpha1.InstallPlanSpec{ 331 Approval: operatorsv1alpha1.ApprovalAutomatic, 332 Approved: true, 333 ClusterServiceVersionNames: []string{}, 334 }, 335 } 336 Expect(ctx.Ctx().Client().Create(context.Background(), &plan)).To(Succeed()) 337 plan.Status = operatorsv1alpha1.InstallPlanStatus{ 338 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 339 CatalogSources: []string{}, 340 Plan: []*operatorsv1alpha1.Step{ 341 { 342 Resolving: "test-csv-two", 343 Status: operatorsv1alpha1.StepStatusUnknown, 344 Resource: operatorsv1alpha1.StepResource{ 345 Name: crd.GetName(), 346 Version: apiextensionsv1.SchemeGroupVersion.String(), 347 Kind: "CustomResourceDefinition", 348 Manifest: manifest, 349 }, 350 }, 351 }, 352 } 353 Expect(ctx.Ctx().Client().Status().Update(context.Background(), &plan)).To(Succeed()) 354 Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { 355 return &plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&plan), &plan) 356 }).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete)) 357 }) 358 359 AfterEach(func() { 360 Eventually(func() error { 361 return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &csv)) 362 }).Should(Succeed()) 363 Eventually(func() error { 364 return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &plan)) 365 }).Should(Succeed()) 366 }) 367 368 It("has one annotation for each ClusterServiceVersion", func() { 369 Eventually(func() ([]struct{ Key, Value string }, error) { 370 if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&crd), &crd); err != nil { 371 return nil, err 372 } 373 var pairs []struct{ Key, Value string } 374 for k, v := range crd.GetAnnotations() { 375 pairs = append(pairs, struct{ Key, Value string }{Key: k, Value: v}) 376 } 377 return pairs, nil 378 }).Should(ConsistOf( 379 MatchFields(IgnoreExtras, Fields{ 380 "Key": HavePrefix("operatorframework.io/installed-alongside-"), 381 "Value": Equal(fmt.Sprintf("%s/test-csv", generatedNamespace.GetName())), 382 }), 383 MatchFields(IgnoreExtras, Fields{ 384 "Key": HavePrefix("operatorframework.io/installed-alongside-"), 385 "Value": Equal(fmt.Sprintf("%s/test-csv-two", generatedNamespace.GetName())), 386 }), 387 )) 388 }) 389 }) 390 }) 391 392 When("an error is encountered during InstallPlan step execution", func() { 393 var ( 394 plan *operatorsv1alpha1.InstallPlan 395 owned *corev1.ConfigMap 396 ) 397 398 BeforeEach(func() { 399 By(`It's hard to reliably generate transient`) 400 By(`errors in an uninstrumented end-to-end`) 401 By(`test, so simulate it by constructing an`) 402 By(`error state that can be easily corrected`) 403 By(`during a test.`) 404 owned = &corev1.ConfigMap{ 405 ObjectMeta: metav1.ObjectMeta{ 406 Namespace: generatedNamespace.GetName(), 407 Name: "test-owned", 408 OwnerReferences: []metav1.OwnerReference{{ 409 APIVersion: "operators.coreos.com/v1alpha1", 410 Kind: "ClusterServiceVersion", 411 Name: "test-owner", // Does not exist! 412 UID: "", // catalog-operator populates this if the named CSV exists. 413 }}, 414 }, 415 } 416 417 scheme := runtime.NewScheme() 418 Expect(corev1.AddToScheme(scheme)).To(Succeed()) 419 var manifest bytes.Buffer 420 Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(owned, &manifest)).To(Succeed()) 421 422 plan = &operatorsv1alpha1.InstallPlan{ 423 ObjectMeta: metav1.ObjectMeta{ 424 Namespace: generatedNamespace.GetName(), 425 Name: "test-plan", 426 }, 427 Spec: operatorsv1alpha1.InstallPlanSpec{ 428 Approval: operatorsv1alpha1.ApprovalAutomatic, 429 Approved: true, 430 ClusterServiceVersionNames: []string{}, 431 }, 432 } 433 Expect(ctx.Ctx().Client().Create(context.Background(), plan)).To(Succeed()) 434 plan.Status = operatorsv1alpha1.InstallPlanStatus{ 435 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 436 CatalogSources: []string{}, 437 Plan: []*operatorsv1alpha1.Step{ 438 { 439 Status: operatorsv1alpha1.StepStatusUnknown, 440 Resource: operatorsv1alpha1.StepResource{ 441 Name: owned.GetName(), 442 Version: "v1", 443 Kind: "ConfigMap", 444 Manifest: manifest.String(), 445 }, 446 }, 447 }, 448 } 449 Expect(ctx.Ctx().Client().Status().Update(context.Background(), plan)).To(Succeed()) 450 }) 451 452 AfterEach(func() { 453 Expect(ctx.Ctx().Client().Delete(context.Background(), owned)).To(Or( 454 Succeed(), 455 WithTransform(apierrors.IsNotFound, BeTrue()), 456 )) 457 Expect(ctx.Ctx().Client().Delete(context.Background(), plan)).To(Or( 458 Succeed(), 459 WithTransform(apierrors.IsNotFound, BeTrue()), 460 )) 461 }) 462 463 It("times out if the error persists", func() { 464 Eventually( 465 func() (*operatorsv1alpha1.InstallPlan, error) { 466 return plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(plan), plan) 467 }, 468 90*time.Second, 469 ).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseFailed)) 470 }) 471 472 It("succeeds if there is no error on a later attempt", func() { 473 owner := newCSV("test-owner", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil) 474 Expect(ctx.Ctx().Client().Create(context.Background(), &owner)).To(Succeed()) 475 Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { 476 return plan, ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(plan), plan) 477 }).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete)) 478 }) 479 }) 480 481 When("an InstallPlan transfers ownership of a ServiceAccount to a new ClusterServiceVersion", func() { 482 var ( 483 csv1, csv2 operatorsv1alpha1.ClusterServiceVersion 484 sa corev1.ServiceAccount 485 plan operatorsv1alpha1.InstallPlan 486 ) 487 488 BeforeEach(func() { 489 csv1 = newCSV("test-csv-old", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil) 490 Expect(ctx.Ctx().Client().Create(context.Background(), &csv1)).To(Succeed()) 491 csv2 = newCSV("test-csv-new", generatedNamespace.GetName(), "", semver.Version{}, nil, nil, nil) 492 Expect(ctx.Ctx().Client().Create(context.Background(), &csv2)).To(Succeed()) 493 494 sa = corev1.ServiceAccount{ 495 ObjectMeta: metav1.ObjectMeta{ 496 Namespace: generatedNamespace.GetName(), 497 Name: "test-serviceaccount", 498 OwnerReferences: []metav1.OwnerReference{ 499 { 500 APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(), 501 Kind: operatorsv1alpha1.ClusterServiceVersionKind, 502 Name: csv1.GetName(), 503 UID: csv1.GetUID(), 504 }, 505 }, 506 }, 507 } 508 Expect(ctx.Ctx().Client().Create(context.Background(), &sa)).To(Succeed()) 509 510 scheme := runtime.NewScheme() 511 Expect(corev1.AddToScheme(scheme)).To(Succeed()) 512 var manifest bytes.Buffer 513 Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&corev1.ServiceAccount{ 514 ObjectMeta: metav1.ObjectMeta{ 515 Namespace: generatedNamespace.GetName(), 516 Name: "test-serviceaccount", 517 OwnerReferences: []metav1.OwnerReference{ 518 { 519 APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(), 520 Kind: operatorsv1alpha1.ClusterServiceVersionKind, 521 Name: csv2.GetName(), 522 }, 523 }, 524 }, 525 }, &manifest)).To(Succeed()) 526 527 plan = operatorsv1alpha1.InstallPlan{ 528 ObjectMeta: metav1.ObjectMeta{ 529 Namespace: generatedNamespace.GetName(), 530 Name: "test-plan", 531 }, 532 Spec: operatorsv1alpha1.InstallPlanSpec{ 533 Approval: operatorsv1alpha1.ApprovalAutomatic, 534 Approved: true, 535 ClusterServiceVersionNames: []string{}, 536 }, 537 } 538 Expect(ctx.Ctx().Client().Create(context.Background(), &plan)).To(Succeed()) 539 plan.Status = operatorsv1alpha1.InstallPlanStatus{ 540 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 541 CatalogSources: []string{}, 542 Plan: []*operatorsv1alpha1.Step{ 543 { 544 Status: operatorsv1alpha1.StepStatusUnknown, 545 Resource: operatorsv1alpha1.StepResource{ 546 Name: sa.GetName(), 547 Version: "v1", 548 Kind: "ServiceAccount", 549 Manifest: manifest.String(), 550 }, 551 }, 552 }, 553 } 554 Expect(ctx.Ctx().Client().Status().Update(context.Background(), &plan)).To(Succeed()) 555 }) 556 557 AfterEach(func() { 558 Expect(ctx.Ctx().Client().Delete(context.Background(), &sa)).To(Or( 559 Succeed(), 560 WithTransform(apierrors.IsNotFound, BeTrue()), 561 )) 562 Expect(ctx.Ctx().Client().Delete(context.Background(), &csv1)).To(Or( 563 Succeed(), 564 WithTransform(apierrors.IsNotFound, BeTrue()), 565 )) 566 Expect(ctx.Ctx().Client().Delete(context.Background(), &csv2)).To(Or( 567 Succeed(), 568 WithTransform(apierrors.IsNotFound, BeTrue()), 569 )) 570 Expect(ctx.Ctx().Client().Delete(context.Background(), &plan)).To(Or( 571 Succeed(), 572 WithTransform(apierrors.IsNotFound, BeTrue()), 573 )) 574 }) 575 576 It("preserves owner references to both the old and the new ClusterServiceVersion", func() { 577 Eventually(func() ([]metav1.OwnerReference, error) { 578 if err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(&sa), &sa); err != nil { 579 return nil, err 580 } 581 return sa.GetOwnerReferences(), nil 582 }).Should(ContainElements([]metav1.OwnerReference{ 583 { 584 APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(), 585 Kind: operatorsv1alpha1.ClusterServiceVersionKind, 586 Name: csv1.GetName(), 587 UID: csv1.GetUID(), 588 }, 589 { 590 APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(), 591 Kind: operatorsv1alpha1.ClusterServiceVersionKind, 592 Name: csv2.GetName(), 593 UID: csv2.GetUID(), 594 }, 595 })) 596 }) 597 }) 598 599 When("a ClusterIP service exists", func() { 600 var ( 601 service *corev1.Service 602 ) 603 604 BeforeEach(func() { 605 service = &corev1.Service{ 606 TypeMeta: metav1.TypeMeta{ 607 Kind: "Service", 608 APIVersion: "v1", 609 }, 610 ObjectMeta: metav1.ObjectMeta{ 611 Namespace: generatedNamespace.GetName(), 612 Name: "test-service", 613 }, 614 Spec: corev1.ServiceSpec{ 615 Type: corev1.ServiceTypeClusterIP, 616 Ports: []corev1.ServicePort{ 617 { 618 Port: 12345, 619 }, 620 }, 621 }, 622 } 623 624 Expect(ctx.Ctx().Client().Create(context.Background(), service.DeepCopy())).To(Succeed()) 625 }) 626 627 AfterEach(func() { 628 Expect(ctx.Ctx().Client().Delete(context.Background(), service)).To(Succeed()) 629 }) 630 631 It("it can be updated by an InstallPlan step", func() { 632 scheme := runtime.NewScheme() 633 Expect(corev1.AddToScheme(scheme)).To(Succeed()) 634 var manifest bytes.Buffer 635 Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(service, &manifest)).To(Succeed()) 636 637 plan := &operatorsv1alpha1.InstallPlan{ 638 ObjectMeta: metav1.ObjectMeta{ 639 Namespace: generatedNamespace.GetName(), 640 Name: "test-plan", 641 }, 642 Spec: operatorsv1alpha1.InstallPlanSpec{ 643 Approval: operatorsv1alpha1.ApprovalAutomatic, 644 Approved: true, 645 ClusterServiceVersionNames: []string{}, 646 }, 647 } 648 649 Expect(ctx.Ctx().Client().Create(context.Background(), plan)).To(Succeed()) 650 plan.Status = operatorsv1alpha1.InstallPlanStatus{ 651 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 652 CatalogSources: []string{}, 653 Plan: []*operatorsv1alpha1.Step{ 654 { 655 Status: operatorsv1alpha1.StepStatusUnknown, 656 Resource: operatorsv1alpha1.StepResource{ 657 Name: service.Name, 658 Version: "v1", 659 Kind: "Service", 660 Manifest: manifest.String(), 661 }, 662 }, 663 }, 664 } 665 Expect(ctx.Ctx().Client().Status().Update(context.Background(), plan)).To(Succeed()) 666 667 key := client.ObjectKeyFromObject(plan) 668 669 Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { 670 return plan, ctx.Ctx().Client().Get(context.Background(), key, plan) 671 }).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete)) 672 Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), plan))).To(Succeed()) 673 }) 674 }) 675 676 It("with CSVs across multiple catalog sources", func() { 677 678 log := func(s string) { 679 GinkgoT().Logf("%s: %s", time.Now().Format("15:04:05.9999"), s) 680 } 681 682 mainPackageName := genName("nginx-") 683 dependentPackageName := genName("nginxdep-") 684 685 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 686 dependentPackageStable := fmt.Sprintf("%s-stable", dependentPackageName) 687 688 stableChannel := "stable" 689 690 dependentCRD := newCRD(genName("ins-")) 691 mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil) 692 dependentCSV := newCSV(dependentPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil) 693 694 defer func() { 695 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 696 }() 697 698 dependentCatalogName := genName("mock-ocs-dependent-") 699 mainCatalogName := genName("mock-ocs-main-") 700 701 By(`Create separate manifests for each CatalogSource`) 702 mainManifests := []registry.PackageManifest{ 703 { 704 PackageName: mainPackageName, 705 Channels: []registry.PackageChannel{ 706 {Name: stableChannel, CurrentCSVName: mainPackageStable}, 707 }, 708 DefaultChannelName: stableChannel, 709 }, 710 } 711 712 dependentManifests := []registry.PackageManifest{ 713 { 714 PackageName: dependentPackageName, 715 Channels: []registry.PackageChannel{ 716 {Name: stableChannel, CurrentCSVName: dependentPackageStable}, 717 }, 718 DefaultChannelName: stableChannel, 719 }, 720 } 721 722 By(`Defer CRD clean up`) 723 defer func() { 724 Eventually(func() error { 725 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), metav1.DeleteOptions{})) 726 }).Should(Succeed()) 727 }() 728 729 By(`Create the catalog sources`) 730 require.NotEqual(GinkgoT(), "", generatedNamespace.GetName()) 731 _, cleanupDependentCatalogSource := createInternalCatalogSource(c, crc, dependentCatalogName, generatedNamespace.GetName(), dependentManifests, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, []operatorsv1alpha1.ClusterServiceVersion{dependentCSV}) 732 defer cleanupDependentCatalogSource() 733 734 By(`Attempt to get the catalog source before creating install plan`) 735 _, err := fetchCatalogSourceOnStatus(crc, dependentCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 736 require.NoError(GinkgoT(), err) 737 738 _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, nil, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) 739 defer cleanupMainCatalogSource() 740 741 By(`Attempt to get the catalog source before creating install plan`) 742 _, err = fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 743 require.NoError(GinkgoT(), err) 744 745 By(`Create expected install plan step sources`) 746 expectedStepSources := map[registry.ResourceKey]registry.ResourceKey{ 747 {Name: dependentCRD.Name, Kind: "CustomResourceDefinition"}: {Name: dependentCatalogName, Namespace: generatedNamespace.GetName()}, 748 {Name: dependentPackageStable, Kind: operatorsv1alpha1.ClusterServiceVersionKind}: {Name: dependentCatalogName, Namespace: generatedNamespace.GetName()}, 749 {Name: mainPackageStable, Kind: operatorsv1alpha1.ClusterServiceVersionKind}: {Name: mainCatalogName, Namespace: generatedNamespace.GetName()}, 750 {Name: strings.Join([]string{dependentPackageStable, dependentCatalogName, generatedNamespace.GetName()}, "-"), Kind: operatorsv1alpha1.SubscriptionKind}: {Name: dependentCatalogName, Namespace: generatedNamespace.GetName()}, 751 } 752 753 subscriptionName := genName("sub-nginx-") 754 subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 755 defer subscriptionCleanup() 756 757 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 758 require.NoError(GinkgoT(), err) 759 require.NotNil(GinkgoT(), subscription) 760 761 installPlanName := subscription.Status.InstallPlanRef.Name 762 763 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 764 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 765 require.NoError(GinkgoT(), err) 766 log(fmt.Sprintf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)) 767 768 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 769 770 By(`Fetch installplan again to check for unnecessary control loops`) 771 fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, fetchedInstallPlan.GetName(), generatedNamespace.GetName(), func(fip *operatorsv1alpha1.InstallPlan) bool { 772 By(`Don't compare object meta as labels can be applied by the operator controller.`) 773 Expect(equality.Semantic.DeepEqual(fetchedInstallPlan.Spec, fip.Spec)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip)) 774 Expect(equality.Semantic.DeepEqual(fetchedInstallPlan.Status, fip.Status)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip)) 775 return true 776 }) 777 require.NoError(GinkgoT(), err) 778 require.Equal(GinkgoT(), len(expectedStepSources), len(fetchedInstallPlan.Status.Plan), "Number of resolved steps matches the number of expected steps") 779 780 By(`Ensure resolved step resources originate from the correct catalog sources`) 781 log(fmt.Sprintf("%#v", expectedStepSources)) 782 for _, step := range fetchedInstallPlan.Status.Plan { 783 log(fmt.Sprintf("checking %s", step.Resource)) 784 key := registry.ResourceKey{Name: step.Resource.Name, Kind: step.Resource.Kind} 785 expectedSource, ok := expectedStepSources[key] 786 require.True(GinkgoT(), ok, "didn't find %v", key) 787 require.Equal(GinkgoT(), expectedSource.Name, step.Resource.CatalogSource) 788 require.Equal(GinkgoT(), expectedSource.Namespace, step.Resource.CatalogSourceNamespace) 789 790 By(`delete`) 791 } 792 EXPECTED: 793 for key := range expectedStepSources { 794 for _, step := range fetchedInstallPlan.Status.Plan { 795 if step.Resource.Name == key.Name && step.Resource.Kind == key.Kind { 796 continue EXPECTED 797 } 798 } 799 GinkgoT().Fatalf("expected step %s not found in %#v", key, fetchedInstallPlan.Status.Plan) 800 } 801 802 log("All expected resources resolved") 803 804 By(`Verify that the dependent subscription is in a good state`) 805 dependentSubscription, err := fetchSubscription(crc, generatedNamespace.GetName(), strings.Join([]string{dependentPackageStable, dependentCatalogName, generatedNamespace.GetName()}, "-"), subscriptionStateAtLatestChecker()) 806 require.NoError(GinkgoT(), err) 807 require.NotNil(GinkgoT(), dependentSubscription) 808 require.NotNil(GinkgoT(), dependentSubscription.Status.InstallPlanRef) 809 require.Equal(GinkgoT(), dependentCSV.GetName(), dependentSubscription.Status.CurrentCSV) 810 811 By(`Verify CSV is created`) 812 _, err = fetchCSV(crc, generatedNamespace.GetName(), dependentCSV.GetName(), csvAnyChecker) 813 require.NoError(GinkgoT(), err) 814 815 By(`Update dependent subscription in catalog and wait for csv to update`) 816 updatedDependentCSV := newCSV(dependentPackageStable+"-v2", generatedNamespace.GetName(), dependentPackageStable, semver.MustParse("0.1.1"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil) 817 dependentManifests = []registry.PackageManifest{ 818 { 819 PackageName: dependentPackageName, 820 Channels: []registry.PackageChannel{ 821 {Name: stableChannel, CurrentCSVName: updatedDependentCSV.GetName()}, 822 }, 823 DefaultChannelName: stableChannel, 824 }, 825 } 826 827 updateInternalCatalog(GinkgoT(), c, crc, dependentCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, []operatorsv1alpha1.ClusterServiceVersion{dependentCSV, updatedDependentCSV}, dependentManifests) 828 829 By(`Wait for subscription to update`) 830 updatedDepSubscription, err := fetchSubscription(crc, generatedNamespace.GetName(), strings.Join([]string{dependentPackageStable, dependentCatalogName, generatedNamespace.GetName()}, "-"), subscriptionHasCurrentCSV(updatedDependentCSV.GetName())) 831 require.NoError(GinkgoT(), err) 832 833 By(`Verify installplan created and installed`) 834 fetchedUpdatedDepInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedDepSubscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 835 require.NoError(GinkgoT(), err) 836 log(fmt.Sprintf("Install plan %s fetched with status %s", fetchedUpdatedDepInstallPlan.GetName(), fetchedUpdatedDepInstallPlan.Status.Phase)) 837 require.NotEqual(GinkgoT(), fetchedInstallPlan.GetName(), fetchedUpdatedDepInstallPlan.GetName()) 838 839 By(`Wait for csv to update`) 840 _, err = fetchCSV(crc, generatedNamespace.GetName(), updatedDependentCSV.GetName(), csvAnyChecker) 841 require.NoError(GinkgoT(), err) 842 }) 843 844 Context("creation with pre existing CRD owners", func() { 845 846 It("OnePreExistingCRDOwner", func() { 847 848 mainPackageName := genName("nginx-") 849 dependentPackageName := genName("nginx-dep-") 850 851 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 852 mainPackageBeta := fmt.Sprintf("%s-beta", mainPackageName) 853 dependentPackageStable := fmt.Sprintf("%s-stable", dependentPackageName) 854 dependentPackageBeta := fmt.Sprintf("%s-beta", dependentPackageName) 855 856 stableChannel := "stable" 857 betaChannel := "beta" 858 859 By(`Create manifests`) 860 mainManifests := []registry.PackageManifest{ 861 { 862 PackageName: mainPackageName, 863 Channels: []registry.PackageChannel{ 864 {Name: stableChannel, CurrentCSVName: mainPackageStable}, 865 }, 866 DefaultChannelName: stableChannel, 867 }, 868 { 869 PackageName: dependentPackageName, 870 Channels: []registry.PackageChannel{ 871 {Name: stableChannel, CurrentCSVName: dependentPackageStable}, 872 {Name: betaChannel, CurrentCSVName: dependentPackageBeta}, 873 }, 874 DefaultChannelName: stableChannel, 875 }, 876 } 877 878 By(`Create new CRDs`) 879 mainCRD := newCRD(genName("ins-")) 880 dependentCRD := newCRD(genName("ins-")) 881 882 By(`Create new CSVs`) 883 mainStableCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil) 884 mainBetaCSV := newCSV(mainPackageBeta, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil) 885 dependentStableCSV := newCSV(dependentPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil) 886 dependentBetaCSV := newCSV(dependentPackageBeta, generatedNamespace.GetName(), dependentPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil) 887 888 By(`Defer CRD clean up`) 889 defer func() { 890 Eventually(func() error { 891 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{})) 892 }).Should(Succeed()) 893 Eventually(func() error { 894 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), metav1.DeleteOptions{})) 895 }).Should(Succeed()) 896 }() 897 898 defer func() { 899 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 900 }() 901 902 By(`Create the catalog source`) 903 mainCatalogSourceName := genName("mock-ocs-main-" + strings.ToLower(K8sSafeCurrentTestDescription()) + "-") 904 _, cleanupCatalogSource := createInternalCatalogSource(c, crc, mainCatalogSourceName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{dependentCRD, mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{dependentBetaCSV, dependentStableCSV, mainStableCSV, mainBetaCSV}) 905 defer cleanupCatalogSource() 906 907 By(`Attempt to get the catalog source before creating install plan(s)`) 908 _, err := fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 909 require.NoError(GinkgoT(), err) 910 911 expectedSteps := map[registry.ResourceKey]struct{}{ 912 {Name: mainCRD.Name, Kind: "CustomResourceDefinition"}: {}, 913 {Name: mainPackageStable, Kind: operatorsv1alpha1.ClusterServiceVersionKind}: {}, 914 } 915 916 By(`Create the preexisting CRD and CSV`) 917 cleanupCRD, err := createCRD(c, dependentCRD) 918 require.NoError(GinkgoT(), err) 919 defer cleanupCRD() 920 cleanupCSV, err := createCSV(c, crc, dependentBetaCSV, generatedNamespace.GetName(), true, false) 921 require.NoError(GinkgoT(), err) 922 defer cleanupCSV() 923 GinkgoT().Log("Dependent CRD and preexisting CSV created") 924 925 subscriptionName := genName("sub-nginx-") 926 subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogSourceName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 927 defer subscriptionCleanup() 928 929 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 930 require.NoError(GinkgoT(), err) 931 require.NotNil(GinkgoT(), subscription) 932 933 installPlanName := subscription.Status.InstallPlanRef.Name 934 935 By(`Wait for InstallPlan to be status: Complete or Failed before checking resource presence`) 936 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed)) 937 require.NoError(GinkgoT(), err) 938 GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) 939 940 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 941 942 By(`Fetch installplan again to check for unnecessary control loops`) 943 fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, fetchedInstallPlan.GetName(), generatedNamespace.GetName(), func(fip *operatorsv1alpha1.InstallPlan) bool { 944 Expect(equality.Semantic.DeepEqual(fetchedInstallPlan, fip)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip)) 945 return true 946 }) 947 require.NoError(GinkgoT(), err) 948 949 for _, step := range fetchedInstallPlan.Status.Plan { 950 GinkgoT().Logf("%#v", step) 951 } 952 require.Equal(GinkgoT(), len(fetchedInstallPlan.Status.Plan), len(expectedSteps), "number of expected steps does not match installed") 953 GinkgoT().Logf("Number of resolved steps matches the number of expected steps") 954 955 for _, step := range fetchedInstallPlan.Status.Plan { 956 key := registry.ResourceKey{ 957 Name: step.Resource.Name, 958 Kind: step.Resource.Kind, 959 } 960 _, ok := expectedSteps[key] 961 require.True(GinkgoT(), ok) 962 963 By(`Remove the entry from the expected steps set (to ensure no duplicates in resolved plan)`) 964 delete(expectedSteps, key) 965 } 966 967 By(`Should have removed every matching step`) 968 require.Equal(GinkgoT(), 0, len(expectedSteps), "Actual resource steps do not match expected") 969 970 By(`Delete CRDs`) 971 Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &mainCRD))).To(Succeed()) 972 Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &dependentCRD))).To(Succeed()) 973 }) 974 }) 975 976 Describe("with CRD schema change", func() { 977 type schemaPayload struct { 978 name string 979 expectedPhase operatorsv1alpha1.InstallPlanPhase 980 oldCRD *apiextensionsv1.CustomResourceDefinition 981 intermediateCRD *apiextensionsv1.CustomResourceDefinition 982 newCRD *apiextensionsv1.CustomResourceDefinition 983 } 984 985 var min float64 = 2 986 var max float64 = 256 987 var newMax float64 = 50 988 // generated outside of the test table so that the same naming can be used for both old and new CSVs 989 mainCRDPlural := genName("testcrd-") 990 991 // excluded: new CRD, same version, same schema - won't trigger a CRD update 992 tableEntries := []TableEntry{ 993 Entry("all existing versions are present, different (backwards compatible) schema", schemaPayload{ 994 name: "all existing versions are present, different (backwards compatible) schema", 995 expectedPhase: operatorsv1alpha1.InstallPlanPhaseComplete, 996 oldCRD: func() *apiextensionsv1.CustomResourceDefinition { 997 oldCRD := newCRD(mainCRDPlural + "a") 998 oldCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 999 { 1000 Name: "v1alpha1", 1001 Served: true, 1002 Storage: true, 1003 Schema: &apiextensionsv1.CustomResourceValidation{ 1004 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1005 Type: "object", 1006 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1007 "spec": { 1008 Type: "object", 1009 Description: "Spec of a test object.", 1010 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1011 "scalar": { 1012 Type: "number", 1013 Description: "Scalar value that should have a min and max.", 1014 Minimum: &min, 1015 Maximum: &max, 1016 }, 1017 }, 1018 }, 1019 }, 1020 }, 1021 }, 1022 }, 1023 } 1024 return &oldCRD 1025 }(), 1026 newCRD: func() *apiextensionsv1.CustomResourceDefinition { 1027 newCRD := newCRD(mainCRDPlural + "a") 1028 newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 1029 { 1030 Name: "v1alpha1", 1031 Served: true, 1032 Storage: false, 1033 Schema: &apiextensionsv1.CustomResourceValidation{ 1034 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1035 Type: "object", 1036 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1037 "spec": { 1038 Type: "object", 1039 Description: "Spec of a test object.", 1040 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1041 "scalar": { 1042 Type: "number", 1043 Description: "Scalar value that should have a min and max.", 1044 Minimum: &min, 1045 Maximum: &max, 1046 }, 1047 }, 1048 }, 1049 }, 1050 }, 1051 }, 1052 }, 1053 { 1054 Name: "v1alpha2", 1055 Served: true, 1056 Storage: true, 1057 Schema: &apiextensionsv1.CustomResourceValidation{ 1058 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1059 Type: "object", 1060 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1061 "spec": { 1062 Type: "object", 1063 Description: "Spec of a test object.", 1064 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1065 "scalar": { 1066 Type: "number", 1067 Description: "Scalar value that should have a min and max.", 1068 Minimum: &min, 1069 Maximum: &max, 1070 }, 1071 }, 1072 }, 1073 }, 1074 }, 1075 }, 1076 }, 1077 } 1078 return &newCRD 1079 }(), 1080 }), 1081 Entry("all existing versions are present, different (backwards incompatible) schema", schemaPayload{name: "all existing versions are present, different (backwards incompatible) schema", 1082 expectedPhase: operatorsv1alpha1.InstallPlanPhaseFailed, 1083 oldCRD: func() *apiextensionsv1.CustomResourceDefinition { 1084 oldCRD := newCRD(mainCRDPlural + "b") 1085 oldCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 1086 { 1087 Name: "v1alpha1", 1088 Served: true, 1089 Storage: true, 1090 Schema: &apiextensionsv1.CustomResourceValidation{ 1091 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1092 Type: "object", 1093 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1094 "spec": { 1095 Type: "object", 1096 Description: "Spec of a test object.", 1097 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1098 "scalar": { 1099 Type: "number", 1100 Description: "Scalar value that should have a min and max.", 1101 }, 1102 }, 1103 }, 1104 }, 1105 }, 1106 }, 1107 }, 1108 } 1109 return &oldCRD 1110 }(), 1111 newCRD: func() *apiextensionsv1.CustomResourceDefinition { 1112 newCRD := newCRD(mainCRDPlural + "b") 1113 newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 1114 { 1115 Name: "v1alpha1", 1116 Served: true, 1117 Storage: true, 1118 Schema: &apiextensionsv1.CustomResourceValidation{ 1119 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1120 Type: "object", 1121 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1122 "spec": { 1123 Type: "object", 1124 Description: "Spec of a test object.", 1125 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1126 "scalar": { 1127 Type: "number", 1128 Description: "Scalar value that should have a min and max.", 1129 Minimum: &min, 1130 Maximum: &newMax, 1131 }, 1132 }, 1133 }, 1134 }, 1135 }, 1136 }, 1137 }, 1138 } 1139 return &newCRD 1140 }(), 1141 }), 1142 Entry("missing existing versions in new CRD", schemaPayload{name: "missing existing versions in new CRD", 1143 expectedPhase: operatorsv1alpha1.InstallPlanPhaseComplete, 1144 oldCRD: func() *apiextensionsv1.CustomResourceDefinition { 1145 oldCRD := newCRD(mainCRDPlural + "c") 1146 oldCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 1147 { 1148 Name: "v1alpha1", 1149 Served: true, 1150 Storage: true, 1151 Schema: &apiextensionsv1.CustomResourceValidation{ 1152 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1153 Type: "object", 1154 Description: "my crd schema", 1155 }, 1156 }, 1157 }, 1158 { 1159 Name: "v1alpha2", 1160 Served: true, 1161 Storage: false, 1162 Schema: &apiextensionsv1.CustomResourceValidation{ 1163 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1164 Type: "object", 1165 Description: "my crd schema", 1166 }, 1167 }, 1168 }, 1169 } 1170 return &oldCRD 1171 }(), 1172 newCRD: func() *apiextensionsv1.CustomResourceDefinition { 1173 newCRD := newCRD(mainCRDPlural + "c") 1174 newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 1175 { 1176 Name: "v1alpha1", 1177 Served: true, 1178 Storage: true, 1179 Schema: &apiextensionsv1.CustomResourceValidation{ 1180 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1181 Type: "object", 1182 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1183 "spec": { 1184 Type: "object", 1185 Description: "Spec of a test object.", 1186 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1187 "scalar": { 1188 Type: "number", 1189 Description: "Scalar value that should have a min and max.", 1190 Minimum: &min, 1191 Maximum: &max, 1192 }, 1193 }, 1194 }, 1195 }, 1196 }, 1197 }, 1198 }, 1199 { 1200 Name: "v1", 1201 Served: true, 1202 Storage: false, 1203 Schema: &apiextensionsv1.CustomResourceValidation{ 1204 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1205 Type: "object", 1206 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1207 "spec": { 1208 Type: "object", 1209 Description: "Spec of a test object.", 1210 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1211 "scalar": { 1212 Type: "number", 1213 Description: "Scalar value that should have a min and max.", 1214 Minimum: &min, 1215 Maximum: &max, 1216 }, 1217 }, 1218 }, 1219 }, 1220 }, 1221 }, 1222 }, 1223 } 1224 return &newCRD 1225 }()}), 1226 Entry("existing version is present in new CRD (deprecated field)", schemaPayload{name: "existing version is present in new CRD (deprecated field)", 1227 expectedPhase: operatorsv1alpha1.InstallPlanPhaseComplete, 1228 oldCRD: func() *apiextensionsv1.CustomResourceDefinition { 1229 oldCRD := newCRD(mainCRDPlural + "d") 1230 return &oldCRD 1231 }(), 1232 newCRD: func() *apiextensionsv1.CustomResourceDefinition { 1233 newCRD := newCRD(mainCRDPlural + "d") 1234 newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 1235 { 1236 Name: "v1alpha1", 1237 Served: true, 1238 Storage: true, 1239 Schema: &apiextensionsv1.CustomResourceValidation{ 1240 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1241 Type: "object", 1242 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1243 "spec": { 1244 Type: "object", 1245 Description: "Spec of a test object.", 1246 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 1247 "scalar": { 1248 Type: "number", 1249 Description: "Scalar value that should have a min and max.", 1250 Minimum: &min, 1251 Maximum: &max, 1252 }, 1253 }, 1254 }, 1255 }, 1256 }, 1257 }, 1258 }, 1259 { 1260 Name: "v1alpha3", 1261 Served: false, 1262 Storage: false, 1263 Schema: &apiextensionsv1.CustomResourceValidation{ 1264 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{Type: "object"}, 1265 }, 1266 }, 1267 } 1268 return &newCRD 1269 }()}), 1270 } 1271 1272 DescribeTable("Test", func(tt schemaPayload) { 1273 1274 mainPackageName := genName("nginx-") 1275 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 1276 mainPackageBeta := fmt.Sprintf("%s-beta", mainPackageName) 1277 1278 stableChannel := "stable" 1279 betaChannel := "beta" 1280 1281 By(`Create manifests`) 1282 mainManifests := []registry.PackageManifest{ 1283 { 1284 PackageName: mainPackageName, 1285 Channels: []registry.PackageChannel{ 1286 {Name: stableChannel, CurrentCSVName: mainPackageStable}, 1287 {Name: betaChannel, CurrentCSVName: mainPackageBeta}, 1288 }, 1289 DefaultChannelName: stableChannel, 1290 }, 1291 } 1292 1293 By(`Create new CSVs`) 1294 mainStableCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, nil, nil) 1295 mainBetaCSV := newCSV(mainPackageBeta, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, nil, nil) 1296 1297 By(`Defer CRD clean up`) 1298 defer func() { 1299 Eventually(func() error { 1300 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.oldCRD.GetName(), metav1.DeleteOptions{})) 1301 }).Should(Succeed()) 1302 Eventually(func() error { 1303 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.newCRD.GetName(), metav1.DeleteOptions{})) 1304 }).Should(Succeed()) 1305 if tt.intermediateCRD != nil { 1306 Eventually(func() error { 1307 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.intermediateCRD.GetName(), metav1.DeleteOptions{})) 1308 }).Should(Succeed()) 1309 } 1310 }() 1311 1312 By(`Existing custom resource`) 1313 existingCR := &unstructured.Unstructured{ 1314 Object: map[string]interface{}{ 1315 "apiVersion": "cluster.com/v1alpha1", 1316 "kind": tt.oldCRD.Spec.Names.Kind, 1317 "metadata": map[string]interface{}{ 1318 "namespace": generatedNamespace.GetName(), 1319 "name": "my-cr-1", 1320 }, 1321 "spec": map[string]interface{}{ 1322 "scalar": 100, 1323 }, 1324 }, 1325 } 1326 1327 By(`Create the catalog source`) 1328 mainCatalogSourceName := genName("mock-ocs-main-") 1329 _, cleanupCatalogSource := createInternalCatalogSource(c, crc, mainCatalogSourceName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV, mainBetaCSV}) 1330 defer cleanupCatalogSource() 1331 1332 By(`Attempt to get the catalog source before creating install plan(s)`) 1333 _, err := fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1334 require.NoError(GinkgoT(), err) 1335 1336 subscriptionName := genName("sub-nginx-alpha-") 1337 cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogSourceName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 1338 defer cleanupSubscription() 1339 1340 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 1341 require.NoError(GinkgoT(), err) 1342 require.NotNil(GinkgoT(), subscription) 1343 1344 installPlanName := subscription.Status.InstallPlanRef.Name 1345 1346 By(`Wait for InstallPlan to be status: Complete or failed before checking resource presence`) 1347 completeOrFailedFunc := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed) 1348 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), completeOrFailedFunc) 1349 require.NoError(GinkgoT(), err) 1350 GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) 1351 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 1352 1353 By(`Ensure that the desired resources have been created`) 1354 expectedSteps := map[registry.ResourceKey]struct{}{ 1355 {Name: tt.oldCRD.Name, Kind: "CustomResourceDefinition"}: {}, 1356 {Name: mainPackageStable, Kind: operatorsv1alpha1.ClusterServiceVersionKind}: {}, 1357 } 1358 1359 require.Equal(GinkgoT(), len(expectedSteps), len(fetchedInstallPlan.Status.Plan), "number of expected steps does not match installed") 1360 1361 for _, step := range fetchedInstallPlan.Status.Plan { 1362 key := registry.ResourceKey{ 1363 Name: step.Resource.Name, 1364 Kind: step.Resource.Kind, 1365 } 1366 _, ok := expectedSteps[key] 1367 require.True(GinkgoT(), ok, "couldn't find %v in expected steps: %#v", key, expectedSteps) 1368 1369 By(`Remove the entry from the expected steps set (to ensure no duplicates in resolved plan)`) 1370 delete(expectedSteps, key) 1371 } 1372 1373 By(`Should have removed every matching step`) 1374 require.Equal(GinkgoT(), 0, len(expectedSteps), "Actual resource steps do not match expected") 1375 1376 By(`Create initial CR`) 1377 cleanupCR, err := createCR(c, existingCR, "cluster.com", "v1alpha1", generatedNamespace.GetName(), tt.oldCRD.Spec.Names.Plural, "my-cr-1") 1378 require.NoError(GinkgoT(), err) 1379 defer cleanupCR() 1380 1381 updateInternalCatalog(GinkgoT(), c, crc, mainCatalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{*tt.newCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV, mainBetaCSV}, mainManifests) 1382 1383 By(`Attempt to get the catalog source before creating install plan(s)`) 1384 _, err = fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1385 require.NoError(GinkgoT(), err) 1386 1387 By(`Update the subscription resource to point to the beta CSV`) 1388 err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { 1389 subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 1390 require.NoError(GinkgoT(), err) 1391 require.NotNil(GinkgoT(), subscription) 1392 1393 subscription.Spec.Channel = betaChannel 1394 subscription, err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Update(context.Background(), subscription, metav1.UpdateOptions{}) 1395 1396 return err 1397 }) 1398 1399 By(`Wait for subscription to have a new installplan`) 1400 subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName())) 1401 require.NoError(GinkgoT(), err) 1402 require.NotNil(GinkgoT(), subscription) 1403 1404 installPlanName = subscription.Status.InstallPlanRef.Name 1405 1406 By(`Wait for InstallPlan to be status: Complete or Failed before checking resource presence`) 1407 fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(tt.expectedPhase)) 1408 require.NoError(GinkgoT(), err) 1409 GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) 1410 1411 require.Equal(GinkgoT(), tt.expectedPhase, fetchedInstallPlan.Status.Phase) 1412 1413 By(`Ensure correct in-cluster resource(s)`) 1414 fetchedCSV, err := fetchCSV(crc, generatedNamespace.GetName(), mainBetaCSV.GetName(), csvAnyChecker) 1415 require.NoError(GinkgoT(), err) 1416 1417 GinkgoT().Logf("All expected resources resolved %s", fetchedCSV.Status.Phase) 1418 }, tableEntries) 1419 1420 }) 1421 1422 Describe("with deprecated version CRD", func() { 1423 1424 // generated outside of the test table so that the same naming can be used for both old and new CSVs 1425 mainCRDPlural := genName("ins") 1426 1427 type schemaPayload struct { 1428 name string 1429 expectedPhase operatorsv1alpha1.InstallPlanPhase 1430 oldCRD *apiextensionsv1.CustomResourceDefinition 1431 intermediateCRD *apiextensionsv1.CustomResourceDefinition 1432 newCRD *apiextensionsv1.CustomResourceDefinition 1433 } 1434 1435 // excluded: new CRD, same version, same schema - won't trigger a CRD update 1436 1437 tableEntries := []TableEntry{ 1438 Entry("upgrade CRD with deprecated version", schemaPayload{ 1439 name: "upgrade CRD with deprecated version", 1440 expectedPhase: operatorsv1alpha1.InstallPlanPhaseComplete, 1441 oldCRD: func() *apiextensionsv1.CustomResourceDefinition { 1442 oldCRD := newCRD(mainCRDPlural) 1443 oldCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 1444 { 1445 Name: "v1alpha1", 1446 Served: true, 1447 Storage: true, 1448 Schema: &apiextensionsv1.CustomResourceValidation{ 1449 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1450 Type: "object", 1451 Description: "my crd schema", 1452 }, 1453 }, 1454 }, 1455 } 1456 return &oldCRD 1457 }(), 1458 intermediateCRD: func() *apiextensionsv1.CustomResourceDefinition { 1459 intermediateCRD := newCRD(mainCRDPlural) 1460 intermediateCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 1461 { 1462 Name: "v1alpha2", 1463 Served: true, 1464 Storage: true, 1465 Schema: &apiextensionsv1.CustomResourceValidation{ 1466 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1467 Type: "object", 1468 Description: "my crd schema", 1469 }, 1470 }, 1471 }, 1472 { 1473 Name: "v1alpha1", 1474 Served: true, 1475 Storage: false, 1476 Schema: &apiextensionsv1.CustomResourceValidation{ 1477 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1478 Type: "object", 1479 Description: "my crd schema", 1480 }, 1481 }, 1482 }, 1483 } 1484 return &intermediateCRD 1485 }(), 1486 newCRD: func() *apiextensionsv1.CustomResourceDefinition { 1487 newCRD := newCRD(mainCRDPlural) 1488 newCRD.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{ 1489 { 1490 Name: "v1alpha2", 1491 Served: true, 1492 Storage: true, 1493 Schema: &apiextensionsv1.CustomResourceValidation{ 1494 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1495 Type: "object", 1496 Description: "my crd schema", 1497 }, 1498 }, 1499 }, 1500 { 1501 Name: "v1beta1", 1502 Served: true, 1503 Storage: false, 1504 Schema: &apiextensionsv1.CustomResourceValidation{ 1505 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1506 Type: "object", 1507 Description: "my crd schema", 1508 }, 1509 }, 1510 }, 1511 { 1512 Name: "v1alpha1", 1513 Served: false, 1514 Storage: false, 1515 Schema: &apiextensionsv1.CustomResourceValidation{ 1516 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1517 Type: "object", 1518 Description: "my crd schema", 1519 }, 1520 }, 1521 }, 1522 } 1523 return &newCRD 1524 }(), 1525 }), 1526 } 1527 1528 DescribeTable("Test", func(tt schemaPayload) { 1529 1530 mainPackageName := genName("nginx-") 1531 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 1532 mainPackageBeta := fmt.Sprintf("%s-beta", mainPackageName) 1533 mainPackageDelta := fmt.Sprintf("%s-delta", mainPackageName) 1534 1535 stableChannel := "stable" 1536 1537 By(`Create manifests`) 1538 mainManifests := []registry.PackageManifest{ 1539 { 1540 PackageName: mainPackageName, 1541 Channels: []registry.PackageChannel{ 1542 {Name: stableChannel, CurrentCSVName: mainPackageStable}, 1543 }, 1544 DefaultChannelName: stableChannel, 1545 }, 1546 } 1547 1548 By(`Create new CSVs`) 1549 mainStableCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, nil, nil) 1550 mainBetaCSV := newCSV(mainPackageBeta, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{*tt.intermediateCRD}, nil, nil) 1551 mainDeltaCSV := newCSV(mainPackageDelta, generatedNamespace.GetName(), mainPackageBeta, semver.MustParse("0.3.0"), []apiextensionsv1.CustomResourceDefinition{*tt.newCRD}, nil, nil) 1552 1553 By(`Defer CRD clean up`) 1554 defer func() { 1555 Eventually(func() error { 1556 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.oldCRD.GetName(), metav1.DeleteOptions{})) 1557 }).Should(Succeed()) 1558 Eventually(func() error { 1559 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.newCRD.GetName(), metav1.DeleteOptions{})) 1560 }).Should(Succeed()) 1561 if tt.intermediateCRD != nil { 1562 Eventually(func() error { 1563 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), tt.intermediateCRD.GetName(), metav1.DeleteOptions{})) 1564 }).Should(Succeed()) 1565 } 1566 }() 1567 1568 By(`Defer crd clean up`) 1569 defer func() { 1570 Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), tt.newCRD))).To(Succeed()) 1571 Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), tt.oldCRD))).To(Succeed()) 1572 Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), tt.intermediateCRD))).To(Succeed()) 1573 }() 1574 1575 By(`Create the catalog source`) 1576 mainCatalogSourceName := genName("mock-ocs-main-") 1577 _, cleanupCatalogSource := createInternalCatalogSource(c, crc, mainCatalogSourceName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{*tt.oldCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV}) 1578 defer cleanupCatalogSource() 1579 1580 By(`Attempt to get the catalog source before creating install plan(s)`) 1581 _, err := fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1582 require.NoError(GinkgoT(), err) 1583 1584 subscriptionName := genName("sub-nginx-") 1585 1586 By(`this subscription will be cleaned up below without the clean up function`) 1587 createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogSourceName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 1588 1589 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 1590 require.NoError(GinkgoT(), err) 1591 require.NotNil(GinkgoT(), subscription) 1592 1593 installPlanName := subscription.Status.InstallPlanRef.Name 1594 1595 By(`Wait for InstallPlan to be status: Complete or failed before checking resource presence`) 1596 completeOrFailedFunc := buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed) 1597 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), completeOrFailedFunc) 1598 require.NoError(GinkgoT(), err) 1599 GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) 1600 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 1601 1602 By(`Ensure CRD versions are accurate`) 1603 expectedVersions := map[string]struct{}{ 1604 "v1alpha1": {}, 1605 } 1606 1607 validateCRDVersions(GinkgoT(), c, tt.oldCRD.GetName(), expectedVersions) 1608 1609 By(`Update the manifest`) 1610 mainManifests = []registry.PackageManifest{ 1611 { 1612 PackageName: mainPackageName, 1613 Channels: []registry.PackageChannel{ 1614 {Name: stableChannel, CurrentCSVName: mainPackageBeta}, 1615 }, 1616 DefaultChannelName: stableChannel, 1617 }, 1618 } 1619 1620 updateInternalCatalog(GinkgoT(), c, crc, mainCatalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{*tt.intermediateCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV, mainBetaCSV}, mainManifests) 1621 By(`Attempt to get the catalog source before creating install plan(s)`) 1622 _, err = fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1623 require.NoError(GinkgoT(), err) 1624 subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(installPlanName)) 1625 require.NoError(GinkgoT(), err) 1626 require.NotNil(GinkgoT(), subscription) 1627 1628 installPlanName = subscription.Status.InstallPlanRef.Name 1629 1630 By(`Wait for InstallPlan to be status: Complete or Failed before checking resource presence`) 1631 fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed)) 1632 require.NoError(GinkgoT(), err) 1633 GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) 1634 require.Equal(GinkgoT(), tt.expectedPhase, fetchedInstallPlan.Status.Phase) 1635 1636 By(`Ensure correct in-cluster resource(s)`) 1637 fetchedCSV, err := fetchCSV(crc, generatedNamespace.GetName(), mainBetaCSV.GetName(), csvSucceededChecker) 1638 require.NoError(GinkgoT(), err) 1639 1640 By(`Ensure CRD versions are accurate`) 1641 expectedVersions = map[string]struct{}{ 1642 "v1alpha1": {}, 1643 "v1alpha2": {}, 1644 } 1645 1646 validateCRDVersions(GinkgoT(), c, tt.oldCRD.GetName(), expectedVersions) 1647 1648 By(`Update the manifest`) 1649 mainManifests = []registry.PackageManifest{ 1650 { 1651 PackageName: mainPackageName, 1652 Channels: []registry.PackageChannel{ 1653 {Name: stableChannel, CurrentCSVName: mainPackageDelta}, 1654 }, 1655 DefaultChannelName: stableChannel, 1656 }, 1657 } 1658 1659 updateInternalCatalog(GinkgoT(), c, crc, mainCatalogSourceName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{*tt.newCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainStableCSV, mainBetaCSV, mainDeltaCSV}, mainManifests) 1660 By(`Attempt to get the catalog source before creating install plan(s)`) 1661 _, err = fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1662 require.NoError(GinkgoT(), err) 1663 subscription, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(installPlanName)) 1664 require.NoError(GinkgoT(), err) 1665 require.NotNil(GinkgoT(), subscription) 1666 1667 installPlanName = subscription.Status.InstallPlanRef.Name 1668 1669 By(`Wait for InstallPlan to be status: Complete or Failed before checking resource presence`) 1670 fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed)) 1671 require.NoError(GinkgoT(), err) 1672 GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) 1673 1674 require.Equal(GinkgoT(), tt.expectedPhase, fetchedInstallPlan.Status.Phase) 1675 1676 By(`Ensure correct in-cluster resource(s)`) 1677 fetchedCSV, err = fetchCSV(crc, generatedNamespace.GetName(), mainDeltaCSV.GetName(), csvSucceededChecker) 1678 require.NoError(GinkgoT(), err) 1679 1680 By(`Ensure CRD versions are accurate`) 1681 expectedVersions = map[string]struct{}{ 1682 "v1alpha2": {}, 1683 "v1beta1": {}, 1684 "v1alpha1": {}, 1685 } 1686 1687 validateCRDVersions(GinkgoT(), c, tt.oldCRD.GetName(), expectedVersions) 1688 GinkgoT().Logf("All expected resources resolved %s", fetchedCSV.Status.Phase) 1689 }, tableEntries) 1690 }) 1691 1692 Describe("update catalog for subscription", func() { 1693 1694 // crdVersionKey uniquely identifies a version within a CRD 1695 type crdVersionKey struct { 1696 name string 1697 served bool 1698 storage bool 1699 } 1700 It("AmplifyPermissions", func() { 1701 1702 defer func() { 1703 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 1704 }() 1705 1706 By(`Build initial catalog`) 1707 mainPackageName := genName("nginx-amplify-") 1708 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 1709 stableChannel := "stable" 1710 crdPlural := genName("ins-amplify-") 1711 crdName := crdPlural + ".cluster.com" 1712 mainCRD := apiextensionsv1.CustomResourceDefinition{ 1713 ObjectMeta: metav1.ObjectMeta{ 1714 Name: crdName, 1715 }, 1716 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 1717 Group: "cluster.com", 1718 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 1719 { 1720 Name: "v1alpha1", 1721 Served: true, 1722 Storage: true, 1723 Schema: &apiextensionsv1.CustomResourceValidation{ 1724 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1725 Type: "object", 1726 Description: "my crd schema", 1727 }, 1728 }, 1729 }, 1730 }, 1731 Names: apiextensionsv1.CustomResourceDefinitionNames{ 1732 Plural: crdPlural, 1733 Singular: crdPlural, 1734 Kind: crdPlural, 1735 ListKind: "list" + crdPlural, 1736 }, 1737 Scope: apiextensionsv1.NamespaceScoped, 1738 }, 1739 } 1740 1741 By(`Generate permissions`) 1742 serviceAccountName := genName("nginx-sa") 1743 permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 1744 { 1745 ServiceAccountName: serviceAccountName, 1746 Rules: []rbacv1.PolicyRule{ 1747 { 1748 Verbs: []string{rbac.VerbAll}, 1749 APIGroups: []string{"cluster.com"}, 1750 Resources: []string{crdPlural}, 1751 }, 1752 }, 1753 }, 1754 } 1755 By(`Generate permissions`) 1756 clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 1757 { 1758 ServiceAccountName: serviceAccountName, 1759 Rules: []rbacv1.PolicyRule{ 1760 { 1761 Verbs: []string{rbac.VerbAll}, 1762 APIGroups: []string{"cluster.com"}, 1763 Resources: []string{crdPlural}, 1764 }, 1765 }, 1766 }, 1767 } 1768 1769 By(`Create the catalog sources`) 1770 mainNamedStrategy := newNginxInstallStrategy(genName("dep-"), permissions, clusterPermissions) 1771 mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, &mainNamedStrategy) 1772 mainCatalogName := genName("mock-ocs-amplify-") 1773 mainManifests := []registry.PackageManifest{ 1774 { 1775 PackageName: mainPackageName, 1776 Channels: []registry.PackageChannel{ 1777 {Name: stableChannel, CurrentCSVName: mainCSV.GetName()}, 1778 }, 1779 DefaultChannelName: stableChannel, 1780 }, 1781 } 1782 1783 By(`Defer CRD clean up`) 1784 defer func() { 1785 Eventually(func() error { 1786 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{})) 1787 }).Should(Succeed()) 1788 }() 1789 1790 _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) 1791 defer cleanupMainCatalogSource() 1792 1793 By(`Attempt to get the catalog source before creating install plan`) 1794 _, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1795 require.NoError(GinkgoT(), err) 1796 1797 subscriptionName := genName("sub-nginx-update-perms1") 1798 subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 1799 defer subscriptionCleanup() 1800 1801 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 1802 require.NoError(GinkgoT(), err) 1803 require.NotNil(GinkgoT(), subscription) 1804 require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef) 1805 require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV) 1806 1807 installPlanName := subscription.Status.InstallPlanRef.Name 1808 1809 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 1810 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 1811 require.NoError(GinkgoT(), err) 1812 1813 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 1814 1815 By(`Verify CSV is created`) 1816 _, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker) 1817 require.NoError(GinkgoT(), err) 1818 1819 By(`Update CatalogSource with a new CSV with more permissions`) 1820 updatedPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 1821 { 1822 ServiceAccountName: serviceAccountName, 1823 Rules: []rbacv1.PolicyRule{ 1824 { 1825 Verbs: []string{rbac.VerbAll}, 1826 APIGroups: []string{"cluster.com"}, 1827 Resources: []string{crdPlural}, 1828 }, 1829 { 1830 Verbs: []string{rbac.VerbAll}, 1831 APIGroups: []string{"local.cluster.com"}, 1832 Resources: []string{"locals"}, 1833 }, 1834 }, 1835 }, 1836 } 1837 updatedClusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 1838 { 1839 ServiceAccountName: serviceAccountName, 1840 Rules: []rbacv1.PolicyRule{ 1841 { 1842 Verbs: []string{rbac.VerbAll}, 1843 APIGroups: []string{"cluster.com"}, 1844 Resources: []string{crdPlural}, 1845 }, 1846 { 1847 Verbs: []string{rbac.VerbAll}, 1848 APIGroups: []string{"two.cluster.com"}, 1849 Resources: []string{"twos"}, 1850 }, 1851 }, 1852 }, 1853 } 1854 1855 By(`Create the catalog sources`) 1856 updatedNamedStrategy := newNginxInstallStrategy(genName("dep-"), updatedPermissions, updatedClusterPermissions) 1857 updatedCSV := newCSV(mainPackageStable+"-next", generatedNamespace.GetName(), mainCSV.GetName(), semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, nil, &updatedNamedStrategy) 1858 updatedManifests := []registry.PackageManifest{ 1859 { 1860 PackageName: mainPackageName, 1861 Channels: []registry.PackageChannel{ 1862 {Name: stableChannel, CurrentCSVName: updatedCSV.GetName()}, 1863 }, 1864 DefaultChannelName: stableChannel, 1865 }, 1866 } 1867 1868 By(`Update catalog with updated CSV with more permissions`) 1869 updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV, updatedCSV}, updatedManifests) 1870 1871 _, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName())) 1872 require.NoError(GinkgoT(), err) 1873 1874 updatedInstallPlanName := subscription.Status.InstallPlanRef.Name 1875 1876 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 1877 fetchedUpdatedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedInstallPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 1878 require.NoError(GinkgoT(), err) 1879 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedUpdatedInstallPlan.Status.Phase) 1880 1881 By(`Wait for csv to update`) 1882 _, err = fetchCSV(crc, generatedNamespace.GetName(), updatedCSV.GetName(), csvSucceededChecker) 1883 require.NoError(GinkgoT(), err) 1884 1885 By(`If the CSV is succeeded, we successfully rolled out the RBAC changes`) 1886 }) 1887 1888 It("AttenuatePermissions", func() { 1889 1890 defer func() { 1891 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 1892 }() 1893 1894 By(`Build initial catalog`) 1895 mainPackageName := genName("nginx-attenuate-") 1896 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 1897 stableChannel := "stable" 1898 crdPlural := genName("ins-attenuate-") 1899 crdName := crdPlural + ".cluster.com" 1900 mainCRD := apiextensionsv1.CustomResourceDefinition{ 1901 ObjectMeta: metav1.ObjectMeta{ 1902 Name: crdName, 1903 }, 1904 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 1905 Group: "cluster.com", 1906 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 1907 { 1908 Name: "v1alpha1", 1909 Served: true, 1910 Storage: true, 1911 Schema: &apiextensionsv1.CustomResourceValidation{ 1912 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 1913 Type: "object", 1914 Description: "my crd schema", 1915 }, 1916 }, 1917 }, 1918 }, 1919 Names: apiextensionsv1.CustomResourceDefinitionNames{ 1920 Plural: crdPlural, 1921 Singular: crdPlural, 1922 Kind: crdPlural, 1923 ListKind: "list" + crdPlural, 1924 }, 1925 Scope: apiextensionsv1.NamespaceScoped, 1926 }, 1927 } 1928 1929 By(`Generate permissions`) 1930 serviceAccountName := genName("nginx-sa") 1931 permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 1932 { 1933 ServiceAccountName: serviceAccountName, 1934 Rules: []rbacv1.PolicyRule{ 1935 { 1936 Verbs: []string{rbac.VerbAll}, 1937 APIGroups: []string{"cluster.com"}, 1938 Resources: []string{crdPlural}, 1939 }, 1940 { 1941 Verbs: []string{rbac.VerbAll}, 1942 APIGroups: []string{"local.cluster.com"}, 1943 Resources: []string{"locals"}, 1944 }, 1945 }, 1946 }, 1947 } 1948 1949 By(`Generate permissions`) 1950 clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 1951 { 1952 ServiceAccountName: serviceAccountName, 1953 Rules: []rbacv1.PolicyRule{ 1954 { 1955 Verbs: []string{rbac.VerbAll}, 1956 APIGroups: []string{"cluster.com"}, 1957 Resources: []string{crdPlural}, 1958 }, 1959 { 1960 Verbs: []string{rbac.VerbAll}, 1961 APIGroups: []string{"two.cluster.com"}, 1962 Resources: []string{"twos"}, 1963 }, 1964 }, 1965 }, 1966 } 1967 1968 By(`Create the catalog sources`) 1969 mainNamedStrategy := newNginxInstallStrategy(genName("dep-"), permissions, clusterPermissions) 1970 mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, &mainNamedStrategy) 1971 mainCatalogName := genName("mock-ocs-main-update-perms1-") 1972 mainManifests := []registry.PackageManifest{ 1973 { 1974 PackageName: mainPackageName, 1975 Channels: []registry.PackageChannel{ 1976 {Name: stableChannel, CurrentCSVName: mainCSV.GetName()}, 1977 }, 1978 DefaultChannelName: stableChannel, 1979 }, 1980 } 1981 1982 By(`Defer CRD clean up`) 1983 defer func() { 1984 Eventually(func() error { 1985 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{})) 1986 }).Should(Succeed()) 1987 }() 1988 1989 _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) 1990 defer cleanupMainCatalogSource() 1991 1992 By(`Attempt to get the catalog source before creating install plan`) 1993 _, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 1994 require.NoError(GinkgoT(), err) 1995 1996 subscriptionName := genName("sub-nginx-update-perms1") 1997 subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 1998 defer subscriptionCleanup() 1999 2000 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 2001 require.NoError(GinkgoT(), err) 2002 require.NotNil(GinkgoT(), subscription) 2003 require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef) 2004 require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV) 2005 2006 installPlanName := subscription.Status.InstallPlanRef.Name 2007 2008 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 2009 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 2010 require.NoError(GinkgoT(), err) 2011 2012 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 2013 2014 By(`Verify CSV is created`) 2015 _, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker) 2016 require.NoError(GinkgoT(), err) 2017 2018 By("Wait for ServiceAccount to have access") 2019 err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { 2020 res, err := c.KubernetesInterface().AuthorizationV1().SubjectAccessReviews().Create(context.Background(), &authorizationv1.SubjectAccessReview{ 2021 Spec: authorizationv1.SubjectAccessReviewSpec{ 2022 User: "system:serviceaccount:" + generatedNamespace.GetName() + ":" + serviceAccountName, 2023 ResourceAttributes: &authorizationv1.ResourceAttributes{ 2024 Group: "cluster.com", 2025 Version: "v1alpha1", 2026 Resource: crdPlural, 2027 Verb: rbac.VerbAll, 2028 }, 2029 }, 2030 }, metav1.CreateOptions{}) 2031 if err != nil { 2032 return false, err 2033 } 2034 if res == nil { 2035 return false, nil 2036 } 2037 GinkgoT().Log("checking serviceaccount for permission") 2038 2039 By("should be allowed") 2040 return res.Status.Allowed, nil 2041 }) 2042 Expect(err).NotTo(HaveOccurred()) 2043 2044 By(`Update CatalogSource with a new CSV with fewer permissions`) 2045 updatedPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 2046 { 2047 ServiceAccountName: serviceAccountName, 2048 Rules: []rbacv1.PolicyRule{ 2049 { 2050 Verbs: []string{rbac.VerbAll}, 2051 APIGroups: []string{"local.cluster.com"}, 2052 Resources: []string{"locals"}, 2053 }, 2054 }, 2055 }, 2056 } 2057 updatedClusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 2058 { 2059 ServiceAccountName: serviceAccountName, 2060 Rules: []rbacv1.PolicyRule{ 2061 { 2062 Verbs: []string{rbac.VerbAll}, 2063 APIGroups: []string{"two.cluster.com"}, 2064 Resources: []string{"twos"}, 2065 }, 2066 }, 2067 }, 2068 } 2069 2070 By(`Create the catalog sources`) 2071 updatedNamedStrategy := newNginxInstallStrategy(genName("dep-"), updatedPermissions, updatedClusterPermissions) 2072 updatedCSV := newCSV(mainPackageStable+"-next", generatedNamespace.GetName(), mainCSV.GetName(), semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, nil, &updatedNamedStrategy) 2073 updatedManifests := []registry.PackageManifest{ 2074 { 2075 PackageName: mainPackageName, 2076 Channels: []registry.PackageChannel{ 2077 {Name: stableChannel, CurrentCSVName: updatedCSV.GetName()}, 2078 }, 2079 DefaultChannelName: stableChannel, 2080 }, 2081 } 2082 2083 By(`Update catalog with updated CSV with more permissions`) 2084 updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV, updatedCSV}, updatedManifests) 2085 2086 By(`Wait for subscription to update its status`) 2087 _, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName())) 2088 require.NoError(GinkgoT(), err) 2089 2090 updatedInstallPlanName := subscription.Status.InstallPlanRef.Name 2091 2092 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 2093 fetchedUpdatedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedInstallPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 2094 require.NoError(GinkgoT(), err) 2095 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedUpdatedInstallPlan.Status.Phase) 2096 2097 By(`Wait for csv to update`) 2098 _, err = fetchCSV(crc, generatedNamespace.GetName(), updatedCSV.GetName(), csvSucceededChecker) 2099 require.NoError(GinkgoT(), err) 2100 2101 By(`Wait for ServiceAccount to not have access anymore`) 2102 err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { 2103 res, err := c.KubernetesInterface().AuthorizationV1().SubjectAccessReviews().Create(context.Background(), &authorizationv1.SubjectAccessReview{ 2104 Spec: authorizationv1.SubjectAccessReviewSpec{ 2105 User: "system:serviceaccount:" + generatedNamespace.GetName() + ":" + serviceAccountName, 2106 ResourceAttributes: &authorizationv1.ResourceAttributes{ 2107 Group: "cluster.com", 2108 Version: "v1alpha1", 2109 Resource: crdPlural, 2110 Verb: rbac.VerbAll, 2111 }, 2112 }, 2113 }, metav1.CreateOptions{}) 2114 if err != nil { 2115 return false, err 2116 } 2117 if res == nil { 2118 return false, nil 2119 } 2120 GinkgoT().Log("checking serviceaccount for permission") 2121 2122 By(`should not be allowed`) 2123 return !res.Status.Allowed, nil 2124 }) 2125 }) 2126 2127 It("StopOnCSVModifications", func() { 2128 2129 defer func() { 2130 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 2131 }() 2132 2133 By(`Build initial catalog`) 2134 mainPackageName := genName("nginx-amplify-") 2135 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 2136 stableChannel := "stable" 2137 crdPlural := genName("ins-amplify-") 2138 crdName := crdPlural + ".cluster.com" 2139 mainCRD := apiextensionsv1.CustomResourceDefinition{ 2140 ObjectMeta: metav1.ObjectMeta{ 2141 Name: crdName, 2142 }, 2143 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 2144 Group: "cluster.com", 2145 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 2146 { 2147 Name: "v1alpha1", 2148 Served: true, 2149 Storage: true, 2150 Schema: &apiextensionsv1.CustomResourceValidation{ 2151 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 2152 Type: "object", 2153 Description: "my crd schema", 2154 }, 2155 }, 2156 }, 2157 }, 2158 Names: apiextensionsv1.CustomResourceDefinitionNames{ 2159 Plural: crdPlural, 2160 Singular: crdPlural, 2161 Kind: crdPlural, 2162 ListKind: "list" + crdPlural, 2163 }, 2164 Scope: apiextensionsv1.NamespaceScoped, 2165 }, 2166 } 2167 2168 By(`Generate permissions`) 2169 serviceAccountName := genName("nginx-sa") 2170 permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 2171 { 2172 ServiceAccountName: serviceAccountName, 2173 Rules: []rbacv1.PolicyRule{ 2174 { 2175 Verbs: []string{rbac.VerbAll}, 2176 APIGroups: []string{"cluster.com"}, 2177 Resources: []string{crdPlural}, 2178 }, 2179 }, 2180 }, 2181 } 2182 2183 By(`Generate permissions`) 2184 clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 2185 { 2186 ServiceAccountName: serviceAccountName, 2187 Rules: []rbacv1.PolicyRule{ 2188 { 2189 Verbs: []string{rbac.VerbAll}, 2190 APIGroups: []string{"cluster.com"}, 2191 Resources: []string{crdPlural}, 2192 }, 2193 }, 2194 }, 2195 } 2196 2197 By(`Create the catalog sources`) 2198 deploymentName := genName("dep-") 2199 mainNamedStrategy := newNginxInstallStrategy(deploymentName, permissions, clusterPermissions) 2200 mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, &mainNamedStrategy) 2201 mainCatalogName := genName("mock-ocs-stomper-") 2202 mainManifests := []registry.PackageManifest{ 2203 { 2204 PackageName: mainPackageName, 2205 Channels: []registry.PackageChannel{ 2206 {Name: stableChannel, CurrentCSVName: mainCSV.GetName()}, 2207 }, 2208 DefaultChannelName: stableChannel, 2209 }, 2210 } 2211 2212 By(`Defer CRD clean up`) 2213 defer func() { 2214 Eventually(func() error { 2215 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{})) 2216 }).Should(Succeed()) 2217 }() 2218 2219 _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) 2220 defer cleanupMainCatalogSource() 2221 2222 By(`Attempt to get the catalog source before creating install plan`) 2223 _, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 2224 require.NoError(GinkgoT(), err) 2225 2226 subscriptionName := genName("sub-nginx-stompy-") 2227 subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 2228 defer subscriptionCleanup() 2229 2230 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 2231 require.NoError(GinkgoT(), err) 2232 require.NotNil(GinkgoT(), subscription) 2233 require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef) 2234 require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV) 2235 2236 installPlanName := subscription.Status.InstallPlanRef.Name 2237 2238 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 2239 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 2240 require.NoError(GinkgoT(), err) 2241 2242 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 2243 2244 By(`Verify CSV is created`) 2245 csv, err := fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker) 2246 require.NoError(GinkgoT(), err) 2247 2248 addedEnvVar := corev1.EnvVar{Name: "EXAMPLE", Value: "value"} 2249 modifiedDetails := operatorsv1alpha1.StrategyDetailsDeployment{ 2250 DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ 2251 { 2252 Name: deploymentName, 2253 Spec: appsv1.DeploymentSpec{ 2254 Selector: &metav1.LabelSelector{ 2255 MatchLabels: map[string]string{"app": "nginx"}, 2256 }, 2257 Replicas: &singleInstance, 2258 Template: corev1.PodTemplateSpec{ 2259 ObjectMeta: metav1.ObjectMeta{ 2260 Labels: map[string]string{"app": "nginx"}, 2261 }, 2262 Spec: corev1.PodSpec{Containers: []corev1.Container{ 2263 { 2264 Name: genName("nginx"), 2265 Image: *dummyImage, 2266 Ports: []corev1.ContainerPort{{ContainerPort: 80}}, 2267 ImagePullPolicy: corev1.PullIfNotPresent, 2268 Env: []corev1.EnvVar{addedEnvVar}, 2269 }, 2270 }}, 2271 }, 2272 }, 2273 }, 2274 }, 2275 Permissions: permissions, 2276 ClusterPermissions: clusterPermissions, 2277 } 2278 2279 // wrapping the csv update in an eventually helps eliminate a flake in this test 2280 // it can happen that the csv changes in the meantime (e.g. reconciler adds a condition) 2281 // and the update fails with a conflict 2282 Eventually(func() error { 2283 csv, err := crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Get(context.Background(), csv.GetName(), metav1.GetOptions{}) 2284 if err != nil { 2285 return nil 2286 } 2287 2288 // update spec 2289 csv.Spec.InstallStrategy = operatorsv1alpha1.NamedInstallStrategy{ 2290 StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, 2291 StrategySpec: modifiedDetails, 2292 } 2293 2294 // update csv 2295 _, err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Update(context.Background(), csv, metav1.UpdateOptions{}) 2296 return err 2297 }).Should(Succeed()) 2298 2299 By(`Wait for csv to update`) 2300 _, err = fetchCSV(crc, generatedNamespace.GetName(), csv.GetName(), csvSucceededChecker) 2301 require.NoError(GinkgoT(), err) 2302 2303 By(`Should have the updated env var`) 2304 err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { 2305 dep, err := c.GetDeployment(generatedNamespace.GetName(), deploymentName) 2306 if err != nil { 2307 return false, nil 2308 } 2309 if len(dep.Spec.Template.Spec.Containers[0].Env) == 0 { 2310 return false, nil 2311 } 2312 for _, envVar := range dep.Spec.Template.Spec.Containers[0].Env { 2313 if envVar == addedEnvVar { 2314 return true, nil 2315 } 2316 } 2317 return false, nil 2318 }) 2319 require.NoError(GinkgoT(), err) 2320 2321 By(`Create the catalog sources`) 2322 By(`Updated csv has the same deployment strategy as main`) 2323 updatedCSV := newCSV(mainPackageStable+"-next", generatedNamespace.GetName(), mainCSV.GetName(), semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, nil, &mainNamedStrategy) 2324 updatedManifests := []registry.PackageManifest{ 2325 { 2326 PackageName: mainPackageName, 2327 Channels: []registry.PackageChannel{ 2328 {Name: stableChannel, CurrentCSVName: updatedCSV.GetName()}, 2329 }, 2330 DefaultChannelName: stableChannel, 2331 }, 2332 } 2333 2334 By(`Update catalog with updated CSV with more permissions`) 2335 updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV, updatedCSV}, updatedManifests) 2336 2337 _, err = fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName())) 2338 require.NoError(GinkgoT(), err) 2339 2340 updatedInstallPlanName := subscription.Status.InstallPlanRef.Name 2341 2342 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 2343 fetchedUpdatedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedInstallPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 2344 require.NoError(GinkgoT(), err) 2345 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedUpdatedInstallPlan.Status.Phase) 2346 2347 By(`Wait for csv to update`) 2348 _, err = fetchCSV(crc, generatedNamespace.GetName(), updatedCSV.GetName(), csvSucceededChecker) 2349 require.NoError(GinkgoT(), err) 2350 2351 By(`Should have created deployment and stomped on the env changes`) 2352 updatedDep, err := c.GetDeployment(generatedNamespace.GetName(), deploymentName) 2353 require.NoError(GinkgoT(), err) 2354 require.NotNil(GinkgoT(), updatedDep) 2355 2356 By(`Should have the updated env var`) 2357 for _, envVar := range updatedDep.Spec.Template.Spec.Containers[0].Env { 2358 require.False(GinkgoT(), envVar == addedEnvVar) 2359 } 2360 }) 2361 2362 It("UpdateSingleExistingCRDOwner", func() { 2363 2364 mainPackageName := genName("nginx-update-") 2365 2366 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 2367 mainPackageBeta := fmt.Sprintf("%s-beta", mainPackageName) 2368 2369 stableChannel := "stable" 2370 2371 crdPlural := genName("ins-update-") 2372 crdName := crdPlural + ".cluster.com" 2373 mainCRD := apiextensionsv1.CustomResourceDefinition{ 2374 ObjectMeta: metav1.ObjectMeta{ 2375 Name: crdName, 2376 }, 2377 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 2378 Group: "cluster.com", 2379 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 2380 { 2381 Name: "v1alpha1", 2382 Served: true, 2383 Storage: true, 2384 Schema: &apiextensionsv1.CustomResourceValidation{ 2385 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 2386 Type: "object", 2387 Description: "my crd schema", 2388 }, 2389 }, 2390 }, 2391 }, 2392 Names: apiextensionsv1.CustomResourceDefinitionNames{ 2393 Plural: crdPlural, 2394 Singular: crdPlural, 2395 Kind: crdPlural, 2396 ListKind: "list" + crdPlural, 2397 }, 2398 Scope: apiextensionsv1.NamespaceScoped, 2399 }, 2400 } 2401 2402 updatedCRD := apiextensionsv1.CustomResourceDefinition{ 2403 ObjectMeta: metav1.ObjectMeta{ 2404 Name: crdName, 2405 }, 2406 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 2407 Group: "cluster.com", 2408 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 2409 { 2410 Name: "v1alpha1", 2411 Served: true, 2412 Storage: true, 2413 Schema: &apiextensionsv1.CustomResourceValidation{ 2414 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 2415 Type: "object", 2416 Description: "my crd schema", 2417 }, 2418 }, 2419 }, 2420 { 2421 Name: "v1alpha2", 2422 Served: true, 2423 Storage: false, 2424 Schema: &apiextensionsv1.CustomResourceValidation{ 2425 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 2426 Type: "object", 2427 Description: "my crd schema", 2428 }, 2429 }, 2430 }, 2431 }, 2432 Names: apiextensionsv1.CustomResourceDefinitionNames{ 2433 Plural: crdPlural, 2434 Singular: crdPlural, 2435 Kind: crdPlural, 2436 ListKind: "list" + crdPlural, 2437 }, 2438 Scope: apiextensionsv1.NamespaceScoped, 2439 }, 2440 } 2441 2442 mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{mainCRD}, nil, nil) 2443 betaCSV := newCSV(mainPackageBeta, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.2.0"), []apiextensionsv1.CustomResourceDefinition{updatedCRD}, nil, nil) 2444 2445 By(`Defer CRD clean up`) 2446 defer func() { 2447 Eventually(func() error { 2448 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{})) 2449 }).Should(Succeed()) 2450 Eventually(func() error { 2451 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), updatedCRD.GetName(), metav1.DeleteOptions{})) 2452 }).Should(Succeed()) 2453 }() 2454 2455 defer func() { 2456 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 2457 }() 2458 2459 mainCatalogName := genName("mock-ocs-main-update-") 2460 2461 By(`Create separate manifests for each CatalogSource`) 2462 mainManifests := []registry.PackageManifest{ 2463 { 2464 PackageName: mainPackageName, 2465 Channels: []registry.PackageChannel{ 2466 {Name: stableChannel, CurrentCSVName: mainPackageStable}, 2467 }, 2468 DefaultChannelName: stableChannel, 2469 }, 2470 } 2471 2472 By(`Create the catalog sources`) 2473 _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{mainCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) 2474 defer cleanupMainCatalogSource() 2475 2476 By(`Attempt to get the catalog source before creating install plan`) 2477 _, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 2478 require.NoError(GinkgoT(), err) 2479 2480 subscriptionName := genName("sub-nginx-update-") 2481 createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 2482 2483 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 2484 require.NoError(GinkgoT(), err) 2485 require.NotNil(GinkgoT(), subscription) 2486 require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef) 2487 require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV) 2488 2489 installPlanName := subscription.Status.InstallPlanRef.Name 2490 2491 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 2492 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 2493 require.NoError(GinkgoT(), err) 2494 2495 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 2496 2497 By(`Fetch installplan again to check for unnecessary control loops`) 2498 fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, fetchedInstallPlan.GetName(), generatedNamespace.GetName(), func(fip *operatorsv1alpha1.InstallPlan) bool { 2499 Expect(equality.Semantic.DeepEqual(fetchedInstallPlan, fip)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip)) 2500 return true 2501 }) 2502 require.NoError(GinkgoT(), err) 2503 2504 By(`Verify CSV is created`) 2505 _, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvAnyChecker) 2506 require.NoError(GinkgoT(), err) 2507 2508 mainManifests = []registry.PackageManifest{ 2509 { 2510 PackageName: mainPackageName, 2511 Channels: []registry.PackageChannel{ 2512 {Name: stableChannel, CurrentCSVName: mainPackageBeta}, 2513 }, 2514 DefaultChannelName: stableChannel, 2515 }, 2516 } 2517 2518 updateInternalCatalog(GinkgoT(), c, crc, mainCatalogName, generatedNamespace.GetName(), []apiextensionsv1.CustomResourceDefinition{updatedCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV, betaCSV}, mainManifests) 2519 By(`Wait for subscription to update`) 2520 updatedSubscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanDifferentChecker(fetchedInstallPlan.GetName())) 2521 require.NoError(GinkgoT(), err) 2522 2523 By(`Verify installplan created and installed`) 2524 fetchedUpdatedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, updatedSubscription.Status.InstallPlanRef.Name, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 2525 require.NoError(GinkgoT(), err) 2526 require.NotEqual(GinkgoT(), fetchedInstallPlan.GetName(), fetchedUpdatedInstallPlan.GetName()) 2527 2528 By(`Wait for csv to update`) 2529 _, err = fetchCSV(crc, generatedNamespace.GetName(), betaCSV.GetName(), csvAnyChecker) 2530 require.NoError(GinkgoT(), err) 2531 2532 By(`Get the CRD to see if it is updated`) 2533 fetchedCRD, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), crdName, metav1.GetOptions{}) 2534 require.NoError(GinkgoT(), err) 2535 require.Equal(GinkgoT(), len(fetchedCRD.Spec.Versions), len(updatedCRD.Spec.Versions), "The CRD versions counts don't match") 2536 2537 fetchedCRDVersions := map[crdVersionKey]struct{}{} 2538 for _, version := range fetchedCRD.Spec.Versions { 2539 key := crdVersionKey{ 2540 name: version.Name, 2541 served: version.Served, 2542 storage: version.Storage, 2543 } 2544 fetchedCRDVersions[key] = struct{}{} 2545 } 2546 2547 for _, version := range updatedCRD.Spec.Versions { 2548 key := crdVersionKey{ 2549 name: version.Name, 2550 served: version.Served, 2551 storage: version.Storage, 2552 } 2553 _, ok := fetchedCRDVersions[key] 2554 require.True(GinkgoT(), ok, "couldn't find %v in fetched CRD versions: %#v", key, fetchedCRDVersions) 2555 } 2556 }) 2557 2558 It("UpdatePreexistingCRDFailed", func() { 2559 2560 defer func() { 2561 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 2562 }() 2563 2564 mainPackageName := genName("nginx-update2-") 2565 2566 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 2567 2568 stableChannel := "stable" 2569 2570 crdPlural := genName("ins-update2-") 2571 crdName := crdPlural + ".cluster.com" 2572 mainCRD := apiextensionsv1.CustomResourceDefinition{ 2573 ObjectMeta: metav1.ObjectMeta{ 2574 Name: crdName, 2575 }, 2576 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 2577 Group: "cluster.com", 2578 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 2579 { 2580 Name: "v1alpha1", 2581 Served: true, 2582 Storage: true, 2583 Schema: &apiextensionsv1.CustomResourceValidation{ 2584 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 2585 Type: "object", 2586 Description: "my crd schema", 2587 }, 2588 }, 2589 }, 2590 }, 2591 Names: apiextensionsv1.CustomResourceDefinitionNames{ 2592 Plural: crdPlural, 2593 Singular: crdPlural, 2594 Kind: crdPlural, 2595 ListKind: "list" + crdPlural, 2596 }, 2597 Scope: apiextensionsv1.NamespaceScoped, 2598 }, 2599 } 2600 2601 updatedCRD := apiextensionsv1.CustomResourceDefinition{ 2602 ObjectMeta: metav1.ObjectMeta{ 2603 Name: crdName, 2604 }, 2605 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 2606 Group: "cluster.com", 2607 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 2608 { 2609 Name: "v1alpha1", 2610 Served: true, 2611 Storage: true, 2612 Schema: &apiextensionsv1.CustomResourceValidation{ 2613 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 2614 Type: "object", 2615 Description: "my crd schema", 2616 }, 2617 }, 2618 }, 2619 { 2620 Name: "v1alpha2", 2621 Served: true, 2622 Storage: false, 2623 Schema: &apiextensionsv1.CustomResourceValidation{ 2624 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 2625 Type: "object", 2626 Description: "my crd schema", 2627 }, 2628 }, 2629 }, 2630 }, 2631 Names: apiextensionsv1.CustomResourceDefinitionNames{ 2632 Plural: crdPlural, 2633 Singular: crdPlural, 2634 Kind: crdPlural, 2635 ListKind: "list" + crdPlural, 2636 }, 2637 Scope: apiextensionsv1.NamespaceScoped, 2638 }, 2639 } 2640 2641 expectedCRDVersions := map[crdVersionKey]struct{}{} 2642 for _, version := range mainCRD.Spec.Versions { 2643 key := crdVersionKey{ 2644 name: version.Name, 2645 served: version.Served, 2646 storage: version.Storage, 2647 } 2648 expectedCRDVersions[key] = struct{}{} 2649 } 2650 2651 By(`Create the initial CSV`) 2652 cleanupCRD, err := createCRD(c, mainCRD) 2653 require.NoError(GinkgoT(), err) 2654 defer cleanupCRD() 2655 2656 mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, nil) 2657 2658 mainCatalogName := genName("mock-ocs-main-update2-") 2659 2660 By(`Create separate manifests for each CatalogSource`) 2661 mainManifests := []registry.PackageManifest{ 2662 { 2663 PackageName: mainPackageName, 2664 Channels: []registry.PackageChannel{ 2665 {Name: stableChannel, CurrentCSVName: mainPackageStable}, 2666 }, 2667 DefaultChannelName: stableChannel, 2668 }, 2669 } 2670 2671 By(`Create the catalog sources`) 2672 _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{updatedCRD}, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) 2673 defer cleanupMainCatalogSource() 2674 2675 By(`Attempt to get the catalog source before creating install plan`) 2676 _, err = fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 2677 require.NoError(GinkgoT(), err) 2678 2679 subscriptionName := genName("sub-nginx-update2-") 2680 subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 2681 defer subscriptionCleanup() 2682 2683 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 2684 require.NoError(GinkgoT(), err) 2685 require.NotNil(GinkgoT(), subscription) 2686 require.NotNil(GinkgoT(), subscription.Status.InstallPlanRef) 2687 require.Equal(GinkgoT(), mainCSV.GetName(), subscription.Status.CurrentCSV) 2688 2689 installPlanName := subscription.Status.InstallPlanRef.Name 2690 2691 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 2692 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 2693 require.NoError(GinkgoT(), err) 2694 2695 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 2696 2697 By(`Fetch installplan again to check for unnecessary control loops`) 2698 fetchedInstallPlan, err = fetchInstallPlan(GinkgoT(), crc, fetchedInstallPlan.GetName(), generatedNamespace.GetName(), func(fip *operatorsv1alpha1.InstallPlan) bool { 2699 Expect(equality.Semantic.DeepEqual(fetchedInstallPlan, fip)).Should(BeTrue(), diff.ObjectDiff(fetchedInstallPlan, fip)) 2700 return true 2701 }) 2702 require.NoError(GinkgoT(), err) 2703 2704 By(`Verify CSV is created`) 2705 _, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvAnyChecker) 2706 require.NoError(GinkgoT(), err) 2707 2708 By(`Get the CRD to see if it is updated`) 2709 fetchedCRD, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), crdName, metav1.GetOptions{}) 2710 require.NoError(GinkgoT(), err) 2711 require.Equal(GinkgoT(), len(fetchedCRD.Spec.Versions), len(mainCRD.Spec.Versions), "The CRD versions counts don't match") 2712 2713 fetchedCRDVersions := map[crdVersionKey]struct{}{} 2714 for _, version := range fetchedCRD.Spec.Versions { 2715 key := crdVersionKey{ 2716 name: version.Name, 2717 served: version.Served, 2718 storage: version.Storage, 2719 } 2720 fetchedCRDVersions[key] = struct{}{} 2721 } 2722 2723 for _, version := range mainCRD.Spec.Versions { 2724 key := crdVersionKey{ 2725 name: version.Name, 2726 served: version.Served, 2727 storage: version.Storage, 2728 } 2729 _, ok := fetchedCRDVersions[key] 2730 require.True(GinkgoT(), ok, "couldn't find %v in fetched CRD versions: %#v", key, fetchedCRDVersions) 2731 } 2732 }) 2733 }) 2734 2735 It("creation with permissions", func() { 2736 By(`This It spec creates an InstallPlan with a CSV containing a set of permissions to be resolved.`) 2737 2738 packageName := genName("nginx") 2739 stableChannel := "stable" 2740 stableCSVName := packageName + "-stable" 2741 2742 By("Create manifests") 2743 manifests := []registry.PackageManifest{ 2744 { 2745 PackageName: packageName, 2746 Channels: []registry.PackageChannel{ 2747 { 2748 Name: stableChannel, 2749 CurrentCSVName: stableCSVName, 2750 }, 2751 }, 2752 DefaultChannelName: stableChannel, 2753 }, 2754 } 2755 2756 By("Create new CRDs") 2757 crdPlural := genName("ins") 2758 crd := newCRD(crdPlural) 2759 2760 By("Defer CRD clean up") 2761 defer func() { 2762 Eventually(func() error { 2763 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{})) 2764 }).Should(Succeed()) 2765 }() 2766 2767 By("Generate permissions") 2768 By(`Permissions must be different than ClusterPermissions defined below if OLM is going to lift role/rolebindings to cluster level.`) 2769 serviceAccountName := genName("nginx-sa") 2770 permissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 2771 { 2772 ServiceAccountName: serviceAccountName, 2773 Rules: []rbacv1.PolicyRule{ 2774 { 2775 Verbs: []string{rbac.VerbAll}, 2776 APIGroups: []string{"cluster.com"}, 2777 Resources: []string{crdPlural}, 2778 }, 2779 { 2780 Verbs: []string{rbac.VerbAll}, 2781 APIGroups: []string{corev1.GroupName}, 2782 Resources: []string{corev1.ResourceConfigMaps.String()}, 2783 }, 2784 }, 2785 }, 2786 } 2787 2788 By("Generate permissions") 2789 clusterPermissions := []operatorsv1alpha1.StrategyDeploymentPermissions{ 2790 { 2791 ServiceAccountName: serviceAccountName, 2792 Rules: []rbacv1.PolicyRule{ 2793 { 2794 Verbs: []string{rbac.VerbAll}, 2795 APIGroups: []string{"cluster.com"}, 2796 Resources: []string{crdPlural}, 2797 }, 2798 }, 2799 }, 2800 } 2801 2802 By("Create a new NamedInstallStrategy") 2803 namedStrategy := newNginxInstallStrategy(genName("dep-"), permissions, clusterPermissions) 2804 2805 By("Create new CSVs") 2806 stableCSV := newCSV(stableCSVName, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, &namedStrategy) 2807 2808 defer func() { 2809 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 2810 }() 2811 2812 By("Create CatalogSource") 2813 mainCatalogSourceName := genName("nginx-catalog") 2814 _, cleanupCatalogSource := createInternalCatalogSource(c, crc, mainCatalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{stableCSV}) 2815 defer cleanupCatalogSource() 2816 2817 By("Attempt to get CatalogSource") 2818 _, err := fetchCatalogSourceOnStatus(crc, mainCatalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 2819 require.NoError(GinkgoT(), err) 2820 2821 By("Creating a Subscription") 2822 subscriptionName := genName("sub-nginx-") 2823 // Subscription is explitly deleted as part of the test to avoid CSV being recreated, 2824 // so ignore cleanup function 2825 _ = createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogSourceName, packageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 2826 2827 By("Attempt to get Subscription") 2828 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 2829 require.NoError(GinkgoT(), err) 2830 require.NotNil(GinkgoT(), subscription) 2831 2832 installPlanName := subscription.Status.InstallPlanRef.Name 2833 2834 By("Attempt to get InstallPlan") 2835 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed, operatorsv1alpha1.InstallPlanPhaseComplete)) 2836 require.NoError(GinkgoT(), err) 2837 require.NotEqual(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseFailed, fetchedInstallPlan.Status.Phase, "InstallPlan failed") 2838 2839 By("Expect correct RBAC resources to be resolved and created") 2840 expectedSteps := map[registry.ResourceKey]struct{}{ 2841 {Name: crd.Name, Kind: "CustomResourceDefinition"}: {}, 2842 {Name: stableCSVName, Kind: "ClusterServiceVersion"}: {}, 2843 {Name: serviceAccountName, Kind: "ServiceAccount"}: {}, 2844 {Name: stableCSVName, Kind: "Role"}: {}, 2845 {Name: stableCSVName, Kind: "RoleBinding"}: {}, 2846 {Name: stableCSVName, Kind: "ClusterRole"}: {}, 2847 {Name: stableCSVName, Kind: "ClusterRoleBinding"}: {}, 2848 } 2849 2850 require.Equal(GinkgoT(), len(expectedSteps), len(fetchedInstallPlan.Status.Plan), "number of expected steps does not match installed") 2851 2852 for _, step := range fetchedInstallPlan.Status.Plan { 2853 key := registry.ResourceKey{ 2854 Name: step.Resource.Name, 2855 Kind: step.Resource.Kind, 2856 } 2857 for expected := range expectedSteps { 2858 if expected == key { 2859 delete(expectedSteps, expected) 2860 } else if strings.HasPrefix(key.Name, expected.Name) && key.Kind == expected.Kind { 2861 delete(expectedSteps, expected) 2862 } else { 2863 GinkgoT().Logf("Found unexpected step %#v, expected %#v: name has prefix: %v kinds match %v", key, expected, strings.HasPrefix(key.Name, expected.Name), key.Kind == expected.Kind) 2864 } 2865 } 2866 2867 By("This operator was installed into a global operator group, so the roles should have been lifted to clusterroles") 2868 if step.Resource.Kind == "Role" { 2869 err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { 2870 _, err = c.GetClusterRole(step.Resource.Name) 2871 if err != nil { 2872 if apierrors.IsNotFound(err) { 2873 return false, nil 2874 } 2875 return false, err 2876 } 2877 return true, nil 2878 }) 2879 require.NoError(GinkgoT(), err) 2880 } 2881 if step.Resource.Kind == "RoleBinding" { 2882 err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { 2883 _, err = c.GetClusterRoleBinding(step.Resource.Name) 2884 if err != nil { 2885 if apierrors.IsNotFound(err) { 2886 return false, nil 2887 } 2888 return false, err 2889 } 2890 return true, nil 2891 }) 2892 require.NoError(GinkgoT(), err) 2893 } 2894 } 2895 2896 By("Should have removed every matching step") 2897 require.Equal(GinkgoT(), 0, len(expectedSteps), "Actual resource steps do not match expected: %#v", expectedSteps) 2898 2899 By(fmt.Sprintf("Explicitly deleting subscription %s/%s", generatedNamespace.GetName(), subscriptionName)) 2900 err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Delete(context.Background(), subscriptionName, metav1.DeleteOptions{}) 2901 By("Looking for no error OR IsNotFound error") 2902 require.NoError(GinkgoT(), client.IgnoreNotFound(err)) 2903 2904 By("Waiting for the Subscription to delete") 2905 err = waitForSubscriptionToDelete(generatedNamespace.GetName(), subscriptionName, crc) 2906 require.NoError(GinkgoT(), client.IgnoreNotFound(err)) 2907 2908 By(fmt.Sprintf("Explicitly deleting csv %s/%s", generatedNamespace.GetName(), stableCSVName)) 2909 err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.Background(), stableCSVName, metav1.DeleteOptions{}) 2910 By("Looking for no error OR IsNotFound error") 2911 require.NoError(GinkgoT(), client.IgnoreNotFound(err)) 2912 By("Waiting for the CSV to delete") 2913 err = waitForCsvToDelete(generatedNamespace.GetName(), stableCSVName, crc) 2914 require.NoError(GinkgoT(), client.IgnoreNotFound(err)) 2915 2916 nCrs := 0 2917 nCrbs := 0 2918 By("Waiting for CRBs and CRs and SAs to delete") 2919 Eventually(func() bool { 2920 2921 crbs, err := c.KubernetesInterface().RbacV1().ClusterRoleBindings().List(context.Background(), metav1.ListOptions{LabelSelector: fmt.Sprintf("%v=%v", ownerutil.OwnerKey, stableCSVName)}) 2922 if err != nil { 2923 GinkgoT().Logf("error getting crbs: %v", err) 2924 return false 2925 } 2926 if n := len(crbs.Items); n != 0 { 2927 if n != nCrbs { 2928 GinkgoT().Logf("CRBs remaining: %v", n) 2929 nCrbs = n 2930 } 2931 return false 2932 } 2933 2934 crs, err := c.KubernetesInterface().RbacV1().ClusterRoles().List(context.Background(), metav1.ListOptions{LabelSelector: fmt.Sprintf("%v=%v", ownerutil.OwnerKey, stableCSVName)}) 2935 if err != nil { 2936 GinkgoT().Logf("error getting crs: %v", err) 2937 return false 2938 } 2939 if n := len(crs.Items); n != 0 { 2940 if n != nCrs { 2941 GinkgoT().Logf("CRs remaining: %v", n) 2942 nCrs = n 2943 } 2944 return false 2945 } 2946 2947 _, err = c.KubernetesInterface().CoreV1().ServiceAccounts(generatedNamespace.GetName()).Get(context.Background(), serviceAccountName, metav1.GetOptions{}) 2948 if client.IgnoreNotFound(err) != nil { 2949 GinkgoT().Logf("error getting sa %s/%s: %v", generatedNamespace.GetName(), serviceAccountName, err) 2950 return false 2951 } 2952 2953 return true 2954 }, pollDuration, pollInterval).Should(BeTrue()) 2955 By("Cleaning up the test") 2956 }) 2957 2958 It("CRD validation", func() { 2959 By(`Tests if CRD validation works with the "minimum" property after being`) 2960 By(`pulled from a CatalogSource's operator-registry.`) 2961 2962 crdPlural := genName("ins") 2963 crdName := crdPlural + ".cluster.com" 2964 var min float64 = 2 2965 var max float64 = 256 2966 2967 By(`Create CRD with offending property`) 2968 crd := apiextensionsv1.CustomResourceDefinition{ 2969 ObjectMeta: metav1.ObjectMeta{ 2970 Name: crdName, 2971 }, 2972 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 2973 Group: "cluster.com", 2974 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 2975 { 2976 Name: "v1alpha1", 2977 Served: true, 2978 Storage: true, 2979 Schema: &apiextensionsv1.CustomResourceValidation{ 2980 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 2981 Type: "object", 2982 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 2983 "spec": { 2984 Type: "object", 2985 Description: "Spec of a test object.", 2986 Properties: map[string]apiextensionsv1.JSONSchemaProps{ 2987 "scalar": { 2988 Type: "number", 2989 Description: "Scalar value that should have a min and max.", 2990 Minimum: &min, 2991 Maximum: &max, 2992 }, 2993 }, 2994 }, 2995 }, 2996 }, 2997 }, 2998 }, 2999 }, 3000 Names: apiextensionsv1.CustomResourceDefinitionNames{ 3001 Plural: crdPlural, 3002 Singular: crdPlural, 3003 Kind: crdPlural, 3004 ListKind: "list" + crdPlural, 3005 }, 3006 Scope: apiextensionsv1.NamespaceScoped, 3007 }, 3008 } 3009 3010 By(`Defer CRD clean up`) 3011 defer func() { 3012 Eventually(func() error { 3013 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{})) 3014 }).Should(Succeed()) 3015 }() 3016 3017 By(`Create CSV`) 3018 packageName := genName("nginx-") 3019 stableChannel := "stable" 3020 packageNameStable := packageName + "-" + stableChannel 3021 csv := newCSV(packageNameStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{crd}, nil, nil) 3022 3023 By(`Create PackageManifests`) 3024 manifests := []registry.PackageManifest{ 3025 { 3026 PackageName: packageName, 3027 Channels: []registry.PackageChannel{ 3028 {Name: stableChannel, CurrentCSVName: packageNameStable}, 3029 }, 3030 DefaultChannelName: stableChannel, 3031 }, 3032 } 3033 3034 By(`Create the CatalogSource`) 3035 catalogSourceName := genName("mock-nginx-") 3036 _, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalogSourceName, generatedNamespace.GetName(), manifests, []apiextensionsv1.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csv}) 3037 defer cleanupCatalogSource() 3038 3039 By(`Attempt to get the catalog source before creating install plan`) 3040 _, err := fetchCatalogSourceOnStatus(crc, catalogSourceName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 3041 require.NoError(GinkgoT(), err) 3042 3043 subscriptionName := genName("sub-nginx-") 3044 cleanupSubscription := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, catalogSourceName, packageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 3045 defer cleanupSubscription() 3046 3047 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 3048 require.NoError(GinkgoT(), err) 3049 require.NotNil(GinkgoT(), subscription) 3050 3051 installPlanName := subscription.Status.InstallPlanRef.Name 3052 3053 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 3054 fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete, operatorsv1alpha1.InstallPlanPhaseFailed)) 3055 require.NoError(GinkgoT(), err) 3056 GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase) 3057 3058 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 3059 }) 3060 3061 It("[FLAKE] consistent generation", func() { 3062 By(`This It spec verifies that, in cases where there are multiple options to fulfil a dependency`) 3063 By(`across multiple catalogs, we only generate one installplan with one set of resolved resources.`) 3064 //issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2633 3065 3066 By(`Configure catalogs:`) 3067 By(`) - one catalog with a package that has a dependency`) 3068 By(`) - several duplicate catalog with a package that satisfies the dependency`) 3069 By(`Install the package from the main catalog`) 3070 By(`Should see only 1 installplan created`) 3071 By(`Should see the main CSV installed`) 3072 3073 log := func(s string) { 3074 GinkgoT().Logf("%s: %s", time.Now().Format("15:04:05.9999"), s) 3075 } 3076 3077 mainPackageName := genName("nginx-") 3078 dependentPackageName := genName("nginxdep-") 3079 3080 mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName) 3081 dependentPackageStable := fmt.Sprintf("%s-stable", dependentPackageName) 3082 3083 stableChannel := "stable" 3084 3085 dependentCRD := newCRD(genName("ins-")) 3086 mainCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil) 3087 dependentCSV := newCSV(dependentPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{dependentCRD}, nil, nil) 3088 3089 defer func() { 3090 require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 3091 }() 3092 3093 dependentCatalogName := genName("mock-ocs-dependent-") 3094 mainCatalogName := genName("mock-ocs-main-") 3095 3096 By(`Create separate manifests for each CatalogSource`) 3097 mainManifests := []registry.PackageManifest{ 3098 { 3099 PackageName: mainPackageName, 3100 Channels: []registry.PackageChannel{ 3101 {Name: stableChannel, CurrentCSVName: mainPackageStable}, 3102 }, 3103 DefaultChannelName: stableChannel, 3104 }, 3105 } 3106 3107 dependentManifests := []registry.PackageManifest{ 3108 { 3109 PackageName: dependentPackageName, 3110 Channels: []registry.PackageChannel{ 3111 {Name: stableChannel, CurrentCSVName: dependentPackageStable}, 3112 }, 3113 DefaultChannelName: stableChannel, 3114 }, 3115 } 3116 3117 By(`Defer CRD clean up`) 3118 defer func() { 3119 Eventually(func() error { 3120 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), metav1.DeleteOptions{})) 3121 }).Should(Succeed()) 3122 }() 3123 3124 By(`Create the dependent catalog source`) 3125 _, cleanupDependentCatalogSource := createInternalCatalogSource(c, crc, dependentCatalogName, generatedNamespace.GetName(), dependentManifests, []apiextensionsv1.CustomResourceDefinition{dependentCRD}, []operatorsv1alpha1.ClusterServiceVersion{dependentCSV}) 3126 defer cleanupDependentCatalogSource() 3127 3128 By(`Attempt to get the catalog source before creating install plan`) 3129 dependentCatalogSource, err := fetchCatalogSourceOnStatus(crc, dependentCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 3130 require.NoError(GinkgoT(), err) 3131 3132 By(`Create the alt dependent catalog sources`) 3133 var wg sync.WaitGroup 3134 for i := 0; i < 4; i++ { // Creating more increases the odds that the race condition will be triggered 3135 wg.Add(1) 3136 go func(i int) { 3137 defer GinkgoRecover() 3138 By(`Create a CatalogSource pointing to the grpc pod`) 3139 addressSource := &operatorsv1alpha1.CatalogSource{ 3140 TypeMeta: metav1.TypeMeta{ 3141 Kind: operatorsv1alpha1.CatalogSourceKind, 3142 APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion, 3143 }, 3144 Spec: operatorsv1alpha1.CatalogSourceSpec{ 3145 SourceType: operatorsv1alpha1.SourceTypeGrpc, 3146 Address: dependentCatalogSource.Status.RegistryServiceStatus.Address(), 3147 GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ 3148 SecurityContextConfig: operatorsv1alpha1.Restricted, 3149 }, 3150 }, 3151 } 3152 addressSource.SetName(genName("alt-dep-")) 3153 3154 _, err := crc.OperatorsV1alpha1().CatalogSources(generatedNamespace.GetName()).Create(context.Background(), addressSource, metav1.CreateOptions{}) 3155 require.NoError(GinkgoT(), err) 3156 3157 By(`Attempt to get the catalog source before creating install plan`) 3158 _, err = fetchCatalogSourceOnStatus(crc, addressSource.GetName(), generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 3159 require.NoError(GinkgoT(), err) 3160 wg.Done() 3161 }(i) 3162 } 3163 wg.Wait() 3164 3165 By(`Create the main catalog source`) 3166 _, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, nil, []operatorsv1alpha1.ClusterServiceVersion{mainCSV}) 3167 defer cleanupMainCatalogSource() 3168 3169 By(`Attempt to get the catalog source before creating install plan`) 3170 _, err = fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced()) 3171 require.NoError(GinkgoT(), err) 3172 3173 subscriptionName := genName("sub-nginx-") 3174 subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 3175 defer subscriptionCleanup() 3176 3177 subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker()) 3178 require.NoError(GinkgoT(), err) 3179 require.NotNil(GinkgoT(), subscription) 3180 3181 installPlanName := subscription.Status.InstallPlanRef.Name 3182 3183 By(`Wait for InstallPlan to be status: Complete before checking resource presence`) 3184 fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete)) 3185 require.NoError(GinkgoT(), err) 3186 log(fmt.Sprintf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)) 3187 3188 require.Equal(GinkgoT(), operatorsv1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase) 3189 3190 By(`Verify CSV is created`) 3191 _, err = fetchCSV(crc, generatedNamespace.GetName(), mainCSV.GetName(), csvSucceededChecker) 3192 require.NoError(GinkgoT(), err) 3193 3194 By(`Make sure to clean up the installed CRD`) 3195 deleteOpts := &metav1.DeleteOptions{} 3196 defer func() { 3197 require.NoError(GinkgoT(), c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), *deleteOpts)) 3198 }() 3199 3200 By(`ensure there is only one installplan`) 3201 ips, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).List(context.Background(), metav1.ListOptions{}) 3202 require.NoError(GinkgoT(), err) 3203 require.Equal(GinkgoT(), 1, len(ips.Items), "If this test fails it should be taken seriously and not treated as a flake. \n%v", ips.Items) 3204 }) 3205 3206 When("an InstallPlan is created with no valid OperatorGroup present", func() { 3207 var ( 3208 installPlanName string 3209 ) 3210 3211 BeforeEach(func() { 3212 By(`Make sure there are no OGs in the namespace already`) 3213 require.NoError(GinkgoT(), crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})) 3214 3215 By(`Create InstallPlan`) 3216 installPlanName = "ip" 3217 ip := newInstallPlanWithDummySteps(installPlanName, generatedNamespace.GetName(), operatorsv1alpha1.InstallPlanPhaseInstalling) 3218 outIP, err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Create(context.Background(), ip, metav1.CreateOptions{}) 3219 Expect(err).NotTo(HaveOccurred()) 3220 Expect(outIP).NotTo(BeNil()) 3221 3222 By(`The status gets ignored on create so we need to update it else the InstallPlan sync ignores`) 3223 By(`InstallPlans without any steps or bundle lookups`) 3224 outIP.Status = ip.Status 3225 _, err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).UpdateStatus(context.Background(), outIP, metav1.UpdateOptions{}) 3226 Expect(err).NotTo(HaveOccurred()) 3227 }) 3228 3229 AfterEach(func() { 3230 err := crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.Background(), installPlanName, metav1.DeleteOptions{}) 3231 Expect(err).NotTo(HaveOccurred()) 3232 }) 3233 3234 It("[FLAKE] should clear up the condition in the InstallPlan status that contains an error message when a valid OperatorGroup is created", func() { 3235 By(`issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2636`) 3236 3237 By(`first wait for a condition with a message exists`) 3238 cond := operatorsv1alpha1.InstallPlanCondition{Type: operatorsv1alpha1.InstallPlanInstalled, Status: corev1.ConditionFalse, Reason: operatorsv1alpha1.InstallPlanReasonInstallCheckFailed, 3239 Message: "no operator group found that is managing this namespace"} 3240 3241 Eventually(func() bool { 3242 fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseInstalling)) 3243 if err != nil || fetchedInstallPlan == nil { 3244 return false 3245 } 3246 if fetchedInstallPlan.Status.Phase != operatorsv1alpha1.InstallPlanPhaseInstalling { 3247 return false 3248 } 3249 return hasCondition(fetchedInstallPlan, cond) 3250 }, 5*time.Minute, interval).Should(BeTrue()) 3251 3252 By(`Create an operatorgroup for the same namespace`) 3253 og := &operatorsv1.OperatorGroup{ 3254 ObjectMeta: metav1.ObjectMeta{ 3255 Name: "og", 3256 Namespace: generatedNamespace.GetName(), 3257 }, 3258 Spec: operatorsv1.OperatorGroupSpec{ 3259 TargetNamespaces: []string{generatedNamespace.GetName()}, 3260 }, 3261 } 3262 Eventually(func() error { 3263 return ctx.Ctx().Client().Create(context.Background(), og) 3264 }, timeout, interval).Should(Succeed(), "could not create OperatorGroup") 3265 3266 By(`Wait for the OperatorGroup to be synced`) 3267 Eventually( 3268 func() ([]string, error) { 3269 err := ctx.Ctx().Client().Get(context.Background(), client.ObjectKeyFromObject(og), og) 3270 ctx.Ctx().Logf("Waiting for OperatorGroup(%v) to be synced with status.namespaces: %v", og.Name, og.Status.Namespaces) 3271 return og.Status.Namespaces, err 3272 }, 3273 1*time.Minute, 3274 interval, 3275 ).Should(ContainElement(generatedNamespace.GetName())) 3276 3277 By(`check that the condition has been cleared up`) 3278 Eventually(func() (bool, error) { 3279 fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseInstalling)) 3280 if err != nil { 3281 return false, err 3282 } 3283 if fetchedInstallPlan == nil { 3284 return false, err 3285 } 3286 if hasCondition(fetchedInstallPlan, cond) { 3287 return false, nil 3288 } 3289 return true, nil 3290 }).Should(BeTrue()) 3291 }) 3292 }) 3293 3294 It("compresses installplan step resource manifests to configmap references", func() { 3295 By(`Test ensures that all steps for index-based catalogs are references to configmaps. This avoids the problem`) 3296 By(`of installplans growing beyond the etcd size limit when manifests are written to the ip status.`) 3297 catsrc := &operatorsv1alpha1.CatalogSource{ 3298 ObjectMeta: metav1.ObjectMeta{ 3299 Name: genName("kiali-"), 3300 Namespace: generatedNamespace.GetName(), 3301 Labels: map[string]string{"olm.catalogSource": "kaili-catalog"}, 3302 }, 3303 Spec: operatorsv1alpha1.CatalogSourceSpec{ 3304 Image: "quay.io/operator-framework/ci-index:latest", 3305 SourceType: operatorsv1alpha1.SourceTypeGrpc, 3306 GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{ 3307 SecurityContextConfig: operatorsv1alpha1.Restricted, 3308 }, 3309 }, 3310 } 3311 catsrc, err := crc.OperatorsV1alpha1().CatalogSources(catsrc.GetNamespace()).Create(context.Background(), catsrc, metav1.CreateOptions{}) 3312 Expect(err).ToNot(HaveOccurred()) 3313 3314 By(`Wait for the CatalogSource to be ready`) 3315 catsrc, err = fetchCatalogSourceOnStatus(crc, catsrc.GetName(), catsrc.GetNamespace(), catalogSourceRegistryPodSynced()) 3316 Expect(err).ToNot(HaveOccurred()) 3317 3318 By(`Generate a Subscription`) 3319 subName := genName("kiali-") 3320 cleanUpSubscriptionFn := createSubscriptionForCatalog(crc, catsrc.GetNamespace(), subName, catsrc.GetName(), "kiali", stableChannel, "", operatorsv1alpha1.ApprovalAutomatic) 3321 defer cleanUpSubscriptionFn() 3322 3323 sub, err := fetchSubscription(crc, catsrc.GetNamespace(), subName, subscriptionHasInstallPlanChecker()) 3324 Expect(err).ToNot(HaveOccurred()) 3325 3326 By(`Wait for the expected InstallPlan's execution to either fail or succeed`) 3327 ipName := sub.Status.InstallPlanRef.Name 3328 ip, err := waitForInstallPlan(crc, ipName, sub.GetNamespace(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed, operatorsv1alpha1.InstallPlanPhaseComplete)) 3329 Expect(err).ToNot(HaveOccurred()) 3330 Expect(operatorsv1alpha1.InstallPlanPhaseComplete).To(Equal(ip.Status.Phase), "InstallPlan not complete") 3331 3332 By(`Ensure the InstallPlan contains the steps resolved from the bundle image`) 3333 operatorName := "kiali-operator" 3334 expectedSteps := map[registry.ResourceKey]struct{}{ 3335 {Name: operatorName, Kind: "ClusterServiceVersion"}: {}, 3336 {Name: "kialis.kiali.io", Kind: "CustomResourceDefinition"}: {}, 3337 {Name: "monitoringdashboards.monitoring.kiali.io", Kind: "CustomResourceDefinition"}: {}, 3338 {Name: operatorName, Kind: "ServiceAccount"}: {}, 3339 {Name: operatorName, Kind: "ClusterRole"}: {}, 3340 {Name: operatorName, Kind: "ClusterRoleBinding"}: {}, 3341 } 3342 Expect(ip.Status.Plan).To(HaveLen(len(expectedSteps)), "number of expected steps does not match installed: %v", ip.Status.Plan) 3343 3344 for _, step := range ip.Status.Plan { 3345 key := registry.ResourceKey{ 3346 Name: step.Resource.Name, 3347 Kind: step.Resource.Kind, 3348 } 3349 for expected := range expectedSteps { 3350 if strings.HasPrefix(key.Name, expected.Name) && key.Kind == expected.Kind { 3351 delete(expectedSteps, expected) 3352 } 3353 } 3354 } 3355 Expect(expectedSteps).To(HaveLen(0), "Actual resource steps do not match expected: %#v", expectedSteps) 3356 3357 By(`Ensure that all the steps have a configmap based reference`) 3358 for _, step := range ip.Status.Plan { 3359 manifest := step.Resource.Manifest 3360 var ref catalog.UnpackedBundleReference 3361 err := json.Unmarshal([]byte(manifest), &ref) 3362 Expect(err).ToNot(HaveOccurred()) 3363 Expect(ref.Kind).To(Equal("ConfigMap")) 3364 } 3365 }) 3366 3367 It("limits installed resources if the scoped serviceaccount has no permissions", func() { 3368 By("creating a scoped serviceaccount specified in the operatorgroup") 3369 By(`create SA`) 3370 sa := &corev1.ServiceAccount{ 3371 ObjectMeta: metav1.ObjectMeta{ 3372 Name: genName("sa-"), 3373 Namespace: generatedNamespace.GetName(), 3374 }, 3375 } 3376 _, err := c.KubernetesInterface().CoreV1().ServiceAccounts(generatedNamespace.GetName()).Create(context.Background(), sa, metav1.CreateOptions{}) 3377 Expect(err).ToNot(HaveOccurred()) 3378 By(`Create token secret for the serviceaccount`) 3379 _, cleanupSE := newTokenSecret(c, generatedNamespace.GetName(), sa.GetName()) 3380 defer cleanupSE() 3381 3382 By(`role has no explicit permissions`) 3383 role := &rbacv1.ClusterRole{ 3384 ObjectMeta: metav1.ObjectMeta{ 3385 Name: genName("role-"), 3386 }, 3387 Rules: []rbacv1.PolicyRule{}, 3388 } 3389 3390 By(`bind role to SA`) 3391 rb := &rbacv1.ClusterRoleBinding{ 3392 ObjectMeta: metav1.ObjectMeta{ 3393 Name: genName("rb-"), 3394 }, 3395 RoleRef: rbacv1.RoleRef{ 3396 Name: role.GetName(), 3397 Kind: "ClusterRole", 3398 APIGroup: "rbac.authorization.k8s.io", 3399 }, 3400 Subjects: []rbacv1.Subject{ 3401 { 3402 Kind: "ServiceAccount", 3403 Name: sa.GetName(), 3404 APIGroup: "", 3405 Namespace: sa.GetNamespace(), 3406 }, 3407 }, 3408 } 3409 3410 _, err = c.KubernetesInterface().RbacV1().ClusterRoleBindings().Create(context.Background(), rb, metav1.CreateOptions{}) 3411 Expect(err).ToNot(HaveOccurred()) 3412 defer c.KubernetesInterface().RbacV1().ClusterRoles().Delete(context.Background(), role.GetName(), metav1.DeleteOptions{}) 3413 3414 By(`Update the existing OG to use the ServiceAccount`) 3415 Eventually(func() error { 3416 existingOG, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Get(context.Background(), fmt.Sprintf("%s-operatorgroup", generatedNamespace.GetName()), metav1.GetOptions{}) 3417 if err != nil { 3418 return err 3419 } 3420 existingOG.Spec.ServiceAccountName = sa.GetName() 3421 _, err = crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Update(context.Background(), existingOG, metav1.UpdateOptions{}) 3422 return err 3423 }).Should(Succeed()) 3424 3425 By(`Wait for the OperatorGroup to be synced and have a status.ServiceAccountRef`) 3426 By(`before moving on. Otherwise the catalog operator treats it as an invalid OperatorGroup`) 3427 By(`and the InstallPlan is resynced`) 3428 Eventually(func() (*corev1.ObjectReference, error) { 3429 outOG, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Get(context.Background(), fmt.Sprintf("%s-operatorgroup", generatedNamespace.GetName()), metav1.GetOptions{}) 3430 if err != nil { 3431 return nil, err 3432 } 3433 ctx.Ctx().Logf("[DEBUG] Operator Group Status: %+v\n", outOG.Status) 3434 return outOG.Status.ServiceAccountRef, nil 3435 }).ShouldNot(BeNil()) 3436 3437 crd := apiextensionsv1.CustomResourceDefinition{ 3438 ObjectMeta: metav1.ObjectMeta{ 3439 Name: "ins" + ".cluster.com", 3440 }, 3441 TypeMeta: metav1.TypeMeta{ 3442 Kind: "CustomResourceDefinition", 3443 APIVersion: "v1", 3444 }, 3445 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 3446 Group: "cluster.com", 3447 Names: apiextensionsv1.CustomResourceDefinitionNames{ 3448 Plural: "ins", 3449 Singular: "ins", 3450 Kind: "ins", 3451 ListKind: "ins" + "list", 3452 }, 3453 Scope: apiextensionsv1.NamespaceScoped, 3454 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 3455 { 3456 Name: "v1alpha1", 3457 Served: true, 3458 Storage: true, 3459 Schema: &apiextensionsv1.CustomResourceValidation{ 3460 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 3461 Type: "object", 3462 Description: "my crd schema", 3463 }, 3464 }, 3465 }, 3466 }, 3467 }, 3468 } 3469 3470 By(`Defer CRD clean up`) 3471 defer func() { 3472 Eventually(func() error { 3473 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{})) 3474 }).Should(Succeed()) 3475 }() 3476 3477 scheme := runtime.NewScheme() 3478 Expect(apiextensionsv1.AddToScheme(scheme)).To(Succeed()) 3479 var crdManifest bytes.Buffer 3480 Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&crd, &crdManifest)).To(Succeed()) 3481 By("using the OLM client to create the CRD") 3482 plan := &operatorsv1alpha1.InstallPlan{ 3483 ObjectMeta: metav1.ObjectMeta{ 3484 Namespace: generatedNamespace.GetName(), 3485 Name: genName("ip-"), 3486 }, 3487 Spec: operatorsv1alpha1.InstallPlanSpec{ 3488 Approval: operatorsv1alpha1.ApprovalAutomatic, 3489 Approved: true, 3490 ClusterServiceVersionNames: []string{}, 3491 }, 3492 } 3493 3494 Expect(ctx.Ctx().Client().Create(context.Background(), plan)).To(Succeed()) 3495 plan.Status = operatorsv1alpha1.InstallPlanStatus{ 3496 AttenuatedServiceAccountRef: &corev1.ObjectReference{ 3497 Name: sa.GetName(), 3498 Namespace: sa.GetNamespace(), 3499 Kind: "ServiceAccount", 3500 }, 3501 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 3502 CatalogSources: []string{}, 3503 Plan: []*operatorsv1alpha1.Step{ 3504 { 3505 Status: operatorsv1alpha1.StepStatusUnknown, 3506 Resource: operatorsv1alpha1.StepResource{ 3507 Name: crd.GetName(), 3508 Version: "v1", 3509 Kind: "CustomResourceDefinition", 3510 Manifest: crdManifest.String(), 3511 }, 3512 }, 3513 }, 3514 } 3515 Expect(ctx.Ctx().Client().Status().Update(context.Background(), plan)).To(Succeed()) 3516 3517 key := client.ObjectKeyFromObject(plan) 3518 3519 Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { 3520 return plan, ctx.Ctx().Client().Get(context.Background(), key, plan) 3521 }).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete)) 3522 3523 By(`delete installplan, then create one with an additional resource that the SA does not have permissions to create`) 3524 By(`expect installplan to fail`) 3525 By("failing to install resources that are not explicitly allowed in the SA") 3526 err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.Background(), plan.GetName(), metav1.DeleteOptions{}) 3527 Expect(err).ToNot(HaveOccurred()) 3528 3529 service := &corev1.Service{ 3530 TypeMeta: metav1.TypeMeta{ 3531 Kind: "Service", 3532 APIVersion: "v1", 3533 }, 3534 ObjectMeta: metav1.ObjectMeta{ 3535 Namespace: generatedNamespace.GetName(), 3536 Name: "test-service", 3537 }, 3538 Spec: corev1.ServiceSpec{ 3539 Type: corev1.ServiceTypeClusterIP, 3540 Ports: []corev1.ServicePort{ 3541 { 3542 Port: 12345, 3543 }, 3544 }, 3545 }, 3546 } 3547 3548 Expect(corev1.AddToScheme(scheme)).To(Succeed()) 3549 var manifest bytes.Buffer 3550 Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(service, &manifest)).To(Succeed()) 3551 3552 newPlan := &operatorsv1alpha1.InstallPlan{ 3553 ObjectMeta: metav1.ObjectMeta{ 3554 Namespace: generatedNamespace.GetName(), 3555 Name: genName("ip-"), 3556 }, 3557 Spec: operatorsv1alpha1.InstallPlanSpec{ 3558 Approval: operatorsv1alpha1.ApprovalAutomatic, 3559 Approved: true, 3560 ClusterServiceVersionNames: []string{}, 3561 }, 3562 } 3563 3564 Expect(ctx.Ctx().Client().Create(context.Background(), newPlan)).To(Succeed()) 3565 newPlan.Status = operatorsv1alpha1.InstallPlanStatus{ 3566 StartTime: &metav1.Time{Time: time.Unix(0, 0)}, // disable retries 3567 AttenuatedServiceAccountRef: &corev1.ObjectReference{ 3568 Name: sa.GetName(), 3569 Namespace: sa.GetNamespace(), 3570 Kind: "ServiceAccount", 3571 }, 3572 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 3573 CatalogSources: []string{}, 3574 Plan: []*operatorsv1alpha1.Step{ 3575 { 3576 Status: operatorsv1alpha1.StepStatusUnknown, 3577 Resource: operatorsv1alpha1.StepResource{ 3578 Name: service.Name, 3579 Version: "v1", 3580 Kind: "Service", 3581 Manifest: manifest.String(), 3582 }, 3583 }, 3584 }, 3585 } 3586 Expect(ctx.Ctx().Client().Status().Update(context.Background(), newPlan)).To(Succeed()) 3587 3588 ipPhaseCheckerFunc := buildInstallPlanMessageCheckFunc(`cannot create resource "services" in API group`) 3589 _, err = fetchInstallPlanWithNamespace(GinkgoT(), crc, newPlan.Name, newPlan.Namespace, ipPhaseCheckerFunc) 3590 require.NoError(GinkgoT(), err) 3591 3592 Expect(client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &crd))).To(Succeed()) 3593 }) 3594 3595 It("uses the correct client when installing resources from an installplan", func() { 3596 By("creating a scoped serviceaccount specifified in the operatorgroup") 3597 By(`create SA`) 3598 sa := &corev1.ServiceAccount{ 3599 ObjectMeta: metav1.ObjectMeta{ 3600 Name: genName("sa-"), 3601 Namespace: generatedNamespace.GetName(), 3602 }, 3603 } 3604 _, err := c.KubernetesInterface().CoreV1().ServiceAccounts(generatedNamespace.GetName()).Create(context.Background(), sa, metav1.CreateOptions{}) 3605 Expect(err).ToNot(HaveOccurred()) 3606 By(`Create token secret for the serviceaccount`) 3607 _, cleanupSE := newTokenSecret(c, generatedNamespace.GetName(), sa.GetName()) 3608 defer cleanupSE() 3609 3610 By(`see https://github.com/operator-framework/operator-lifecycle-manager/blob/master/doc/design/scoped-operator-install.md`) 3611 By(`create permissions with the ability to get and list CRDs, but not create CRDs`) 3612 role := &rbacv1.ClusterRole{ 3613 ObjectMeta: metav1.ObjectMeta{ 3614 Name: genName("role-"), 3615 }, 3616 Rules: []rbacv1.PolicyRule{ 3617 { 3618 APIGroups: []string{"operators.coreos.com"}, 3619 Resources: []string{"subscriptions", "clusterserviceversions"}, 3620 Verbs: []string{"get", "create", "update", "patch"}, 3621 }, 3622 { 3623 APIGroups: []string{""}, 3624 Resources: []string{"services", "serviceaccounts", "configmaps", "endpoints", "events", "persistentvolumeclaims", "pods"}, 3625 Verbs: []string{"create", "delete", "get", "list", "update", "patch", "watch"}, 3626 }, 3627 { 3628 APIGroups: []string{"apps"}, 3629 Resources: []string{"deployments", "replicasets", "statefulsets"}, 3630 Verbs: []string{"list", "watch", "get", "create", "update", "patch", "delete"}, 3631 }, 3632 { 3633 APIGroups: []string{"apiextensions.k8s.io"}, 3634 Resources: []string{"customresourcedefinitions"}, 3635 Verbs: []string{"get", "list", "watch"}, 3636 }, 3637 }, 3638 } 3639 3640 _, err = c.KubernetesInterface().RbacV1().ClusterRoles().Create(context.Background(), role, metav1.CreateOptions{}) 3641 Expect(err).ToNot(HaveOccurred()) 3642 3643 By(`bind role to SA`) 3644 rb := &rbacv1.ClusterRoleBinding{ 3645 ObjectMeta: metav1.ObjectMeta{ 3646 Name: genName("rb-"), 3647 }, 3648 RoleRef: rbacv1.RoleRef{ 3649 Name: role.GetName(), 3650 Kind: "ClusterRole", 3651 APIGroup: "rbac.authorization.k8s.io", 3652 }, 3653 Subjects: []rbacv1.Subject{ 3654 { 3655 Kind: "ServiceAccount", 3656 Name: sa.GetName(), 3657 APIGroup: "", 3658 Namespace: sa.GetNamespace(), 3659 }, 3660 }, 3661 } 3662 3663 _, err = c.KubernetesInterface().RbacV1().ClusterRoleBindings().Create(context.Background(), rb, metav1.CreateOptions{}) 3664 Expect(err).ToNot(HaveOccurred()) 3665 defer c.KubernetesInterface().RbacV1().ClusterRoles().Delete(context.Background(), role.GetName(), metav1.DeleteOptions{}) 3666 3667 By(`Update the existing OG to use the ServiceAccount`) 3668 Eventually(func() error { 3669 existingOG, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Get(context.Background(), fmt.Sprintf("%s-operatorgroup", generatedNamespace.GetName()), metav1.GetOptions{}) 3670 if err != nil { 3671 return err 3672 } 3673 existingOG.Spec.ServiceAccountName = sa.GetName() 3674 _, err = crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Update(context.Background(), existingOG, metav1.UpdateOptions{}) 3675 return err 3676 }).Should(Succeed()) 3677 3678 By(`Wait for the OperatorGroup to be synced and have a status.ServiceAccountRef`) 3679 By(`before moving on. Otherwise the catalog operator treats it as an invalid OperatorGroup`) 3680 By(`and the InstallPlan is resynced`) 3681 Eventually(func() (*corev1.ObjectReference, error) { 3682 outOG, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Get(context.Background(), fmt.Sprintf("%s-operatorgroup", generatedNamespace.GetName()), metav1.GetOptions{}) 3683 if err != nil { 3684 return nil, err 3685 } 3686 ctx.Ctx().Logf("[DEBUG] Operator Group Status: %+v\n", outOG.Status) 3687 return outOG.Status.ServiceAccountRef, nil 3688 }).ShouldNot(BeNil()) 3689 3690 By("using the OLM client to install CRDs from the installplan and the scoped client for other resources") 3691 3692 crd := apiextensionsv1.CustomResourceDefinition{ 3693 ObjectMeta: metav1.ObjectMeta{ 3694 Name: "ins" + ".cluster.com", 3695 }, 3696 TypeMeta: metav1.TypeMeta{ 3697 Kind: "CustomResourceDefinition", 3698 APIVersion: "v1", 3699 }, 3700 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 3701 Group: "cluster.com", 3702 Names: apiextensionsv1.CustomResourceDefinitionNames{ 3703 Plural: "ins", 3704 Singular: "ins", 3705 Kind: "ins", 3706 ListKind: "ins" + "list", 3707 }, 3708 Scope: apiextensionsv1.NamespaceScoped, 3709 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 3710 { 3711 Name: "v1alpha1", 3712 Served: true, 3713 Storage: true, 3714 Schema: &apiextensionsv1.CustomResourceValidation{ 3715 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 3716 Type: "object", 3717 Description: "my crd schema", 3718 }, 3719 }, 3720 }, 3721 }, 3722 }, 3723 } 3724 csv := newCSV("stable", generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), nil, nil, nil) 3725 3726 By(`Defer CRD clean up`) 3727 defer func() { 3728 Eventually(func() error { 3729 return client.IgnoreNotFound(ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{})) 3730 }).Should(Succeed()) 3731 Eventually(func() error { 3732 return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &csv)) 3733 }).Should(Succeed()) 3734 }() 3735 3736 scheme := runtime.NewScheme() 3737 Expect(apiextensionsv1.AddToScheme(scheme)).To(Succeed()) 3738 Expect(operatorsv1alpha1.AddToScheme(scheme)).To(Succeed()) 3739 var crdManifest, csvManifest bytes.Buffer 3740 Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&crd, &crdManifest)).To(Succeed()) 3741 Expect(k8sjson.NewSerializer(k8sjson.DefaultMetaFactory, scheme, scheme, false).Encode(&csv, &csvManifest)).To(Succeed()) 3742 3743 plan := &operatorsv1alpha1.InstallPlan{ 3744 ObjectMeta: metav1.ObjectMeta{ 3745 Namespace: generatedNamespace.GetName(), 3746 Name: genName("ip-"), 3747 }, 3748 Spec: operatorsv1alpha1.InstallPlanSpec{ 3749 Approval: operatorsv1alpha1.ApprovalAutomatic, 3750 Approved: true, 3751 ClusterServiceVersionNames: []string{csv.GetName()}, 3752 }, 3753 } 3754 3755 Expect(ctx.Ctx().Client().Create(context.Background(), plan)).To(Succeed()) 3756 plan.Status = operatorsv1alpha1.InstallPlanStatus{ 3757 AttenuatedServiceAccountRef: &corev1.ObjectReference{ 3758 Name: sa.GetName(), 3759 Namespace: sa.GetNamespace(), 3760 Kind: "ServiceAccount", 3761 }, 3762 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 3763 CatalogSources: []string{}, 3764 Plan: []*operatorsv1alpha1.Step{ 3765 { 3766 Status: operatorsv1alpha1.StepStatusUnknown, 3767 Resource: operatorsv1alpha1.StepResource{ 3768 Name: csv.GetName(), 3769 Version: "v1alpha1", 3770 Kind: "ClusterServiceVersion", 3771 Manifest: csvManifest.String(), 3772 }, 3773 }, 3774 { 3775 Status: operatorsv1alpha1.StepStatusUnknown, 3776 Resource: operatorsv1alpha1.StepResource{ 3777 Name: crd.GetName(), 3778 Version: "v1", 3779 Kind: "CustomResourceDefinition", 3780 Manifest: crdManifest.String(), 3781 }, 3782 }, 3783 }, 3784 } 3785 Expect(ctx.Ctx().Client().Status().Update(context.Background(), plan)).To(Succeed()) 3786 3787 key := client.ObjectKeyFromObject(plan) 3788 3789 Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { 3790 return plan, ctx.Ctx().Client().Get(context.Background(), key, plan) 3791 }).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete)) 3792 3793 By(`delete installplan, and create one with just a CSV resource which should succeed`) 3794 By("installing additional resources that are allowed in the SA") 3795 err = crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Delete(context.Background(), plan.GetName(), metav1.DeleteOptions{}) 3796 Expect(err).ToNot(HaveOccurred()) 3797 3798 newPlan := &operatorsv1alpha1.InstallPlan{ 3799 ObjectMeta: metav1.ObjectMeta{ 3800 Namespace: generatedNamespace.GetName(), 3801 Name: genName("ip-"), 3802 }, 3803 Spec: operatorsv1alpha1.InstallPlanSpec{ 3804 Approval: operatorsv1alpha1.ApprovalAutomatic, 3805 Approved: true, 3806 ClusterServiceVersionNames: []string{csv.GetName()}, 3807 }, 3808 } 3809 3810 Expect(ctx.Ctx().Client().Create(context.Background(), newPlan)).To(Succeed()) 3811 newPlan.Status = operatorsv1alpha1.InstallPlanStatus{ 3812 AttenuatedServiceAccountRef: &corev1.ObjectReference{ 3813 Name: sa.GetName(), 3814 Namespace: sa.GetNamespace(), 3815 Kind: "ServiceAccount", 3816 }, 3817 Phase: operatorsv1alpha1.InstallPlanPhaseInstalling, 3818 CatalogSources: []string{}, 3819 Plan: []*operatorsv1alpha1.Step{ 3820 { 3821 Status: operatorsv1alpha1.StepStatusUnknown, 3822 Resource: operatorsv1alpha1.StepResource{ 3823 Name: csv.GetName(), 3824 Version: "v1alpha1", 3825 Kind: "ClusterServiceVersion", 3826 Manifest: csvManifest.String(), 3827 }, 3828 }, 3829 }, 3830 } 3831 Expect(ctx.Ctx().Client().Status().Update(context.Background(), newPlan)).To(Succeed()) 3832 3833 newKey := client.ObjectKeyFromObject(newPlan) 3834 3835 Eventually(func() (*operatorsv1alpha1.InstallPlan, error) { 3836 return newPlan, ctx.Ctx().Client().Get(context.Background(), newKey, newPlan) 3837 }).Should(HavePhase(operatorsv1alpha1.InstallPlanPhaseComplete)) 3838 }) 3839 }) 3840 3841 type checkInstallPlanFunc func(fip *operatorsv1alpha1.InstallPlan) bool 3842 3843 func validateCRDVersions(t GinkgoTInterface, c operatorclient.ClientInterface, name string, expectedVersions map[string]struct{}) { 3844 By(`Retrieve CRD information`) 3845 crd, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), name, metav1.GetOptions{}) 3846 require.NoError(t, err) 3847 3848 require.Equal(t, len(expectedVersions), len(crd.Spec.Versions), "number of CRD versions don't not match installed") 3849 3850 for _, version := range crd.Spec.Versions { 3851 _, ok := expectedVersions[version.Name] 3852 require.True(t, ok, "couldn't find %v in expected versions: %#v", version.Name, expectedVersions) 3853 3854 By(`Remove the entry from the expected steps set (to ensure no duplicates in resolved plan)`) 3855 delete(expectedVersions, version.Name) 3856 } 3857 3858 By(`Should have removed every matching version`) 3859 require.Equal(t, 0, len(expectedVersions), "Actual CRD versions do not match expected") 3860 } 3861 3862 func buildInstallPlanMessageCheckFunc(substring string) checkInstallPlanFunc { 3863 var lastMessage string 3864 lastTime := time.Now() 3865 return func(fip *operatorsv1alpha1.InstallPlan) bool { 3866 if fip.Status.Message != lastMessage { 3867 ctx.Ctx().Logf("waiting %s for installplan %s/%s to have message substring %q, have message %q", time.Since(lastTime), fip.Namespace, fip.Name, substring, fip.Status.Message) 3868 lastMessage = fip.Status.Message 3869 lastTime = time.Now() 3870 } 3871 return strings.Contains(fip.Status.Message, substring) 3872 } 3873 } 3874 3875 func buildInstallPlanPhaseCheckFunc(phases ...operatorsv1alpha1.InstallPlanPhase) checkInstallPlanFunc { 3876 var lastPhase operatorsv1alpha1.InstallPlanPhase 3877 lastTime := time.Now() 3878 return func(fip *operatorsv1alpha1.InstallPlan) bool { 3879 if fip.Status.Phase != lastPhase { 3880 ctx.Ctx().Logf("waiting %s for installplan %s/%s to be phases %v, in phase %s", time.Since(lastTime), fip.Namespace, fip.Name, phases, fip.Status.Phase) 3881 lastPhase = fip.Status.Phase 3882 lastTime = time.Now() 3883 } 3884 satisfiesAny := false 3885 for _, phase := range phases { 3886 satisfiesAny = satisfiesAny || fip.Status.Phase == phase 3887 } 3888 return satisfiesAny 3889 } 3890 } 3891 3892 func buildInstallPlanCleanupFunc(crc versioned.Interface, namespace string, installPlan *operatorsv1alpha1.InstallPlan) cleanupFunc { 3893 return func() { 3894 deleteOptions := &metav1.DeleteOptions{} 3895 for _, step := range installPlan.Status.Plan { 3896 if step.Resource.Kind == operatorsv1alpha1.ClusterServiceVersionKind { 3897 if err := crc.OperatorsV1alpha1().ClusterServiceVersions(namespace).Delete(context.Background(), step.Resource.Name, *deleteOptions); err != nil { 3898 fmt.Println(err) 3899 } 3900 } 3901 } 3902 3903 if err := crc.OperatorsV1alpha1().InstallPlans(namespace).Delete(context.Background(), installPlan.GetName(), *deleteOptions); err != nil { 3904 fmt.Println(err) 3905 } 3906 3907 err := waitForDelete(func() error { 3908 _, err := crc.OperatorsV1alpha1().InstallPlans(namespace).Get(context.Background(), installPlan.GetName(), metav1.GetOptions{}) 3909 return err 3910 }) 3911 3912 if err != nil { 3913 fmt.Println(err) 3914 } 3915 } 3916 } 3917 3918 func fetchInstallPlan(t GinkgoTInterface, c versioned.Interface, name string, namespace string, checkPhase checkInstallPlanFunc) (*operatorsv1alpha1.InstallPlan, error) { 3919 return fetchInstallPlanWithNamespace(t, c, name, namespace, checkPhase) 3920 } 3921 3922 func fetchInstallPlanWithNamespace(t GinkgoTInterface, c versioned.Interface, name string, namespace string, checkPhase checkInstallPlanFunc) (*operatorsv1alpha1.InstallPlan, error) { 3923 var fetchedInstallPlan *operatorsv1alpha1.InstallPlan 3924 var err error 3925 3926 err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { 3927 fetchedInstallPlan, err = c.OperatorsV1alpha1().InstallPlans(namespace).Get(context.Background(), name, metav1.GetOptions{}) 3928 if err != nil || fetchedInstallPlan == nil { 3929 return false, err 3930 } 3931 3932 return checkPhase(fetchedInstallPlan), nil 3933 }) 3934 return fetchedInstallPlan, err 3935 } 3936 3937 // do not return an error if the installplan has not been created yet 3938 func waitForInstallPlan(c versioned.Interface, name string, namespace string, checkPhase checkInstallPlanFunc) (*operatorsv1alpha1.InstallPlan, error) { 3939 var fetchedInstallPlan *operatorsv1alpha1.InstallPlan 3940 var err error 3941 3942 err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { 3943 fetchedInstallPlan, err = c.OperatorsV1alpha1().InstallPlans(namespace).Get(context.Background(), name, metav1.GetOptions{}) 3944 if err != nil && !apierrors.IsNotFound(err) { 3945 return false, err 3946 } 3947 3948 return checkPhase(fetchedInstallPlan), nil 3949 }) 3950 return fetchedInstallPlan, err 3951 } 3952 3953 func newNginxInstallStrategy(name string, permissions []operatorsv1alpha1.StrategyDeploymentPermissions, clusterPermissions []operatorsv1alpha1.StrategyDeploymentPermissions) operatorsv1alpha1.NamedInstallStrategy { 3954 By(`Create an nginx details deployment`) 3955 details := operatorsv1alpha1.StrategyDetailsDeployment{ 3956 DeploymentSpecs: []operatorsv1alpha1.StrategyDeploymentSpec{ 3957 { 3958 Name: name, 3959 Spec: appsv1.DeploymentSpec{ 3960 Selector: &metav1.LabelSelector{ 3961 MatchLabels: map[string]string{"app": "nginx"}, 3962 }, 3963 Replicas: &singleInstance, 3964 Template: corev1.PodTemplateSpec{ 3965 ObjectMeta: metav1.ObjectMeta{ 3966 Labels: map[string]string{"app": "nginx"}, 3967 }, 3968 Spec: corev1.PodSpec{Containers: []corev1.Container{ 3969 { 3970 Name: genName("nginx"), 3971 Image: *dummyImage, 3972 Ports: []corev1.ContainerPort{{ContainerPort: 80}}, 3973 ImagePullPolicy: corev1.PullIfNotPresent, 3974 }, 3975 }}, 3976 }, 3977 }, 3978 }, 3979 }, 3980 Permissions: permissions, 3981 ClusterPermissions: clusterPermissions, 3982 } 3983 namedStrategy := operatorsv1alpha1.NamedInstallStrategy{ 3984 StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, 3985 StrategySpec: details, 3986 } 3987 3988 return namedStrategy 3989 } 3990 3991 func newCRD(plural string) apiextensionsv1.CustomResourceDefinition { 3992 crd := apiextensionsv1.CustomResourceDefinition{ 3993 ObjectMeta: metav1.ObjectMeta{ 3994 Name: plural + ".cluster.com", 3995 }, 3996 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 3997 Group: "cluster.com", 3998 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 3999 { 4000 Name: "v1alpha1", 4001 Served: true, 4002 Storage: true, 4003 Schema: &apiextensionsv1.CustomResourceValidation{ 4004 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ 4005 Type: "object", 4006 Description: "my crd schema", 4007 }, 4008 }, 4009 }, 4010 }, 4011 Names: apiextensionsv1.CustomResourceDefinitionNames{ 4012 Plural: plural, 4013 Singular: plural, 4014 Kind: plural, 4015 ListKind: plural + "list", 4016 }, 4017 Scope: apiextensionsv1.NamespaceScoped, 4018 }, 4019 } 4020 4021 return crd 4022 } 4023 4024 func newCSV(name, namespace, replaces string, version semver.Version, owned []apiextensionsv1.CustomResourceDefinition, required []apiextensionsv1.CustomResourceDefinition, namedStrategy *operatorsv1alpha1.NamedInstallStrategy) operatorsv1alpha1.ClusterServiceVersion { 4025 csvType = metav1.TypeMeta{ 4026 Kind: operatorsv1alpha1.ClusterServiceVersionKind, 4027 APIVersion: operatorsv1alpha1.SchemeGroupVersion.String(), 4028 } 4029 4030 By(`set a simple default strategy if none given`) 4031 var strategy operatorsv1alpha1.NamedInstallStrategy 4032 if namedStrategy == nil { 4033 strategy = newNginxInstallStrategy(genName("dep"), nil, nil) 4034 } else { 4035 strategy = *namedStrategy 4036 } 4037 4038 csv := operatorsv1alpha1.ClusterServiceVersion{ 4039 TypeMeta: csvType, 4040 ObjectMeta: metav1.ObjectMeta{ 4041 Name: name, 4042 Namespace: namespace, 4043 }, 4044 Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ 4045 Replaces: replaces, 4046 Version: opver.OperatorVersion{Version: version}, 4047 MinKubeVersion: "0.0.0", 4048 InstallModes: []operatorsv1alpha1.InstallMode{ 4049 { 4050 Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, 4051 Supported: true, 4052 }, 4053 { 4054 Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, 4055 Supported: true, 4056 }, 4057 { 4058 Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, 4059 Supported: true, 4060 }, 4061 { 4062 Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, 4063 Supported: true, 4064 }, 4065 }, 4066 InstallStrategy: strategy, 4067 CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{ 4068 Owned: nil, 4069 Required: nil, 4070 }, 4071 }, 4072 } 4073 4074 By(`Populate owned and required`) 4075 for _, crd := range owned { 4076 crdVersion := "v1alpha1" 4077 for _, v := range crd.Spec.Versions { 4078 if v.Served && v.Storage { 4079 crdVersion = v.Name 4080 break 4081 } 4082 } 4083 desc := operatorsv1alpha1.CRDDescription{ 4084 Name: crd.GetName(), 4085 Version: crdVersion, 4086 Kind: crd.Spec.Names.Plural, 4087 DisplayName: crd.GetName(), 4088 Description: crd.GetName(), 4089 } 4090 csv.Spec.CustomResourceDefinitions.Owned = append(csv.Spec.CustomResourceDefinitions.Owned, desc) 4091 } 4092 4093 for _, crd := range required { 4094 crdVersion := "v1alpha1" 4095 for _, v := range crd.Spec.Versions { 4096 if v.Served && v.Storage { 4097 crdVersion = v.Name 4098 break 4099 } 4100 } 4101 desc := operatorsv1alpha1.CRDDescription{ 4102 Name: crd.GetName(), 4103 Version: crdVersion, 4104 Kind: crd.Spec.Names.Plural, 4105 DisplayName: crd.GetName(), 4106 Description: crd.GetName(), 4107 } 4108 csv.Spec.CustomResourceDefinitions.Required = append(csv.Spec.CustomResourceDefinitions.Required, desc) 4109 } 4110 4111 return csv 4112 } 4113 4114 func newInstallPlanWithDummySteps(name, namespace string, phase operatorsv1alpha1.InstallPlanPhase) *operatorsv1alpha1.InstallPlan { 4115 return &operatorsv1alpha1.InstallPlan{ 4116 ObjectMeta: metav1.ObjectMeta{ 4117 Name: name, 4118 Namespace: namespace, 4119 }, 4120 Spec: operatorsv1alpha1.InstallPlanSpec{ 4121 ClusterServiceVersionNames: []string{"foobar"}, 4122 Approval: operatorsv1alpha1.ApprovalAutomatic, 4123 Approved: true, 4124 }, 4125 Status: operatorsv1alpha1.InstallPlanStatus{ 4126 CatalogSources: []string{"catalog"}, 4127 Phase: phase, 4128 Plan: []*operatorsv1alpha1.Step{ 4129 { 4130 Resource: operatorsv1alpha1.StepResource{ 4131 CatalogSource: "catalog", 4132 CatalogSourceNamespace: namespace, 4133 Group: "", 4134 Version: "v1", 4135 Kind: "Foo", 4136 Name: "bar", 4137 }, 4138 Status: operatorsv1alpha1.StepStatusUnknown, 4139 }, 4140 }, 4141 }, 4142 } 4143 } 4144 4145 func hasCondition(ip *operatorsv1alpha1.InstallPlan, expectedCondition operatorsv1alpha1.InstallPlanCondition) bool { 4146 for _, cond := range ip.Status.Conditions { 4147 if cond.Type == expectedCondition.Type && cond.Message == expectedCondition.Message && cond.Status == expectedCondition.Status { 4148 return true 4149 } 4150 } 4151 return false 4152 }