github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/google/resource_compute_url_map.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 "strconv" 7 8 "github.com/hashicorp/terraform/helper/schema" 9 "google.golang.org/api/compute/v1" 10 "google.golang.org/api/googleapi" 11 ) 12 13 func resourceComputeUrlMap() *schema.Resource { 14 return &schema.Resource{ 15 Create: resourceComputeUrlMapCreate, 16 Read: resourceComputeUrlMapRead, 17 Update: resourceComputeUrlMapUpdate, 18 Delete: resourceComputeUrlMapDelete, 19 20 Schema: map[string]*schema.Schema{ 21 "default_service": &schema.Schema{ 22 Type: schema.TypeString, 23 Required: true, 24 }, 25 26 "name": &schema.Schema{ 27 Type: schema.TypeString, 28 Required: true, 29 ForceNew: true, 30 }, 31 32 "description": &schema.Schema{ 33 Type: schema.TypeString, 34 Optional: true, 35 }, 36 37 "fingerprint": &schema.Schema{ 38 Type: schema.TypeString, 39 Computed: true, 40 }, 41 42 "host_rule": &schema.Schema{ 43 Type: schema.TypeSet, 44 Optional: true, 45 // TODO(evandbrown): Enable when lists support validation 46 //ValidateFunc: validateHostRules, 47 Elem: &schema.Resource{ 48 Schema: map[string]*schema.Schema{ 49 "description": &schema.Schema{ 50 Type: schema.TypeString, 51 Optional: true, 52 }, 53 54 "hosts": &schema.Schema{ 55 Type: schema.TypeList, 56 Required: true, 57 Elem: &schema.Schema{Type: schema.TypeString}, 58 }, 59 60 "path_matcher": &schema.Schema{ 61 Type: schema.TypeString, 62 Required: true, 63 }, 64 }, 65 }, 66 }, 67 68 "id": &schema.Schema{ 69 Type: schema.TypeString, 70 Computed: true, 71 }, 72 73 "path_matcher": &schema.Schema{ 74 Type: schema.TypeList, 75 Optional: true, 76 Elem: &schema.Resource{ 77 Schema: map[string]*schema.Schema{ 78 "default_service": &schema.Schema{ 79 Type: schema.TypeString, 80 Required: true, 81 }, 82 83 "description": &schema.Schema{ 84 Type: schema.TypeString, 85 Optional: true, 86 }, 87 88 "name": &schema.Schema{ 89 Type: schema.TypeString, 90 Required: true, 91 }, 92 93 "path_rule": &schema.Schema{ 94 Type: schema.TypeList, 95 Required: true, 96 Elem: &schema.Resource{ 97 Schema: map[string]*schema.Schema{ 98 "paths": &schema.Schema{ 99 Type: schema.TypeList, 100 Required: true, 101 Elem: &schema.Schema{Type: schema.TypeString}, 102 }, 103 104 "service": &schema.Schema{ 105 Type: schema.TypeString, 106 Required: true, 107 }, 108 }, 109 }, 110 }, 111 }, 112 }, 113 }, 114 115 "project": &schema.Schema{ 116 Type: schema.TypeString, 117 Optional: true, 118 ForceNew: true, 119 }, 120 121 "self_link": &schema.Schema{ 122 Type: schema.TypeString, 123 Computed: true, 124 }, 125 126 "test": &schema.Schema{ 127 Type: schema.TypeList, 128 Optional: true, 129 Elem: &schema.Resource{ 130 Schema: map[string]*schema.Schema{ 131 "description": &schema.Schema{ 132 Type: schema.TypeString, 133 Optional: true, 134 }, 135 136 "host": &schema.Schema{ 137 Type: schema.TypeString, 138 Required: true, 139 }, 140 141 "path": &schema.Schema{ 142 Type: schema.TypeString, 143 Required: true, 144 }, 145 146 "service": &schema.Schema{ 147 Type: schema.TypeString, 148 Required: true, 149 }, 150 }, 151 }, 152 }, 153 }, 154 } 155 } 156 157 func createHostRule(v interface{}) *compute.HostRule { 158 _hostRule := v.(map[string]interface{}) 159 160 _hosts := _hostRule["hosts"].([]interface{}) 161 hosts := make([]string, len(_hosts)) 162 163 for i, v := range _hosts { 164 hosts[i] = v.(string) 165 } 166 167 pathMatcher := _hostRule["path_matcher"].(string) 168 169 hostRule := &compute.HostRule{ 170 Hosts: hosts, 171 PathMatcher: pathMatcher, 172 } 173 174 if v, ok := _hostRule["description"]; ok { 175 hostRule.Description = v.(string) 176 } 177 178 return hostRule 179 } 180 181 func createPathMatcher(v interface{}) *compute.PathMatcher { 182 _pathMatcher := v.(map[string]interface{}) 183 184 _pathRules := _pathMatcher["path_rule"].([]interface{}) 185 pathRules := make([]*compute.PathRule, len(_pathRules)) 186 187 for ip, vp := range _pathRules { 188 _pathRule := vp.(map[string]interface{}) 189 190 _paths := _pathRule["paths"].([]interface{}) 191 paths := make([]string, len(_paths)) 192 193 for ipp, vpp := range _paths { 194 paths[ipp] = vpp.(string) 195 } 196 197 service := _pathRule["service"].(string) 198 199 pathRule := &compute.PathRule{ 200 Paths: paths, 201 Service: service, 202 } 203 204 pathRules[ip] = pathRule 205 } 206 207 name := _pathMatcher["name"].(string) 208 defaultService := _pathMatcher["default_service"].(string) 209 210 pathMatcher := &compute.PathMatcher{ 211 PathRules: pathRules, 212 Name: name, 213 DefaultService: defaultService, 214 } 215 216 if vp, okp := _pathMatcher["description"]; okp { 217 pathMatcher.Description = vp.(string) 218 } 219 220 return pathMatcher 221 } 222 223 func createUrlMapTest(v interface{}) *compute.UrlMapTest { 224 _test := v.(map[string]interface{}) 225 226 host := _test["host"].(string) 227 path := _test["path"].(string) 228 service := _test["service"].(string) 229 230 test := &compute.UrlMapTest{ 231 Host: host, 232 Path: path, 233 Service: service, 234 } 235 236 if vp, okp := _test["description"]; okp { 237 test.Description = vp.(string) 238 } 239 240 return test 241 } 242 243 func resourceComputeUrlMapCreate(d *schema.ResourceData, meta interface{}) error { 244 config := meta.(*Config) 245 246 project, err := getProject(d, config) 247 if err != nil { 248 return err 249 } 250 251 name := d.Get("name").(string) 252 defaultService := d.Get("default_service").(string) 253 254 urlMap := &compute.UrlMap{ 255 Name: name, 256 DefaultService: defaultService, 257 } 258 259 if v, ok := d.GetOk("description"); ok { 260 urlMap.Description = v.(string) 261 } 262 263 _hostRules := d.Get("host_rule").(*schema.Set) 264 urlMap.HostRules = make([]*compute.HostRule, _hostRules.Len()) 265 266 for i, v := range _hostRules.List() { 267 urlMap.HostRules[i] = createHostRule(v) 268 } 269 270 _pathMatchers := d.Get("path_matcher").([]interface{}) 271 urlMap.PathMatchers = make([]*compute.PathMatcher, len(_pathMatchers)) 272 273 for i, v := range _pathMatchers { 274 urlMap.PathMatchers[i] = createPathMatcher(v) 275 } 276 277 _tests := make([]interface{}, 0) 278 if v, ok := d.GetOk("test"); ok { 279 _tests = v.([]interface{}) 280 } 281 urlMap.Tests = make([]*compute.UrlMapTest, len(_tests)) 282 283 for i, v := range _tests { 284 urlMap.Tests[i] = createUrlMapTest(v) 285 } 286 287 op, err := config.clientCompute.UrlMaps.Insert(project, urlMap).Do() 288 289 if err != nil { 290 return fmt.Errorf("Error, failed to insert Url Map %s: %s", name, err) 291 } 292 293 err = computeOperationWaitGlobal(config, op, project, "Insert Url Map") 294 295 if err != nil { 296 return fmt.Errorf("Error, failed waitng to insert Url Map %s: %s", name, err) 297 } 298 299 return resourceComputeUrlMapRead(d, meta) 300 } 301 302 func resourceComputeUrlMapRead(d *schema.ResourceData, meta interface{}) error { 303 config := meta.(*Config) 304 305 project, err := getProject(d, config) 306 if err != nil { 307 return err 308 } 309 310 name := d.Get("name").(string) 311 312 urlMap, err := config.clientCompute.UrlMaps.Get(project, name).Do() 313 314 if err != nil { 315 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 316 log.Printf("[WARN] Removing URL Map %q because it's gone", d.Get("name").(string)) 317 // The resource doesn't exist anymore 318 d.SetId("") 319 320 return nil 321 } 322 323 return fmt.Errorf("Error, failed to get Url Map %s: %s", name, err) 324 } 325 326 d.SetId(name) 327 d.Set("self_link", urlMap.SelfLink) 328 d.Set("id", strconv.FormatUint(urlMap.Id, 10)) 329 d.Set("fingerprint", urlMap.Fingerprint) 330 331 hostRuleMap := make(map[string]*compute.HostRule) 332 for _, v := range urlMap.HostRules { 333 hostRuleMap[v.PathMatcher] = v 334 } 335 336 /* Only read host rules into our TF state that we have defined */ 337 _hostRules := d.Get("host_rule").(*schema.Set).List() 338 _newHostRules := make([]interface{}, 0) 339 for _, v := range _hostRules { 340 _hostRule := v.(map[string]interface{}) 341 _pathMatcher := _hostRule["path_matcher"].(string) 342 343 /* Delete local entries that are no longer found on the GCE server */ 344 if hostRule, ok := hostRuleMap[_pathMatcher]; ok { 345 _newHostRule := make(map[string]interface{}) 346 _newHostRule["path_matcher"] = _pathMatcher 347 348 hostsSet := make(map[string]bool) 349 for _, host := range hostRule.Hosts { 350 hostsSet[host] = true 351 } 352 353 /* Only store hosts we are keeping track of */ 354 _newHosts := make([]interface{}, 0) 355 for _, vp := range _hostRule["hosts"].([]interface{}) { 356 if _, okp := hostsSet[vp.(string)]; okp { 357 _newHosts = append(_newHosts, vp) 358 } 359 } 360 361 _newHostRule["hosts"] = _newHosts 362 _newHostRule["description"] = hostRule.Description 363 364 _newHostRules = append(_newHostRules, _newHostRule) 365 } 366 } 367 368 d.Set("host_rule", _newHostRules) 369 370 pathMatcherMap := make(map[string]*compute.PathMatcher) 371 for _, v := range urlMap.PathMatchers { 372 pathMatcherMap[v.Name] = v 373 } 374 375 /* Only read path matchers into our TF state that we have defined */ 376 _pathMatchers := d.Get("path_matcher").([]interface{}) 377 _newPathMatchers := make([]interface{}, 0) 378 for _, v := range _pathMatchers { 379 _pathMatcher := v.(map[string]interface{}) 380 _name := _pathMatcher["name"].(string) 381 382 if pathMatcher, ok := pathMatcherMap[_name]; ok { 383 _newPathMatcher := make(map[string]interface{}) 384 _newPathMatcher["name"] = _name 385 _newPathMatcher["default_service"] = pathMatcher.DefaultService 386 _newPathMatcher["description"] = pathMatcher.Description 387 388 _newPathRules := make([]interface{}, len(pathMatcher.PathRules)) 389 for ip, pathRule := range pathMatcher.PathRules { 390 _newPathRule := make(map[string]interface{}) 391 _newPathRule["service"] = pathRule.Service 392 _paths := make([]interface{}, len(pathRule.Paths)) 393 394 for ipp, vpp := range pathRule.Paths { 395 _paths[ipp] = vpp 396 } 397 398 _newPathRule["paths"] = _paths 399 400 _newPathRules[ip] = _newPathRule 401 } 402 403 _newPathMatcher["path_rule"] = _newPathRules 404 _newPathMatchers = append(_newPathMatchers, _newPathMatcher) 405 } 406 } 407 408 d.Set("path_matcher", _newPathMatchers) 409 410 testMap := make(map[string]*compute.UrlMapTest) 411 for _, v := range urlMap.Tests { 412 testMap[fmt.Sprintf("%s/%s", v.Host, v.Path)] = v 413 } 414 415 _tests := make([]interface{}, 0) 416 /* Only read tests into our TF state that we have defined */ 417 if v, ok := d.GetOk("test"); ok { 418 _tests = v.([]interface{}) 419 } 420 _newTests := make([]interface{}, 0) 421 for _, v := range _tests { 422 _test := v.(map[string]interface{}) 423 _host := _test["host"].(string) 424 _path := _test["path"].(string) 425 426 /* Delete local entries that are no longer found on the GCE server */ 427 if test, ok := testMap[fmt.Sprintf("%s/%s", _host, _path)]; ok { 428 _newTest := make(map[string]interface{}) 429 _newTest["host"] = _host 430 _newTest["path"] = _path 431 _newTest["description"] = test.Description 432 _newTest["service"] = test.Service 433 434 _newTests = append(_newTests, _newTest) 435 } 436 } 437 438 d.Set("test", _newTests) 439 440 return nil 441 } 442 443 func resourceComputeUrlMapUpdate(d *schema.ResourceData, meta interface{}) error { 444 config := meta.(*Config) 445 446 project, err := getProject(d, config) 447 if err != nil { 448 return err 449 } 450 451 name := d.Get("name").(string) 452 urlMap, err := config.clientCompute.UrlMaps.Get(project, name).Do() 453 if err != nil { 454 return fmt.Errorf("Error, failed to get Url Map %s: %s", name, err) 455 } 456 457 urlMap.DefaultService = d.Get("default_service").(string) 458 459 if v, ok := d.GetOk("description"); ok { 460 urlMap.Description = v.(string) 461 } 462 463 if d.HasChange("host_rule") { 464 _oldHostRules, _newHostRules := d.GetChange("host_rule") 465 _oldHostRulesMap := make(map[string]interface{}) 466 _newHostRulesMap := make(map[string]interface{}) 467 468 for _, v := range _oldHostRules.(*schema.Set).List() { 469 _hostRule := v.(map[string]interface{}) 470 _oldHostRulesMap[_hostRule["path_matcher"].(string)] = v 471 } 472 473 for _, v := range _newHostRules.(*schema.Set).List() { 474 _hostRule := v.(map[string]interface{}) 475 _newHostRulesMap[_hostRule["path_matcher"].(string)] = v 476 } 477 478 newHostRules := make([]*compute.HostRule, 0) 479 /* Decide which host rules to keep */ 480 for _, v := range urlMap.HostRules { 481 /* If it's in the old state, we have ownership over the host rule */ 482 if vOld, ok := _oldHostRulesMap[v.PathMatcher]; ok { 483 if vNew, ok := _newHostRulesMap[v.PathMatcher]; ok { 484 /* Adjust for any changes made to this rule */ 485 _newHostRule := vNew.(map[string]interface{}) 486 _oldHostRule := vOld.(map[string]interface{}) 487 _newHostsSet := make(map[string]bool) 488 _oldHostsSet := make(map[string]bool) 489 490 hostRule := &compute.HostRule{ 491 PathMatcher: v.PathMatcher, 492 } 493 494 for _, v := range _newHostRule["hosts"].([]interface{}) { 495 _newHostsSet[v.(string)] = true 496 } 497 498 for _, v := range _oldHostRule["hosts"].([]interface{}) { 499 _oldHostsSet[v.(string)] = true 500 } 501 502 /* Only add hosts that have been added locally or are new, 503 * not touching those from the GCE server state */ 504 for _, host := range v.Hosts { 505 _, okNew := _newHostsSet[host] 506 _, okOld := _oldHostsSet[host] 507 508 /* Drop deleted hosts */ 509 if okOld && !okNew { 510 continue 511 } 512 513 hostRule.Hosts = append(hostRule.Hosts, host) 514 515 /* Kep track of the fact that this host was added */ 516 delete(_newHostsSet, host) 517 } 518 519 /* Now add in the brand new entries */ 520 for host, _ := range _newHostsSet { 521 hostRule.Hosts = append(hostRule.Hosts, host) 522 } 523 524 if v, ok := _newHostRule["description"]; ok { 525 hostRule.Description = v.(string) 526 } 527 528 newHostRules = append(newHostRules, hostRule) 529 530 /* Record that we've include this host rule */ 531 delete(_newHostRulesMap, v.PathMatcher) 532 } else { 533 /* It's been deleted */ 534 continue 535 } 536 } else { 537 if vNew, ok := _newHostRulesMap[v.PathMatcher]; ok { 538 newHostRules = append(newHostRules, createHostRule(vNew)) 539 540 /* Record that we've include this host rule */ 541 delete(_newHostRulesMap, v.PathMatcher) 542 } else { 543 /* It wasn't created or modified locally */ 544 newHostRules = append(newHostRules, v) 545 } 546 } 547 } 548 549 /* Record brand new host rules (ones not deleted above) */ 550 for _, v := range _newHostRulesMap { 551 newHostRules = append(newHostRules, createHostRule(v)) 552 } 553 554 urlMap.HostRules = newHostRules 555 } 556 557 if d.HasChange("path_matcher") { 558 _oldPathMatchers, _newPathMatchers := d.GetChange("path_matcher") 559 _oldPathMatchersMap := make(map[string]interface{}) 560 _newPathMatchersMap := make(map[string]interface{}) 561 562 for _, v := range _oldPathMatchers.([]interface{}) { 563 _pathMatcher := v.(map[string]interface{}) 564 _oldPathMatchersMap[_pathMatcher["name"].(string)] = v 565 } 566 567 for _, v := range _newPathMatchers.([]interface{}) { 568 _pathMatcher := v.(map[string]interface{}) 569 _newPathMatchersMap[_pathMatcher["name"].(string)] = v 570 } 571 572 newPathMatchers := make([]*compute.PathMatcher, 0) 573 /* Decide which path matchers to keep */ 574 for _, v := range urlMap.PathMatchers { 575 /* If it's in the old state, we have ownership over the host rule */ 576 _, okOld := _oldPathMatchersMap[v.Name] 577 vNew, okNew := _newPathMatchersMap[v.Name] 578 579 /* Drop deleted entries */ 580 if okOld && !okNew { 581 continue 582 } 583 584 /* Don't change entries that don't belong to us */ 585 if !okNew { 586 newPathMatchers = append(newPathMatchers, v) 587 } else { 588 newPathMatchers = append(newPathMatchers, createPathMatcher(vNew)) 589 590 delete(_newPathMatchersMap, v.Name) 591 } 592 } 593 594 /* Record brand new host rules */ 595 for _, v := range _newPathMatchersMap { 596 newPathMatchers = append(newPathMatchers, createPathMatcher(v)) 597 } 598 599 urlMap.PathMatchers = newPathMatchers 600 } 601 602 if d.HasChange("test") { 603 _oldTests, _newTests := d.GetChange("test") 604 _oldTestsMap := make(map[string]interface{}) 605 _newTestsMap := make(map[string]interface{}) 606 607 for _, v := range _oldTests.([]interface{}) { 608 _test := v.(map[string]interface{}) 609 ident := fmt.Sprintf("%s/%s", _test["host"].(string), _test["path"].(string)) 610 _oldTestsMap[ident] = v 611 } 612 613 for _, v := range _newTests.([]interface{}) { 614 _test := v.(map[string]interface{}) 615 ident := fmt.Sprintf("%s/%s", _test["host"].(string), _test["path"].(string)) 616 _newTestsMap[ident] = v 617 } 618 619 newTests := make([]*compute.UrlMapTest, 0) 620 /* Decide which path matchers to keep */ 621 for _, v := range urlMap.Tests { 622 ident := fmt.Sprintf("%s/%s", v.Host, v.Path) 623 /* If it's in the old state, we have ownership over the host rule */ 624 _, okOld := _oldTestsMap[ident] 625 vNew, okNew := _newTestsMap[ident] 626 627 /* Drop deleted entries */ 628 if okOld && !okNew { 629 continue 630 } 631 632 /* Don't change entries that don't belong to us */ 633 if !okNew { 634 newTests = append(newTests, v) 635 } else { 636 newTests = append(newTests, createUrlMapTest(vNew)) 637 638 delete(_newTestsMap, ident) 639 } 640 } 641 642 /* Record brand new host rules */ 643 for _, v := range _newTestsMap { 644 newTests = append(newTests, createUrlMapTest(v)) 645 } 646 647 urlMap.Tests = newTests 648 } 649 op, err := config.clientCompute.UrlMaps.Update(project, urlMap.Name, urlMap).Do() 650 651 if err != nil { 652 return fmt.Errorf("Error, failed to update Url Map %s: %s", name, err) 653 } 654 655 err = computeOperationWaitGlobal(config, op, project, "Update Url Map") 656 657 if err != nil { 658 return fmt.Errorf("Error, failed waitng to update Url Map %s: %s", name, err) 659 } 660 661 return resourceComputeUrlMapRead(d, meta) 662 } 663 664 func resourceComputeUrlMapDelete(d *schema.ResourceData, meta interface{}) error { 665 config := meta.(*Config) 666 667 project, err := getProject(d, config) 668 if err != nil { 669 return err 670 } 671 672 name := d.Get("name").(string) 673 674 op, err := config.clientCompute.UrlMaps.Delete(project, name).Do() 675 676 if err != nil { 677 return fmt.Errorf("Error, failed to delete Url Map %s: %s", name, err) 678 } 679 680 err = computeOperationWaitGlobal(config, op, project, "Delete Url Map") 681 682 if err != nil { 683 return fmt.Errorf("Error, failed waitng to delete Url Map %s: %s", name, err) 684 } 685 686 return nil 687 } 688 689 func validateHostRules(v interface{}, k string) (ws []string, es []error) { 690 pathMatchers := make(map[string]bool) 691 hostRules := v.([]interface{}) 692 for _, hri := range hostRules { 693 hr := hri.(map[string]interface{}) 694 pm := hr["path_matcher"].(string) 695 if pathMatchers[pm] { 696 es = append(es, fmt.Errorf("Multiple host_rule entries with the same path_matcher are not allowed. Please collapse all hosts with the same path_matcher into one host_rule")) 697 return 698 } 699 pathMatchers[pm] = true 700 } 701 return 702 }