provider/openstack: Fixing Image ID/Name areas

This commit cleans up areas that configure the image_id and image_name.

It enables the ability to not have to specify an image_id or image_name
when booting from a volume.

It also prevents Terraform from reporting an error when an image name is no
longer able to be resolved from an image ID. This usually happens when the
image has been deleted, but there are still running instances that were based
off of it.

The image_id and image_name parameters no longer immediately take a default
value from the OS_IMAGE_ID and OS_IMAGE_NAME environment variables. If no other
resolution of an image_id or image_name were found, then these variables will
be referenced. This further supports booting from a volume.

Finally, documentation was updated to take into account booting from a volume.
This commit is contained in:
Joe Topjian 2015-09-12 20:21:55 +00:00
parent 3d3f8122a9
commit 4a5cd0b415
2 changed files with 90 additions and 55 deletions

View File

@ -6,6 +6,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"log" "log"
"os"
"time" "time"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
@ -45,18 +46,16 @@ func resourceComputeInstanceV2() *schema.Resource {
ForceNew: false, ForceNew: false,
}, },
"image_id": &schema.Schema{ "image_id": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Computed: true, Computed: true,
DefaultFunc: envDefaultFunc("OS_IMAGE_ID"),
}, },
"image_name": &schema.Schema{ "image_name": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Computed: true, Computed: true,
DefaultFunc: envDefaultFunc("OS_IMAGE_NAME"),
}, },
"flavor_id": &schema.Schema{ "flavor_id": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -297,7 +296,11 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
var createOpts servers.CreateOptsBuilder var createOpts servers.CreateOptsBuilder
imageId, err := getImageID(computeClient, d) // Determines the Image ID using the following rules:
// If a bootable block_device was specified, ignore the image altogether.
// If an image_id was specified, use it.
// If an image_name was specified, look up the image ID, report if error.
imageId, err := getImageIDFromConfig(computeClient, d)
if err != nil { if err != nil {
return err return err
} }
@ -372,7 +375,16 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
} }
log.Printf("[DEBUG] Create Options: %#v", createOpts) log.Printf("[DEBUG] Create Options: %#v", createOpts)
server, err := servers.Create(computeClient, createOpts).Extract()
// If a block_device is used, use the bootfromvolume.Create function as it allows an empty ImageRef.
// Otherwise, use the normal servers.Create function.
var server *servers.Server
if _, ok := d.GetOk("block_device"); ok {
server, err = bootfromvolume.Create(computeClient, createOpts).Extract()
} else {
server, err = servers.Create(computeClient, createOpts).Extract()
}
if err != nil { if err != nil {
return fmt.Errorf("Error creating OpenStack server: %s", err) return fmt.Errorf("Error creating OpenStack server: %s", err)
} }
@ -554,17 +566,10 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err
} }
d.Set("flavor_name", flavor.Name) d.Set("flavor_name", flavor.Name)
imageId, ok := server.Image["id"].(string) // Set the instance's image information appropriately
if !ok { if err := setImageInformation(computeClient, server, d); err != nil {
return fmt.Errorf("Error setting OpenStack server's image: %v", server.Image)
}
d.Set("image_id", imageId)
image, err := images.Get(computeClient, imageId).Extract()
if err != nil {
return err return err
} }
d.Set("image_name", image.Name)
// volume attachments // volume attachments
if err := getVolumeAttachments(computeClient, d); err != nil { if err := getVolumeAttachments(computeClient, d); err != nil {
@ -980,44 +985,72 @@ func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw
return schedulerHints return schedulerHints
} }
func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { func getImageIDFromConfig(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {
imageId := d.Get("image_id").(string) // If block_device was used, an Image does not need to be specified.
// If an Image was specified, ignore it
if _, ok := d.GetOk("block_device"); ok {
return "", nil
}
if imageId != "" { if imageId := d.Get("image_id").(string); imageId != "" {
return imageId, nil
} else {
// try the OS_IMAGE_ID environment variable
if v := os.Getenv("OS_IMAGE_ID"); v != "" {
return v, nil
}
}
imageName := d.Get("image_name").(string)
if imageName == "" {
// try the OS_IMAGE_NAME environment variable
if v := os.Getenv("OS_IMAGE_NAME"); v != "" {
imageName = v
}
}
if imageName != "" {
imageId, err := images.IDFromName(computeClient, imageName)
if err != nil {
return "", err
}
return imageId, nil return imageId, nil
} }
imageCount := 0 return "", fmt.Errorf("Neither a boot device, image ID, or image name were able to be determined.")
imageName := d.Get("image_name").(string) }
if imageName != "" {
pager := images.ListDetail(client, &images.ListOpts{
Name: imageName,
})
pager.EachPage(func(page pagination.Page) (bool, error) {
imageList, err := images.ExtractImages(page)
if err != nil {
return false, err
}
for _, i := range imageList { func setImageInformation(computeClient *gophercloud.ServiceClient, server *servers.Server, d *schema.ResourceData) error {
if i.Name == imageName { // If block_device was used, an Image does not need to be specified.
imageCount++ // If an Image was specified, ignore it
imageId = i.ID if _, ok := d.GetOk("block_device"); ok {
} d.Set("image_id", "Attempt to boot from volume - no image supplied")
} return nil
return true, nil }
})
switch imageCount { imageId := server.Image["id"].(string)
case 0: if imageId != "" {
return "", fmt.Errorf("Unable to find image: %s", imageName) d.Set("image_id", imageId)
case 1: if image, err := images.Get(computeClient, imageId).Extract(); err != nil {
return imageId, nil errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
default: if !ok {
return "", fmt.Errorf("Found %d images matching %s", imageCount, imageName) return err
}
if errCode.Actual == 404 {
// If the image name can't be found, set the value to "Image not found".
// The most likely scenario is that the image no longer exists in the Image Service
// but the instance still has a record from when it existed.
d.Set("image_name", "Image not found")
return nil
} else {
return err
}
} else {
d.Set("image_name", image.Name)
} }
} }
return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.")
return nil
} }
func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {

View File

@ -35,11 +35,13 @@ The following arguments are supported:
* `name` - (Required) A unique name for the resource. * `name` - (Required) A unique name for the resource.
* `image_id` - (Optional; Required if `image_name` is empty) The image ID of * `image_id` - (Optional; Required if `image_name` is empty and not booting
the desired image for the server. Changing this creates a new server. from a volume) The image ID of the desired image for the server. Changing
this creates a new server.
* `image_name` - (Optional; Required if `image_id` is empty) The name of the * `image_name` - (Optional; Required if `image_id` is empty and not booting
desired image for the server. Changing this creates a new server. from a volume) The name of the desired image for the server. Changing this
creates a new server.
* `flavor_id` - (Optional; Required if `flavor_name` is empty) The flavor ID of * `flavor_id` - (Optional; Required if `flavor_name` is empty) The flavor ID of
the desired flavor for the server. Changing this resizes the existing server. the desired flavor for the server. Changing this resizes the existing server.