diff --git a/docs/resources/machine_catalog.md b/docs/resources/machine_catalog.md index 05ccc6e..4caee90 100644 --- a/docs/resources/machine_catalog.md +++ b/docs/resources/machine_catalog.md @@ -675,7 +675,9 @@ Optional: - `machine_profile` (Attributes) The name of the virtual machine or template spec that will be used to identify the default value for the tags, virtual machine size, boot diagnostics, host cache property of OS disk, accelerated networking and availability zone.
Required when provisioning_type is set to PVSStreaming or when identity_type is set to `AzureAD` (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config--machine_profile)) - `master_image_note` (String) The note for the master image. - `prepared_image` (Attributes) Specifying the prepared master image to be used for machine catalog. (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config--prepared_image)) -- `use_azure_compute_gallery` (Attributes) Use this to place prepared image in Azure Compute Gallery. Required when `storage_type = Azure_Ephemeral_OS_Disk`. (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config--use_azure_compute_gallery)) +- `use_azure_compute_gallery` (Attributes) Use this to place prepared image in Azure Compute Gallery. Required when `storage_type = Azure_Ephemeral_OS_Disk`. + +~> **Please Note** `use_azure_compute_gallery` cannot be specified when the prepared image is using a shared image gallery. The machine catalog will inherit the azure compute gallery settings of the prepared image. (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config--use_azure_compute_gallery)) - `use_managed_disks` (Boolean) Indicate whether to use Azure managed disks for the provisioned virtual machine. - `vda_resource_group` (String) Designated resource group where the VDA VMs will be located on Azure. - `writeback_cache` (Attributes) Write-back Cache config. Leave this empty to disable Write-back Cache. Write-back Cache requires Machine image with Write-back Cache plugin installed. (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config--writeback_cache)) diff --git a/internal/daas/image_definition/image_version_resource.go b/internal/daas/image_definition/image_version_resource.go index 42f1270..072e704 100644 --- a/internal/daas/image_definition/image_version_resource.go +++ b/internal/daas/image_definition/image_version_resource.go @@ -100,7 +100,7 @@ func (r *ImageVersionResource) Create(ctx context.Context, req resource.CreateRe } if !plan.AzureImageSpecs.IsNull() { - azureImageSpecs := util.ObjectValueToTypedObject[AzureImageSpecsModel](ctx, &resp.Diagnostics, plan.AzureImageSpecs) + azureImageSpecs := util.ObjectValueToTypedObject[util.AzureImageSpecsModel](ctx, &resp.Diagnostics, plan.AzureImageSpecs) sharedSubscription := azureImageSpecs.SharedSubscription.ValueString() resourceGroup := azureImageSpecs.ResourceGroup.ValueString() masterImage := azureImageSpecs.MasterImage.ValueString() @@ -495,7 +495,7 @@ func (r *ImageVersionResource) ModifyPlan(ctx context.Context, req resource.Modi ) return } - azureImageSpecs := util.ObjectValueToTypedObject[AzureImageSpecsModel](ctx, &resp.Diagnostics, plan.AzureImageSpecs) + azureImageSpecs := util.ObjectValueToTypedObject[util.AzureImageSpecsModel](ctx, &resp.Diagnostics, plan.AzureImageSpecs) // Validate image version machine profile usage consistency within the image definition imageVersionsInDefinition, err := getImageVersions(ctx, &resp.Diagnostics, r.client, plan.ImageDefinition.ValueString()) if err != nil { diff --git a/internal/daas/image_definition/image_version_resource_model.go b/internal/daas/image_definition/image_version_resource_model.go index 983252e..a8815fe 100644 --- a/internal/daas/image_definition/image_version_resource_model.go +++ b/internal/daas/image_definition/image_version_resource_model.go @@ -12,11 +12,9 @@ import ( "github.com/citrix/terraform-provider-citrix/internal/util" "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" @@ -27,95 +25,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type AzureImageSpecsModel struct { - // Required Attributes - ServiceOffering types.String `tfsdk:"service_offering"` - LicenseType types.String `tfsdk:"license_type"` - StorageType types.String `tfsdk:"storage_type"` - - // Optional Attributes - MachineProfile types.Object `tfsdk:"machine_profile"` - DiskEncryptionSet types.Object `tfsdk:"disk_encryption_set"` - - // Master Image Attributes - ResourceGroup types.String `tfsdk:"resource_group"` - SharedSubscription types.String `tfsdk:"shared_subscription"` - MasterImage types.String `tfsdk:"master_image"` - GalleryImage types.Object `tfsdk:"gallery_image"` -} - -func (AzureImageSpecsModel) GetSchema() schema.SingleNestedAttribute { - galleryImageSchema := util.GalleryImageModel{}.GetSchema() - galleryImageSchema.Validators = []validator.Object{ - objectvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("resource_group"), - }...), - } - return schema.SingleNestedAttribute{ - Description: "Image configuration for Azure image version.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "service_offering": schema.StringAttribute{ - Description: "The Azure VM Sku to use when creating machines.", - Required: true, - }, - "license_type": schema.StringAttribute{ - Description: "Windows license type used to provision virtual machines in Azure at the base compute rate. License types include: `Windows_Client` and `Windows_Server`.", - Optional: true, - Validators: []validator.String{ - stringvalidator.OneOf( - util.WindowsClientLicenseType, - util.WindowsServerLicenseType, - ), - }, - }, - "storage_type": schema.StringAttribute{ - Description: "Storage account type used for provisioned virtual machine disks on Azure. Storage types include: `Standard_LRS`, `StandardSSD_LRS` and `Premium_LRS`.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - util.StandardLRS, - util.StandardSSDLRS, - util.Premium_LRS, - ), - }, - }, - "machine_profile": util.AzureMachineProfileModel{}.GetSchema(), - "disk_encryption_set": util.AzureDiskEncryptionSetModel{}.GetSchema(), - "resource_group": schema.StringAttribute{ - Description: "The Azure Resource Group where the managed disk / snapshot for creating machines is located.", - Required: true, - }, - "shared_subscription": schema.StringAttribute{ - Description: "The Azure Subscription ID where the managed disk / snapshot for creating machines is located. Only required if the image is not in the same subscription of the hypervisor.", - Optional: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - }, - "master_image": schema.StringAttribute{ - Description: "The name of the virtual machine snapshot or VM template that will be used. This identifies the hard disk to be used and the default values for the memory and processors. Omit this field if you want to use gallery_image.", - Optional: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("gallery_image")), - }, - }, - "gallery_image": galleryImageSchema, - }, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.RequiresReplace(), - }, - Validators: []validator.Object{ - objectvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("vsphere_image_specs")), - }, - } -} - -func (AzureImageSpecsModel) GetAttributes() map[string]schema.Attribute { - return AzureImageSpecsModel{}.GetSchema().Attributes -} - type VsphereImageSpecsModel struct { MasterImageVm types.String `tfsdk:"master_image_vm"` ImageSnapshot types.String `tfsdk:"image_snapshot"` @@ -273,7 +182,7 @@ func (ImageVersionModel) GetSchema() schema.Schema { Computed: true, Default: stringdefault.StaticString(""), }, - "azure_image_specs": AzureImageSpecsModel{}.GetSchema(), + "azure_image_specs": util.AzureImageSpecsModel{}.GetSchema(), "vsphere_image_specs": VsphereImageSpecsModel{}.GetSchema(), "session_support": schema.StringAttribute{ Description: "Session support for the image version.", @@ -320,7 +229,7 @@ func (r ImageVersionModel) RefreshPropertyValues(ctx context.Context, diagnostic switch imageContext.GetPluginFactoryName() { case util.AZURERM_FACTORY_NAME: - azureImageSpecs := util.ObjectValueToTypedObject[AzureImageSpecsModel](ctx, diagnostics, r.AzureImageSpecs) + azureImageSpecs := util.ObjectValueToTypedObject[util.AzureImageSpecsModel](ctx, diagnostics, r.AzureImageSpecs) azureImageSpecs.ServiceOffering = parseAzureImageVersionServiceOffering(imageScheme.GetServiceOffering()) @@ -337,7 +246,7 @@ func (r ImageVersionModel) RefreshPropertyValues(ctx context.Context, diagnostic azureImageSpecs.MachineProfile = updatedMachineProfile } - azureImageSpecs = ParseMasterImageToAzureImageModel(ctx, diagnostics, azureImageSpecs, masterImage) + azureImageSpecs = util.ParseMasterImageToAzureImageModel(ctx, diagnostics, azureImageSpecs, masterImage) r.AzureImageSpecs = util.TypedObjectToObjectValue(ctx, diagnostics, azureImageSpecs) case util.VMWARE_FACTORY_NAME: imageScheme := imageContext.GetImageScheme() @@ -373,33 +282,6 @@ func (r ImageVersionModel) RefreshPropertyValues(ctx context.Context, diagnostic return r } -func ParseMasterImageToAzureImageModel(ctx context.Context, diagnostics *diag.Diagnostics, azureImageSpecs AzureImageSpecsModel, masterImage citrixorchestration.HypervisorResourceRefResponseModel) AzureImageSpecsModel { - masterImageXdPath := masterImage.GetXDPath() - masterImageSegments := strings.Split(masterImageXdPath, "\\") - masterImageLastIndex := len(masterImageSegments) - masterImageResourceTag := strings.Split(masterImageSegments[masterImageLastIndex-1], ".") - masterImageResourceType := masterImageResourceTag[len(masterImageResourceTag)-1] - if strings.EqualFold(masterImageResourceType, util.ImageVersionResourceType) { - azureImageSpecs.GalleryImage, - azureImageSpecs.ResourceGroup, - azureImageSpecs.SharedSubscription = - util.ParseMasterImageToUpdateGalleryImageModel(ctx, diagnostics, azureImageSpecs.GalleryImage, masterImage, masterImageSegments, masterImageLastIndex) - - // Clear other master image details - azureImageSpecs.MasterImage = types.StringNull() - } else { - // Snapshot or Managed Disk - azureImageSpecs.MasterImage, - azureImageSpecs.ResourceGroup, - azureImageSpecs.SharedSubscription, - azureImageSpecs.GalleryImage, - _, - _ = - util.ParseMasterImageToUpdateAzureImageSpecs(ctx, diagnostics, masterImageResourceType, masterImage, masterImageSegments, masterImageLastIndex) - } - return azureImageSpecs -} - func parseVsphereImageXdPath(masterImageXdPath string) (string, string) { vmIndex := strings.Index(masterImageXdPath, ".vm") if vmIndex == -1 { diff --git a/internal/daas/machine_catalog/machine_catalog_common_utils.go b/internal/daas/machine_catalog/machine_catalog_common_utils.go index 467be57..02d6df2 100644 --- a/internal/daas/machine_catalog/machine_catalog_common_utils.go +++ b/internal/daas/machine_catalog/machine_catalog_common_utils.go @@ -499,22 +499,23 @@ func validateInUseMachineAccounts(ctx context.Context, diagnostics *diag.Diagnos return nil } -func validateImageVersion(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, plan MachineCatalogResourceModel, preparedImageConfig PreparedImageConfigModel, machineConfigAttributeName string) (citrixorchestration.ImageSchemeResponseModel, citrixorchestration.ImageVersionSpecResponseModel, error) { +func validateImageVersion(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, plan MachineCatalogResourceModel, preparedImageConfig PreparedImageConfigModel, machineConfigAttributeName string) (*citrixorchestration.ImageDefinitionResponseModel, citrixorchestration.ImageSchemeResponseModel, citrixorchestration.ImageVersionSpecResponseModel, error) { isPreparedImageSupported := util.CheckProductVersion(client, diagnostics, 121, 118, 7, 41, "Error using Prepared Image in citrix_machine_catalog resource", "Prepared Image") var imageScheme citrixorchestration.ImageSchemeResponseModel var imageSpecs citrixorchestration.ImageVersionSpecResponseModel + var imageDefinition *citrixorchestration.ImageDefinitionResponseModel if !isPreparedImageSupported { err := fmt.Errorf("Prepared Image is not supported in this version of Citrix Virtual Apps and Desktops service.") - return imageScheme, imageSpecs, err + return nil, imageScheme, imageSpecs, err } imageDefinition, err := image_definition.GetImageDefinition(ctx, client, diagnostics, preparedImageConfig.ImageDefinition.ValueString()) if err != nil { - return imageScheme, imageSpecs, err + return imageDefinition, imageScheme, imageSpecs, err } imageVersion, err := image_definition.GetImageVersion(ctx, client, diagnostics, imageDefinition.GetId(), preparedImageConfig.ImageVersion.ValueString()) if err != nil { - return imageScheme, imageSpecs, err + return imageDefinition, imageScheme, imageSpecs, err } imageVersionStatus := imageVersion.GetImageVersionStatus() @@ -524,7 +525,7 @@ func validateImageVersion(ctx context.Context, diagnostics *diag.Diagnostics, cl "Error validating azure_machine_config", err.Error(), ) - return imageScheme, imageSpecs, err + return imageDefinition, imageScheme, imageSpecs, err } imageContextConfigured := false @@ -549,11 +550,12 @@ func validateImageVersion(ctx context.Context, diagnostics *diag.Diagnostics, cl fmt.Sprintf("Error validating `%s`", machineConfigAttributeName), err.Error(), ) - return imageScheme, imageSpecs, err + return imageDefinition, imageScheme, imageSpecs, err } } } - return imageScheme, imageSpecs, nil + + return imageDefinition, imageScheme, imageSpecs, nil } func getMachineAccountDeleteOptionValue(v string) citrixorchestration.MachineAccountDeleteOption { @@ -565,3 +567,17 @@ func getMachineAccountDeleteOptionValue(v string) citrixorchestration.MachineAcc return *machineAccountDeleteOption } + +func IsAzureImageDefinitionUsingSharedImageGallery(imageDefinitionResp *citrixorchestration.ImageDefinitionResponseModel) bool { + preparedImageUseSharedGallery := false + imgDefinitionConn := imageDefinitionResp.GetHypervisorConnections() + if len(imgDefinitionConn) > 0 { + customProperties := imgDefinitionConn[0].GetCustomProperties() + for _, property := range customProperties { + if property.GetName() == "UseSharedImageGallery" { + preparedImageUseSharedGallery, _ = strconv.ParseBool(property.GetValue()) + } + } + } + return preparedImageUseSharedGallery +} diff --git a/internal/daas/machine_catalog/machine_catalog_mcs_pvs_utils.go b/internal/daas/machine_catalog/machine_catalog_mcs_pvs_utils.go index a1e735e..e7fba9d 100644 --- a/internal/daas/machine_catalog/machine_catalog_mcs_pvs_utils.go +++ b/internal/daas/machine_catalog/machine_catalog_mcs_pvs_utils.go @@ -12,6 +12,7 @@ import ( "github.com/citrix/citrix-daas-rest-go/citrixorchestration" citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" + "github.com/citrix/terraform-provider-citrix/internal/daas/image_definition" "github.com/citrix/terraform-provider-citrix/internal/util" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -108,7 +109,10 @@ func buildProvSchemeForCatalog(ctx context.Context, client *citrixdaasclient.Cit provisioningScheme.SetResourcePool(provisioningSchemePlan.HypervisorResourcePool.ValueString()) if hypervisor.GetConnectionType() != citrixorchestration.HYPERVISORCONNECTIONTYPE_CUSTOM || hypervisor.GetPluginId() != util.NUTANIX_PLUGIN_ID { - customProperties := parseCustomPropertiesToClientModel(ctx, diag, provisioningSchemePlan, hypervisor.ConnectionType, provisioningType, false) + customProperties, err := parseCustomPropertiesToClientModel(ctx, diag, client, provisioningSchemePlan, hypervisor.ConnectionType, provisioningType, false) + if err != nil { + return nil, err + } provisioningScheme.SetCustomProperties(customProperties) } @@ -617,7 +621,10 @@ func setProvSchemePropertiesForUpdateCatalog(provisioningSchemePlan Provisioning body.SetNetworkMapping(networkMapping) } - customProperties := parseCustomPropertiesToClientModel(ctx, diagnostics, provisioningSchemePlan, hypervisor.ConnectionType, provisioningType, true) + customProperties, err := parseCustomPropertiesToClientModel(ctx, diagnostics, client, provisioningSchemePlan, hypervisor.ConnectionType, provisioningType, true) + if err != nil { + return body, err + } body.SetCustomProperties(customProperties) return body, nil @@ -852,6 +859,7 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas usePreparedImage := false imageDefinition := "" imageVersion := "" + preparedImageUseSharedGallery := false var err error var httpResp *http.Response updateCustomProperties := []citrixorchestration.NameValueStringPairModel{} @@ -941,6 +949,12 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas preparedImageModel := util.ObjectValueToTypedObject[PreparedImageConfigModel](ctx, &resp.Diagnostics, azureMachineConfigModel.AzurePreparedImage) imageDefinition = preparedImageModel.ImageDefinition.ValueString() imageVersion = preparedImageModel.ImageVersion.ValueString() + + imageDefinitionResp, err := image_definition.GetImageDefinition(ctx, client, &resp.Diagnostics, imageDefinition) + if err != nil { + return err + } + preparedImageUseSharedGallery = IsAzureImageDefinitionUsingSharedImageGallery(imageDefinitionResp) } // Set reboot options if configured @@ -958,16 +972,7 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas } } - if !azureMachineConfigModel.UseAzureComputeGallery.IsNull() { - azureComputeGalleryModel := util.ObjectValueToTypedObject[AzureComputeGallerySettings](ctx, &resp.Diagnostics, azureMachineConfigModel.UseAzureComputeGallery) - util.AppendNameValueStringPair(&updateCustomProperties, "UseSharedImageGallery", "true") - util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaRatio", strconv.Itoa(int(azureComputeGalleryModel.ReplicaRatio.ValueInt64()))) - util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaMaximum", strconv.Itoa(int(azureComputeGalleryModel.ReplicaMaximum.ValueInt64()))) - } else { - util.AppendNameValueStringPair(&updateCustomProperties, "UseSharedImageGallery", "false") - util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaRatio", "") - util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaMaximum", "") - } + updateCustomProperties = appendUseAzureComputeGalleryCustomProperties(ctx, &resp.Diagnostics, updateCustomProperties, azureMachineConfigModel, preparedImageUseSharedGallery) } // For both MCS and PVS @@ -1442,7 +1447,7 @@ func (r MachineCatalogResourceModel) updateCatalogWithProvScheme(ctx context.Con return r } -func parseCustomPropertiesToClientModel(ctx context.Context, diagnostics *diag.Diagnostics, provisioningScheme ProvisioningSchemeModel, connectionType citrixorchestration.HypervisorConnectionType, provisioningType *citrixorchestration.ProvisioningType, isUpdateOperation bool) []citrixorchestration.NameValueStringPairModel { +func parseCustomPropertiesToClientModel(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, provisioningScheme ProvisioningSchemeModel, connectionType citrixorchestration.HypervisorConnectionType, provisioningType *citrixorchestration.ProvisioningType, isUpdateOperation bool) ([]citrixorchestration.NameValueStringPairModel, error) { var res = &[]citrixorchestration.NameValueStringPairModel{} var isPvsStreamingCatalog = *provisioningType == citrixorchestration.PROVISIONINGTYPE_PVS_STREAMING switch connectionType { @@ -1489,16 +1494,20 @@ func parseCustomPropertiesToClientModel(ctx context.Context, diagnostics *diag.D } } if !isPvsStreamingCatalog { - if !azureMachineConfigModel.UseAzureComputeGallery.IsNull() { - azureComputeGalleryModel := util.ObjectValueToTypedObject[AzureComputeGallerySettings](ctx, diagnostics, azureMachineConfigModel.UseAzureComputeGallery) - util.AppendNameValueStringPair(res, "UseSharedImageGallery", "true") - util.AppendNameValueStringPair(res, "SharedImageGalleryReplicaRatio", strconv.Itoa(int(azureComputeGalleryModel.ReplicaRatio.ValueInt64()))) - util.AppendNameValueStringPair(res, "SharedImageGalleryReplicaMaximum", strconv.Itoa(int(azureComputeGalleryModel.ReplicaMaximum.ValueInt64()))) - } else { - util.AppendNameValueStringPair(res, "UseSharedImageGallery", "false") - util.AppendNameValueStringPair(res, "SharedImageGalleryReplicaRatio", "") - util.AppendNameValueStringPair(res, "SharedImageGalleryReplicaMaximum", "") + preparedImageUseSharedGallery := false + if !azureMachineConfigModel.AzurePreparedImage.IsNull() { + // Handle prepared image + preparedImageModel := util.ObjectValueToTypedObject[PreparedImageConfigModel](ctx, diagnostics, azureMachineConfigModel.AzurePreparedImage) + imageDefinition := preparedImageModel.ImageDefinition.ValueString() + + imageDefinitionResp, err := image_definition.GetImageDefinition(ctx, client, diagnostics, imageDefinition) + if err != nil { + return nil, err + } + preparedImageUseSharedGallery = IsAzureImageDefinitionUsingSharedImageGallery(imageDefinitionResp) } + + *res = appendUseAzureComputeGalleryCustomProperties(ctx, diagnostics, *res, azureMachineConfigModel, preparedImageUseSharedGallery) } } @@ -1535,14 +1544,14 @@ func parseCustomPropertiesToClientModel(ctx context.Context, diagnostics *diag.D } } case citrixorchestration.HYPERVISORCONNECTIONTYPE_V_CENTER: - return nil + return nil, nil } if len(*res) == 0 { - return nil + return nil, nil } - return *res + return *res, nil } func getOnPremImage(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diags *diag.Diagnostics, hypervisorName, resourcePoolName, image, snapshot, resourcePoolPath, action string) (*citrixorchestration.HypervisorResourceResponseModel, error) { @@ -1795,3 +1804,20 @@ func validateMcsPvsMachineCatalogDeleteOptions(diagnostics *diag.Diagnostics, da return nil } + +func appendUseAzureComputeGalleryCustomProperties(ctx context.Context, diagnostics *diag.Diagnostics, updateCustomProperties []citrixorchestration.NameValueStringPairModel, azureMachineConfigModel AzureMachineConfigModel, preparedImageUseSharedGallery bool) []citrixorchestration.NameValueStringPairModel { + if !azureMachineConfigModel.UseAzureComputeGallery.IsNull() && !preparedImageUseSharedGallery { + azureComputeGalleryModel := util.ObjectValueToTypedObject[AzureComputeGallerySettings](ctx, diagnostics, azureMachineConfigModel.UseAzureComputeGallery) + util.AppendNameValueStringPair(&updateCustomProperties, "UseSharedImageGallery", "true") + util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaRatio", strconv.Itoa(int(azureComputeGalleryModel.ReplicaRatio.ValueInt64()))) + util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaMaximum", strconv.Itoa(int(azureComputeGalleryModel.ReplicaMaximum.ValueInt64()))) + } else if preparedImageUseSharedGallery { + util.AppendNameValueStringPair(&updateCustomProperties, "UseSharedImageGallery", "true") + } else { + util.AppendNameValueStringPair(&updateCustomProperties, "UseSharedImageGallery", "false") + util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaRatio", "") + util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaMaximum", "") + } + + return updateCustomProperties +} diff --git a/internal/daas/machine_catalog/machine_catalog_resource.go b/internal/daas/machine_catalog/machine_catalog_resource.go index e8c7a59..f04cb7e 100644 --- a/internal/daas/machine_catalog/machine_catalog_resource.go +++ b/internal/daas/machine_catalog/machine_catalog_resource.go @@ -1329,7 +1329,7 @@ func (r *machineCatalogResource) ModifyPlan(ctx context.Context, req resource.Mo azureMachineConfig := util.ObjectValueToTypedObject[AzureMachineConfigModel](ctx, &resp.Diagnostics, provSchemePlan.AzureMachineConfig) if !azureMachineConfig.AzurePreparedImage.IsUnknown() && !azureMachineConfig.AzurePreparedImage.IsNull() { azurePreparedImage := util.ObjectValueToTypedObject[PreparedImageConfigModel](ctx, &resp.Diagnostics, azureMachineConfig.AzurePreparedImage) - imageScheme, imageSpecs, err := validateImageVersion(ctx, &resp.Diagnostics, r.client, plan, azurePreparedImage, "azure_machine_config") + imageDefinition, imageScheme, imageSpecs, err := validateImageVersion(ctx, &resp.Diagnostics, r.client, plan, azurePreparedImage, "azure_machine_config") if err != nil { return } @@ -1360,6 +1360,26 @@ func (r *machineCatalogResource) ModifyPlan(ctx context.Context, req resource.Mo return } + // Validate the usage of shared gallery image + imgDefinitionConn := imageDefinition.GetHypervisorConnections() + if len(imgDefinitionConn) > 0 { + preparedImageUseSharedGallery := false + customProperties := imgDefinitionConn[0].GetCustomProperties() + for _, property := range customProperties { + if property.GetName() == "UseSharedImageGallery" { + preparedImageUseSharedGallery, _ = strconv.ParseBool(property.GetValue()) + } + } + if preparedImageUseSharedGallery && !azureMachineConfig.UseAzureComputeGallery.IsUnknown() && !azureMachineConfig.UseAzureComputeGallery.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("use_azure_compute_gallery"), + "Incorrect Attribute Value", + "`use_azure_compute_gallery` cannot be specified when the prepared image is using a shared image gallery. The machine catalog will inherit the azure compute gallery settings of the prepared image.", + ) + return + } + } + // Validate disk encryption set for _, customerProperty := range imageScheme.GetCustomProperties() { if strings.EqualFold(customerProperty.GetName(), "DiskEncryptionSetId") && customerProperty.GetValue() != "" { @@ -1401,7 +1421,7 @@ func (r *machineCatalogResource) ModifyPlan(ctx context.Context, req resource.Mo if !vsphereMachineConfig.VspherePreparedImage.IsUnknown() && !vsphereMachineConfig.VspherePreparedImage.IsNull() { vspherePreparedImage := util.ObjectValueToTypedObject[PreparedImageConfigModel](ctx, &resp.Diagnostics, vsphereMachineConfig.VspherePreparedImage) - imageScheme, imageSpecs, err := validateImageVersion(ctx, &resp.Diagnostics, r.client, plan, vspherePreparedImage, "vsphere_machine_config") + _, imageScheme, imageSpecs, err := validateImageVersion(ctx, &resp.Diagnostics, r.client, plan, vspherePreparedImage, "vsphere_machine_config") if err != nil { return } diff --git a/internal/daas/machine_catalog/machine_config.go b/internal/daas/machine_catalog/machine_config.go index f74eb48..da546da 100644 --- a/internal/daas/machine_catalog/machine_config.go +++ b/internal/daas/machine_catalog/machine_config.go @@ -382,13 +382,13 @@ func (VsphereMachineConfigModel) GetAttributes() map[string]schema.Attribute { type XenserverMachineConfigModel struct { /** XenServer Hypervisor **/ - MasterImageVm types.String `tfsdk:"master_image_vm"` - ImageSnapshot types.String `tfsdk:"image_snapshot"` - MasterImageNote types.String `tfsdk:"master_image_note"` - ImageUpdateRebootOptions types.Object `tfsdk:"image_update_reboot_options"` - CpuCount types.Int64 `tfsdk:"cpu_count"` - MemoryMB types.Int64 `tfsdk:"memory_mb"` - WritebackCache types.Object `tfsdk:"writeback_cache"` // XenserverWritebackCacheModel + MasterImageVm types.String `tfsdk:"master_image_vm"` + ImageSnapshot types.String `tfsdk:"image_snapshot"` + MasterImageNote types.String `tfsdk:"master_image_note"` + ImageUpdateRebootOptions types.Object `tfsdk:"image_update_reboot_options"` + CpuCount types.Int64 `tfsdk:"cpu_count"` + MemoryMB types.Int64 `tfsdk:"memory_mb"` + WritebackCache types.Object `tfsdk:"writeback_cache"` // XenserverWritebackCacheModel } func (XenserverMachineConfigModel) GetSchema() schema.SingleNestedAttribute { @@ -775,8 +775,9 @@ type AzureComputeGallerySettings struct { func (AzureComputeGallerySettings) GetSchema() schema.SingleNestedAttribute { return schema.SingleNestedAttribute{ - Description: "Use this to place prepared image in Azure Compute Gallery. Required when `storage_type = Azure_Ephemeral_OS_Disk`.", - Optional: true, + Description: "Use this to place prepared image in Azure Compute Gallery. Required when `storage_type = Azure_Ephemeral_OS_Disk`." + + "\n\n~> **Please Note** `use_azure_compute_gallery` cannot be specified when the prepared image is using a shared image gallery. The machine catalog will inherit the azure compute gallery settings of the prepared image.", + Optional: true, Attributes: map[string]schema.Attribute{ "replica_ratio": schema.Int64Attribute{ Description: "The ratio of virtual machines to image replicas that you want Azure to keep.", @@ -1410,8 +1411,8 @@ func (mc *XenserverMachineConfigModel) RefreshProperties(ctx context.Context, di writebackCache.WriteBackCacheMemorySizeMB = types.Int64Value(int64(provScheme.GetWriteBackCacheMemorySizeMB())) } } - mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, writebackCache) -} + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, writebackCache) + } func (mc *NutanixMachineConfigModel) RefreshProperties(catalog citrixorchestration.MachineCatalogDetailResponseModel) { provScheme := catalog.GetProvisioningScheme() diff --git a/internal/util/image.go b/internal/util/image.go index ea12d50..f728d1c 100644 --- a/internal/util/image.go +++ b/internal/util/image.go @@ -321,6 +321,95 @@ func (AzureMachineProfileModel) GetDataSourceAttributes() map[string]dataSourceS return AzureMachineProfileModel{}.GetDataSourceSchema().Attributes } +type AzureImageSpecsModel struct { + // Required Attributes + ServiceOffering types.String `tfsdk:"service_offering"` + LicenseType types.String `tfsdk:"license_type"` + StorageType types.String `tfsdk:"storage_type"` + + // Optional Attributes + MachineProfile types.Object `tfsdk:"machine_profile"` + DiskEncryptionSet types.Object `tfsdk:"disk_encryption_set"` + + // Master Image Attributes + ResourceGroup types.String `tfsdk:"resource_group"` + SharedSubscription types.String `tfsdk:"shared_subscription"` + MasterImage types.String `tfsdk:"master_image"` + GalleryImage types.Object `tfsdk:"gallery_image"` +} + +func (AzureImageSpecsModel) GetSchema() schema.SingleNestedAttribute { + galleryImageSchema := GalleryImageModel{}.GetSchema() + galleryImageSchema.Validators = []validator.Object{ + objectvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("resource_group"), + }...), + } + return schema.SingleNestedAttribute{ + Description: "Image configuration for Azure image version.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "service_offering": schema.StringAttribute{ + Description: "The Azure VM Sku to use when creating machines.", + Required: true, + }, + "license_type": schema.StringAttribute{ + Description: "Windows license type used to provision virtual machines in Azure at the base compute rate. License types include: `Windows_Client` and `Windows_Server`.", + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf( + WindowsClientLicenseType, + WindowsServerLicenseType, + ), + }, + }, + "storage_type": schema.StringAttribute{ + Description: "Storage account type used for provisioned virtual machine disks on Azure. Storage types include: `Standard_LRS`, `StandardSSD_LRS` and `Premium_LRS`.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + StandardLRS, + StandardSSDLRS, + Premium_LRS, + ), + }, + }, + "machine_profile": AzureMachineProfileModel{}.GetSchema(), + "disk_encryption_set": AzureDiskEncryptionSetModel{}.GetSchema(), + "resource_group": schema.StringAttribute{ + Description: "The Azure Resource Group where the managed disk / snapshot for creating machines is located.", + Required: true, + }, + "shared_subscription": schema.StringAttribute{ + Description: "The Azure Subscription ID where the managed disk / snapshot for creating machines is located. Only required if the image is not in the same subscription of the hypervisor.", + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "master_image": schema.StringAttribute{ + Description: "The name of the virtual machine snapshot or VM template that will be used. This identifies the hard disk to be used and the default values for the memory and processors. Omit this field if you want to use gallery_image.", + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("gallery_image")), + }, + }, + "gallery_image": galleryImageSchema, + }, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplace(), + }, + Validators: []validator.Object{ + objectvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("vsphere_image_specs")), + }, + } +} + +func (AzureImageSpecsModel) GetAttributes() map[string]schema.Attribute { + return AzureImageSpecsModel{}.GetSchema().Attributes +} + func HandleMachineProfileForAzureMcsPvsCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diag *diag.Diagnostics, hypervisorName string, resourcePoolName string, machineProfile AzureMachineProfileModel, errorTitle string) (string, error) { machineProfileResourceGroup := machineProfile.MachineProfileResourceGroup.ValueString() machineProfileVmOrTemplateSpecVersion := machineProfile.MachineProfileVmName.ValueString() @@ -581,3 +670,30 @@ func ParseMasterImageToUpdateAzureImageSpecs(ctx context.Context, diagnostics *d return masterImageToReturn, resourceGroupToReturn, sharedSubscriptionToReturn, galleryImageToReturn, storageAccountToReturn, containerToReturn } + +func ParseMasterImageToAzureImageModel(ctx context.Context, diagnostics *diag.Diagnostics, azureImageSpecs AzureImageSpecsModel, masterImage citrixorchestration.HypervisorResourceRefResponseModel) AzureImageSpecsModel { + masterImageXdPath := masterImage.GetXDPath() + masterImageSegments := strings.Split(masterImageXdPath, "\\") + masterImageLastIndex := len(masterImageSegments) + masterImageResourceTag := strings.Split(masterImageSegments[masterImageLastIndex-1], ".") + masterImageResourceType := masterImageResourceTag[len(masterImageResourceTag)-1] + if strings.EqualFold(masterImageResourceType, ImageVersionResourceType) { + azureImageSpecs.GalleryImage, + azureImageSpecs.ResourceGroup, + azureImageSpecs.SharedSubscription = + ParseMasterImageToUpdateGalleryImageModel(ctx, diagnostics, azureImageSpecs.GalleryImage, masterImage, masterImageSegments, masterImageLastIndex) + + // Clear other master image details + azureImageSpecs.MasterImage = types.StringNull() + } else { + // Snapshot or Managed Disk + azureImageSpecs.MasterImage, + azureImageSpecs.ResourceGroup, + azureImageSpecs.SharedSubscription, + azureImageSpecs.GalleryImage, + _, + _ = + ParseMasterImageToUpdateAzureImageSpecs(ctx, diagnostics, masterImageResourceType, masterImage, masterImageSegments, masterImageLastIndex) + } + return azureImageSpecs +}