From 8ea8588c525199b94ac9c343e718072cd254e119 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Mon, 20 Feb 2017 10:03:17 -0700 Subject: [PATCH] provider/openstack: Image Data Source (#12097) * vendor: Updating Gophercloud * provider/openstack: Image Data Source This commit adds the openstack_images_image_v2 data source which is able to query the Image Service v2 API for a specific image. --- .../data_source_openstack_images_image.go | 255 ++++++++++++++++++ ...data_source_openstack_images_image_test.go | 136 ++++++++++ builtin/providers/openstack/provider.go | 4 + .../imageservice/v2/imagedata/requests.go | 2 +- .../imageservice/v2/images/results.go | 5 + vendor/vendor.json | 12 +- .../openstack/d/images_image_v2.html.markdown | 71 +++++ website/source/layouts/openstack.erb | 9 + 8 files changed, 487 insertions(+), 7 deletions(-) create mode 100644 builtin/providers/openstack/data_source_openstack_images_image.go create mode 100644 builtin/providers/openstack/data_source_openstack_images_image_test.go create mode 100644 website/source/docs/providers/openstack/d/images_image_v2.html.markdown diff --git a/builtin/providers/openstack/data_source_openstack_images_image.go b/builtin/providers/openstack/data_source_openstack_images_image.go new file mode 100644 index 000000000..da03b2be7 --- /dev/null +++ b/builtin/providers/openstack/data_source_openstack_images_image.go @@ -0,0 +1,255 @@ +package openstack + +import ( + "fmt" + "log" + "sort" + + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" + "github.com/gophercloud/gophercloud/pagination" + + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceImagesImageV2() *schema.Resource { + return &schema.Resource{ + Read: dataSourceImagesImageV2Read, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), + }, + + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "visibility": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "owner": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "size_min": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "size_max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "sort_key": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "name", + }, + + "sort_direction": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "asc", + ValidateFunc: dataSourceImagesImageV2SortDirection, + }, + + "tag": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "most_recent": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + + // Computed values + "container_format": { + Type: schema.TypeString, + Computed: true, + }, + + "disk_format": { + Type: schema.TypeString, + Computed: true, + }, + + "min_disk_gb": { + Type: schema.TypeInt, + Computed: true, + }, + + "min_ram_mb": { + Type: schema.TypeInt, + Computed: true, + }, + + "protected": { + Type: schema.TypeBool, + Computed: true, + }, + + "checksum": { + Type: schema.TypeString, + Computed: true, + }, + + "size_bytes": { + Type: schema.TypeInt, + Computed: true, + }, + + "metadata": { + Type: schema.TypeMap, + Computed: true, + }, + + "updated_at": { + Type: schema.TypeString, + Computed: true, + }, + + "file": { + Type: schema.TypeString, + Computed: true, + }, + + "schema": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// dataSourceImagesImageV2Read performs the image lookup. +func dataSourceImagesImageV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + imageClient, err := config.imageV2Client(GetRegion(d)) + if err != nil { + return fmt.Errorf("Error creating OpenStack image client: %s", err) + } + + visibility := resourceImagesImageV2VisibilityFromString(d.Get("visibility").(string)) + + listOpts := images.ListOpts{ + Name: d.Get("name").(string), + Visibility: visibility, + Owner: d.Get("owner").(string), + Status: images.ImageStatusActive, + SizeMin: int64(d.Get("size_min").(int)), + SizeMax: int64(d.Get("size_max").(int)), + SortKey: d.Get("sort_key").(string), + SortDir: d.Get("sort_direction").(string), + Tag: d.Get("tag").(string), + } + + var allImages []images.Image + pager := images.List(imageClient, listOpts) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + images, err := images.ExtractImages(page) + if err != nil { + return false, err + } + + for _, i := range images { + allImages = append(allImages, i) + } + + return true, nil + }) + + if err != nil { + return fmt.Errorf("Unable to retrieve images: %s", err) + } + + var image images.Image + if len(allImages) < 1 { + return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") + } + + if len(allImages) > 1 { + recent := d.Get("most_recent").(bool) + log.Printf("[DEBUG] openstack_images_image: multiple results found and `most_recent` is set to: %t", recent) + if recent { + image = mostRecentImage(allImages) + } else { + return fmt.Errorf("Your query returned more than one result. Please try a more " + + "specific search criteria, or set `most_recent` attribute to true.") + } + } else { + image = allImages[0] + } + + log.Printf("[DEBUG] openstack_images_image: Single Image found: %s", image.ID) + return dataSourceImagesImageV2Attributes(d, &image) +} + +// dataSourceImagesImageV2Attributes populates the fields of an Image resource. +func dataSourceImagesImageV2Attributes(d *schema.ResourceData, image *images.Image) error { + log.Printf("[DEBUG] openstack_images_image details: %#v", image) + + d.SetId(image.ID) + d.Set("name", image.Name) + d.Set("tags", image.Tags) + d.Set("container_format", image.ContainerFormat) + d.Set("disk_format", image.DiskFormat) + d.Set("min_disk_gb", image.MinDiskGigabytes) + d.Set("min_ram_mb", image.MinRAMMegabytes) + d.Set("owner", image.Owner) + d.Set("protected", image.Protected) + d.Set("visibility", image.Visibility) + d.Set("checksum", image.Checksum) + d.Set("size_bytes", image.SizeBytes) + d.Set("metadata", image.Metadata) + d.Set("created_at", image.CreatedAt) + d.Set("updated_at", image.UpdatedAt) + d.Set("file", image.File) + d.Set("schema", image.Schema) + + return nil +} + +type imageSort []images.Image + +func (a imageSort) Len() int { return len(a) } +func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a imageSort) Less(i, j int) bool { + itime := a[i].UpdatedAt + jtime := a[j].UpdatedAt + return itime.Unix() < jtime.Unix() +} + +// Returns the most recent Image out of a slice of images. +func mostRecentImage(images []images.Image) images.Image { + sortedImages := images + sort.Sort(imageSort(sortedImages)) + return sortedImages[len(sortedImages)-1] +} + +func dataSourceImagesImageV2SortDirection(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "asc" && value != "desc" { + err := fmt.Errorf("%s must be either asc or desc", k) + errors = append(errors, err) + } + return +} diff --git a/builtin/providers/openstack/data_source_openstack_images_image_test.go b/builtin/providers/openstack/data_source_openstack_images_image_test.go new file mode 100644 index 000000000..2d134fa66 --- /dev/null +++ b/builtin/providers/openstack/data_source_openstack_images_image_test.go @@ -0,0 +1,136 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccOpenStackImagesV2ImageDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccOpenStackImagesV2ImageDataSource_cirros, + }, + resource.TestStep{ + Config: testAccOpenStackImagesV2ImageDataSource_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesV2DataSourceID("data.openstack_images_image_v2.image_1"), + resource.TestCheckResourceAttr( + "data.openstack_images_image_v2.image_1", "name", "CirrOS-tf"), + resource.TestCheckResourceAttr( + "data.openstack_images_image_v2.image_1", "container_format", "bare"), + resource.TestCheckResourceAttr( + "data.openstack_images_image_v2.image_1", "disk_format", "qcow2"), + resource.TestCheckResourceAttr( + "data.openstack_images_image_v2.image_1", "min_disk_gb", "0"), + resource.TestCheckResourceAttr( + "data.openstack_images_image_v2.image_1", "min_ram_mb", "0"), + resource.TestCheckResourceAttr( + "data.openstack_images_image_v2.image_1", "protected", "false"), + resource.TestCheckResourceAttr( + "data.openstack_images_image_v2.image_1", "visibility", "private"), + ), + }, + }, + }) +} + +func TestAccOpenStackImagesV2ImageDataSource_testQueries(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccOpenStackImagesV2ImageDataSource_cirros, + }, + resource.TestStep{ + Config: testAccOpenStackImagesV2ImageDataSource_queryTag, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesV2DataSourceID("data.openstack_images_image_v2.image_1"), + ), + }, + resource.TestStep{ + Config: testAccOpenStackImagesV2ImageDataSource_querySizeMin, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesV2DataSourceID("data.openstack_images_image_v2.image_1"), + ), + }, + resource.TestStep{ + Config: testAccOpenStackImagesV2ImageDataSource_querySizeMax, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesV2DataSourceID("data.openstack_images_image_v2.image_1"), + ), + }, + }, + }) +} + +func testAccCheckImagesV2DataSourceID(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Can't find image data source: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Image data source ID not set") + } + + return nil + } +} + +// Standard CirrOS image +const testAccOpenStackImagesV2ImageDataSource_cirros = ` +resource "openstack_images_image_v2" "image_1" { + name = "CirrOS-tf" + container_format = "bare" + disk_format = "qcow2" + image_source_url = "http://download.cirros-cloud.net/0.3.5/cirros-0.3.5-x86_64-disk.img" + tags = ["cirros-tf"] +} +` + +var testAccOpenStackImagesV2ImageDataSource_basic = fmt.Sprintf(` +%s + +data "openstack_images_image_v2" "image_1" { + most_recent = true + name = "${openstack_images_image_v2.image_1.name}" +} +`, testAccOpenStackImagesV2ImageDataSource_cirros) + +var testAccOpenStackImagesV2ImageDataSource_queryTag = fmt.Sprintf(` +%s + +data "openstack_images_image_v2" "image_1" { + most_recent = true + visibility = "private" + tag = "cirros-tf" +} +`, testAccOpenStackImagesV2ImageDataSource_cirros) + +var testAccOpenStackImagesV2ImageDataSource_querySizeMin = fmt.Sprintf(` +%s + +data "openstack_images_image_v2" "image_1" { + most_recent = true + visibility = "private" + size_min = "13000000" +} +`, testAccOpenStackImagesV2ImageDataSource_cirros) + +var testAccOpenStackImagesV2ImageDataSource_querySizeMax = fmt.Sprintf(` +%s + +data "openstack_images_image_v2" "image_1" { + most_recent = true + visibility = "private" + size_max = "23000000" +} +`, testAccOpenStackImagesV2ImageDataSource_cirros) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 1772ba531..c4c44fa01 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -134,6 +134,10 @@ func Provider() terraform.ResourceProvider { }, }, + DataSourcesMap: map[string]*schema.Resource{ + "openstack_images_image_v2": dataSourceImagesImageV2(), + }, + ResourcesMap: map[string]*schema.Resource{ "openstack_blockstorage_volume_v1": resourceBlockStorageVolumeV1(), "openstack_blockstorage_volume_v2": resourceBlockStorageVolumeV2(), diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go index b1aac8eb0..7fd6951d3 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/requests.go @@ -8,7 +8,7 @@ import ( ) // Upload uploads image file -func Upload(client *gophercloud.ServiceClient, id string, data io.ReadSeeker) (r UploadResult) { +func Upload(client *gophercloud.ServiceClient, id string, data io.Reader) (r UploadResult) { _, r.Err = client.Put(uploadURL(client, id), data, nil, &gophercloud.RequestOpts{ MoreHeaders: map[string]string{"Content-Type": "application/octet-stream"}, OkCodes: []int{204}, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go index ae674deaf..09996f4af 100644 --- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go +++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go @@ -156,6 +156,11 @@ func (r ImagePage) NextPageURL() (string, error) { if err != nil { return "", err } + + if s.Next == "" { + return "", nil + } + return nextPageURL(r.URL.String(), s.Next), nil } diff --git a/vendor/vendor.json b/vendor/vendor.json index 1b0ef84da..97a961409 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1434,16 +1434,16 @@ "revisionTime": "2017-02-14T04:36:15Z" }, { - "checksumSHA1": "ApyhoqnMmvOyj4SVmCAMEPRt0CY=", + "checksumSHA1": "5+wNKnxGvSGV8lHS+7km0ZiNEts=", "path": "github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata", - "revision": "b06120d13e262ceaf890ef38ee30898813696af0", - "revisionTime": "2017-02-14T04:36:15Z" + "revision": "f47ca3a2d457dd4601b823eb17ecc3094baf5fab", + "revisionTime": "2017-02-17T17:23:12Z" }, { - "checksumSHA1": "suBNiSHEsfnRAY+kGSGg0P2TaUQ=", + "checksumSHA1": "TG1z1hNllqjUgBpNnqZTxHqXBTs=", "path": "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images", - "revision": "b06120d13e262ceaf890ef38ee30898813696af0", - "revisionTime": "2017-02-14T04:36:15Z" + "revision": "f47ca3a2d457dd4601b823eb17ecc3094baf5fab", + "revisionTime": "2017-02-17T17:23:12Z" }, { "checksumSHA1": "aTHxjMlfNXFJ3l2TZyvIwqt/3kM=", diff --git a/website/source/docs/providers/openstack/d/images_image_v2.html.markdown b/website/source/docs/providers/openstack/d/images_image_v2.html.markdown new file mode 100644 index 000000000..70283acf4 --- /dev/null +++ b/website/source/docs/providers/openstack/d/images_image_v2.html.markdown @@ -0,0 +1,71 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_images_image_v2" +sidebar_current: "docs-openstack-datasource-images-image-v2" +description: |- + Get information on an OpenStack Image. +--- + +# openstack\_images\_image\_v2 + +Use this data source to get the ID of an available OpenStack image. + +## Example Usage + +``` +data "openstack_images_image_v2" "ubuntu" { + name = "Ubuntu 16.04" + most_recent = true +} +``` + +## Argument Reference + +* `region` - (Required) The region in which to obtain the V2 Glance client. + A Glance client is needed to create an Image that can be used with + a compute instance. If omitted, the `OS_REGION_NAME` environment variable + is used. + +* `most_recent` - (Optional) If more than one result is returned, use the most + recent image. + +* `name` - (Optional) The name of the image. + +* `owner` - (Optional) The owner (UUID) of the image. + +* `size_min` - (Optional) The minimum size (in bytes) of the image to return. + +* `size_max` - (Optional) The maximum size (in bytes) of the image to return. + +* `sort_direction` - (Optional) Order the results in either `asc` or `desc`. + +* `sort_key` - (Optional) Sort images based on a certain key. Defaults to `name`. + +* `tag` - (Optional) Search for images with a specific tag. + +* `visibility` - (Optional) The visibility of the image. Must be one of + "public", "private", "community", or "shared". Defaults to "private". + + +## Attributes Reference + +`id` is set to the ID of the found image. In addition, the following attributes +are exported: + +* `checksum` - The checksum of the data associated with the image. +* `created_at` - The date the image was created. +* `container_format`: The format of the image's container. +* `disk_format`: The format of the image's disk. +* `file` - the trailing path after the glance endpoint that represent the +location of the image or the path to retrieve it. +* `metadata` - The metadata associated with the image. + Image metadata allow for meaningfully define the image properties + and tags. See http://docs.openstack.org/developer/glance/metadefs-concepts.html. +* `min_disk_gb`: The minimum amount of disk space required to use the image. +* `min_ram_mb`: The minimum amount of ram required to use the image. +* `protected` - Whether or not the image is protected. +* `schema` - The path to the JSON-schema that represent + the image or image +* `size_bytes` - The size of the image (in bytes). +* `tags` - See Argument Reference above. +* `update_at` - The date the image was last updated. diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index b196b6260..4f0581349 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -10,6 +10,15 @@ OpenStack Provider + > + Data Sources + + + > Block Storage Resources