github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/google/resource_storage_bucket.go (about)

     1  package google
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/resource"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  
    12  	"google.golang.org/api/googleapi"
    13  	"google.golang.org/api/storage/v1"
    14  )
    15  
    16  func resourceStorageBucket() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceStorageBucketCreate,
    19  		Read:   resourceStorageBucketRead,
    20  		Update: resourceStorageBucketUpdate,
    21  		Delete: resourceStorageBucketDelete,
    22  		Importer: &schema.ResourceImporter{
    23  			State: resourceStorageBucketStateImporter,
    24  		},
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"name": &schema.Schema{
    28  				Type:     schema.TypeString,
    29  				Required: true,
    30  				ForceNew: true,
    31  			},
    32  
    33  			"force_destroy": &schema.Schema{
    34  				Type:     schema.TypeBool,
    35  				Optional: true,
    36  				Default:  false,
    37  			},
    38  
    39  			"location": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Default:  "US",
    42  				Optional: true,
    43  				ForceNew: true,
    44  			},
    45  
    46  			"predefined_acl": &schema.Schema{
    47  				Type:       schema.TypeString,
    48  				Deprecated: "Please use resource \"storage_bucket_acl.predefined_acl\" instead.",
    49  				Optional:   true,
    50  				ForceNew:   true,
    51  			},
    52  
    53  			"project": &schema.Schema{
    54  				Type:     schema.TypeString,
    55  				Optional: true,
    56  				ForceNew: true,
    57  			},
    58  
    59  			"self_link": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Computed: true,
    62  			},
    63  
    64  			"url": &schema.Schema{
    65  				Type:     schema.TypeString,
    66  				Computed: true,
    67  			},
    68  
    69  			"storage_class": &schema.Schema{
    70  				Type:     schema.TypeString,
    71  				Optional: true,
    72  				Default:  "STANDARD",
    73  				ForceNew: true,
    74  			},
    75  
    76  			"website": &schema.Schema{
    77  				Type:     schema.TypeList,
    78  				Optional: true,
    79  				Elem: &schema.Resource{
    80  					Schema: map[string]*schema.Schema{
    81  						"main_page_suffix": &schema.Schema{
    82  							Type:     schema.TypeString,
    83  							Optional: true,
    84  						},
    85  						"not_found_page": &schema.Schema{
    86  							Type:     schema.TypeString,
    87  							Optional: true,
    88  						},
    89  					},
    90  				},
    91  			},
    92  
    93  			"cors": &schema.Schema{
    94  				Type:     schema.TypeList,
    95  				Optional: true,
    96  				Elem: &schema.Resource{
    97  					Schema: map[string]*schema.Schema{
    98  						"origin": &schema.Schema{
    99  							Type:     schema.TypeList,
   100  							Optional: true,
   101  							Elem: &schema.Schema{
   102  								Type: schema.TypeString,
   103  							},
   104  						},
   105  						"method": &schema.Schema{
   106  							Type:     schema.TypeList,
   107  							Optional: true,
   108  							Elem: &schema.Schema{
   109  								Type: schema.TypeString,
   110  							},
   111  						},
   112  						"response_header": &schema.Schema{
   113  							Type:     schema.TypeList,
   114  							Optional: true,
   115  							Elem: &schema.Schema{
   116  								Type: schema.TypeString,
   117  							},
   118  						},
   119  						"max_age_seconds": &schema.Schema{
   120  							Type:     schema.TypeInt,
   121  							Optional: true,
   122  						},
   123  					},
   124  				},
   125  			},
   126  		},
   127  	}
   128  }
   129  
   130  func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error {
   131  	config := meta.(*Config)
   132  
   133  	project, err := getProject(d, config)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	// Get the bucket and acl
   139  	bucket := d.Get("name").(string)
   140  	location := d.Get("location").(string)
   141  
   142  	// Create a bucket, setting the acl, location and name.
   143  	sb := &storage.Bucket{Name: bucket, Location: location}
   144  
   145  	if v, ok := d.GetOk("storage_class"); ok {
   146  		sb.StorageClass = v.(string)
   147  	}
   148  
   149  	if v, ok := d.GetOk("website"); ok {
   150  		websites := v.([]interface{})
   151  
   152  		if len(websites) > 1 {
   153  			return fmt.Errorf("At most one website block is allowed")
   154  		}
   155  
   156  		sb.Website = &storage.BucketWebsite{}
   157  
   158  		website := websites[0].(map[string]interface{})
   159  
   160  		if v, ok := website["not_found_page"]; ok {
   161  			sb.Website.NotFoundPage = v.(string)
   162  		}
   163  
   164  		if v, ok := website["main_page_suffix"]; ok {
   165  			sb.Website.MainPageSuffix = v.(string)
   166  		}
   167  	}
   168  
   169  	if v, ok := d.GetOk("cors"); ok {
   170  		sb.Cors = expandCors(v.([]interface{}))
   171  	}
   172  
   173  	var res *storage.Bucket
   174  
   175  	err = resource.Retry(1*time.Minute, func() *resource.RetryError {
   176  		call := config.clientStorage.Buckets.Insert(project, sb)
   177  		if v, ok := d.GetOk("predefined_acl"); ok {
   178  			call = call.PredefinedAcl(v.(string))
   179  		}
   180  
   181  		res, err = call.Do()
   182  		if err == nil {
   183  			return nil
   184  		}
   185  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 429 {
   186  			return resource.RetryableError(gerr)
   187  		}
   188  		return resource.NonRetryableError(err)
   189  	})
   190  
   191  	if err != nil {
   192  		fmt.Printf("Error creating bucket %s: %v", bucket, err)
   193  		return err
   194  	}
   195  
   196  	log.Printf("[DEBUG] Created bucket %v at location %v\n\n", res.Name, res.SelfLink)
   197  
   198  	d.SetId(res.Id)
   199  	return resourceStorageBucketRead(d, meta)
   200  }
   201  
   202  func resourceStorageBucketUpdate(d *schema.ResourceData, meta interface{}) error {
   203  	config := meta.(*Config)
   204  
   205  	sb := &storage.Bucket{}
   206  
   207  	if d.HasChange("website") {
   208  		if v, ok := d.GetOk("website"); ok {
   209  			websites := v.([]interface{})
   210  
   211  			if len(websites) > 1 {
   212  				return fmt.Errorf("At most one website block is allowed")
   213  			}
   214  
   215  			// Setting fields to "" to be explicit that the PATCH call will
   216  			// delete this field.
   217  			if len(websites) == 0 {
   218  				sb.Website.NotFoundPage = ""
   219  				sb.Website.MainPageSuffix = ""
   220  			} else {
   221  				website := websites[0].(map[string]interface{})
   222  				sb.Website = &storage.BucketWebsite{}
   223  				if v, ok := website["not_found_page"]; ok {
   224  					sb.Website.NotFoundPage = v.(string)
   225  				} else {
   226  					sb.Website.NotFoundPage = ""
   227  				}
   228  
   229  				if v, ok := website["main_page_suffix"]; ok {
   230  					sb.Website.MainPageSuffix = v.(string)
   231  				} else {
   232  					sb.Website.MainPageSuffix = ""
   233  				}
   234  			}
   235  		}
   236  	}
   237  
   238  	if v, ok := d.GetOk("cors"); ok {
   239  		sb.Cors = expandCors(v.([]interface{}))
   240  	}
   241  
   242  	res, err := config.clientStorage.Buckets.Patch(d.Get("name").(string), sb).Do()
   243  
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	log.Printf("[DEBUG] Patched bucket %v at location %v\n\n", res.Name, res.SelfLink)
   249  
   250  	// Assign the bucket ID as the resource ID
   251  	d.Set("self_link", res.SelfLink)
   252  	d.SetId(res.Id)
   253  
   254  	return nil
   255  }
   256  
   257  func resourceStorageBucketRead(d *schema.ResourceData, meta interface{}) error {
   258  	config := meta.(*Config)
   259  
   260  	// Get the bucket and acl
   261  	bucket := d.Get("name").(string)
   262  	res, err := config.clientStorage.Buckets.Get(bucket).Do()
   263  
   264  	if err != nil {
   265  		return handleNotFoundError(err, d, fmt.Sprintf("Storage Bucket %q", d.Get("name").(string)))
   266  	}
   267  
   268  	log.Printf("[DEBUG] Read bucket %v at location %v\n\n", res.Name, res.SelfLink)
   269  
   270  	// Update the bucket ID according to the resource ID
   271  	d.Set("self_link", res.SelfLink)
   272  	d.Set("url", fmt.Sprintf("gs://%s", bucket))
   273  	d.Set("storage_class", res.StorageClass)
   274  	d.Set("location", res.Location)
   275  	d.Set("cors", flattenCors(res.Cors))
   276  	d.SetId(res.Id)
   277  	return nil
   278  }
   279  
   280  func resourceStorageBucketDelete(d *schema.ResourceData, meta interface{}) error {
   281  	config := meta.(*Config)
   282  
   283  	// Get the bucket
   284  	bucket := d.Get("name").(string)
   285  
   286  	for {
   287  		res, err := config.clientStorage.Objects.List(bucket).Do()
   288  		if err != nil {
   289  			fmt.Printf("Error Objects.List failed: %v", err)
   290  			return err
   291  		}
   292  
   293  		if len(res.Items) != 0 {
   294  			if d.Get("force_destroy").(bool) {
   295  				// purge the bucket...
   296  				log.Printf("[DEBUG] GCS Bucket attempting to forceDestroy\n\n")
   297  
   298  				for _, object := range res.Items {
   299  					log.Printf("[DEBUG] Found %s", object.Name)
   300  					if err := config.clientStorage.Objects.Delete(bucket, object.Name).Do(); err != nil {
   301  						log.Fatalf("Error trying to delete object: %s %s\n\n", object.Name, err)
   302  					} else {
   303  						log.Printf("Object deleted: %s \n\n", object.Name)
   304  					}
   305  				}
   306  
   307  			} else {
   308  				delete_err := errors.New("Error trying to delete a bucket containing objects without `force_destroy` set to true")
   309  				log.Printf("Error! %s : %s\n\n", bucket, delete_err)
   310  				return delete_err
   311  			}
   312  		} else {
   313  			break // 0 items, bucket empty
   314  		}
   315  	}
   316  
   317  	// remove empty bucket
   318  	err := resource.Retry(1*time.Minute, func() *resource.RetryError {
   319  		err := config.clientStorage.Buckets.Delete(bucket).Do()
   320  		if err == nil {
   321  			return nil
   322  		}
   323  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 429 {
   324  			return resource.RetryableError(gerr)
   325  		}
   326  		return resource.NonRetryableError(err)
   327  	})
   328  	if err != nil {
   329  		fmt.Printf("Error deleting bucket %s: %v\n\n", bucket, err)
   330  		return err
   331  	}
   332  	log.Printf("[DEBUG] Deleted bucket %v\n\n", bucket)
   333  
   334  	return nil
   335  }
   336  
   337  func resourceStorageBucketStateImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
   338  	d.Set("name", d.Id())
   339  	return []*schema.ResourceData{d}, nil
   340  }
   341  
   342  func expandCors(configured []interface{}) []*storage.BucketCors {
   343  	corsRules := make([]*storage.BucketCors, 0, len(configured))
   344  	for _, raw := range configured {
   345  		data := raw.(map[string]interface{})
   346  		corsRule := storage.BucketCors{
   347  			Origin:         convertSchemaArrayToStringArray(data["origin"].([]interface{})),
   348  			Method:         convertSchemaArrayToStringArray(data["method"].([]interface{})),
   349  			ResponseHeader: convertSchemaArrayToStringArray(data["response_header"].([]interface{})),
   350  			MaxAgeSeconds:  int64(data["max_age_seconds"].(int)),
   351  		}
   352  
   353  		corsRules = append(corsRules, &corsRule)
   354  	}
   355  	return corsRules
   356  }
   357  
   358  func convertSchemaArrayToStringArray(input []interface{}) []string {
   359  	output := make([]string, 0, len(input))
   360  	for _, val := range input {
   361  		output = append(output, val.(string))
   362  	}
   363  
   364  	return output
   365  }
   366  
   367  func flattenCors(corsRules []*storage.BucketCors) []map[string]interface{} {
   368  	corsRulesSchema := make([]map[string]interface{}, 0, len(corsRules))
   369  	for _, corsRule := range corsRules {
   370  		data := map[string]interface{}{
   371  			"origin":          corsRule.Origin,
   372  			"method":          corsRule.Method,
   373  			"response_header": corsRule.ResponseHeader,
   374  			"max_age_seconds": corsRule.MaxAgeSeconds,
   375  		}
   376  
   377  		corsRulesSchema = append(corsRulesSchema, data)
   378  	}
   379  	return corsRulesSchema
   380  }