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 }