@@ -23,6 +23,7 @@ import (
23
23
24
24
"github.com/go-logr/logr"
25
25
26
+ "github.com/IBM-Cloud/bluemix-go/crn"
26
27
"github.com/IBM/go-sdk-core/v5/core"
27
28
"github.com/IBM/platform-services-go-sdk/globaltaggingv1"
28
29
"github.com/IBM/platform-services-go-sdk/resourcecontrollerv2"
@@ -275,7 +276,7 @@ func (s *VPCClusterScope) GetResourceGroupID() (string, error) {
275
276
// If the Resource Group is not defined in Spec, we generate the name based on the cluster name.
276
277
resourceGroupName := s .IBMVPCCluster .Spec .ResourceGroup
277
278
if resourceGroupName == "" {
278
- resourceGroupName = s .IBMVPCCluster . Name
279
+ resourceGroupName = s .Name ()
279
280
}
280
281
281
282
// Retrieve the Resource Group based on the name.
@@ -367,6 +368,16 @@ func (s *VPCClusterScope) SetResourceStatus(resourceType infrav1beta2.ResourceTy
367
368
s .IBMVPCCluster .Status .Network .VPC = resource
368
369
}
369
370
s .NetworkStatus ().VPC .Set (* resource )
371
+ case infrav1beta2 .ResourceTypeCustomImage :
372
+ if s .IBMVPCCluster .Status .Image == nil {
373
+ s .IBMVPCCluster .Status .Image = & infrav1beta2.ResourceStatus {
374
+ ID : resource .ID ,
375
+ Name : resource .Name ,
376
+ Ready : resource .Ready ,
377
+ }
378
+ return
379
+ }
380
+ s .IBMVPCCluster .Status .Image .Set (* resource )
370
381
default :
371
382
s .V (3 ).Info ("unsupported resource type" , "resourceType" , resourceType )
372
383
}
@@ -486,9 +497,186 @@ func (s *VPCClusterScope) createVPC() (*vpcv1.VPC, error) {
486
497
} else if vpcDetails == nil {
487
498
return nil , fmt .Errorf ("no vpc details after creation" )
488
499
}
489
- if err = s .TagResource (s .IBMVPCCluster .Name , * vpcDetails .CRN ); err != nil {
500
+
501
+ // NOTE: This tagging is only attempted once. We may wish to refactor in case this single attempt fails.
502
+ if err = s .TagResource (s .Name (), * vpcDetails .CRN ); err != nil {
490
503
return nil , fmt .Errorf ("error tagging vpc: %w" , err )
491
504
}
492
505
493
506
return vpcDetails , nil
494
507
}
508
+
509
+ // ReconcileVPCCustomImage reconciles the VPC Custom Image.
510
+ func (s * VPCClusterScope ) ReconcileVPCCustomImage () (bool , error ) {
511
+ var imageID * string
512
+ // Attempt to collect VPC Custom Image info from Status.
513
+ if s .IBMVPCCluster .Status .Image != nil {
514
+ if s .IBMVPCCluster .Status .Image .ID != "" {
515
+ imageID = ptr .To (s .IBMVPCCluster .Status .Image .ID )
516
+ } else if s .IBMVPCCluster .Status .Image .Name != nil {
517
+ image , err := s .VPCClient .GetImageByName (* s .IBMVPCCluster .Status .Image .Name )
518
+ if err != nil {
519
+ return false , fmt .Errorf ("error checking vpc custom image by name: %w" , err )
520
+ }
521
+ // If the image was found via name, we should be able to get its ID.
522
+ if image != nil {
523
+ imageID = image .ID
524
+ }
525
+ }
526
+ } else if s .IBMVPCCluster .Spec .Image .CRN != nil {
527
+ // Parse the supplied Image CRN for Id, to perform image lookup.
528
+ imageCRN , err := crn .Parse (* s .IBMVPCCluster .Spec .Image .CRN )
529
+ if err != nil {
530
+ return false , fmt .Errorf ("error parsing vpc custom image crn: %w" , err )
531
+ }
532
+ if imageCRN .Resource == "" {
533
+ return false , fmt .Errorf ("error parsing vpc custom image crn, missing resource id" )
534
+ }
535
+ // If we didn't hit an error during parsing, and Resource was set, set that as the Image ID.
536
+ imageID = ptr .To (imageCRN .Resource )
537
+ }
538
+
539
+ // Check status of VPC Custom Image.
540
+ if imageID != nil {
541
+ image , _ , err := s .VPCClient .GetImage (& vpcv1.GetImageOptions {
542
+ ID : imageID ,
543
+ })
544
+ if err != nil {
545
+ return false , fmt .Errorf ("error retrieving vpc custom image by id: %w" , err )
546
+ }
547
+ if image == nil {
548
+ return false , fmt .Errorf ("error failed to retrieve vpc custom image with id %s" , * imageID )
549
+ }
550
+ s .V (3 ).Info ("Found VPC Custom Image with provided id" , "imageID" , imageID )
551
+
552
+ requeue := true
553
+ if image .Status != nil && * image .Status == string (vpcv1 .ImageStatusAvailableConst ) {
554
+ requeue = false
555
+ }
556
+ s .SetResourceStatus (infrav1beta2 .ResourceTypeCustomImage , & infrav1beta2.ResourceStatus {
557
+ ID : * imageID ,
558
+ Name : image .Name ,
559
+ // Ready status will be invert of the need to requeue.
560
+ Ready : ! requeue ,
561
+ })
562
+ return requeue , nil
563
+ }
564
+
565
+ // No VPC Custom Image exists or was found.
566
+ // So, check if the Image spec was defined, as it contains all the data necessary to reconcile.
567
+ if s .IBMVPCCluster .Spec .Image == nil {
568
+ // If no Image spec was defined, we expect it is maintained externally and continue without reconciling. For example, using a Catalog Offering Custom Image, which may be in another account, which means it cannot be looked up, but can be used when creating Instances.
569
+ s .V (3 ).Info ("No VPC Custom Image defined, skipping reconciliation" )
570
+ return false , nil
571
+ }
572
+
573
+ // Create Custom Image.
574
+ s .Info ("Creating a VPC Custom Image" )
575
+ image , err := s .createCustomImage ()
576
+ if err != nil {
577
+ return false , fmt .Errorf ("error failure trying to create vpc custom image: %w" , err )
578
+ } else if image == nil {
579
+ return false , fmt .Errorf ("error no vpc custom image creation results" )
580
+ }
581
+
582
+ s .Info ("Successfully created VPC Custom Image" )
583
+ s .SetResourceStatus (infrav1beta2 .ResourceTypeCustomImage , & infrav1beta2.ResourceStatus {
584
+ ID : * image .ID ,
585
+ Name : image .Name ,
586
+ // We must wait for the image to be ready, on followup reconciliation loops.
587
+ Ready : false ,
588
+ })
589
+ return true , nil
590
+ }
591
+
592
+ // createCustomImage will create a new VPC Custom Image.
593
+ func (s * VPCClusterScope ) createCustomImage () (* vpcv1.Image , error ) {
594
+ if s .IBMVPCCluster .Spec .Image == nil {
595
+ return nil , fmt .Errorf ("error failed to create vpc custom image, no image spec defined" )
596
+ }
597
+
598
+ // Collect the Resource Group ID.
599
+ var resourceGroupID * string
600
+ // Check Resource Group in Image spec.
601
+ if s .IBMVPCCluster .Spec .Image .ResourceGroup != nil {
602
+ if s .IBMVPCCluster .Spec .Image .ResourceGroup .ID != "" {
603
+ resourceGroupID = ptr .To (s .IBMVPCCluster .Spec .Image .ResourceGroup .ID )
604
+ } else if s .IBMVPCCluster .Spec .Image .ResourceGroup .Name != nil {
605
+ id , err := s .ResourceManagerClient .GetResourceGroupByName (* s .IBMVPCCluster .Spec .Image .ResourceGroup .Name )
606
+ if err != nil {
607
+ return nil , fmt .Errorf ("error retrieving resource group by name: %w" , err )
608
+ }
609
+ resourceGroupID = id .ID
610
+ }
611
+ } else {
612
+ // Otherwise, we will use the cluster Resource Group ID, as we expect to create all resources in that Resource Group.
613
+ id , err := s .GetResourceGroupID ()
614
+ if err != nil {
615
+ return nil , fmt .Errorf ("error retrieving resource group id: %w" , err )
616
+ }
617
+ resourceGroupID = ptr .To (id )
618
+ }
619
+
620
+ // We must have an OperatingSystem value supplied in order to create the Custom Image.
621
+ // NOTE(cjschaef): Perhaps we could try defaulting this value, so it isn't required for Custom Image creation.
622
+ if s .IBMVPCCluster .Spec .Image .OperatingSystem == nil {
623
+ return nil , fmt .Errorf ("error failed to create vpc custom image due to missing operatingSystem" )
624
+ }
625
+
626
+ // Build the COS Object URL using the ImageSpec
627
+ fileHRef , err := s .buildCOSObjectHRef ()
628
+ if err != nil {
629
+ return nil , fmt .Errorf ("error building vpc custom image file href: %w" , err )
630
+ } else if fileHRef == nil {
631
+ return nil , fmt .Errorf ("error failed to build vpc custom image file href" )
632
+ }
633
+
634
+ options := & vpcv1.CreateImageOptions {
635
+ ImagePrototype : & vpcv1.ImagePrototype {
636
+ Name : s .IBMVPCCluster .Spec .Image .Name ,
637
+ File : & vpcv1.ImageFilePrototype {
638
+ Href : fileHRef ,
639
+ },
640
+ OperatingSystem : & vpcv1.OperatingSystemIdentity {
641
+ Name : s .IBMVPCCluster .Spec .Image .OperatingSystem ,
642
+ },
643
+ ResourceGroup : & vpcv1.ResourceGroupIdentity {
644
+ ID : resourceGroupID ,
645
+ },
646
+ },
647
+ }
648
+
649
+ imageDetails , _ , err := s .VPCClient .CreateImage (options )
650
+ if err != nil {
651
+ return nil , fmt .Errorf ("error unknown failure creating vpc custom image: %w" , err )
652
+ }
653
+ if imageDetails == nil || imageDetails .ID == nil || imageDetails .Name == nil || imageDetails .CRN == nil {
654
+ return nil , fmt .Errorf ("error failed creating custom image" )
655
+ }
656
+
657
+ // NOTE: This tagging is only attempted once. We may wish to refactor in case this single attempt fails.
658
+ if err := s .TagResource (s .Name (), * imageDetails .CRN ); err != nil {
659
+ return nil , fmt .Errorf ("error failure tagging vpc custom image: %w" , err )
660
+ }
661
+ return imageDetails , nil
662
+ }
663
+
664
+ // buildCOSObjectHRef will build the HRef path to a COS Object that can be used for VPC Custom Image creation.
665
+ func (s * VPCClusterScope ) buildCOSObjectHRef () (* string , error ) {
666
+ // We need COS details in order to create the Custom Image from.
667
+ if s .IBMVPCCluster .Spec .Image .COSInstance == nil || s .IBMVPCCluster .Spec .Image .COSBucket == nil || s .IBMVPCCluster .Spec .Image .COSObject == nil {
668
+ return nil , fmt .Errorf ("error failed to build cos object href, cos details missing" )
669
+ }
670
+
671
+ // Get COS Bucket Region, defaulting to cluster Region if not specified.
672
+ bucketRegion := s .IBMVPCCluster .Spec .Region
673
+ if s .IBMVPCCluster .Spec .Image .COSBucketRegion != nil {
674
+ bucketRegion = * s .IBMVPCCluster .Spec .Image .COSBucketRegion
675
+ }
676
+
677
+ // Expected HRef format:
678
+ // cos://<bucket_region>/<bucket_name>/<object_name>
679
+ href := fmt .Sprintf ("cos://%s/%s/%s" , bucketRegion , * s .IBMVPCCluster .Spec .Image .COSBucket , * s .IBMVPCCluster .Spec .Image .COSObject )
680
+ s .V (3 ).Info ("building image ref" , "href" , href )
681
+ return ptr .To (href ), nil
682
+ }
0 commit comments