diff --git a/internal/provider/services.go b/internal/provider/services.go index ca6cd4ec3882..fcdcf77fc013 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -116,6 +116,7 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration { appservice.Registration{}, batch.Registration{}, bot.Registration{}, + compute.Registration{}, consumption.Registration{}, containers.Registration{}, costmanagement.Registration{}, diff --git a/internal/services/compute/client/client.go b/internal/services/compute/client/client.go index d7deed8d6dfc..1bd1bd10fdb8 100644 --- a/internal/services/compute/client/client.go +++ b/internal/services/compute/client/client.go @@ -7,31 +7,33 @@ import ( ) type Client struct { - AvailabilitySetsClient *compute.AvailabilitySetsClient - CapacityReservationsClient *compute.CapacityReservationsClient - CapacityReservationGroupsClient *compute.CapacityReservationGroupsClient - DedicatedHostsClient *compute.DedicatedHostsClient - DedicatedHostGroupsClient *compute.DedicatedHostGroupsClient - DisksClient *compute.DisksClient - DiskAccessClient *compute.DiskAccessesClient - DiskEncryptionSetsClient *compute.DiskEncryptionSetsClient - GalleriesClient *compute.GalleriesClient - GalleryImagesClient *compute.GalleryImagesClient - GalleryImageVersionsClient *compute.GalleryImageVersionsClient - ProximityPlacementGroupsClient *compute.ProximityPlacementGroupsClient - MarketplaceAgreementsClient *marketplaceordering.MarketplaceAgreementsClient - ImagesClient *compute.ImagesClient - SnapshotsClient *compute.SnapshotsClient - UsageClient *compute.UsageClient - VMExtensionImageClient *compute.VirtualMachineExtensionImagesClient - VMExtensionClient *compute.VirtualMachineExtensionsClient - VMScaleSetClient *compute.VirtualMachineScaleSetsClient - VMScaleSetExtensionsClient *compute.VirtualMachineScaleSetExtensionsClient - VMScaleSetRollingUpgradesClient *compute.VirtualMachineScaleSetRollingUpgradesClient - VMScaleSetVMsClient *compute.VirtualMachineScaleSetVMsClient - VMClient *compute.VirtualMachinesClient - VMImageClient *compute.VirtualMachineImagesClient - SSHPublicKeysClient *compute.SSHPublicKeysClient + AvailabilitySetsClient *compute.AvailabilitySetsClient + CapacityReservationsClient *compute.CapacityReservationsClient + CapacityReservationGroupsClient *compute.CapacityReservationGroupsClient + DedicatedHostsClient *compute.DedicatedHostsClient + DedicatedHostGroupsClient *compute.DedicatedHostGroupsClient + DisksClient *compute.DisksClient + DiskAccessClient *compute.DiskAccessesClient + DiskEncryptionSetsClient *compute.DiskEncryptionSetsClient + GalleriesClient *compute.GalleriesClient + GalleryApplicationsClient *compute.GalleryApplicationsClient + GalleryApplicationVersionsClient *compute.GalleryApplicationVersionsClient + GalleryImagesClient *compute.GalleryImagesClient + GalleryImageVersionsClient *compute.GalleryImageVersionsClient + ProximityPlacementGroupsClient *compute.ProximityPlacementGroupsClient + MarketplaceAgreementsClient *marketplaceordering.MarketplaceAgreementsClient + ImagesClient *compute.ImagesClient + SnapshotsClient *compute.SnapshotsClient + UsageClient *compute.UsageClient + VMExtensionImageClient *compute.VirtualMachineExtensionImagesClient + VMExtensionClient *compute.VirtualMachineExtensionsClient + VMScaleSetClient *compute.VirtualMachineScaleSetsClient + VMScaleSetExtensionsClient *compute.VirtualMachineScaleSetExtensionsClient + VMScaleSetRollingUpgradesClient *compute.VirtualMachineScaleSetRollingUpgradesClient + VMScaleSetVMsClient *compute.VirtualMachineScaleSetVMsClient + VMClient *compute.VirtualMachinesClient + VMImageClient *compute.VirtualMachineImagesClient + SSHPublicKeysClient *compute.SSHPublicKeysClient } func NewClient(o *common.ClientOptions) *Client { @@ -62,6 +64,12 @@ func NewClient(o *common.ClientOptions) *Client { galleriesClient := compute.NewGalleriesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&galleriesClient.Client, o.ResourceManagerAuthorizer) + galleryApplicationsClient := compute.NewGalleryApplicationsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&galleryApplicationsClient.Client, o.ResourceManagerAuthorizer) + + galleryApplicationVersionsClient := compute.NewGalleryApplicationVersionsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&galleryApplicationVersionsClient.Client, o.ResourceManagerAuthorizer) + galleryImagesClient := compute.NewGalleryImagesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&galleryImagesClient.Client, o.ResourceManagerAuthorizer) @@ -111,30 +119,32 @@ func NewClient(o *common.ClientOptions) *Client { o.ConfigureClient(&sshPublicKeysClient.Client, o.ResourceManagerAuthorizer) return &Client{ - AvailabilitySetsClient: &availabilitySetsClient, - CapacityReservationsClient: &capacityReservationsClient, - CapacityReservationGroupsClient: &capacityReservationGroupsClient, - DedicatedHostsClient: &dedicatedHostsClient, - DedicatedHostGroupsClient: &dedicatedHostGroupsClient, - DisksClient: &disksClient, - DiskAccessClient: &diskAccessClient, - DiskEncryptionSetsClient: &diskEncryptionSetsClient, - GalleriesClient: &galleriesClient, - GalleryImagesClient: &galleryImagesClient, - GalleryImageVersionsClient: &galleryImageVersionsClient, - ImagesClient: &imagesClient, - MarketplaceAgreementsClient: &marketplaceAgreementsClient, - ProximityPlacementGroupsClient: &proximityPlacementGroupsClient, - SnapshotsClient: &snapshotsClient, - UsageClient: &usageClient, - VMExtensionImageClient: &vmExtensionImageClient, - VMExtensionClient: &vmExtensionClient, - VMScaleSetClient: &vmScaleSetClient, - VMScaleSetExtensionsClient: &vmScaleSetExtensionsClient, - VMScaleSetRollingUpgradesClient: &vmScaleSetRollingUpgradesClient, - VMScaleSetVMsClient: &vmScaleSetVMsClient, - VMClient: &vmClient, - VMImageClient: &vmImageClient, - SSHPublicKeysClient: &sshPublicKeysClient, + AvailabilitySetsClient: &availabilitySetsClient, + CapacityReservationsClient: &capacityReservationsClient, + CapacityReservationGroupsClient: &capacityReservationGroupsClient, + DedicatedHostsClient: &dedicatedHostsClient, + DedicatedHostGroupsClient: &dedicatedHostGroupsClient, + DisksClient: &disksClient, + DiskAccessClient: &diskAccessClient, + DiskEncryptionSetsClient: &diskEncryptionSetsClient, + GalleriesClient: &galleriesClient, + GalleryApplicationsClient: &galleryApplicationsClient, + GalleryApplicationVersionsClient: &galleryApplicationVersionsClient, + GalleryImagesClient: &galleryImagesClient, + GalleryImageVersionsClient: &galleryImageVersionsClient, + ImagesClient: &imagesClient, + MarketplaceAgreementsClient: &marketplaceAgreementsClient, + ProximityPlacementGroupsClient: &proximityPlacementGroupsClient, + SnapshotsClient: &snapshotsClient, + UsageClient: &usageClient, + VMExtensionImageClient: &vmExtensionImageClient, + VMExtensionClient: &vmExtensionClient, + VMScaleSetClient: &vmScaleSetClient, + VMScaleSetExtensionsClient: &vmScaleSetExtensionsClient, + VMScaleSetRollingUpgradesClient: &vmScaleSetRollingUpgradesClient, + VMScaleSetVMsClient: &vmScaleSetVMsClient, + VMClient: &vmClient, + VMImageClient: &vmImageClient, + SSHPublicKeysClient: &sshPublicKeysClient, } } diff --git a/internal/services/compute/gallery_application_resource.go b/internal/services/compute/gallery_application_resource.go new file mode 100644 index 000000000000..57b336896d8f --- /dev/null +++ b/internal/services/compute/gallery_application_resource.go @@ -0,0 +1,362 @@ +package compute + +import ( + "context" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute" + "github.com/Azure/go-autorest/autorest/date" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type GalleryApplicationResource struct{} + +var ( + _ sdk.ResourceWithUpdate = GalleryApplicationResource{} + _ sdk.ResourceWithCustomizeDiff = GalleryApplicationResource{} +) + +type GalleryApplicationModel struct { + Name string `tfschema:"name"` + GalleryId string `tfschema:"gallery_id"` + Location string `tfschema:"location"` + SupportedOSType string `tfschema:"supported_os_type"` + Description string `tfschema:"description"` + EndOfLifeDate string `tfschema:"end_of_life_date"` + Eula string `tfschema:"eula"` + PrivacyStatementURI string `tfschema:"privacy_statement_uri"` + ReleaseNoteURI string `tfschema:"release_note_uri"` + Tags map[string]string `tfschema:"tags"` +} + +func (r GalleryApplicationResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.GalleryApplicationName, + }, + + "gallery_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SharedImageGalleryID, + }, + + "location": commonschema.Location(), + + "supported_os_type": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(compute.OperatingSystemTypesWindows), + string(compute.OperatingSystemTypesLinux), + }, false), + }, + + "description": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "end_of_life_date": { + Type: pluginsdk.TypeString, + Optional: true, + DiffSuppressFunc: suppress.RFC3339Time, + ValidateFunc: validation.IsRFC3339Time, + }, + + "eula": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "privacy_statement_uri": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "release_note_uri": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "tags": tags.Schema(), + } +} + +func (r GalleryApplicationResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} + +func (r GalleryApplicationResource) ResourceType() string { + return "azurerm_gallery_application" +} + +func (r GalleryApplicationResource) ModelObject() interface{} { + return &GalleryApplicationModel{} +} + +func (r GalleryApplicationResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.GalleryApplicationID +} + +func (r GalleryApplicationResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var state GalleryApplicationModel + if err := metadata.Decode(&state); err != nil { + return err + } + + client := metadata.Client.Compute.GalleryApplicationsClient + subscriptionId := metadata.Client.Account.SubscriptionId + + galleryId, err := parse.SharedImageGalleryID(state.GalleryId) + if err != nil { + return err + } + + id := parse.NewGalleryApplicationID(subscriptionId, galleryId.ResourceGroup, galleryId.GalleryName, state.Name) + + existing, err := client.Get(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName) + if err != nil && !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for the presence of existing %q: %+v", id, err) + } + if !utils.ResponseWasNotFound(existing.Response) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + input := compute.GalleryApplication{ + Location: utils.String(location.Normalize(state.Location)), + GalleryApplicationProperties: &compute.GalleryApplicationProperties{ + SupportedOSType: compute.OperatingSystemTypes(state.SupportedOSType), + }, + Tags: tags.FromTypedObject(state.Tags), + } + + if state.Description != "" { + input.GalleryApplicationProperties.Description = utils.String(state.Description) + } + + if state.EndOfLifeDate != "" { + endOfLifeDate, _ := time.Parse(time.RFC3339, state.EndOfLifeDate) + input.GalleryApplicationProperties.EndOfLifeDate = &date.Time{ + Time: endOfLifeDate, + } + } + + if state.Eula != "" { + input.GalleryApplicationProperties.Eula = utils.String(state.Eula) + } + + if state.PrivacyStatementURI != "" { + input.GalleryApplicationProperties.PrivacyStatementURI = utils.String(state.PrivacyStatementURI) + } + + if state.ReleaseNoteURI != "" { + input.GalleryApplicationProperties.ReleaseNoteURI = utils.String(state.ReleaseNoteURI) + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName, input) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + Timeout: 30 * time.Minute, + } +} + +func (r GalleryApplicationResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Compute.GalleryApplicationsClient + id, err := parse.GalleryApplicationID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + metadata.Logger.Infof("%q was not found - removing from state!", *id) + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + galleryId := parse.NewSharedImageGalleryID(id.SubscriptionId, id.ResourceGroup, id.GalleryName) + + state := &GalleryApplicationModel{ + Name: id.ApplicationName, + GalleryId: galleryId.ID(), + Location: location.NormalizeNilable(resp.Location), + Tags: tags.ToTypedObject(resp.Tags), + } + + if props := resp.GalleryApplicationProperties; props != nil { + if v := props.Description; v != nil { + state.Description = *props.Description + } + + if v := props.EndOfLifeDate; v != nil { + state.EndOfLifeDate = props.EndOfLifeDate.Format(time.RFC3339) + } + + if v := props.Eula; v != nil { + state.Eula = *props.Eula + } + + if v := props.PrivacyStatementURI; v != nil { + state.PrivacyStatementURI = *props.PrivacyStatementURI + } + + if v := props.ReleaseNoteURI; v != nil { + state.ReleaseNoteURI = *props.ReleaseNoteURI + } + + state.SupportedOSType = string(props.SupportedOSType) + } + + return metadata.Encode(state) + }, + Timeout: 5 * time.Minute, + } +} + +func (r GalleryApplicationResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + id, err := parse.GalleryApplicationID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var state GalleryApplicationModel + if err := metadata.Decode(&state); err != nil { + return err + } + + client := metadata.Client.Compute.GalleryApplicationsClient + existing, err := client.Get(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + if metadata.ResourceData.HasChange("description") { + existing.GalleryApplicationProperties.Description = utils.String(state.Description) + } + + if metadata.ResourceData.HasChange("end_of_life_date") { + endOfLifeDate, _ := time.Parse(time.RFC3339, state.EndOfLifeDate) + existing.GalleryApplicationProperties.EndOfLifeDate = &date.Time{ + Time: endOfLifeDate, + } + } + + if metadata.ResourceData.HasChange("eula") { + existing.GalleryApplicationProperties.Eula = utils.String(state.Eula) + } + + if metadata.ResourceData.HasChange("privacy_statement_uri") { + existing.GalleryApplicationProperties.PrivacyStatementURI = utils.String(state.PrivacyStatementURI) + } + + if metadata.ResourceData.HasChange("release_note_uri") { + existing.GalleryApplicationProperties.ReleaseNoteURI = utils.String(state.ReleaseNoteURI) + } + + if metadata.ResourceData.HasChange("tags") { + existing.Tags = tags.FromTypedObject(state.Tags) + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName, existing) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for update of %s: %+v", id, err) + } + + return nil + }, + Timeout: 30 * time.Minute, + } +} + +func (r GalleryApplicationResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Compute.GalleryApplicationsClient + id, err := parse.GalleryApplicationID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName) + if err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for deletion of %s: %+v", id, err) + } + return nil + }, + Timeout: 30 * time.Minute, + } +} + +func (r GalleryApplicationResource) CustomizeDiff() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + rd := metadata.ResourceDiff + + if oldVal, newVal := rd.GetChange("end_of_life_date"); oldVal.(string) != "" && newVal.(string) == "" { + if err := rd.ForceNew("end_of_life_date"); err != nil { + return err + } + } + + if oldVal, newVal := rd.GetChange("privacy_statement_uri"); oldVal.(string) != "" && newVal.(string) == "" { + if err := rd.ForceNew("privacy_statement_uri"); err != nil { + return err + } + } + + if oldVal, newVal := rd.GetChange("release_note_uri"); oldVal.(string) != "" && newVal.(string) == "" { + if err := rd.ForceNew("release_note_uri"); err != nil { + return err + } + } + + return nil + }, + } +} diff --git a/internal/services/compute/gallery_application_resource_test.go b/internal/services/compute/gallery_application_resource_test.go new file mode 100644 index 000000000000..9e5e2abed532 --- /dev/null +++ b/internal/services/compute/gallery_application_resource_test.go @@ -0,0 +1,519 @@ +package compute_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type GalleryApplicationResource struct{} + +func TestAccGalleryApplication_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplication_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccGalleryApplication_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplication_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplication_description(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.description(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.descriptionUpdated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.description(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplication_endOfLifeDate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + + endOfLifeDate := time.Now().Add(time.Hour * 10).Format(time.RFC3339) + endOfLifeDateUpdated := time.Now().Add(time.Hour * 20).Format(time.RFC3339) + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.endOfLifeDate(data, endOfLifeDate), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.endOfLifeDate(data, endOfLifeDateUpdated), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplication_eula(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.eula(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.eulaUpdated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.eula(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplication_privacyStatementURI(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.privacyStatementURI(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.privacyStatementURIUpdated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplication_releaseNoteURI(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.releaseNoteURI(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.releaseNoteURIUpdated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplication_tags(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application", "test") + r := GalleryApplicationResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.tags(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.tagsUpdated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.tags(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r GalleryApplicationResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.GalleryApplicationID(state.ID) + if err != nil { + return nil, err + } + resp, err := client.Compute.GalleryApplicationsClient.Get(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + return utils.Bool(resp.ID != nil), nil +} + +func (r GalleryApplicationResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctest-compute-%[2]d" + location = "%[1]s" +} + +resource "azurerm_shared_image_gallery" "test" { + name = "acctestsig%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} +`, data.Locations.Primary, data.RandomInteger) +} + +func (r GalleryApplicationResource) basic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) requiresImport(data acceptance.TestData) string { + config := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "import" { + name = azurerm_gallery_application.test.name + gallery_id = azurerm_gallery_application.test.gallery_id + location = azurerm_gallery_application.test.location + supported_os_type = azurerm_gallery_application.test.supported_os_type +} +`, config) +} + +func (r GalleryApplicationResource) complete(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + description = "This is the gallery application description." + end_of_life_date = "%s" + eula = "https://eula.net" + privacy_statement_uri = "https://privacy.statement.net" + release_note_uri = "https://release.note.net" + + tags = { + ENV = "Test" + } +} +`, template, data.RandomInteger, time.Now().Add(time.Hour*10).Format(time.RFC3339)) +} + +func (r GalleryApplicationResource) description(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + description = "This is the gallery application description." +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) descriptionUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + description = "This is the gallery application description updated." +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) endOfLifeDate(data acceptance.TestData, endOfLifeDate string) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + end_of_life_date = "%s" +} +`, template, data.RandomInteger, endOfLifeDate) +} + +func (r GalleryApplicationResource) eula(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + eula = "https://eula.net" +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) eulaUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + eula = "https://eula2.net" +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) privacyStatementURI(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + privacy_statement_uri = "https://privacy.statement.net" +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) privacyStatementURIUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + privacy_statement_uri = "https://privacy.statement2.net" +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) releaseNoteURI(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + release_note_uri = "https://release.note2.net" +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) releaseNoteURIUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + release_note_uri = "https://release.note.net" +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) tags(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + tags = { + ENV = "Test" + } +} +`, template, data.RandomInteger) +} + +func (r GalleryApplicationResource) tagsUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" + + tags = { + ENV = "Test2" + } +} +`, template, data.RandomInteger) +} diff --git a/internal/services/compute/gallery_application_version_resource.go b/internal/services/compute/gallery_application_version_resource.go new file mode 100644 index 000000000000..83d6d6025377 --- /dev/null +++ b/internal/services/compute/gallery_application_version_resource.go @@ -0,0 +1,534 @@ +package compute + +import ( + "context" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute" + "github.com/Azure/go-autorest/autorest/date" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type GalleryApplicationVersionResource struct{} + +var ( + _ sdk.ResourceWithUpdate = GalleryApplicationVersionResource{} + _ sdk.ResourceWithCustomizeDiff = GalleryApplicationVersionResource{} +) + +type GalleryApplicationVersionModel struct { + Name string `tfschema:"name"` + GalleryApplicationId string `tfschema:"gallery_application_id"` + Location string `tfschema:"location"` + EnableHealthCheck bool `tfschema:"enable_health_check"` + EndOfLifeDate string `tfschema:"end_of_life_date"` + ExcludeFromLatest bool `tfschema:"exclude_from_latest"` + ManageAction []ManageAction `tfschema:"manage_action"` + Source []Source `tfschema:"source"` + TargetRegion []TargetRegion `tfschema:"target_region"` + Tags map[string]string `tfschema:"tags"` +} + +type Source struct { + MediaLink string `tfschema:"media_link"` + DefaultConfigurationLink string `tfschema:"default_configuration_link"` +} + +type ManageAction struct { + Install string `tfschema:"install"` + Remove string `tfschema:"remove"` + Update string `tfschema:"update"` +} + +type TargetRegion struct { + Name string `tfschema:"name"` + RegionalReplicaCount int `tfschema:"regional_replica_count"` + StorageAccountType string `tfschema:"storage_account_type"` +} + +func (r GalleryApplicationVersionResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.GalleryApplicationVersionName, + }, + + "gallery_application_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.GalleryApplicationID, + }, + + "location": commonschema.Location(), + + "enable_health_check": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "end_of_life_date": { + Type: pluginsdk.TypeString, + Optional: true, + DiffSuppressFunc: suppress.RFC3339Time, + ValidateFunc: validation.IsRFC3339Time, + }, + + "exclude_from_latest": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "manage_action": { + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "install": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 4096), + }, + + "remove": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 4096), + }, + + "update": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 4096), + }, + }, + }, + }, + + "source": { + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "media_link": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsURLWithScheme([]string{"http", "https"}), + }, + + "default_configuration_link": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IsURLWithScheme([]string{"http", "https"}), + }, + }, + }, + }, + + "target_region": { + // This needs to be a `TypeList` due to the `StateFunc` on the nested property `name` + // See: https://github.com/hashicorp/terraform-plugin-sdk/issues/160 + Type: pluginsdk.TypeList, + Required: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + StateFunc: location.StateFunc, + DiffSuppressFunc: location.DiffSuppressFunc, + }, + + "regional_replica_count": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 10), + }, + + "storage_account_type": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(compute.StorageAccountTypePremiumLRS), + string(compute.StorageAccountTypeStandardLRS), + string(compute.StorageAccountTypeStandardZRS), + }, false), + Default: string(compute.StorageAccountTypeStandardLRS), + }, + }, + }, + }, + + "tags": tags.Schema(), + } +} + +func (r GalleryApplicationVersionResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} + +func (r GalleryApplicationVersionResource) ResourceType() string { + return "azurerm_gallery_application_version" +} + +func (r GalleryApplicationVersionResource) ModelObject() interface{} { + return &GalleryApplicationVersionModel{} +} + +func (r GalleryApplicationVersionResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.GalleryApplicationVersionID +} + +func (r GalleryApplicationVersionResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var state GalleryApplicationVersionModel + if err := metadata.Decode(&state); err != nil { + return err + } + + client := metadata.Client.Compute.GalleryApplicationVersionsClient + subscriptionId := metadata.Client.Account.SubscriptionId + + galleryApplicationId, err := parse.GalleryApplicationID(state.GalleryApplicationId) + if err != nil { + return err + } + + id := parse.NewGalleryApplicationVersionID(subscriptionId, galleryApplicationId.ResourceGroup, galleryApplicationId.GalleryName, galleryApplicationId.ApplicationName, state.Name) + existing, err := client.Get(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName, id.VersionName, "") + if err != nil && !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for the presence of existing %q: %+v", id, err) + } + if !utils.ResponseWasNotFound(existing.Response) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + input := compute.GalleryApplicationVersion{ + Location: utils.String(location.Normalize(state.Location)), + GalleryApplicationVersionProperties: &compute.GalleryApplicationVersionProperties{ + PublishingProfile: &compute.GalleryApplicationVersionPublishingProfile{ + EnableHealthCheck: utils.Bool(state.EnableHealthCheck), + ExcludeFromLatest: utils.Bool(state.ExcludeFromLatest), + ManageActions: expandGalleryApplicationVersionManageAction(state.ManageAction), + Source: expandGalleryApplicationVersionSource(state.Source), + TargetRegions: expandGalleryApplicationVersionTargetRegion(state.TargetRegion), + }, + }, + Tags: tags.FromTypedObject(state.Tags), + } + + if state.EndOfLifeDate != "" { + endOfLifeDate, _ := time.Parse(time.RFC3339, state.EndOfLifeDate) + input.GalleryApplicationVersionProperties.PublishingProfile.EndOfLifeDate = &date.Time{ + Time: endOfLifeDate, + } + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName, id.VersionName, input) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + Timeout: 30 * time.Minute, + } +} + +func (r GalleryApplicationVersionResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Compute.GalleryApplicationVersionsClient + id, err := parse.GalleryApplicationVersionID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName, id.VersionName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + metadata.Logger.Infof("%q was not found - removing from state!", *id) + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + galleryApplicationId := parse.NewGalleryApplicationID(id.SubscriptionId, id.ResourceGroup, id.GalleryName, id.ApplicationName) + + state := &GalleryApplicationVersionModel{ + Name: id.VersionName, + GalleryApplicationId: galleryApplicationId.ID(), + Location: location.NormalizeNilable(resp.Location), + Tags: tags.ToTypedObject(resp.Tags), + } + + if props := resp.GalleryApplicationVersionProperties; props != nil { + if publishingProfile := props.PublishingProfile; publishingProfile != nil { + if publishingProfile.EnableHealthCheck != nil { + state.EnableHealthCheck = *publishingProfile.EnableHealthCheck + } + + if publishingProfile.EndOfLifeDate != nil { + state.EndOfLifeDate = publishingProfile.EndOfLifeDate.Format(time.RFC3339) + } + + if publishingProfile.ExcludeFromLatest != nil { + state.ExcludeFromLatest = *publishingProfile.ExcludeFromLatest + } + + if publishingProfile.ManageActions != nil { + state.ManageAction = flattenGalleryApplicationVersionManageAction(publishingProfile.ManageActions) + } + + if publishingProfile.Source != nil { + state.Source = flattenGalleryApplicationVersionSource(publishingProfile.Source) + } + + if publishingProfile.TargetRegions != nil { + state.TargetRegion = flattenGalleryApplicationVersionTargetRegion(publishingProfile.TargetRegions) + } + } + } + + return metadata.Encode(state) + }, + Timeout: 5 * time.Minute, + } +} + +func (r GalleryApplicationVersionResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + id, err := parse.GalleryApplicationVersionID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var state GalleryApplicationVersionModel + if err := metadata.Decode(&state); err != nil { + return err + } + + client := metadata.Client.Compute.GalleryApplicationVersionsClient + existing, err := client.Get(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName, id.VersionName, "") + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + if existing.PublishingProfile == nil { + existing.PublishingProfile = &compute.GalleryApplicationVersionPublishingProfile{} + } + + if metadata.ResourceData.HasChange("enable_health_check") { + existing.PublishingProfile.EnableHealthCheck = utils.Bool(state.EnableHealthCheck) + } + + if metadata.ResourceData.HasChange("end_of_life_date") { + endOfLifeDate, _ := time.Parse(time.RFC3339, state.EndOfLifeDate) + existing.GalleryApplicationVersionProperties.PublishingProfile.EndOfLifeDate = &date.Time{ + Time: endOfLifeDate, + } + } + + if metadata.ResourceData.HasChange("exclude_from_latest") { + existing.PublishingProfile.ExcludeFromLatest = utils.Bool(state.ExcludeFromLatest) + } + + if metadata.ResourceData.HasChange("manage_actions") { + existing.GalleryApplicationVersionProperties.PublishingProfile.ManageActions = expandGalleryApplicationVersionManageAction(state.ManageAction) + } + if metadata.ResourceData.HasChange("source") { + existing.GalleryApplicationVersionProperties.PublishingProfile.Source = expandGalleryApplicationVersionSource(state.Source) + } + + if metadata.ResourceData.HasChange("target_region") { + existing.GalleryApplicationVersionProperties.PublishingProfile.TargetRegions = expandGalleryApplicationVersionTargetRegion(state.TargetRegion) + } + + if metadata.ResourceData.HasChange("tags") { + existing.Tags = tags.FromTypedObject(state.Tags) + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName, id.VersionName, existing) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for update of %s: %+v", id, err) + } + + return nil + }, + Timeout: 30 * time.Minute, + } +} + +func (r GalleryApplicationVersionResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Compute.GalleryApplicationVersionsClient + id, err := parse.GalleryApplicationVersionID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName, id.VersionName) + if err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for deletion of %s: %+v", id, err) + } + return nil + }, + Timeout: 30 * time.Minute, + } +} + +func (r GalleryApplicationVersionResource) CustomizeDiff() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + rd := metadata.ResourceDiff + + if oldVal, newVal := rd.GetChange("end_of_life_date"); oldVal.(string) != "" && newVal.(string) == "" { + if err := rd.ForceNew("end_of_life_date"); err != nil { + return err + } + } + + return nil + }, + } +} + +func expandGalleryApplicationVersionManageAction(input []ManageAction) *compute.UserArtifactManage { + if len(input) == 0 { + return &compute.UserArtifactManage{} + } + v := input[0] + return &compute.UserArtifactManage{ + Install: utils.String(v.Install), + Remove: utils.String(v.Remove), + Update: utils.String(v.Update), + } +} + +func flattenGalleryApplicationVersionManageAction(input *compute.UserArtifactManage) []ManageAction { + if input == nil { + return nil + } + + obj := ManageAction{} + + if input.Install != nil { + obj.Install = *input.Install + } + + if input.Remove != nil { + obj.Remove = *input.Remove + } + + if input.Update != nil { + obj.Update = *input.Update + } + + return []ManageAction{obj} +} + +func expandGalleryApplicationVersionSource(input []Source) *compute.UserArtifactSource { + if len(input) == 0 { + return &compute.UserArtifactSource{} + } + v := input[0] + return &compute.UserArtifactSource{ + MediaLink: utils.String(v.MediaLink), + DefaultConfigurationLink: utils.String(v.DefaultConfigurationLink), + } +} + +func flattenGalleryApplicationVersionSource(input *compute.UserArtifactSource) []Source { + if input == nil { + return nil + } + + obj := Source{} + + if input.MediaLink != nil { + obj.MediaLink = *input.MediaLink + } + + if input.DefaultConfigurationLink != nil { + obj.DefaultConfigurationLink = *input.DefaultConfigurationLink + } + + return []Source{obj} +} + +func expandGalleryApplicationVersionTargetRegion(input []TargetRegion) *[]compute.TargetRegion { + results := make([]compute.TargetRegion, 0) + for _, item := range input { + results = append(results, compute.TargetRegion{ + Name: utils.String(location.Normalize(item.Name)), + RegionalReplicaCount: utils.Int32(int32(item.RegionalReplicaCount)), + StorageAccountType: compute.StorageAccountType(item.StorageAccountType), + }) + } + return &results +} + +func flattenGalleryApplicationVersionTargetRegion(input *[]compute.TargetRegion) []TargetRegion { + if input == nil { + return nil + } + + results := make([]TargetRegion, 0) + + for _, item := range *input { + obj := TargetRegion{} + + if item.Name != nil { + obj.Name = location.Normalize(*item.Name) + } + + if item.RegionalReplicaCount != nil { + obj.RegionalReplicaCount = int(*item.RegionalReplicaCount) + } + + if item.StorageAccountType != "" { + obj.StorageAccountType = string(item.StorageAccountType) + } + results = append(results, obj) + } + return results +} diff --git a/internal/services/compute/gallery_application_version_resource_test.go b/internal/services/compute/gallery_application_version_resource_test.go new file mode 100644 index 000000000000..9179a87a027b --- /dev/null +++ b/internal/services/compute/gallery_application_version_resource_test.go @@ -0,0 +1,678 @@ +package compute_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type GalleryApplicationVersionResource struct{} + +func TestAccGalleryApplicationVersion_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application_version", "test") + r := GalleryApplicationVersionResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("enable_health_check").HasValue("false"), + check.That(data.ResourceName).Key("exclude_from_latest").HasValue("false"), + check.That(data.ResourceName).Key("target_region.0.storage_account_type").HasValue("Standard_LRS"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplicationVersion_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application_version", "test") + r := GalleryApplicationVersionResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccGalleryApplicationVersion_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application_version", "test") + r := GalleryApplicationVersionResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplicationVersion_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application_version", "test") + r := GalleryApplicationVersionResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplicationVersion_enableHealthCheck(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application_version", "test") + r := GalleryApplicationVersionResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.enableHealthCheck(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.enableHealthCheckUpdated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.enableHealthCheck(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplicationVersion_endOfLifeDate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application_version", "test") + r := GalleryApplicationVersionResource{} + + endOfLifeDate := time.Now().Add(time.Hour * 10).Format(time.RFC3339) + endOfLifeDateUpdated := time.Now().Add(time.Hour * 20).Format(time.RFC3339) + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.endOfLifeDate(data, endOfLifeDate), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.endOfLifeDate(data, endOfLifeDateUpdated), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplicationVersion_excludeFromLatest(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application_version", "test") + r := GalleryApplicationVersionResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.excludeFromLatest(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.excludeFromLatestUpdated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.excludeFromLatest(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplicationVersion_targetRegion(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application_version", "test") + r := GalleryApplicationVersionResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.targetRegion(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.targetRegionUpdated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.targetRegion(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccGalleryApplicationVersion_tags(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_gallery_application_version", "test") + r := GalleryApplicationVersionResource{} + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.tags(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.tagsUpdated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.tags(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r GalleryApplicationVersionResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.GalleryApplicationVersionID(state.ID) + if err != nil { + return nil, err + } + resp, err := client.Compute.GalleryApplicationVersionsClient.Get(ctx, id.ResourceGroup, id.GalleryName, id.ApplicationName, id.VersionName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + return utils.Bool(resp.ID != nil), nil +} + +func (r GalleryApplicationVersionResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctest-compute-%[2]d" + location = "%[1]s" +} + +resource "azurerm_shared_image_gallery" "test" { + name = "acctestsig%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_gallery_application" "test" { + name = "acctest-app-%[2]d" + gallery_id = azurerm_shared_image_gallery.test.id + location = azurerm_resource_group.test.location + supported_os_type = "Linux" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "test" { + name = "acctestsc%[3]s" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "blob" +} + +resource "azurerm_storage_blob" "test" { + name = "scripts" + storage_account_name = azurerm_storage_account.test.name + storage_container_name = azurerm_storage_container.test.name + type = "Block" + source_content = "[scripts file content]" +} + +`, data.Locations.Primary, data.RandomInteger, data.RandomString) +} + +func (r GalleryApplicationVersionResource) basic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } +} +`, template) +} + +func (r GalleryApplicationVersionResource) requiresImport(data acceptance.TestData) string { + config := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "import" { + name = azurerm_gallery_application_version.test.name + gallery_application_id = azurerm_gallery_application_version.test.gallery_application_id + location = azurerm_gallery_application_version.test.location + + manage_action { + install = azurerm_gallery_application_version.test.manage_action.0.install + remove = azurerm_gallery_application_version.test.manage_action.0.remove + } + + source { + media_link = azurerm_gallery_application_version.test.source.0.media_link + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } +} +`, config) +} + +func (r GalleryApplicationVersionResource) complete(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + enable_health_check = true + end_of_life_date = "%s" + exclude_from_latest = true + + manage_action { + install = "[install command]" + remove = "[remove command]" + update = "[update command]" + } + + source { + media_link = azurerm_storage_blob.test.id + default_configuration_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + storage_account_type = "Premium_LRS" + } + + tags = { + ENV = "Test" + } +} +`, template, time.Now().Add(time.Hour*10).Format(time.RFC3339)) +} + +func (r GalleryApplicationVersionResource) enableHealthCheck(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + enable_health_check = true + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } +} +`, template) +} + +func (r GalleryApplicationVersionResource) enableHealthCheckUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + enable_health_check = false + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } +} +`, template) +} + +func (r GalleryApplicationVersionResource) endOfLifeDate(data acceptance.TestData, endOfLifeDate string) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + end_of_life_date = "%s" + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } +} +`, template, endOfLifeDate) +} + +func (r GalleryApplicationVersionResource) excludeFromLatest(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + exclude_from_latest = true + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } +} +`, template) +} + +func (r GalleryApplicationVersionResource) excludeFromLatestUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + exclude_from_latest = false + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } +} +`, template) +} + +func (r GalleryApplicationVersionResource) targetRegion(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } + + target_region { + name = "%s" + regional_replica_count = 2 + storage_account_type = "Premium_LRS" + } +} +`, template, data.Locations.Secondary) +} + +func (r GalleryApplicationVersionResource) targetRegionUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } + + target_region { + name = "%s" + regional_replica_count = 3 + storage_account_type = "Premium_LRS" + } +} +`, template, data.Locations.Secondary) +} + +func (r GalleryApplicationVersionResource) tags(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } + + tags = { + ENV = "Test" + } +} +`, template) +} + +func (r GalleryApplicationVersionResource) tagsUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_gallery_application_version" "test" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.test.id + location = azurerm_gallery_application.test.location + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.test.id + } + + target_region { + name = azurerm_gallery_application.test.location + regional_replica_count = 1 + } + + tags = { + ENV = "Test2" + } +} +`, template) +} diff --git a/internal/services/compute/parse/gallery_application.go b/internal/services/compute/parse/gallery_application.go new file mode 100644 index 000000000000..f3fdc29f5e8b --- /dev/null +++ b/internal/services/compute/parse/gallery_application.go @@ -0,0 +1,75 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +type GalleryApplicationId struct { + SubscriptionId string + ResourceGroup string + GalleryName string + ApplicationName string +} + +func NewGalleryApplicationID(subscriptionId, resourceGroup, galleryName, applicationName string) GalleryApplicationId { + return GalleryApplicationId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + GalleryName: galleryName, + ApplicationName: applicationName, + } +} + +func (id GalleryApplicationId) String() string { + segments := []string{ + fmt.Sprintf("Application Name %q", id.ApplicationName), + fmt.Sprintf("Gallery Name %q", id.GalleryName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Gallery Application", segmentsStr) +} + +func (id GalleryApplicationId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/galleries/%s/applications/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.GalleryName, id.ApplicationName) +} + +// GalleryApplicationID parses a GalleryApplication ID into an GalleryApplicationId struct +func GalleryApplicationID(input string) (*GalleryApplicationId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := GalleryApplicationId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.GalleryName, err = id.PopSegment("galleries"); err != nil { + return nil, err + } + if resourceId.ApplicationName, err = id.PopSegment("applications"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/compute/parse/gallery_application_test.go b/internal/services/compute/parse/gallery_application_test.go new file mode 100644 index 000000000000..07413f91c804 --- /dev/null +++ b/internal/services/compute/parse/gallery_application_test.go @@ -0,0 +1,128 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.Id = GalleryApplicationId{} + +func TestGalleryApplicationIDFormatter(t *testing.T) { + actual := NewGalleryApplicationID("12345678-1234-9876-4563-123456789012", "resGroup1", "gallery1", "galleryApplication1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestGalleryApplicationID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *GalleryApplicationId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing GalleryName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/", + Error: true, + }, + + { + // missing value for GalleryName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/", + Error: true, + }, + + { + // missing ApplicationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/", + Error: true, + }, + + { + // missing value for ApplicationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1", + Expected: &GalleryApplicationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + GalleryName: "gallery1", + ApplicationName: "galleryApplication1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.COMPUTE/GALLERIES/GALLERY1/APPLICATIONS/GALLERYAPPLICATION1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := GalleryApplicationID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.GalleryName != v.Expected.GalleryName { + t.Fatalf("Expected %q but got %q for GalleryName", v.Expected.GalleryName, actual.GalleryName) + } + if actual.ApplicationName != v.Expected.ApplicationName { + t.Fatalf("Expected %q but got %q for ApplicationName", v.Expected.ApplicationName, actual.ApplicationName) + } + } +} diff --git a/internal/services/compute/parse/gallery_application_version.go b/internal/services/compute/parse/gallery_application_version.go new file mode 100644 index 000000000000..5e5e6d2f085d --- /dev/null +++ b/internal/services/compute/parse/gallery_application_version.go @@ -0,0 +1,81 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +type GalleryApplicationVersionId struct { + SubscriptionId string + ResourceGroup string + GalleryName string + ApplicationName string + VersionName string +} + +func NewGalleryApplicationVersionID(subscriptionId, resourceGroup, galleryName, applicationName, versionName string) GalleryApplicationVersionId { + return GalleryApplicationVersionId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + GalleryName: galleryName, + ApplicationName: applicationName, + VersionName: versionName, + } +} + +func (id GalleryApplicationVersionId) String() string { + segments := []string{ + fmt.Sprintf("Version Name %q", id.VersionName), + fmt.Sprintf("Application Name %q", id.ApplicationName), + fmt.Sprintf("Gallery Name %q", id.GalleryName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Gallery Application Version", segmentsStr) +} + +func (id GalleryApplicationVersionId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/galleries/%s/applications/%s/versions/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.GalleryName, id.ApplicationName, id.VersionName) +} + +// GalleryApplicationVersionID parses a GalleryApplicationVersion ID into an GalleryApplicationVersionId struct +func GalleryApplicationVersionID(input string) (*GalleryApplicationVersionId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := GalleryApplicationVersionId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.GalleryName, err = id.PopSegment("galleries"); err != nil { + return nil, err + } + if resourceId.ApplicationName, err = id.PopSegment("applications"); err != nil { + return nil, err + } + if resourceId.VersionName, err = id.PopSegment("versions"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/compute/parse/gallery_application_version_test.go b/internal/services/compute/parse/gallery_application_version_test.go new file mode 100644 index 000000000000..eabb8d975e37 --- /dev/null +++ b/internal/services/compute/parse/gallery_application_version_test.go @@ -0,0 +1,144 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.Id = GalleryApplicationVersionId{} + +func TestGalleryApplicationVersionIDFormatter(t *testing.T) { + actual := NewGalleryApplicationVersionID("12345678-1234-9876-4563-123456789012", "resGroup1", "gallery1", "galleryApplication1", "galleryApplicationVersion1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1/versions/galleryApplicationVersion1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestGalleryApplicationVersionID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *GalleryApplicationVersionId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing GalleryName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/", + Error: true, + }, + + { + // missing value for GalleryName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/", + Error: true, + }, + + { + // missing ApplicationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/", + Error: true, + }, + + { + // missing value for ApplicationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/", + Error: true, + }, + + { + // missing VersionName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1/", + Error: true, + }, + + { + // missing value for VersionName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1/versions/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1/versions/galleryApplicationVersion1", + Expected: &GalleryApplicationVersionId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + GalleryName: "gallery1", + ApplicationName: "galleryApplication1", + VersionName: "galleryApplicationVersion1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.COMPUTE/GALLERIES/GALLERY1/APPLICATIONS/GALLERYAPPLICATION1/VERSIONS/GALLERYAPPLICATIONVERSION1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := GalleryApplicationVersionID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.GalleryName != v.Expected.GalleryName { + t.Fatalf("Expected %q but got %q for GalleryName", v.Expected.GalleryName, actual.GalleryName) + } + if actual.ApplicationName != v.Expected.ApplicationName { + t.Fatalf("Expected %q but got %q for ApplicationName", v.Expected.ApplicationName, actual.ApplicationName) + } + if actual.VersionName != v.Expected.VersionName { + t.Fatalf("Expected %q but got %q for VersionName", v.Expected.VersionName, actual.VersionName) + } + } +} diff --git a/internal/services/compute/registration.go b/internal/services/compute/registration.go index e9e6caa24eb5..77a248942932 100644 --- a/internal/services/compute/registration.go +++ b/internal/services/compute/registration.go @@ -1,6 +1,7 @@ package compute import ( + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" ) @@ -74,3 +75,14 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { return resources } + +func (r Registration) DataSources() []sdk.DataSource { + return []sdk.DataSource{} +} + +func (r Registration) Resources() []sdk.Resource { + return []sdk.Resource{ + GalleryApplicationResource{}, + GalleryApplicationVersionResource{}, + } +} diff --git a/internal/services/compute/resourceids.go b/internal/services/compute/resourceids.go index 75cc0253c84e..c5cb641c2933 100644 --- a/internal/services/compute/resourceids.go +++ b/internal/services/compute/resourceids.go @@ -6,6 +6,8 @@ package compute //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=DedicatedHostGroup -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/hostGroups/hostGroup1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=DedicatedHost -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/hostGroups/hostGroup1/hosts/host1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=DiskEncryptionSet -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/diskEncryptionSets/set1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=GalleryApplication -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=GalleryApplicationVersion -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1/versions/galleryApplicationVersion1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=Image -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/images/image1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ManagedDisk -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/disks/disk1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ProximityPlacementGroup -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/proximityPlacementGroups/group1 diff --git a/internal/services/compute/validate/compute.go b/internal/services/compute/validate/compute.go index 6b2910f6c781..3b23edd19c41 100644 --- a/internal/services/compute/validate/compute.go +++ b/internal/services/compute/validate/compute.go @@ -50,6 +50,31 @@ func SharedImageVersionName(v interface{}, k string) (warnings []string, errors return warnings, errors } +func GalleryApplicationName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if !regexp.MustCompile(`^[A-Za-z0-9._-]+$`).MatchString(value) { + errors = append(errors, fmt.Errorf("%s can only contain alphanumeric, full stops, dashes and underscores. Got %q.", k, value)) + } + + length := len(value) + if length > 80 { + errors = append(errors, fmt.Errorf("%s can be up to 80 characters, currently %d.", k, length)) + } + + return warnings, errors +} + +func GalleryApplicationVersionName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if !regexp.MustCompile(`^([0-9]{1,10}\.[0-9]{1,10}\.[0-9]{1,10})$`).MatchString(value) && value != "latest" && value != "recent" { + errors = append(errors, fmt.Errorf("Expected %s to be in the format `1.2.3`, `latest`, or `recent` but got %q.", k, value)) + } + + return warnings, errors +} + // VirtualMachineTimeZone returns a case-sensitive validation function for the Time Zones for a Virtual Machine func VirtualMachineTimeZone() pluginsdk.SchemaValidateFunc { return virtualMachineTimeZone(false) diff --git a/internal/services/compute/validate/compute_test.go b/internal/services/compute/validate/compute_test.go index a018cd888871..f7773a8c8221 100644 --- a/internal/services/compute/validate/compute_test.go +++ b/internal/services/compute/validate/compute_test.go @@ -174,6 +174,116 @@ func TestSharedImageVersionName(t *testing.T) { } } +func TestGalleryApplicationName(t *testing.T) { + cases := []struct { + Input string + ShouldError bool + }{ + { + Input: "", + ShouldError: true, + }, + { + Input: "hello", + ShouldError: false, + }, + { + Input: "hello123", + ShouldError: false, + }, + { + Input: "hello.123", + ShouldError: false, + }, + { + Input: "hello,123", + ShouldError: true, + }, + { + Input: "hello_123", + ShouldError: false, + }, + { + Input: "hello-123", + ShouldError: false, + }, + { + Input: strings.Repeat("a", 80), + ShouldError: false, + }, + { + Input: strings.Repeat("a", 81), + ShouldError: true, + }, + } + + for _, tc := range cases { + t.Run(tc.Input, func(t *testing.T) { + _, errors := GalleryApplicationName(tc.Input, "test") + + hasErrors := len(errors) > 0 + if !hasErrors && tc.ShouldError { + t.Fatalf("Expected an error but didn't get one for %q", tc.Input) + } + + if hasErrors && !tc.ShouldError { + t.Fatalf("Expected to get no errors for %q but got %d", tc.Input, len(errors)) + } + }) + } +} + +func TestGalleryApplicationVersionName(t *testing.T) { + cases := []struct { + Input string + ShouldError bool + }{ + { + Input: "", + ShouldError: true, + }, + { + Input: "a.b.c", + ShouldError: true, + }, + { + Input: "1.2.3", + ShouldError: false, + }, + { + Input: "0.0.1", + ShouldError: false, + }, + { + Input: "hello", + ShouldError: true, + }, + { + Input: "1.2.3.4", + ShouldError: true, + }, + { + Input: "hell0-there", + ShouldError: true, + }, + } + + for _, tc := range cases { + t.Run(tc.Input, func(t *testing.T) { + _, errors := GalleryApplicationVersionName(tc.Input, "test") + + hasErrors := len(errors) > 0 + if !hasErrors && tc.ShouldError { + t.Fatalf("Expected an error but didn't get one for %q", tc.Input) + } + + if hasErrors && !tc.ShouldError { + t.Fatalf("Expected to get no errors for %q but got %d", tc.Input, len(errors)) + } + }) + } +} + func TestVirtualMachineTimeZone(t *testing.T) { cases := []struct { Value string diff --git a/internal/services/compute/validate/gallery_application_id.go b/internal/services/compute/validate/gallery_application_id.go new file mode 100644 index 000000000000..74d3c84c52d4 --- /dev/null +++ b/internal/services/compute/validate/gallery_application_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse" +) + +func GalleryApplicationID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.GalleryApplicationID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/internal/services/compute/validate/gallery_application_id_test.go b/internal/services/compute/validate/gallery_application_id_test.go new file mode 100644 index 000000000000..3fa7f71d9aab --- /dev/null +++ b/internal/services/compute/validate/gallery_application_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestGalleryApplicationID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing GalleryName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/", + Valid: false, + }, + + { + // missing value for GalleryName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/", + Valid: false, + }, + + { + // missing ApplicationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/", + Valid: false, + }, + + { + // missing value for ApplicationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.COMPUTE/GALLERIES/GALLERY1/APPLICATIONS/GALLERYAPPLICATION1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := GalleryApplicationID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/internal/services/compute/validate/gallery_application_version_id.go b/internal/services/compute/validate/gallery_application_version_id.go new file mode 100644 index 000000000000..4865352012e6 --- /dev/null +++ b/internal/services/compute/validate/gallery_application_version_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse" +) + +func GalleryApplicationVersionID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.GalleryApplicationVersionID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/internal/services/compute/validate/gallery_application_version_id_test.go b/internal/services/compute/validate/gallery_application_version_id_test.go new file mode 100644 index 000000000000..11deac673b9c --- /dev/null +++ b/internal/services/compute/validate/gallery_application_version_id_test.go @@ -0,0 +1,100 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestGalleryApplicationVersionID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing GalleryName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/", + Valid: false, + }, + + { + // missing value for GalleryName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/", + Valid: false, + }, + + { + // missing ApplicationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/", + Valid: false, + }, + + { + // missing value for ApplicationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/", + Valid: false, + }, + + { + // missing VersionName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1/", + Valid: false, + }, + + { + // missing value for VersionName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1/versions/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1/versions/galleryApplicationVersion1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.COMPUTE/GALLERIES/GALLERY1/APPLICATIONS/GALLERYAPPLICATION1/VERSIONS/GALLERYAPPLICATIONVERSION1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := GalleryApplicationVersionID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/r/gallery_application.html.markdown b/website/docs/r/gallery_application.html.markdown new file mode 100644 index 000000000000..17452748dd32 --- /dev/null +++ b/website/docs/r/gallery_application.html.markdown @@ -0,0 +1,82 @@ +--- +subcategory: "Compute" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_gallery_application" +description: |- + Manages a Gallery Application. +--- + +# azurerm_gallery_application + +Manages a Gallery Application. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-rg" + location = "West Europe" +} + +resource "azurerm_shared_image_gallery" "example" { + name = "example-gallery" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location +} + +resource "azurerm_gallery_application" "example" { + name = "example-app" + gallery_id = azurerm_shared_image_gallery.example.id + location = azurerm_resource_group.example.location + supported_os_type = "Linux" +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Gallery Application. Changing this forces a new resource to be created. + +* `gallery_id` - (Required) The ID of the Shared Image Gallery. Changing this forces a new resource to be created. + +* `location` - (Required) The Azure Region where the Gallery Application exists. Changing this forces a new resource to be created. + +* `supported_os_type` - (Required) The type of the Operating System supported for the Gallery Application. Possible values are `Linux` and `Windows`. Changing this forces a new resource to be created. + +--- + +* `description` - (Optional) A description of the Gallery Application. + +* `end_of_life_date` - (Optional) The end of life date in RFC3339 format of the Gallery Application. + +* `eula` - (Optional) The End User Licence Agreement of the Gallery Application. + +* `privacy_statement_uri` - (Optional) The URI containing the Privacy Statement associated with the Gallery Application. + +* `release_note_uri` - (Optional) The URI containing the Release Notes associated with the Gallery Application. + +* `tags` - (Optional) A mapping of tags to assign to the Gallery Application. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Gallery Application. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Gallery Application. +* `read` - (Defaults to 5 minutes) Used when retrieving the Gallery Application. +* `update` - (Defaults to 30 minutes) Used when updating the Gallery Application. +* `delete` - (Defaults to 30 minutes) Used when deleting the Gallery Application. + +## Import + +Gallery Applications can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_gallery_application.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1 +``` diff --git a/website/docs/r/gallery_application_version.html.markdown b/website/docs/r/gallery_application_version.html.markdown new file mode 100644 index 000000000000..cf9a4050d067 --- /dev/null +++ b/website/docs/r/gallery_application_version.html.markdown @@ -0,0 +1,152 @@ +--- +subcategory: "Compute" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_gallery_application_version" +description: |- + Manages a Gallery Application Version. +--- + +# azurerm_gallery_application_version + +Manages a Gallery Application Version. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-rg" + location = "West Europe" +} + +resource "azurerm_shared_image_gallery" "example" { + name = "example-gallery" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location +} + +resource "azurerm_gallery_application" "example" { + name = "example-app" + gallery_id = azurerm_shared_image_gallery.example.id + location = azurerm_resource_group.example.location + supported_os_type = "Linux" +} + +resource "azurerm_storage_account" "example" { + name = "example-storage" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "example" { + name = "example-container" + storage_account_name = azurerm_storage_account.example.name + container_access_type = "blob" +} + +resource "azurerm_storage_blob" "example" { + name = "scripts" + storage_account_name = azurerm_storage_account.example.name + storage_container_name = azurerm_storage_container.example.name + type = "Block" + source_content = "[scripts file content]" +} + +resource "azurerm_gallery_application_version" "example" { + name = "0.0.1" + gallery_application_id = azurerm_gallery_application.example.id + location = azurerm_gallery_application.example.location + + manage_action { + install = "[install command]" + remove = "[remove command]" + } + + source { + media_link = azurerm_storage_blob.example.id + } + + target_region { + name = azurerm_gallery_application.example.location + regional_replica_count = 1 + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The version name of the Gallery Application Version, such as `1.0.0`. Changing this forces a new resource to be created. + +* `gallery_application_id` - (Required) The ID of the Gallery Application. Changing this forces a new resource to be created. + +* `location` - (Required) The Azure Region where the Gallery Application Version exists. Changing this forces a new resource to be created. + +* `manage_action` - (Required) A `manage_action` block as defined below. + +* `source` - (Required) A `source` block as defined below. + +* `target_region` - (Required) One or more `target_region` blocks as defined below. + +--- + +* `enable_health_check` - (Optional) Should the Gallery Application reports health. Defaults to `false`. + +* `end_of_life_date` - (Optional) The end of life date in RFC3339 format of the Gallery Application Version. + +* `exclude_from_latest` - (Optional) Should the Gallery Application Version be excluded from the `latest` filter? If set to `true` this Gallery Application Version won't be returned for the `latest` version. Defaults to `false`. + +* `tags` - (Optional) A mapping of tags to assign to the Gallery Application Version. + +--- + +A `manage_action` block supports the following: + +* `install` - (Required) The command to install the Gallery Application. Changing this forces a new resource to be created. + +* `remove` - (Required) The command to remove the Gallery Application. Changing this forces a new resource to be created. + +* `update` - (Optional) The command to update the Gallery Application. Changing this forces a new resource to be created. + +--- + +A `source` block supports the following: + +* `media_link` - (Required) The Storage Blob URI of the source application package. Changing this forces a new resource to be created. + +* `default_configuration_link` - (Optional) The Storage Blob URI of the default configuration. Changing this forces a new resource to be created. + +--- + +A `target_region` block supports the following: + +* `name` - (Required) The Azure Region in which the Gallery Application Version exists. + +* `regional_replica_count` - (Required) The number of replicas of the Gallery Application Version to be created per region. Possible values are between `1` and `10`. + +* `storage_account_type` - (Optional) The storage account type for the Gallery Application Version. Possible values are `Standard_LRS`, `Premium_LRS` and `Standard_ZRS`. Defaults to `Standard_LRS`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Gallery Application Version. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Gallery Application Version. +* `read` - (Defaults to 5 minutes) Used when retrieving the Gallery Application Version. +* `update` - (Defaults to 30 minutes) Used when updating the Gallery Application Version. +* `delete` - (Defaults to 30 minutes) Used when deleting the Gallery Application Version. + +## Import + +Gallery Application Versions can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_gallery_application_version.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Compute/galleries/gallery1/applications/galleryApplication1/versions/galleryApplicationVersion1 +```