diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go index 3db0361bd..949621c56 100644 --- a/builtin/providers/openstack/provider_test.go +++ b/builtin/providers/openstack/provider_test.go @@ -60,6 +60,13 @@ func testAccPreCheck(t *testing.T) { } } +func testAccPreCheckAdminOnly(t *testing.T) { + v := os.Getenv("OS_USERNAME") + if v != "admin" { + t.Skip("Skipping test because it requires the admin user") + } +} + func TestProvider(t *testing.T) { if err := Provider().(*schema.Provider).InternalValidate(); err != nil { t.Fatalf("err: %s", err) diff --git a/builtin/providers/openstack/resource_openstack_images_image_v2.go b/builtin/providers/openstack/resource_openstack_images_image_v2.go index 78d6d87b3..5b61e54bb 100644 --- a/builtin/providers/openstack/resource_openstack_images_image_v2.go +++ b/builtin/providers/openstack/resource_openstack_images_image_v2.go @@ -141,9 +141,10 @@ func resourceImagesImageV2() *schema.Resource { }, "tags": &schema.Schema{ - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, }, "update_at": &schema.Schema{ @@ -156,7 +157,7 @@ func resourceImagesImageV2() *schema.Resource { Optional: true, ForceNew: false, ValidateFunc: resourceImagesImageV2ValidateVisibility, - Default: images.ImageVisibilityPrivate, + Default: "private", }, }, } @@ -182,11 +183,8 @@ func resourceImagesImageV2Create(d *schema.ResourceData, meta interface{}) error } if v, ok := d.GetOk("tags"); ok { - var tags []string - for _, tag := range v.([]interface{}) { - tags = append(tags, tag.(string)) - } - createOpts.Tags = tags + tags := v.(*schema.Set).List() + createOpts.Tags = resourceImagesImageV2BuildTags(tags) } d.Partial(true) @@ -273,9 +271,8 @@ func resourceImagesImageV2Read(d *schema.ResourceData, meta interface{}) error { d.Set("name", img.Name) d.Set("protected", img.Protected) d.Set("size_bytes", img.SizeBytes) - d.Set("tags", resourceImagesImageV2RemoveEmptyTags(img.Tags)) + d.Set("tags", img.Tags) d.Set("visibility", img.Visibility) - return nil } @@ -289,7 +286,8 @@ func resourceImagesImageV2Update(d *schema.ResourceData, meta interface{}) error updateOpts := make(images.UpdateOpts, 0) if d.HasChange("visibility") { - v := images.UpdateVisibility{Visibility: d.Get("visibility").(images.ImageVisibility)} + visibility := resourceImagesImageV2VisibilityFromString(d.Get("visibility").(string)) + v := images.UpdateVisibility{Visibility: visibility} updateOpts = append(updateOpts, v) } @@ -299,7 +297,10 @@ func resourceImagesImageV2Update(d *schema.ResourceData, meta interface{}) error } if d.HasChange("tags") { - v := images.ReplaceImageTags{NewTags: d.Get("tags").([]string)} + tags := d.Get("tags").(*schema.Set).List() + v := images.ReplaceImageTags{ + NewTags: resourceImagesImageV2BuildTags(tags), + } updateOpts = append(updateOpts, v) } @@ -330,12 +331,22 @@ func resourceImagesImageV2Delete(d *schema.ResourceData, meta interface{}) error } func resourceImagesImageV2ValidateVisibility(v interface{}, k string) (ws []string, errors []error) { - value := v.(images.ImageVisibility) - if value == images.ImageVisibilityPublic || value == images.ImageVisibilityPrivate || value == images.ImageVisibilityShared || value == images.ImageVisibilityCommunity { - return + value := v.(string) + validVisibilities := []string{ + "public", + "private", + "shared", + "community", } - errors = append(errors, fmt.Errorf("%q must be one of %q, %q, %q, %q", k, images.ImageVisibilityPublic, images.ImageVisibilityPrivate, images.ImageVisibilityCommunity, images.ImageVisibilityShared)) + for _, v := range validVisibilities { + if value == v { + return + } + } + + err := fmt.Errorf("%s must be one of %s", k, validVisibilities) + errors = append(errors, err) return } @@ -476,12 +487,11 @@ func resourceImagesImageV2RefreshFunc(client *gophercloud.ServiceClient, id stri } } -func resourceImagesImageV2RemoveEmptyTags(s []string) []string { - var r []string - for _, str := range s { - if str != "" { - r = append(r, str) - } +func resourceImagesImageV2BuildTags(v []interface{}) []string { + var tags []string + for _, tag := range v { + tags = append(tags, tag.(string)) } - return r + + return tags } diff --git a/builtin/providers/openstack/resource_openstack_images_image_v2_test.go b/builtin/providers/openstack/resource_openstack_images_image_v2_test.go index a1b9572bb..cbaf716ba 100644 --- a/builtin/providers/openstack/resource_openstack_images_image_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_images_image_v2_test.go @@ -36,7 +36,7 @@ func TestAccImagesImageV2_basic(t *testing.T) { }) } -func TestAccImagesImageV2_with_tags(t *testing.T) { +func TestAccImagesImageV2_name(t *testing.T) { var image images.Image resource.Test(t, resource.TestCase{ @@ -45,7 +45,35 @@ func TestAccImagesImageV2_with_tags(t *testing.T) { CheckDestroy: testAccCheckImagesImageV2Destroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccImagesImageV2_with_tags, + Config: testAccImagesImageV2_name_1, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesImageV2Exists("openstack_images_image_v2.image_1", &image), + resource.TestCheckResourceAttr( + "openstack_images_image_v2.image_1", "name", "Rancher TerraformAccTest"), + ), + }, + resource.TestStep{ + Config: testAccImagesImageV2_name_2, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesImageV2Exists("openstack_images_image_v2.image_1", &image), + resource.TestCheckResourceAttr( + "openstack_images_image_v2.image_1", "name", "TerraformAccTest Rancher"), + ), + }, + }, + }) +} + +func TestAccImagesImageV2_tags(t *testing.T) { + var image images.Image + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckImagesImageV2Destroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccImagesImageV2_tags_1, Check: resource.ComposeTestCheckFunc( testAccCheckImagesImageV2Exists("openstack_images_image_v2.image_1", &image), testAccCheckImagesImageV2HasTag("openstack_images_image_v2.image_1", "foo"), @@ -53,6 +81,56 @@ func TestAccImagesImageV2_with_tags(t *testing.T) { testAccCheckImagesImageV2TagCount("openstack_images_image_v2.image_1", 2), ), }, + resource.TestStep{ + Config: testAccImagesImageV2_tags_2, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesImageV2Exists("openstack_images_image_v2.image_1", &image), + testAccCheckImagesImageV2HasTag("openstack_images_image_v2.image_1", "foo"), + testAccCheckImagesImageV2HasTag("openstack_images_image_v2.image_1", "bar"), + testAccCheckImagesImageV2HasTag("openstack_images_image_v2.image_1", "baz"), + testAccCheckImagesImageV2TagCount("openstack_images_image_v2.image_1", 3), + ), + }, + resource.TestStep{ + Config: testAccImagesImageV2_tags_3, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesImageV2Exists("openstack_images_image_v2.image_1", &image), + testAccCheckImagesImageV2HasTag("openstack_images_image_v2.image_1", "foo"), + testAccCheckImagesImageV2HasTag("openstack_images_image_v2.image_1", "baz"), + testAccCheckImagesImageV2TagCount("openstack_images_image_v2.image_1", 2), + ), + }, + }, + }) +} + +func TestAccImagesImageV2_visibility(t *testing.T) { + var image images.Image + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAdminOnly(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckImagesImageV2Destroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccImagesImageV2_visibility_1, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesImageV2Exists("openstack_images_image_v2.image_1", &image), + resource.TestCheckResourceAttr( + "openstack_images_image_v2.image_1", "visibility", "private"), + ), + }, + resource.TestStep{ + Config: testAccImagesImageV2_visibility_2, + Check: resource.ComposeTestCheckFunc( + testAccCheckImagesImageV2Exists("openstack_images_image_v2.image_1", &image), + resource.TestCheckResourceAttr( + "openstack_images_image_v2.image_1", "visibility", "public"), + ), + }, }, }) } @@ -188,7 +266,23 @@ var testAccImagesImageV2_basic = ` disk_format = "qcow2" }` -var testAccImagesImageV2_with_tags = ` +var testAccImagesImageV2_name_1 = ` + resource "openstack_images_image_v2" "image_1" { + name = "Rancher TerraformAccTest" + image_source_url = "https://releases.rancher.com/os/latest/rancheros-openstack.img" + container_format = "bare" + disk_format = "qcow2" + }` + +var testAccImagesImageV2_name_2 = ` + resource "openstack_images_image_v2" "image_1" { + name = "TerraformAccTest Rancher" + image_source_url = "https://releases.rancher.com/os/latest/rancheros-openstack.img" + container_format = "bare" + disk_format = "qcow2" + }` + +var testAccImagesImageV2_tags_1 = ` resource "openstack_images_image_v2" "image_1" { name = "Rancher TerraformAccTest" image_source_url = "https://releases.rancher.com/os/latest/rancheros-openstack.img" @@ -196,3 +290,39 @@ var testAccImagesImageV2_with_tags = ` disk_format = "qcow2" tags = ["foo","bar"] }` + +var testAccImagesImageV2_tags_2 = ` + resource "openstack_images_image_v2" "image_1" { + name = "Rancher TerraformAccTest" + image_source_url = "https://releases.rancher.com/os/latest/rancheros-openstack.img" + container_format = "bare" + disk_format = "qcow2" + tags = ["foo","bar","baz"] + }` + +var testAccImagesImageV2_tags_3 = ` + resource "openstack_images_image_v2" "image_1" { + name = "Rancher TerraformAccTest" + image_source_url = "https://releases.rancher.com/os/latest/rancheros-openstack.img" + container_format = "bare" + disk_format = "qcow2" + tags = ["foo","baz"] + }` + +var testAccImagesImageV2_visibility_1 = ` + resource "openstack_images_image_v2" "image_1" { + name = "Rancher TerraformAccTest" + image_source_url = "https://releases.rancher.com/os/latest/rancheros-openstack.img" + container_format = "bare" + disk_format = "qcow2" + visibility = "private" + }` + +var testAccImagesImageV2_visibility_2 = ` + resource "openstack_images_image_v2" "image_1" { + name = "Rancher TerraformAccTest" + image_source_url = "https://releases.rancher.com/os/latest/rancheros-openstack.img" + container_format = "bare" + disk_format = "qcow2" + visibility = "public" + }` diff --git a/website/source/docs/providers/openstack/r/images_image_v2.html.markdown b/website/source/docs/providers/openstack/r/images_image_v2.html.markdown index 72933adfc..252e40374 100644 --- a/website/source/docs/providers/openstack/r/images_image_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/images_image_v2.html.markdown @@ -35,11 +35,11 @@ The following arguments are supported: that will be uploaded to Glance. Conflicts with `image_source_url`. * `image_cache_path` - (Optional) This is the directory where the images will - be downloaded. Images will be stored with a filename corresponding to + be downloaded. Images will be stored with a filename corresponding to the url's md5 hash. Defaults to "$HOME/.terraform/image_cache" * `image_source_url` - (Optional) This is the url of the raw image that will - be downloaded in the `image_cache_path` before being uploaded to Glance. + be downloaded in the `image_cache_path` before being uploaded to Glance. Glance is able to download image from internet but the `gophercloud` library does not yet provide a way to do so. Conflicts with `local_file_path`. @@ -49,9 +49,9 @@ The following arguments are supported: * `min_ram_mb` - (Optional) Amount of ram (in MB) required to boot image. Defauts to 0. - + * `name` - (Required) The name of the image. - + * `protected` - (Optional) If true, image will not be deletable. Defaults to false. @@ -61,9 +61,11 @@ The following arguments are supported: is used. Changing this creates a new Image. * `tags` - (Optional) The tags of the image. It must be a list of strings. - -* `visibility` - (Optional) The visibility of the image. Must be one of - "public", "private", "community", or "shared". + At this time, it is not possible to delete all tags of an image. + +* `visibility` - (Optional) The visibility of the image. Must be one of + "public", "private", "community", or "shared". The ability to set the + visibility depends upon the configuration of the OpenStack cloud. Note: The `properties` attribute handling in the gophercloud library is currently buggy and needs to be fixed before being implemented in this resource. @@ -76,8 +78,8 @@ The following attributes are exported: * `container_format` - See Argument Reference above. * `created_at` - The date the image was created. * `disk_format` - See Argument Reference above. -* `file` - the trailing path after the glance - endpoint that represent the location of the image +* `file` - the trailing path after the glance + endpoint that represent the location of the image or the path to retrieve it. * `id` - A unique ID assigned by Glance. * `metadata` - The metadata associated with the image. @@ -89,8 +91,8 @@ The following attributes are exported: * `owner` - The id of the openstack user who owns the image. * `protected` - See Argument Reference above. * `region` - See Argument Reference above. -* `schema` - The path to the JSON-schema that represent - the image or image +* `schema` - The path to the JSON-schema that represent + the image or image * `size_bytes` - The size in bytes of the data associated with the image. * `status` - The status of the image. It can be "queued", "active" or "saving". diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index 6de3b7641..b196b6260 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -49,6 +49,15 @@ +