diff --git a/azure/converters/vmss.go b/azure/converters/vmss.go index d86d755b1f1..6790ba1eef2 100644 --- a/azure/converters/vmss.go +++ b/azure/converters/vmss.go @@ -17,6 +17,8 @@ limitations under the License. package converters import ( + "regexp" + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute" "github.com/Azure/go-autorest/autorest/to" "k8s.io/utils/pointer" @@ -25,6 +27,13 @@ import ( "sigs.k8s.io/cluster-api-provider-azure/azure" ) +const ( + // RegExpStrCommunityGalleryID is a regexp string used for matching community gallery IDs and capturing specific values. + RegExpStrCommunityGalleryID = `/CommunityGalleries/(?P.*)/Images/(?P.*)/Versions/(?P.*)` + // RegExpStrComputeGalleryID is a regexp string used for matching compute gallery IDs and capturing specific values. + RegExpStrComputeGalleryID = `/subscriptions/(?P.*)/resourceGroups/(?P.*)/providers/Microsoft.Compute/galleries/(?P.*)/images/(?P.*)/versions/(?P.*)` +) + // SDKToVMSS converts an Azure SDK VirtualMachineScaleSet to the AzureMachinePool type. func SDKToVMSS(sdkvmss compute.VirtualMachineScaleSet, sdkinstances []compute.VirtualMachineScaleSetVM) *azure.VMSS { vmss := &azure.VMSS{ @@ -150,8 +159,53 @@ func SDKToVMSSVM(sdkInstance compute.VirtualMachineScaleSetVM) *azure.VMSSVM { // SDKImageToImage converts a SDK image reference to infrav1.Image. func SDKImageToImage(sdkImageRef *compute.ImageReference, isThirdPartyImage bool) infrav1.Image { + if sdkImageRef.ID != nil { + return IDImageRefToImage(*sdkImageRef.ID) + } + // community gallery image + if sdkImageRef.CommunityGalleryImageID != nil { + return cgImageRefToImage(*sdkImageRef.CommunityGalleryImageID) + } + // shared gallery image + if sdkImageRef.SharedGalleryImageID != nil { + return sgImageRefToImage(*sdkImageRef.SharedGalleryImageID) + } + // marketplace image + return mpImageRefToImage(sdkImageRef, isThirdPartyImage) +} + +// GetOrchestrationMode returns the compute.OrchestrationMode for the given infrav1.OrchestrationModeType. +func GetOrchestrationMode(modeType infrav1.OrchestrationModeType) compute.OrchestrationMode { + if modeType == infrav1.FlexibleOrchestrationMode { + return compute.OrchestrationModeFlexible + } + return compute.OrchestrationModeUniform +} + +// IDImageRefToImage converts an ID to a infrav1.Image with ComputerGallery set or ID, depending on the structure of the ID. +func IDImageRefToImage(id string) infrav1.Image { + // compute gallery image + if ok, params := getParams(RegExpStrComputeGalleryID, id); ok { + return infrav1.Image{ + ComputeGallery: &infrav1.AzureComputeGalleryImage{ + Gallery: params["gallery"], + Name: params["name"], + Version: params["version"], + SubscriptionID: pointer.String(params["subID"]), + ResourceGroup: pointer.String(params["rg"]), + }, + } + } + + // specific image + return infrav1.Image{ + ID: &id, + } +} + +// mpImageRefToImage converts a marketplace gallery ImageReference to an infrav1.Image. +func mpImageRefToImage(sdkImageRef *compute.ImageReference, isThirdPartyImage bool) infrav1.Image { return infrav1.Image{ - ID: sdkImageRef.ID, Marketplace: &infrav1.AzureMarketplaceImage{ ImagePlan: infrav1.ImagePlan{ Publisher: to.String(sdkImageRef.Publisher), @@ -164,10 +218,49 @@ func SDKImageToImage(sdkImageRef *compute.ImageReference, isThirdPartyImage bool } } -// GetOrchestrationMode returns the compute.OrchestrationMode for the given infrav1.OrchestrationModeType. -func GetOrchestrationMode(modeType infrav1.OrchestrationModeType) compute.OrchestrationMode { - if modeType == infrav1.FlexibleOrchestrationMode { - return compute.OrchestrationModeFlexible +// cgImageRefToImage converts a community gallery ImageReference to an infrav1.Image. +func cgImageRefToImage(id string) infrav1.Image { + if ok, params := getParams(RegExpStrCommunityGalleryID, id); ok { + return infrav1.Image{ + ComputeGallery: &infrav1.AzureComputeGalleryImage{ + Gallery: params["gallery"], + Name: params["name"], + Version: params["version"], + }, + } } - return compute.OrchestrationModeUniform + return infrav1.Image{} +} + +// sgImageRefToImage converts a shared gallery ImageReference to an infrav1.Image. +func sgImageRefToImage(id string) infrav1.Image { + if ok, params := getParams(RegExpStrComputeGalleryID, id); ok { + return infrav1.Image{ + SharedGallery: &infrav1.AzureSharedGalleryImage{ + SubscriptionID: params["subID"], + ResourceGroup: params["rg"], + Gallery: params["gallery"], + Name: params["name"], + Version: params["version"], + }, + } + } + return infrav1.Image{} +} + +func getParams(regStr, str string) (matched bool, params map[string]string) { + re := regexp.MustCompile(regStr) + match := re.FindAllStringSubmatch(str, -1) + + if len(match) == 1 { + params = make(map[string]string) + for i, name := range re.SubexpNames() { + if i > 0 && i <= len(match[0]) { + params[name] = match[0][i] + } + } + matched = true + } + + return matched, params } diff --git a/azure/converters/vmss_test.go b/azure/converters/vmss_test.go index 384363eb58f..59f24780fe7 100644 --- a/azure/converters/vmss_test.go +++ b/azure/converters/vmss_test.go @@ -181,8 +181,7 @@ func Test_SDKToVMSSVM(t *testing.T) { ID: "/subscriptions/foo/resourceGroups/my_resource_group/providers/bar", Name: "instance-000001", Image: infrav1.Image{ - ID: to.StringPtr("imageID"), - Marketplace: &infrav1.AzureMarketplaceImage{}, + ID: to.StringPtr("imageID"), }, State: "Creating", }, @@ -223,20 +222,18 @@ func Test_SDKImageToImage(t *testing.T) { Image infrav1.Image }{ { - Name: "minimal image", + Name: "id image", SDKImageRef: &compute.ImageReference{ ID: to.StringPtr("imageID"), }, IsThirdParty: false, Image: infrav1.Image{ - ID: to.StringPtr("imageID"), - Marketplace: &infrav1.AzureMarketplaceImage{}, + ID: to.StringPtr("imageID"), }, }, { Name: "marketplace image", SDKImageRef: &compute.ImageReference{ - ID: to.StringPtr("imageID"), Publisher: to.StringPtr("publisher"), Offer: to.StringPtr("offer"), Sku: to.StringPtr("sku"), @@ -244,7 +241,6 @@ func Test_SDKImageToImage(t *testing.T) { }, IsThirdParty: true, Image: infrav1.Image{ - ID: to.StringPtr("imageID"), Marketplace: &infrav1.AzureMarketplaceImage{ ImagePlan: infrav1.ImagePlan{ Publisher: "publisher", @@ -256,6 +252,65 @@ func Test_SDKImageToImage(t *testing.T) { }, }, }, + { + Name: "shared gallery image", + SDKImageRef: &compute.ImageReference{ + SharedGalleryImageID: to.StringPtr("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Compute/galleries/gallery/images/image/versions/version"), + }, + Image: infrav1.Image{ + SharedGallery: &infrav1.AzureSharedGalleryImage{ + SubscriptionID: "subscription", + ResourceGroup: "rg", + Gallery: "gallery", + Name: "image", + Version: "version", + }, + }, + }, + { + Name: "community gallery image", + SDKImageRef: &compute.ImageReference{ + CommunityGalleryImageID: to.StringPtr("/CommunityGalleries/gallery/Images/image/Versions/version"), + }, + Image: infrav1.Image{ + ComputeGallery: &infrav1.AzureComputeGalleryImage{ + Gallery: "gallery", + Name: "image", + Version: "version", + }, + }, + }, + { + Name: "compute gallery image", + SDKImageRef: &compute.ImageReference{ + ID: to.StringPtr("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Compute/galleries/gallery/images/image/versions/version"), + }, + Image: infrav1.Image{ + ComputeGallery: &infrav1.AzureComputeGalleryImage{ + Gallery: "gallery", + Name: "image", + Version: "version", + SubscriptionID: to.StringPtr("subscription"), + ResourceGroup: to.StringPtr("rg"), + }, + }, + }, + { + Name: "compute gallery image not formatted as expected", + SDKImageRef: &compute.ImageReference{ + ID: to.StringPtr("/compute/gallery/not/formatted/as/expected"), + }, + Image: infrav1.Image{ + ID: to.StringPtr("/compute/gallery/not/formatted/as/expected"), + }, + }, + { + Name: "community gallery image not formatted as expected", + SDKImageRef: &compute.ImageReference{ + CommunityGalleryImageID: to.StringPtr("/community/gallery/not/formatted/as/expected"), + }, + Image: infrav1.Image{}, + }, } for _, c := range cases { @@ -319,8 +374,7 @@ func Test_SDKVMToVMSSVM(t *testing.T) { Expected: &azure.VMSSVM{ ID: "vmID3", Image: infrav1.Image{ - ID: to.StringPtr("imageID"), - Marketplace: &infrav1.AzureMarketplaceImage{}, + ID: to.StringPtr("imageID"), }, Name: "vmwithstorage", State: "Creating", diff --git a/azure/scope/machinepoolmachine.go b/azure/scope/machinepoolmachine.go index 32ac21709ba..94ad7e7efeb 100644 --- a/azure/scope/machinepoolmachine.go +++ b/azure/scope/machinepoolmachine.go @@ -31,6 +31,7 @@ import ( "k8s.io/utils/pointer" infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" "sigs.k8s.io/cluster-api-provider-azure/azure" + "sigs.k8s.io/cluster-api-provider-azure/azure/converters" infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1" "sigs.k8s.io/cluster-api-provider-azure/util/futures" "sigs.k8s.io/cluster-api-provider-azure/util/tele" @@ -542,6 +543,16 @@ func (s *MachinePoolMachineScope) hasLatestModelApplied(ctx context.Context) (bo return false, errors.New("machinepoolscope image must not be nil") } + // check if image.ID is actually a compute gallery image + if s.instance.Image.ComputeGallery != nil && image.ID != nil { + newImage := converters.IDImageRefToImage(*image.ID) + + // this means the ID was a compute gallery image ID + if newImage.ComputeGallery != nil { + return reflect.DeepEqual(s.instance.Image, newImage), nil + } + } + // if the images match, then the VM is of the same model return reflect.DeepEqual(s.instance.Image, *image), nil }