From 9a8b01ef062697f3bb3602d37d6155278bb8de05 Mon Sep 17 00:00:00 2001 From: arcturusZhang Date: Thu, 24 Sep 2020 15:12:18 +0800 Subject: [PATCH 1/6] Add data source azurerm_images --- .../services/compute/images_data_source.go | 200 ++++++++++++++++++ .../internal/services/compute/registration.go | 1 + .../compute/tests/images_data_source_test.go | 165 +++++++++++++++ website/docs/d/images.html.markdown | 86 ++++++++ 4 files changed, 452 insertions(+) create mode 100644 azurerm/internal/services/compute/images_data_source.go create mode 100644 azurerm/internal/services/compute/tests/images_data_source_test.go create mode 100644 website/docs/d/images.html.markdown diff --git a/azurerm/internal/services/compute/images_data_source.go b/azurerm/internal/services/compute/images_data_source.go new file mode 100644 index 000000000000..1ea6e327f573 --- /dev/null +++ b/azurerm/internal/services/compute/images_data_source.go @@ -0,0 +1,200 @@ +package compute + +import ( + "context" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceArmImages() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmImagesRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "resource_group_name": azure.SchemaResourceGroupNameForDataSource(), + + "tags_filter": tags.Schema(), + + "images": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + + "location": location.SchemaComputed(), + + "zone_resilient": { + Type: schema.TypeBool, + Computed: true, + }, + + "os_disk": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "blob_uri": { + Type: schema.TypeString, + Computed: true, + }, + "caching": { + Type: schema.TypeString, + Computed: true, + }, + "managed_disk_id": { + Type: schema.TypeString, + Computed: true, + }, + "os_state": { + Type: schema.TypeString, + Computed: true, + }, + "os_type": { + Type: schema.TypeString, + Computed: true, + }, + "size_gb": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + + "data_disk": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "blob_uri": { + Type: schema.TypeString, + Computed: true, + }, + "caching": { + Type: schema.TypeString, + Computed: true, + }, + "lun": { + Type: schema.TypeInt, + Computed: true, + }, + "managed_disk_id": { + Type: schema.TypeString, + Computed: true, + }, + "size_gb": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + + "tags": tags.SchemaDataSource(), + }, + }, + }, + }, + } +} + +func dataSourceArmImagesRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.ImagesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + resourceGroup := d.Get("resource_group_name").(string) + filterTags := tags.Expand(d.Get("tags_filter").(map[string]interface{})) + + resp, err := client.ListByResourceGroupComplete(ctx, resourceGroup) + if err != nil { + if utils.ResponseWasNotFound(resp.Response().Response) { + return fmt.Errorf("Images (Resource Group %q) was not found", resourceGroup) + } + return fmt.Errorf("retrieving Images (Resource Group %q): %+v", resourceGroup, err) + } + + images, err := flattenImages(ctx, resp, filterTags) + if err != nil { + return fmt.Errorf("retrieving Images (Resource Group %q): %+v", resourceGroup, err) + } + if len(images) == 0 { + return fmt.Errorf("unable to find any images") + } + + d.SetId(time.Now().UTC().String()) + + d.Set("resource_group_name", resourceGroup) + + if err := d.Set("images", images); err != nil { + return fmt.Errorf("setting `images`: %+v", err) + } + + return nil +} + +func flattenImages(ctx context.Context, iterator compute.ImageListResultIterator, filterTags map[string]*string) ([]interface{}, error) { + results := make([]interface{}, 0) + + var err error + for ; iterator.NotDone(); err = iterator.NextWithContext(ctx) { + if err != nil { + return nil, fmt.Errorf("loading Images list: %+v", err) + } + image := iterator.Value() + found := true + // Loop through our filter tags and see if they match + for k, v := range filterTags { + if v != nil { + // If the tags do not match return false + if image.Tags[k] == nil || *v != *image.Tags[k] { + found = false + } + } + } + + if found { + results = append(results, flattenImage(image)) + } + } + + return results, nil +} + +func flattenImage(input compute.Image) map[string]interface{} { + output := make(map[string]interface{}) + + output["name"] = input.Name + output["location"] = location.NormalizeNilable(input.Location) + + if input.ImageProperties != nil { + if storageProfile := input.ImageProperties.StorageProfile; storageProfile != nil { + output["zone_resilient"] = storageProfile.ZoneResilient + + output["os_disk"] = flattenAzureRmImageOSDisk(storageProfile.OsDisk) + + output["data_disk"] = flattenAzureRmImageDataDisks(storageProfile.DataDisks) + } + } + + output["tags"] = tags.Flatten(input.Tags) + + return output +} diff --git a/azurerm/internal/services/compute/registration.go b/azurerm/internal/services/compute/registration.go index 31c32398ac68..8a351193d8ca 100644 --- a/azurerm/internal/services/compute/registration.go +++ b/azurerm/internal/services/compute/registration.go @@ -27,6 +27,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { "azurerm_disk_encryption_set": dataSourceArmDiskEncryptionSet(), "azurerm_managed_disk": dataSourceArmManagedDisk(), "azurerm_image": dataSourceArmImage(), + "azurerm_images": dataSourceArmImages(), "azurerm_platform_image": dataSourceArmPlatformImage(), "azurerm_proximity_placement_group": dataSourceArmProximityPlacementGroup(), "azurerm_shared_image_gallery": dataSourceArmSharedImageGallery(), diff --git a/azurerm/internal/services/compute/tests/images_data_source_test.go b/azurerm/internal/services/compute/tests/images_data_source_test.go new file mode 100644 index 000000000000..28a247746fca --- /dev/null +++ b/azurerm/internal/services/compute/tests/images_data_source_test.go @@ -0,0 +1,165 @@ +package tests + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" +) + +func TestAccDataSourceAzureRMImages_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_images", "test") + + resourceGroup := fmt.Sprintf("acctestRG-%d", data.RandomInteger) + userName := "testadmin" + password := "Password1234!" + hostName := fmt.Sprintf("tftestcustomimagesrc%d", data.RandomInteger) + sshPort := "22" + storageType := "LRS" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMImageDestroy, + Steps: []resource.TestStep{ + { + // need to create a vm and then reference it in the image creation + Config: testAccAzureRMImage_standaloneImage_setup(data, userName, password, hostName, storageType), + Check: resource.ComposeTestCheckFunc( + testCheckAzureVMExists("azurerm_virtual_machine.testsource", true), + testGeneralizeVMImage(resourceGroup, "testsource", userName, password, hostName, sshPort, data.Locations.Primary), + ), + }, + { + Config: testAccAzureRMImage_standaloneImage_provision(data, userName, password, hostName, storageType, ""), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMImageExists("azurerm_image.test", true), + ), + }, + { + Config: testAccDataSourceImages_basic(data, userName, password, hostName, storageType), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(data.ResourceName, "images.#", "1"), + resource.TestCheckResourceAttr(data.ResourceName, "images.0.os_disk.0.os_type", "Linux"), + ), + }, + }, + }) +} + +func TestAccDataSourceAzureRMImages_tagsFilterError(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_images", "test") + + resourceGroup := fmt.Sprintf("acctestRG-%d", data.RandomInteger) + userName := "testadmin" + password := "Password1234!" + hostName := fmt.Sprintf("tftestcustomimagesrc%d", data.RandomInteger) + sshPort := "22" + storageType := "LRS" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMImageDestroy, + Steps: []resource.TestStep{ + { + // need to create a vm and then reference it in the image creation + Config: testAccAzureRMImage_standaloneImage_setup(data, userName, password, hostName, storageType), + Check: resource.ComposeTestCheckFunc( + testCheckAzureVMExists("azurerm_virtual_machine.testsource", true), + testGeneralizeVMImage(resourceGroup, "testsource", userName, password, hostName, sshPort, data.Locations.Primary), + ), + }, + { + Config: testAccAzureRMImage_standaloneImage_provision(data, userName, password, hostName, storageType, ""), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMImageExists("azurerm_image.test", true), + ), + }, + { + Config: testAccDataSourceImages_tagsFilterError(data, userName, password, hostName, storageType), + ExpectError: regexp.MustCompile("unable to find any images"), + }, + }, + }) +} + +func TestAccDataSourceAzureRMImages_tagsFilter(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_images", "test") + + resourceGroup := fmt.Sprintf("acctestRG-%d", data.RandomInteger) + userName := "testadmin" + password := "Password1234!" + hostName := fmt.Sprintf("tftestcustomimagesrc%d", data.RandomInteger) + sshPort := "22" + storageType := "LRS" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMImageDestroy, + Steps: []resource.TestStep{ + { + // need to create a vm and then reference it in the image creation + Config: testAccAzureRMImage_standaloneImage_setup(data, userName, password, hostName, storageType), + Check: resource.ComposeTestCheckFunc( + testCheckAzureVMExists("azurerm_virtual_machine.testsource", true), + testGeneralizeVMImage(resourceGroup, "testsource", userName, password, hostName, sshPort, data.Locations.Primary), + ), + }, + { + Config: testAccAzureRMImage_standaloneImage_provision(data, userName, password, hostName, storageType, ""), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMImageExists("azurerm_image.test", true), + ), + }, + { + Config: testAccDataSourceImages_tagsFilter(data, userName, password, hostName, storageType), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(data.ResourceName, "images.#", "1"), + ), + }, + }, + }) +} + +func testAccDataSourceImages_basic(data acceptance.TestData, userName, password, hostName, storageType string) string { + template := testAccAzureRMImage_standaloneImage_provision(data, userName, password, hostName, storageType, "") + return fmt.Sprintf(` +%s + +data "azurerm_images" "test" { + resource_group_name = azurerm_image.test.resource_group_name +} +`, template) +} + +func testAccDataSourceImages_tagsFilterError(data acceptance.TestData, userName, password, hostName, storageType string) string { + template := testAccAzureRMImage_standaloneImage_provision(data, userName, password, hostName, storageType, "") + return fmt.Sprintf(` +%s + +data "azurerm_images" "test" { + resource_group_name = azurerm_image.test.resource_group_name + tags_filter = { + environment = "error" + } +} +`, template) +} + +func testAccDataSourceImages_tagsFilter(data acceptance.TestData, userName, password, hostName, storageType string) string { + template := testAccAzureRMImage_standaloneImage_provision(data, userName, password, hostName, storageType, "") + return fmt.Sprintf(` +%s + +data "azurerm_images" "test" { + resource_group_name = azurerm_image.test.resource_group_name + tags_filter = { + environment = "Dev" + } +} +`, template) +} diff --git a/website/docs/d/images.html.markdown b/website/docs/d/images.html.markdown new file mode 100644 index 000000000000..551fb3b95e04 --- /dev/null +++ b/website/docs/d/images.html.markdown @@ -0,0 +1,86 @@ +--- +subcategory: "Compute" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_images" +description: |- + Gets information about existing Images within a Resource Group. + +--- + +# Data Source: azurerm_images + +Use this data source to access information about existing Images within a Resource Group. + +## Example Usage + +```hcl +data "azurerm_images" "example" { + resource_group_name = "example-resources" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `resource_group_name` - The name of the Resource Group in which the Image exists. + +* `tags_filter` - A mapping of tags to filter the list of images against. + +## Attributes Reference + +The following attributes are exported: + +* `images` - An `images` block as defined below: + +--- + +A `images` block exports the following: + +* `name` - The name of the Image. + +* `location` - The supported Azure location where the Image exists. + +* `zone_resilient` - Is zone resiliency enabled? + +* `os_disk` - An `os_disk` block as defined below. + +* `data_disk` - One or more `data_disk` blocks as defined below. + +* `tags` - A mapping of tags assigned to the Shared Image. + +--- + +The `os_disk` block exports the following: + +* `blob_uri` - the URI in Azure storage of the blob used to create the image. + +* `caching` - the caching mode for the OS Disk. + +* `managed_disk_id` - the ID of the Managed Disk used as the OS Disk Image. + +* `os_state` - the State of the OS used in the Image. + +* `os_type` - the type of Operating System used on the OS Disk. + +* `size_gb` - the size of the OS Disk in GB. + +--- + +The `data_disk` block exports the following: + +* `blob_uri` - the URI in Azure storage of the blob used to create the image. + +* `caching` - the caching mode for the Data Disk. + +* `lun` - the logical unit number of the data disk. + +* `managed_disk_id` - the ID of the Managed Disk used as the Data Disk Image. + +* `size_gb` - the size of this Data Disk in GB. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when retrieving the Versions of a Shared Image within a Shared Image Gallery. From b4f37047262be9d088348c846618c73d3f56c829 Mon Sep 17 00:00:00 2001 From: arcturusZhang Date: Thu, 24 Sep 2020 15:15:35 +0800 Subject: [PATCH 2/6] doc update --- website/docs/d/images.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/d/images.html.markdown b/website/docs/d/images.html.markdown index 551fb3b95e04..766c5cdb2e9e 100644 --- a/website/docs/d/images.html.markdown +++ b/website/docs/d/images.html.markdown @@ -31,7 +31,7 @@ The following arguments are supported: The following attributes are exported: -* `images` - An `images` block as defined below: +* `images` - One or more `images` blocks as defined below: --- From ad85732300d87bad9da730f7536c432c052667db Mon Sep 17 00:00:00 2001 From: arcturusZhang Date: Thu, 24 Sep 2020 15:16:24 +0800 Subject: [PATCH 3/6] fix a typo --- website/docs/d/images.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/d/images.html.markdown b/website/docs/d/images.html.markdown index 766c5cdb2e9e..8bd130a99374 100644 --- a/website/docs/d/images.html.markdown +++ b/website/docs/d/images.html.markdown @@ -83,4 +83,4 @@ The `data_disk` block exports the following: The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: -* `read` - (Defaults to 5 minutes) Used when retrieving the Versions of a Shared Image within a Shared Image Gallery. +* `read` - (Defaults to 5 minutes) Used when retrieving the Images within a Resource Group. From 381fdef16cc6de0f3f3607b8c7f345d2283e9eff Mon Sep 17 00:00:00 2001 From: arcturusZhang Date: Thu, 24 Sep 2020 15:16:51 +0800 Subject: [PATCH 4/6] Another typo --- website/docs/d/images.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/d/images.html.markdown b/website/docs/d/images.html.markdown index 8bd130a99374..d3db934c3800 100644 --- a/website/docs/d/images.html.markdown +++ b/website/docs/d/images.html.markdown @@ -47,7 +47,7 @@ A `images` block exports the following: * `data_disk` - One or more `data_disk` blocks as defined below. -* `tags` - A mapping of tags assigned to the Shared Image. +* `tags` - A mapping of tags assigned to the Image. --- From dff3aadf30f5947e6599958dfda0a166b6d01fa8 Mon Sep 17 00:00:00 2001 From: Dapeng Zhang Date: Tue, 27 Oct 2020 12:00:41 +0800 Subject: [PATCH 5/6] Resolve comments --- .../services/compute/images_data_source.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/azurerm/internal/services/compute/images_data_source.go b/azurerm/internal/services/compute/images_data_source.go index 1ea6e327f573..98dfba74c638 100644 --- a/azurerm/internal/services/compute/images_data_source.go +++ b/azurerm/internal/services/compute/images_data_source.go @@ -126,17 +126,17 @@ func dataSourceArmImagesRead(d *schema.ResourceData, meta interface{}) error { resp, err := client.ListByResourceGroupComplete(ctx, resourceGroup) if err != nil { if utils.ResponseWasNotFound(resp.Response().Response) { - return fmt.Errorf("Images (Resource Group %q) was not found", resourceGroup) + return fmt.Errorf("no images were found in Resource Group %q", resourceGroup) } return fmt.Errorf("retrieving Images (Resource Group %q): %+v", resourceGroup, err) } - images, err := flattenImages(ctx, resp, filterTags) + images, err := flattenImagesResult(ctx, resp, filterTags) if err != nil { - return fmt.Errorf("retrieving Images (Resource Group %q): %+v", resourceGroup, err) + return fmt.Errorf("parsing Images (Resource Group %q): %+v", resourceGroup, err) } if len(images) == 0 { - return fmt.Errorf("unable to find any images") + return fmt.Errorf("no images were found that match the specified tags") } d.SetId(time.Now().UTC().String()) @@ -150,14 +150,10 @@ func dataSourceArmImagesRead(d *schema.ResourceData, meta interface{}) error { return nil } -func flattenImages(ctx context.Context, iterator compute.ImageListResultIterator, filterTags map[string]*string) ([]interface{}, error) { +func flattenImagesResult(ctx context.Context, iterator compute.ImageListResultIterator, filterTags map[string]*string) ([]interface{}, error) { results := make([]interface{}, 0) - var err error - for ; iterator.NotDone(); err = iterator.NextWithContext(ctx) { - if err != nil { - return nil, fmt.Errorf("loading Images list: %+v", err) - } + for iterator.NotDone() { image := iterator.Value() found := true // Loop through our filter tags and see if they match @@ -173,6 +169,9 @@ func flattenImages(ctx context.Context, iterator compute.ImageListResultIterator if found { results = append(results, flattenImage(image)) } + if err := iterator.NextWithContext(ctx); err != nil { + return nil, err + } } return results, nil From cf2edecf14a4e26fc4f206e6ff90865fed3db668 Mon Sep 17 00:00:00 2001 From: arcturusZhang Date: Mon, 2 Nov 2020 10:24:32 +0800 Subject: [PATCH 6/6] fix wrong error message --- .../internal/services/compute/tests/images_data_source_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/compute/tests/images_data_source_test.go b/azurerm/internal/services/compute/tests/images_data_source_test.go index 28a247746fca..555db0c24910 100644 --- a/azurerm/internal/services/compute/tests/images_data_source_test.go +++ b/azurerm/internal/services/compute/tests/images_data_source_test.go @@ -80,7 +80,7 @@ func TestAccDataSourceAzureRMImages_tagsFilterError(t *testing.T) { }, { Config: testAccDataSourceImages_tagsFilterError(data, userName, password, hostName, storageType), - ExpectError: regexp.MustCompile("unable to find any images"), + ExpectError: regexp.MustCompile("no images were found that match the specified tags"), }, }, })