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:
parent
3d3f8122a9
commit
4a5cd0b415
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue