github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/openstack/resource_openstack_images_image_v2.go (about)

     1  package openstack
     2  
     3  import (
     4  	"crypto/md5"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"net/http"
    10  	"os"
    11  	"path/filepath"
    12  	"time"
    13  
    14  	"github.com/gophercloud/gophercloud"
    15  	"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata"
    16  	"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
    17  
    18  	"github.com/hashicorp/terraform/helper/resource"
    19  	"github.com/hashicorp/terraform/helper/schema"
    20  )
    21  
    22  func resourceImagesImageV2() *schema.Resource {
    23  	return &schema.Resource{
    24  		Create: resourceImagesImageV2Create,
    25  		Read:   resourceImagesImageV2Read,
    26  		Update: resourceImagesImageV2Update,
    27  		Delete: resourceImagesImageV2Delete,
    28  		Importer: &schema.ResourceImporter{
    29  			State: schema.ImportStatePassthrough,
    30  		},
    31  
    32  		Timeouts: &schema.ResourceTimeout{
    33  			Create: schema.DefaultTimeout(30 * time.Minute),
    34  		},
    35  
    36  		Schema: map[string]*schema.Schema{
    37  			"checksum": &schema.Schema{
    38  				Type:     schema.TypeString,
    39  				Computed: true,
    40  			},
    41  
    42  			"container_format": &schema.Schema{
    43  				Type:         schema.TypeString,
    44  				Required:     true,
    45  				ForceNew:     true,
    46  				ValidateFunc: resourceImagesImageV2ValidateContainerFormat,
    47  			},
    48  
    49  			"created_at": &schema.Schema{
    50  				Type:     schema.TypeString,
    51  				Computed: true,
    52  			},
    53  
    54  			"disk_format": &schema.Schema{
    55  				Type:         schema.TypeString,
    56  				Required:     true,
    57  				ForceNew:     true,
    58  				ValidateFunc: resourceImagesImageV2ValidateDiskFormat,
    59  			},
    60  
    61  			"file": &schema.Schema{
    62  				Type:     schema.TypeString,
    63  				Computed: true,
    64  			},
    65  
    66  			"image_cache_path": &schema.Schema{
    67  				Type:     schema.TypeString,
    68  				Optional: true,
    69  				Default:  fmt.Sprintf("%s/.terraform/image_cache", os.Getenv("HOME")),
    70  			},
    71  
    72  			"image_source_url": &schema.Schema{
    73  				Type:          schema.TypeString,
    74  				Optional:      true,
    75  				ForceNew:      true,
    76  				ConflictsWith: []string{"local_file_path"},
    77  			},
    78  
    79  			"local_file_path": &schema.Schema{
    80  				Type:          schema.TypeString,
    81  				Optional:      true,
    82  				ForceNew:      true,
    83  				ConflictsWith: []string{"image_source_url"},
    84  			},
    85  
    86  			"metadata": &schema.Schema{
    87  				Type:     schema.TypeMap,
    88  				Computed: true,
    89  			},
    90  
    91  			"min_disk_gb": &schema.Schema{
    92  				Type:         schema.TypeInt,
    93  				Optional:     true,
    94  				ForceNew:     true,
    95  				ValidateFunc: validatePositiveInt,
    96  				Default:      0,
    97  			},
    98  
    99  			"min_ram_mb": &schema.Schema{
   100  				Type:         schema.TypeInt,
   101  				Optional:     true,
   102  				ForceNew:     true,
   103  				ValidateFunc: validatePositiveInt,
   104  				Default:      0,
   105  			},
   106  
   107  			"name": &schema.Schema{
   108  				Type:     schema.TypeString,
   109  				Required: true,
   110  				ForceNew: false,
   111  			},
   112  
   113  			"owner": &schema.Schema{
   114  				Type:     schema.TypeString,
   115  				Computed: true,
   116  			},
   117  
   118  			"protected": &schema.Schema{
   119  				Type:     schema.TypeBool,
   120  				Optional: true,
   121  				ForceNew: true,
   122  				Default:  false,
   123  			},
   124  
   125  			"region": &schema.Schema{
   126  				Type:        schema.TypeString,
   127  				Required:    true,
   128  				ForceNew:    true,
   129  				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
   130  			},
   131  
   132  			"schema": &schema.Schema{
   133  				Type:     schema.TypeString,
   134  				Computed: true,
   135  			},
   136  
   137  			"size_bytes": &schema.Schema{
   138  				Type:     schema.TypeInt,
   139  				Computed: true,
   140  			},
   141  
   142  			"status": &schema.Schema{
   143  				Type:     schema.TypeString,
   144  				Computed: true,
   145  			},
   146  
   147  			"tags": &schema.Schema{
   148  				Type:     schema.TypeSet,
   149  				Optional: true,
   150  				Elem:     &schema.Schema{Type: schema.TypeString},
   151  				Set:      schema.HashString,
   152  			},
   153  
   154  			"update_at": &schema.Schema{
   155  				Type:     schema.TypeString,
   156  				Computed: true,
   157  			},
   158  
   159  			"visibility": &schema.Schema{
   160  				Type:         schema.TypeString,
   161  				Optional:     true,
   162  				ForceNew:     false,
   163  				ValidateFunc: resourceImagesImageV2ValidateVisibility,
   164  				Default:      "private",
   165  			},
   166  		},
   167  	}
   168  }
   169  
   170  func resourceImagesImageV2Create(d *schema.ResourceData, meta interface{}) error {
   171  	config := meta.(*Config)
   172  	imageClient, err := config.imageV2Client(GetRegion(d))
   173  	if err != nil {
   174  		return fmt.Errorf("Error creating OpenStack image client: %s", err)
   175  	}
   176  
   177  	protected := d.Get("protected").(bool)
   178  	visibility := resourceImagesImageV2VisibilityFromString(d.Get("visibility").(string))
   179  	createOpts := &images.CreateOpts{
   180  		Name:            d.Get("name").(string),
   181  		ContainerFormat: d.Get("container_format").(string),
   182  		DiskFormat:      d.Get("disk_format").(string),
   183  		MinDisk:         d.Get("min_disk_gb").(int),
   184  		MinRAM:          d.Get("min_ram_mb").(int),
   185  		Protected:       &protected,
   186  		Visibility:      &visibility,
   187  	}
   188  
   189  	if v, ok := d.GetOk("tags"); ok {
   190  		tags := v.(*schema.Set).List()
   191  		createOpts.Tags = resourceImagesImageV2BuildTags(tags)
   192  	}
   193  
   194  	d.Partial(true)
   195  
   196  	log.Printf("[DEBUG] Create Options: %#v", createOpts)
   197  	newImg, err := images.Create(imageClient, createOpts).Extract()
   198  	if err != nil {
   199  		return fmt.Errorf("Error creating Image: %s", err)
   200  	}
   201  
   202  	d.SetId(newImg.ID)
   203  
   204  	// downloading/getting image file props
   205  	imgFilePath, err := resourceImagesImageV2File(d)
   206  	if err != nil {
   207  		return fmt.Errorf("Error opening file for Image: %s", err)
   208  
   209  	}
   210  	fileSize, fileChecksum, err := resourceImagesImageV2FileProps(imgFilePath)
   211  	if err != nil {
   212  		return fmt.Errorf("Error getting file props: %s", err)
   213  	}
   214  
   215  	// upload
   216  	imgFile, err := os.Open(imgFilePath)
   217  	if err != nil {
   218  		return fmt.Errorf("Error opening file %q: %s", imgFilePath, err)
   219  	}
   220  	defer imgFile.Close()
   221  	log.Printf("[WARN] Uploading image %s (%d bytes). This can be pretty long.", d.Id(), fileSize)
   222  
   223  	res := imagedata.Upload(imageClient, d.Id(), imgFile)
   224  	if res.Err != nil {
   225  		return fmt.Errorf("Error while uploading file %q: %s", imgFilePath, res.Err)
   226  	}
   227  
   228  	//wait for active
   229  	stateConf := &resource.StateChangeConf{
   230  		Pending:    []string{string(images.ImageStatusQueued), string(images.ImageStatusSaving)},
   231  		Target:     []string{string(images.ImageStatusActive)},
   232  		Refresh:    resourceImagesImageV2RefreshFunc(imageClient, d.Id(), fileSize, fileChecksum),
   233  		Timeout:    d.Timeout(schema.TimeoutCreate),
   234  		Delay:      10 * time.Second,
   235  		MinTimeout: 3 * time.Second,
   236  	}
   237  
   238  	if _, err = stateConf.WaitForState(); err != nil {
   239  		return fmt.Errorf("Error waiting for Image: %s", err)
   240  	}
   241  
   242  	d.Partial(false)
   243  
   244  	return resourceImagesImageV2Read(d, meta)
   245  }
   246  
   247  func resourceImagesImageV2Read(d *schema.ResourceData, meta interface{}) error {
   248  	config := meta.(*Config)
   249  	imageClient, err := config.imageV2Client(GetRegion(d))
   250  	if err != nil {
   251  		return fmt.Errorf("Error creating OpenStack image client: %s", err)
   252  	}
   253  
   254  	img, err := images.Get(imageClient, d.Id()).Extract()
   255  	if err != nil {
   256  		return CheckDeleted(d, err, "image")
   257  	}
   258  
   259  	log.Printf("[DEBUG] Retrieved Image %s: %#v", d.Id(), img)
   260  
   261  	d.Set("owner", img.Owner)
   262  	d.Set("status", img.Status)
   263  	d.Set("file", img.File)
   264  	d.Set("schema", img.Schema)
   265  	d.Set("checksum", img.Checksum)
   266  	d.Set("size_bytes", img.SizeBytes)
   267  	d.Set("metadata", img.Metadata)
   268  	d.Set("created_at", img.CreatedAt)
   269  	d.Set("update_at", img.UpdatedAt)
   270  	d.Set("container_format", img.ContainerFormat)
   271  	d.Set("disk_format", img.DiskFormat)
   272  	d.Set("min_disk_gb", img.MinDiskGigabytes)
   273  	d.Set("min_ram_mb", img.MinRAMMegabytes)
   274  	d.Set("file", img.File)
   275  	d.Set("name", img.Name)
   276  	d.Set("protected", img.Protected)
   277  	d.Set("size_bytes", img.SizeBytes)
   278  	d.Set("tags", img.Tags)
   279  	d.Set("visibility", img.Visibility)
   280  	return nil
   281  }
   282  
   283  func resourceImagesImageV2Update(d *schema.ResourceData, meta interface{}) error {
   284  	config := meta.(*Config)
   285  	imageClient, err := config.imageV2Client(GetRegion(d))
   286  	if err != nil {
   287  		return fmt.Errorf("Error creating OpenStack image client: %s", err)
   288  	}
   289  
   290  	updateOpts := make(images.UpdateOpts, 0)
   291  
   292  	if d.HasChange("visibility") {
   293  		visibility := resourceImagesImageV2VisibilityFromString(d.Get("visibility").(string))
   294  		v := images.UpdateVisibility{Visibility: visibility}
   295  		updateOpts = append(updateOpts, v)
   296  	}
   297  
   298  	if d.HasChange("name") {
   299  		v := images.ReplaceImageName{NewName: d.Get("name").(string)}
   300  		updateOpts = append(updateOpts, v)
   301  	}
   302  
   303  	if d.HasChange("tags") {
   304  		tags := d.Get("tags").(*schema.Set).List()
   305  		v := images.ReplaceImageTags{
   306  			NewTags: resourceImagesImageV2BuildTags(tags),
   307  		}
   308  		updateOpts = append(updateOpts, v)
   309  	}
   310  
   311  	log.Printf("[DEBUG] Update Options: %#v", updateOpts)
   312  
   313  	_, err = images.Update(imageClient, d.Id(), updateOpts).Extract()
   314  	if err != nil {
   315  		return fmt.Errorf("Error updating image: %s", err)
   316  	}
   317  
   318  	return resourceImagesImageV2Read(d, meta)
   319  }
   320  
   321  func resourceImagesImageV2Delete(d *schema.ResourceData, meta interface{}) error {
   322  	config := meta.(*Config)
   323  	imageClient, err := config.imageV2Client(GetRegion(d))
   324  	if err != nil {
   325  		return fmt.Errorf("Error creating OpenStack image client: %s", err)
   326  	}
   327  
   328  	log.Printf("[DEBUG] Deleting Image %s", d.Id())
   329  	if err := images.Delete(imageClient, d.Id()).Err; err != nil {
   330  		return fmt.Errorf("Error deleting Image: %s", err)
   331  	}
   332  
   333  	d.SetId("")
   334  	return nil
   335  }
   336  
   337  func resourceImagesImageV2ValidateVisibility(v interface{}, k string) (ws []string, errors []error) {
   338  	value := v.(string)
   339  	validVisibilities := []string{
   340  		"public",
   341  		"private",
   342  		"shared",
   343  		"community",
   344  	}
   345  
   346  	for _, v := range validVisibilities {
   347  		if value == v {
   348  			return
   349  		}
   350  	}
   351  
   352  	err := fmt.Errorf("%s must be one of %s", k, validVisibilities)
   353  	errors = append(errors, err)
   354  	return
   355  }
   356  
   357  func validatePositiveInt(v interface{}, k string) (ws []string, errors []error) {
   358  	value := v.(int)
   359  	if value > 0 {
   360  		return
   361  	}
   362  	errors = append(errors, fmt.Errorf("%q must be a positive integer", k))
   363  	return
   364  }
   365  
   366  var DiskFormats = [9]string{"ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vdi", "iso"}
   367  
   368  func resourceImagesImageV2ValidateDiskFormat(v interface{}, k string) (ws []string, errors []error) {
   369  	value := v.(string)
   370  	for i := range DiskFormats {
   371  		if value == DiskFormats[i] {
   372  			return
   373  		}
   374  	}
   375  	errors = append(errors, fmt.Errorf("%q must be one of %v", k, DiskFormats))
   376  	return
   377  }
   378  
   379  var ContainerFormats = [9]string{"ami", "ari", "aki", "bare", "ovf"}
   380  
   381  func resourceImagesImageV2ValidateContainerFormat(v interface{}, k string) (ws []string, errors []error) {
   382  	value := v.(string)
   383  	for i := range ContainerFormats {
   384  		if value == ContainerFormats[i] {
   385  			return
   386  		}
   387  	}
   388  	errors = append(errors, fmt.Errorf("%q must be one of %v", k, ContainerFormats))
   389  	return
   390  }
   391  
   392  func resourceImagesImageV2VisibilityFromString(v string) images.ImageVisibility {
   393  	switch v {
   394  	case "public":
   395  		return images.ImageVisibilityPublic
   396  	case "private":
   397  		return images.ImageVisibilityPrivate
   398  	case "shared":
   399  		return images.ImageVisibilityShared
   400  	case "community":
   401  		return images.ImageVisibilityCommunity
   402  	}
   403  
   404  	return ""
   405  }
   406  
   407  func fileMD5Checksum(f *os.File) (string, error) {
   408  	hash := md5.New()
   409  	if _, err := io.Copy(hash, f); err != nil {
   410  		return "", err
   411  	}
   412  	return hex.EncodeToString(hash.Sum(nil)), nil
   413  }
   414  
   415  func resourceImagesImageV2FileProps(filename string) (int64, string, error) {
   416  	var filesize int64
   417  	var filechecksum string
   418  
   419  	file, err := os.Open(filename)
   420  	if err != nil {
   421  		return -1, "", fmt.Errorf("Error opening file for Image: %s", err)
   422  
   423  	}
   424  	defer file.Close()
   425  
   426  	fstat, err := file.Stat()
   427  	if err != nil {
   428  		return -1, "", fmt.Errorf("Error reading image file %q: %s", file.Name(), err)
   429  	}
   430  
   431  	filesize = fstat.Size()
   432  	filechecksum, err = fileMD5Checksum(file)
   433  
   434  	if err != nil {
   435  		return -1, "", fmt.Errorf("Error computing image file %q checksum: %s", file.Name(), err)
   436  	}
   437  
   438  	return filesize, filechecksum, nil
   439  }
   440  
   441  func resourceImagesImageV2File(d *schema.ResourceData) (string, error) {
   442  	if filename := d.Get("local_file_path").(string); filename != "" {
   443  		return filename, nil
   444  	} else if furl := d.Get("image_source_url").(string); furl != "" {
   445  		dir := d.Get("image_cache_path").(string)
   446  		os.MkdirAll(dir, 0700)
   447  		filename := filepath.Join(dir, fmt.Sprintf("%x.img", md5.Sum([]byte(furl))))
   448  
   449  		if _, err := os.Stat(filename); err != nil {
   450  			if !os.IsNotExist(err) {
   451  				return "", fmt.Errorf("Error while trying to access file %q: %s", filename, err)
   452  			}
   453  			log.Printf("[DEBUG] File doens't exists %s. will download from %s", filename, furl)
   454  			file, err := os.Create(filename)
   455  			if err != nil {
   456  				return "", fmt.Errorf("Error creating file %q: %s", filename, err)
   457  			}
   458  			defer file.Close()
   459  			resp, err := http.Get(furl)
   460  			if err != nil {
   461  				return "", fmt.Errorf("Error downloading image from %q", furl)
   462  			}
   463  			defer resp.Body.Close()
   464  
   465  			if _, err = io.Copy(file, resp.Body); err != nil {
   466  				return "", fmt.Errorf("Error downloading image %q to file %q: %s", furl, filename, err)
   467  			}
   468  			return filename, nil
   469  		} else {
   470  			log.Printf("[DEBUG] File exists %s", filename)
   471  			return filename, nil
   472  		}
   473  	} else {
   474  		return "", fmt.Errorf("Error in config. no file specified")
   475  	}
   476  }
   477  
   478  func resourceImagesImageV2RefreshFunc(client *gophercloud.ServiceClient, id string, fileSize int64, checksum string) resource.StateRefreshFunc {
   479  	return func() (interface{}, string, error) {
   480  		img, err := images.Get(client, id).Extract()
   481  		if err != nil {
   482  			return nil, "", err
   483  		}
   484  		log.Printf("[DEBUG] OpenStack image status is: %s", img.Status)
   485  
   486  		if img.Checksum != checksum || int64(img.SizeBytes) != fileSize {
   487  			return img, fmt.Sprintf("%s", img.Status), fmt.Errorf("Error wrong size %v or checksum %q", img.SizeBytes, img.Checksum)
   488  		}
   489  
   490  		return img, fmt.Sprintf("%s", img.Status), nil
   491  	}
   492  }
   493  
   494  func resourceImagesImageV2BuildTags(v []interface{}) []string {
   495  	var tags []string
   496  	for _, tag := range v {
   497  		tags = append(tags, tag.(string))
   498  	}
   499  
   500  	return tags
   501  }