diff --git a/azurerm/helpers/validate/compute.go b/azurerm/helpers/validate/compute.go index 087456d7a8b0..6b57d300ed1b 100644 --- a/azurerm/helpers/validate/compute.go +++ b/azurerm/helpers/validate/compute.go @@ -43,8 +43,8 @@ func SharedImageName(v interface{}, k string) (warnings []string, errors []error func SharedImageVersionName(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) { - errors = append(errors, fmt.Errorf("Expected %s to be in the format `1.2.3` but got %q.", k, value)) + 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 diff --git a/azurerm/internal/services/compute/data_source_shared_image_version.go b/azurerm/internal/services/compute/data_source_shared_image_version.go index 57aefc02a18e..a43222fe2fd2 100644 --- a/azurerm/internal/services/compute/data_source_shared_image_version.go +++ b/azurerm/internal/services/compute/data_source_shared_image_version.go @@ -1,6 +1,7 @@ package compute import ( + "context" "fmt" "log" "time" @@ -94,28 +95,28 @@ func dataSourceArmSharedImageVersionRead(d *schema.ResourceData, meta interface{ galleryName := d.Get("gallery_name").(string) resourceGroup := d.Get("resource_group_name").(string) - resp, err := client.Get(ctx, resourceGroup, galleryName, imageName, imageVersion, compute.ReplicationStatusTypesReplicationStatus) + image, found, err := obtainImage(client, ctx, resourceGroup, galleryName, imageName, imageVersion) if err != nil { - if utils.ResponseWasNotFound(resp.Response) { - log.Printf("[DEBUG] Shared Image Version %q (Image %q / Gallery %q / Resource Group %q) was not found - removing from state", imageVersion, imageName, galleryName, resourceGroup) - d.SetId("") - return nil - } - return fmt.Errorf("Error retrieving Shared Image Version %q (Image %q / Gallery %q / Resource Group %q): %+v", imageVersion, imageName, galleryName, resourceGroup, err) + d.SetId("") + return err } - d.SetId(*resp.ID) + if !found { + d.SetId("") + return nil + } - d.Set("name", resp.Name) + d.SetId(*image.ID) + d.Set("name", image.Name) d.Set("image_name", imageName) d.Set("gallery_name", galleryName) d.Set("resource_group_name", resourceGroup) - if location := resp.Location; location != nil { + if location := image.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } - if props := resp.GalleryImageVersionProperties; props != nil { + if props := image.GalleryImageVersionProperties; props != nil { if profile := props.PublishingProfile; profile != nil { d.Set("exclude_from_latest", profile.ExcludeFromLatest) @@ -132,7 +133,63 @@ func dataSourceArmSharedImageVersionRead(d *schema.ResourceData, meta interface{ } } - return tags.FlattenAndSet(d, resp.Tags) + return tags.FlattenAndSet(d, image.Tags) +} + +func obtainImage(client *compute.GalleryImageVersionsClient, ctx context.Context, resourceGroup string, galleryName string, galleryImageName string, galleryImageVersionName string) (compute.GalleryImageVersion, bool, error) { + var ( + image compute.GalleryImageVersion + err error + found bool + ) + switch galleryImageVersionName { + case "latest": + images, err := client.ListByGalleryImage(ctx, resourceGroup, galleryName, galleryImageName) + if err != nil { + if utils.ResponseWasNotFound(images.Response().Response) { + log.Printf("[DEBUG] Shared Image Versions (Image %q / Gallery %q / Resource Group %q) was not found - removing from state", galleryImageName, galleryName, resourceGroup) + return image, false, nil + } + return image, false, fmt.Errorf("retrieving Shared Image Versions (Image %q / Gallery %q / Resource Group %q): %+v", galleryImageName, galleryName, resourceGroup, err) + } + // the last image in the list is the latest version + if len(images.Values()) > 0 { + image = images.Values()[len(images.Values())-1] + found = true + } + case "recent": + images, err := client.ListByGalleryImage(ctx, resourceGroup, galleryName, galleryImageName) + if err != nil { + if utils.ResponseWasNotFound(images.Response().Response) { + log.Printf("[DEBUG] Shared Image Versions (Image %q / Gallery %q / Resource Group %q) was not found - removing from state", galleryImageName, galleryName, resourceGroup) + return image, false, nil + } + return image, false, fmt.Errorf("retrieving Shared Image Versions (Image %q / Gallery %q / Resource Group %q): %+v", galleryImageName, galleryName, resourceGroup, err) + } + var recentDate time.Time + // compare dates until we find the image that was updated most recently + for _, currImage := range images.Values() { + if profile := currImage.PublishingProfile; profile != nil { + if profile.PublishedDate != nil && profile.PublishedDate.Time.After(recentDate) { + recentDate = profile.PublishedDate.Time + image = currImage + found = true + } + } + } + default: + image, err = client.Get(ctx, resourceGroup, galleryName, galleryImageName, galleryImageVersionName, compute.ReplicationStatusTypesReplicationStatus) + if err != nil { + if utils.ResponseWasNotFound(image.Response) { + log.Printf("[DEBUG] Shared Image Version %q (Image %q / Gallery %q / Resource Group %q) was not found - removing from state", galleryImageVersionName, galleryImageName, galleryName, resourceGroup) + return image, false, nil + } + return image, false, fmt.Errorf("Error retrieving Shared Image Version %q (Image %q / Gallery %q / Resource Group %q): %+v", galleryImageVersionName, galleryImageName, galleryName, resourceGroup, err) + } + found = true + } + + return image, found, nil } func flattenSharedImageVersionDataSourceTargetRegions(input *[]compute.TargetRegion) []interface{} { diff --git a/azurerm/internal/services/compute/tests/data_source_shared_image_version_test.go b/azurerm/internal/services/compute/tests/data_source_shared_image_version_test.go index 36e9082fec69..64043dc0d73b 100644 --- a/azurerm/internal/services/compute/tests/data_source_shared_image_version_test.go +++ b/azurerm/internal/services/compute/tests/data_source_shared_image_version_test.go @@ -32,7 +32,72 @@ func TestAccDataSourceAzureRMSharedImageVersion_basic(t *testing.T) { { Config: testAccDataSourceSharedImageVersion_basic(data, username, password, hostname), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(data.ResourceName, "managed_image_id"), + resource.TestCheckResourceAttr(data.ResourceName, "target_region.#", "1"), + resource.TestCheckResourceAttr(data.ResourceName, "target_region.0.storage_account_type", "Standard_LRS"), + ), + }, + }, + }) +} + +func TestAccDataSourceAzureRMSharedImageVersion_latest(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_shared_image_version", "test") + username := "testadmin" + password := "Password1234!" + hostname := fmt.Sprintf("tftestcustomimagesrc%d", data.RandomInteger) + resourceGroup := fmt.Sprintf("acctestRG-%d", data.RandomInteger) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSharedImageVersionDestroy, + Steps: []resource.TestStep{ + { + // need to create a vm and then reference it in the image creation + Config: testAccAzureRMSharedImageVersion_setup(data, username, password, hostname), + Destroy: false, + Check: resource.ComposeTestCheckFunc( + testCheckAzureVMExists("azurerm_virtual_machine.testsource", true), + testGeneralizeVMImage(resourceGroup, "testsource", username, password, hostname, "22", data.Locations.Primary), + ), + }, + { + Config: testAccDataSourceSharedImageVersion_customName(data, username, password, hostname, "latest"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(data.ResourceName, "managed_image_id"), + resource.TestCheckResourceAttr(data.ResourceName, "target_region.#", "1"), + resource.TestCheckResourceAttr(data.ResourceName, "target_region.0.storage_account_type", "Standard_LRS"), + ), + }, + }, + }) +} + +func TestAccDataSourceAzureRMSharedImageVersion_recent(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_shared_image_version", "test") + username := "testadmin" + password := "Password1234!" + hostname := fmt.Sprintf("tftestcustomimagesrc%d", data.RandomInteger) + resourceGroup := fmt.Sprintf("acctestRG-%d", data.RandomInteger) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSharedImageVersionDestroy, + Steps: []resource.TestStep{ + { + // need to create a vm and then reference it in the image creation + Config: testAccAzureRMSharedImageVersion_setup(data, username, password, hostname), + Destroy: false, + Check: resource.ComposeTestCheckFunc( + testCheckAzureVMExists("azurerm_virtual_machine.testsource", true), + testGeneralizeVMImage(resourceGroup, "testsource", username, password, hostname, "22", data.Locations.Primary), + ), + }, + { + Config: testAccDataSourceSharedImageVersion_customName(data, username, password, hostname, "recent"), + Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet(data.ResourceName, "managed_image_id"), resource.TestCheckResourceAttr(data.ResourceName, "target_region.#", "1"), resource.TestCheckResourceAttr(data.ResourceName, "target_region.0.storage_account_type", "Standard_LRS"), @@ -55,3 +120,31 @@ data "azurerm_shared_image_version" "test" { } `, template) } + +func testAccDataSourceSharedImageVersion_customName(data acceptance.TestData, username, password, hostname, name string) string { + template := testAccAzureRMSharedImageVersion_imageVersion(data, username, password, hostname) + return fmt.Sprintf(` +%s + +resource "azurerm_shared_image_version" "test2" { + name = "0.0.2" + gallery_name = azurerm_shared_image_gallery.test.name + image_name = azurerm_shared_image.test.name + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + managed_image_id = azurerm_image.test.id + + target_region { + name = azurerm_resource_group.test.location + regional_replica_count = 1 + } +} + +data "azurerm_shared_image_version" "test" { + name = "%s" + gallery_name = azurerm_shared_image_version.test2.gallery_name + image_name = azurerm_shared_image_version.test2.image_name + resource_group_name = azurerm_shared_image_version.test2.resource_group_name +} +`, template, name) +} diff --git a/azurerm/internal/services/compute/tests/data_source_shared_image_versions_test.go b/azurerm/internal/services/compute/tests/data_source_shared_image_versions_test.go index 877153860c15..3b0c6574963e 100644 --- a/azurerm/internal/services/compute/tests/data_source_shared_image_versions_test.go +++ b/azurerm/internal/services/compute/tests/data_source_shared_image_versions_test.go @@ -33,7 +33,6 @@ func TestAccDataSourceAzureRMSharedImageVersions_basic(t *testing.T) { { Config: testAccDataSourceSharedImageVersions_basic(data, username, password, hostname), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(data.ResourceName, "images.0.tags.%", "0"), resource.TestCheckResourceAttrSet(data.ResourceName, "images.0.managed_image_id"), resource.TestCheckResourceAttr(data.ResourceName, "images.0.target_region.#", "1"), resource.TestCheckResourceAttr(data.ResourceName, "images.0.target_region.0.storage_account_type", "Standard_LRS"), diff --git a/website/docs/d/shared_image_version.html.markdown b/website/docs/d/shared_image_version.html.markdown index 6c7e45c86e3d..b34f4ef62552 100644 --- a/website/docs/d/shared_image_version.html.markdown +++ b/website/docs/d/shared_image_version.html.markdown @@ -28,6 +28,8 @@ The following arguments are supported: * `name` - The name of the Image Version. +~> **Note:** You may specify `latest` to obtain the latest version or `recent` to obtain the most recently updated version. + * `image_name` - The name of the Shared Image in which this Version exists. * `gallery_name` - The name of the Shared Image in which the Shared Image exists.