github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/fastly/resource_fastly_service_v1.go (about) 1 package fastly 2 3 import ( 4 "crypto/sha1" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "log" 9 "strings" 10 "time" 11 12 "github.com/hashicorp/terraform/helper/schema" 13 gofastly "github.com/sethvargo/go-fastly" 14 ) 15 16 var fastlyNoServiceFoundErr = errors.New("No matching Fastly Service found") 17 18 func resourceServiceV1() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceServiceV1Create, 21 Read: resourceServiceV1Read, 22 Update: resourceServiceV1Update, 23 Delete: resourceServiceV1Delete, 24 Importer: &schema.ResourceImporter{ 25 State: schema.ImportStatePassthrough, 26 }, 27 28 Schema: map[string]*schema.Schema{ 29 "name": { 30 Type: schema.TypeString, 31 Required: true, 32 Description: "Unique name for this Service", 33 }, 34 35 // Active Version represents the currently activated version in Fastly. In 36 // Terraform, we abstract this number away from the users and manage 37 // creating and activating. It's used internally, but also exported for 38 // users to see. 39 "active_version": { 40 Type: schema.TypeString, 41 Computed: true, 42 }, 43 44 "domain": { 45 Type: schema.TypeSet, 46 Required: true, 47 Elem: &schema.Resource{ 48 Schema: map[string]*schema.Schema{ 49 "name": { 50 Type: schema.TypeString, 51 Required: true, 52 Description: "The domain that this Service will respond to", 53 }, 54 55 "comment": { 56 Type: schema.TypeString, 57 Optional: true, 58 }, 59 }, 60 }, 61 }, 62 63 "condition": { 64 Type: schema.TypeSet, 65 Optional: true, 66 Elem: &schema.Resource{ 67 Schema: map[string]*schema.Schema{ 68 "name": { 69 Type: schema.TypeString, 70 Required: true, 71 }, 72 "statement": { 73 Type: schema.TypeString, 74 Required: true, 75 Description: "The statement used to determine if the condition is met", 76 StateFunc: func(v interface{}) string { 77 value := v.(string) 78 // Trim newlines and spaces, to match Fastly API 79 return strings.TrimSpace(value) 80 }, 81 }, 82 "priority": { 83 Type: schema.TypeInt, 84 Required: true, 85 Description: "A number used to determine the order in which multiple conditions execute. Lower numbers execute first", 86 }, 87 "type": { 88 Type: schema.TypeString, 89 Required: true, 90 Description: "Type of the condition, either `REQUEST`, `RESPONSE`, or `CACHE`", 91 }, 92 }, 93 }, 94 }, 95 96 "default_ttl": { 97 Type: schema.TypeInt, 98 Optional: true, 99 Default: 3600, 100 Description: "The default Time-to-live (TTL) for the version", 101 }, 102 103 "default_host": { 104 Type: schema.TypeString, 105 Optional: true, 106 Computed: true, 107 Description: "The default hostname for the version", 108 }, 109 110 "backend": { 111 Type: schema.TypeSet, 112 Optional: true, 113 Elem: &schema.Resource{ 114 Schema: map[string]*schema.Schema{ 115 // required fields 116 "name": { 117 Type: schema.TypeString, 118 Required: true, 119 Description: "A name for this Backend", 120 }, 121 "address": { 122 Type: schema.TypeString, 123 Required: true, 124 Description: "An IPv4, hostname, or IPv6 address for the Backend", 125 }, 126 // Optional fields, defaults where they exist 127 "auto_loadbalance": { 128 Type: schema.TypeBool, 129 Optional: true, 130 Default: true, 131 Description: "Should this Backend be load balanced", 132 }, 133 "between_bytes_timeout": { 134 Type: schema.TypeInt, 135 Optional: true, 136 Default: 10000, 137 Description: "How long to wait between bytes in milliseconds", 138 }, 139 "connect_timeout": { 140 Type: schema.TypeInt, 141 Optional: true, 142 Default: 1000, 143 Description: "How long to wait for a timeout in milliseconds", 144 }, 145 "error_threshold": { 146 Type: schema.TypeInt, 147 Optional: true, 148 Default: 0, 149 Description: "Number of errors to allow before the Backend is marked as down", 150 }, 151 "first_byte_timeout": { 152 Type: schema.TypeInt, 153 Optional: true, 154 Default: 15000, 155 Description: "How long to wait for the first bytes in milliseconds", 156 }, 157 "max_conn": { 158 Type: schema.TypeInt, 159 Optional: true, 160 Default: 200, 161 Description: "Maximum number of connections for this Backend", 162 }, 163 "port": { 164 Type: schema.TypeInt, 165 Optional: true, 166 Default: 80, 167 Description: "The port number Backend responds on. Default 80", 168 }, 169 "request_condition": { 170 Type: schema.TypeString, 171 Optional: true, 172 Default: "", 173 Description: "Name of a condition, which if met, will select this backend during a request.", 174 }, 175 "shield": { 176 Type: schema.TypeString, 177 Optional: true, 178 Default: "", 179 Description: "The POP of the shield designated to reduce inbound load.", 180 }, 181 "ssl_check_cert": { 182 Type: schema.TypeBool, 183 Optional: true, 184 Default: true, 185 Description: "Be strict on checking SSL certs", 186 }, 187 "ssl_hostname": { 188 Type: schema.TypeString, 189 Optional: true, 190 Default: "", 191 Description: "SSL certificate hostname", 192 }, 193 // UseSSL is something we want to support in the future, but 194 // requires SSL setup we don't yet have 195 // TODO: Provide all SSL fields from https://docs.fastly.com/api/config#backend 196 // "use_ssl": &schema.Schema{ 197 // Type: schema.TypeBool, 198 // Optional: true, 199 // Default: false, 200 // Description: "Whether or not to use SSL to reach the Backend", 201 // }, 202 "weight": { 203 Type: schema.TypeInt, 204 Optional: true, 205 Default: 100, 206 Description: "The portion of traffic to send to a specific origins. Each origin receives weight/total of the traffic.", 207 }, 208 }, 209 }, 210 }, 211 212 "force_destroy": { 213 Type: schema.TypeBool, 214 Optional: true, 215 }, 216 217 "cache_setting": { 218 Type: schema.TypeSet, 219 Optional: true, 220 Elem: &schema.Resource{ 221 Schema: map[string]*schema.Schema{ 222 // required fields 223 "name": { 224 Type: schema.TypeString, 225 Required: true, 226 Description: "A name to refer to this Cache Setting", 227 }, 228 "cache_condition": { 229 Type: schema.TypeString, 230 Required: true, 231 Description: "Name of a condition to check if this Cache Setting applies", 232 }, 233 "action": { 234 Type: schema.TypeString, 235 Optional: true, 236 Description: "Action to take", 237 }, 238 // optional 239 "stale_ttl": { 240 Type: schema.TypeInt, 241 Optional: true, 242 Description: "Max 'Time To Live' for stale (unreachable) objects.", 243 Default: 300, 244 }, 245 "ttl": { 246 Type: schema.TypeInt, 247 Optional: true, 248 Description: "The 'Time To Live' for the object", 249 }, 250 }, 251 }, 252 }, 253 254 "gzip": { 255 Type: schema.TypeSet, 256 Optional: true, 257 Elem: &schema.Resource{ 258 Schema: map[string]*schema.Schema{ 259 // required fields 260 "name": { 261 Type: schema.TypeString, 262 Required: true, 263 Description: "A name to refer to this gzip condition", 264 }, 265 // optional fields 266 "content_types": { 267 Type: schema.TypeSet, 268 Optional: true, 269 Description: "Content types to apply automatic gzip to", 270 Elem: &schema.Schema{Type: schema.TypeString}, 271 }, 272 "extensions": { 273 Type: schema.TypeSet, 274 Optional: true, 275 Description: "File extensions to apply automatic gzip to. Do not include '.'", 276 Elem: &schema.Schema{Type: schema.TypeString}, 277 }, 278 "cache_condition": { 279 Type: schema.TypeString, 280 Optional: true, 281 Default: "", 282 Description: "Name of a condition controlling when this gzip configuration applies.", 283 }, 284 }, 285 }, 286 }, 287 288 "header": { 289 Type: schema.TypeSet, 290 Optional: true, 291 Elem: &schema.Resource{ 292 Schema: map[string]*schema.Schema{ 293 // required fields 294 "name": { 295 Type: schema.TypeString, 296 Required: true, 297 Description: "A name to refer to this Header object", 298 }, 299 "action": { 300 Type: schema.TypeString, 301 Required: true, 302 Description: "One of set, append, delete, regex, or regex_repeat", 303 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 304 var found bool 305 for _, t := range []string{"set", "append", "delete", "regex", "regex_repeat"} { 306 if v.(string) == t { 307 found = true 308 } 309 } 310 if !found { 311 es = append(es, fmt.Errorf( 312 "Fastly Header action is case sensitive and must be one of 'set', 'append', 'delete', 'regex', or 'regex_repeat'; found: %s", v.(string))) 313 } 314 return 315 }, 316 }, 317 "type": { 318 Type: schema.TypeString, 319 Required: true, 320 Description: "Type to manipulate: request, fetch, cache, response", 321 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 322 var found bool 323 for _, t := range []string{"request", "fetch", "cache", "response"} { 324 if v.(string) == t { 325 found = true 326 } 327 } 328 if !found { 329 es = append(es, fmt.Errorf( 330 "Fastly Header type is case sensitive and must be one of 'request', 'fetch', 'cache', or 'response'; found: %s", v.(string))) 331 } 332 return 333 }, 334 }, 335 "destination": { 336 Type: schema.TypeString, 337 Required: true, 338 Description: "Header this affects", 339 }, 340 // Optional fields, defaults where they exist 341 "ignore_if_set": { 342 Type: schema.TypeBool, 343 Optional: true, 344 Default: false, 345 Description: "Don't add the header if it is already. (Only applies to 'set' action.). Default `false`", 346 }, 347 "source": { 348 Type: schema.TypeString, 349 Optional: true, 350 Computed: true, 351 Description: "Variable to be used as a source for the header content (Does not apply to 'delete' action.)", 352 }, 353 "regex": { 354 Type: schema.TypeString, 355 Optional: true, 356 Computed: true, 357 Description: "Regular expression to use (Only applies to 'regex' and 'regex_repeat' actions.)", 358 }, 359 "substitution": { 360 Type: schema.TypeString, 361 Optional: true, 362 Computed: true, 363 Description: "Value to substitute in place of regular expression. (Only applies to 'regex' and 'regex_repeat'.)", 364 }, 365 "priority": { 366 Type: schema.TypeInt, 367 Optional: true, 368 Default: 100, 369 Description: "Lower priorities execute first. (Default: 100.)", 370 }, 371 "request_condition": { 372 Type: schema.TypeString, 373 Optional: true, 374 Default: "", 375 Description: "Optional name of a request condition to apply.", 376 }, 377 "cache_condition": { 378 Type: schema.TypeString, 379 Optional: true, 380 Default: "", 381 Description: "Optional name of a cache condition to apply.", 382 }, 383 "response_condition": { 384 Type: schema.TypeString, 385 Optional: true, 386 Default: "", 387 Description: "Optional name of a response condition to apply.", 388 }, 389 }, 390 }, 391 }, 392 393 "healthcheck": { 394 Type: schema.TypeSet, 395 Optional: true, 396 Elem: &schema.Resource{ 397 Schema: map[string]*schema.Schema{ 398 // required fields 399 "name": { 400 Type: schema.TypeString, 401 Required: true, 402 Description: "A name to refer to this healthcheck", 403 }, 404 "host": { 405 Type: schema.TypeString, 406 Required: true, 407 Description: "Which host to check", 408 }, 409 "path": { 410 Type: schema.TypeString, 411 Required: true, 412 Description: "The path to check", 413 }, 414 // optional fields 415 "check_interval": { 416 Type: schema.TypeInt, 417 Optional: true, 418 Default: 5000, 419 Description: "How often to run the healthcheck in milliseconds", 420 }, 421 "expected_response": { 422 Type: schema.TypeInt, 423 Optional: true, 424 Default: 200, 425 Description: "The status code expected from the host", 426 }, 427 "http_version": { 428 Type: schema.TypeString, 429 Optional: true, 430 Default: "1.1", 431 Description: "Whether to use version 1.0 or 1.1 HTTP", 432 }, 433 "initial": { 434 Type: schema.TypeInt, 435 Optional: true, 436 Default: 2, 437 Description: "When loading a config, the initial number of probes to be seen as OK", 438 }, 439 "method": { 440 Type: schema.TypeString, 441 Optional: true, 442 Default: "HEAD", 443 Description: "Which HTTP method to use", 444 }, 445 "threshold": { 446 Type: schema.TypeInt, 447 Optional: true, 448 Default: 3, 449 Description: "How many healthchecks must succeed to be considered healthy", 450 }, 451 "timeout": { 452 Type: schema.TypeInt, 453 Optional: true, 454 Default: 500, 455 Description: "Timeout in milliseconds", 456 }, 457 "window": { 458 Type: schema.TypeInt, 459 Optional: true, 460 Default: 5, 461 Description: "The number of most recent healthcheck queries to keep for this healthcheck", 462 }, 463 }, 464 }, 465 }, 466 467 "s3logging": { 468 Type: schema.TypeSet, 469 Optional: true, 470 Elem: &schema.Resource{ 471 Schema: map[string]*schema.Schema{ 472 // Required fields 473 "name": { 474 Type: schema.TypeString, 475 Required: true, 476 Description: "Unique name to refer to this logging setup", 477 }, 478 "bucket_name": { 479 Type: schema.TypeString, 480 Required: true, 481 Description: "S3 Bucket name to store logs in", 482 }, 483 "s3_access_key": { 484 Type: schema.TypeString, 485 Optional: true, 486 DefaultFunc: schema.EnvDefaultFunc("FASTLY_S3_ACCESS_KEY", ""), 487 Description: "AWS Access Key", 488 }, 489 "s3_secret_key": { 490 Type: schema.TypeString, 491 Optional: true, 492 DefaultFunc: schema.EnvDefaultFunc("FASTLY_S3_SECRET_KEY", ""), 493 Description: "AWS Secret Key", 494 }, 495 // Optional fields 496 "path": { 497 Type: schema.TypeString, 498 Optional: true, 499 Description: "Path to store the files. Must end with a trailing slash", 500 }, 501 "domain": { 502 Type: schema.TypeString, 503 Optional: true, 504 Description: "Bucket endpoint", 505 }, 506 "gzip_level": { 507 Type: schema.TypeInt, 508 Optional: true, 509 Default: 0, 510 Description: "Gzip Compression level", 511 }, 512 "period": { 513 Type: schema.TypeInt, 514 Optional: true, 515 Default: 3600, 516 Description: "How frequently the logs should be transferred, in seconds (Default 3600)", 517 }, 518 "format": { 519 Type: schema.TypeString, 520 Optional: true, 521 Default: "%h %l %u %t %r %>s", 522 Description: "Apache-style string or VCL variables to use for log formatting", 523 }, 524 "format_version": { 525 Type: schema.TypeInt, 526 Optional: true, 527 Default: 1, 528 Description: "The version of the custom logging format used for the configured endpoint. Can be either 1 or 2. (Default: 1)", 529 ValidateFunc: validateS3FormatVersion, 530 }, 531 "timestamp_format": { 532 Type: schema.TypeString, 533 Optional: true, 534 Default: "%Y-%m-%dT%H:%M:%S.000", 535 Description: "specified timestamp formatting (default `%Y-%m-%dT%H:%M:%S.000`)", 536 }, 537 "response_condition": { 538 Type: schema.TypeString, 539 Optional: true, 540 Default: "", 541 Description: "Name of a condition to apply this logging.", 542 }, 543 }, 544 }, 545 }, 546 547 "papertrail": { 548 Type: schema.TypeSet, 549 Optional: true, 550 Elem: &schema.Resource{ 551 Schema: map[string]*schema.Schema{ 552 // Required fields 553 "name": { 554 Type: schema.TypeString, 555 Required: true, 556 Description: "Unique name to refer to this logging setup", 557 }, 558 "address": { 559 Type: schema.TypeString, 560 Required: true, 561 Description: "The address of the papertrail service", 562 }, 563 "port": { 564 Type: schema.TypeInt, 565 Required: true, 566 Description: "The port of the papertrail service", 567 }, 568 // Optional fields 569 "format": { 570 Type: schema.TypeString, 571 Optional: true, 572 Default: "%h %l %u %t %r %>s", 573 Description: "Apache-style string or VCL variables to use for log formatting", 574 }, 575 "response_condition": { 576 Type: schema.TypeString, 577 Optional: true, 578 Default: "", 579 Description: "Name of a condition to apply this logging", 580 }, 581 }, 582 }, 583 }, 584 585 "response_object": { 586 Type: schema.TypeSet, 587 Optional: true, 588 Elem: &schema.Resource{ 589 Schema: map[string]*schema.Schema{ 590 // Required 591 "name": { 592 Type: schema.TypeString, 593 Required: true, 594 Description: "Unique name to refer to this request object", 595 }, 596 // Optional fields 597 "status": { 598 Type: schema.TypeInt, 599 Optional: true, 600 Default: 200, 601 Description: "The HTTP Status Code of the object", 602 }, 603 "response": { 604 Type: schema.TypeString, 605 Optional: true, 606 Default: "OK", 607 Description: "The HTTP Response of the object", 608 }, 609 "content": { 610 Type: schema.TypeString, 611 Optional: true, 612 Default: "", 613 Description: "The content to deliver for the response object", 614 }, 615 "content_type": { 616 Type: schema.TypeString, 617 Optional: true, 618 Default: "", 619 Description: "The MIME type of the content", 620 }, 621 "request_condition": { 622 Type: schema.TypeString, 623 Optional: true, 624 Default: "", 625 Description: "Name of the condition to be checked during the request phase to see if the object should be delivered", 626 }, 627 "cache_condition": { 628 Type: schema.TypeString, 629 Optional: true, 630 Default: "", 631 Description: "Name of the condition checked after we have retrieved an object. If the condition passes then deliver this Request Object instead.", 632 }, 633 }, 634 }, 635 }, 636 637 "request_setting": { 638 Type: schema.TypeSet, 639 Optional: true, 640 Elem: &schema.Resource{ 641 Schema: map[string]*schema.Schema{ 642 // Required fields 643 "name": { 644 Type: schema.TypeString, 645 Required: true, 646 Description: "Unique name to refer to this Request Setting", 647 }, 648 "request_condition": { 649 Type: schema.TypeString, 650 Required: true, 651 Description: "Name of a request condition to apply.", 652 }, 653 // Optional fields 654 "max_stale_age": { 655 Type: schema.TypeInt, 656 Optional: true, 657 Default: 60, 658 Description: "How old an object is allowed to be, in seconds. Default `60`", 659 }, 660 "force_miss": { 661 Type: schema.TypeBool, 662 Optional: true, 663 Description: "Force a cache miss for the request", 664 }, 665 "force_ssl": { 666 Type: schema.TypeBool, 667 Optional: true, 668 Description: "Forces the request use SSL", 669 }, 670 "action": { 671 Type: schema.TypeString, 672 Optional: true, 673 Description: "Allows you to terminate request handling and immediately perform an action", 674 }, 675 "bypass_busy_wait": { 676 Type: schema.TypeBool, 677 Optional: true, 678 Description: "Disable collapsed forwarding", 679 }, 680 "hash_keys": { 681 Type: schema.TypeString, 682 Optional: true, 683 Description: "Comma separated list of varnish request object fields that should be in the hash key", 684 }, 685 "xff": { 686 Type: schema.TypeString, 687 Optional: true, 688 Default: "append", 689 Description: "X-Forwarded-For options", 690 }, 691 "timer_support": { 692 Type: schema.TypeBool, 693 Optional: true, 694 Description: "Injects the X-Timer info into the request", 695 }, 696 "geo_headers": { 697 Type: schema.TypeBool, 698 Optional: true, 699 Description: "Inject Fastly-Geo-Country, Fastly-Geo-City, and Fastly-Geo-Region", 700 }, 701 "default_host": { 702 Type: schema.TypeString, 703 Optional: true, 704 Description: "the host header", 705 }, 706 }, 707 }, 708 }, 709 "vcl": { 710 Type: schema.TypeSet, 711 Optional: true, 712 Elem: &schema.Resource{ 713 Schema: map[string]*schema.Schema{ 714 "name": { 715 Type: schema.TypeString, 716 Required: true, 717 Description: "A name to refer to this VCL configuration", 718 }, 719 "content": { 720 Type: schema.TypeString, 721 Required: true, 722 Description: "The contents of this VCL configuration", 723 StateFunc: func(v interface{}) string { 724 switch v.(type) { 725 case string: 726 hash := sha1.Sum([]byte(v.(string))) 727 return hex.EncodeToString(hash[:]) 728 default: 729 return "" 730 } 731 }, 732 }, 733 "main": { 734 Type: schema.TypeBool, 735 Optional: true, 736 Default: false, 737 Description: "Should this VCL configuration be the main configuration", 738 }, 739 }, 740 }, 741 }, 742 }, 743 } 744 } 745 746 func resourceServiceV1Create(d *schema.ResourceData, meta interface{}) error { 747 if err := validateVCLs(d); err != nil { 748 return err 749 } 750 751 conn := meta.(*FastlyClient).conn 752 service, err := conn.CreateService(&gofastly.CreateServiceInput{ 753 Name: d.Get("name").(string), 754 Comment: "Managed by Terraform", 755 }) 756 757 if err != nil { 758 return err 759 } 760 761 d.SetId(service.ID) 762 return resourceServiceV1Update(d, meta) 763 } 764 765 func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error { 766 if err := validateVCLs(d); err != nil { 767 return err 768 } 769 770 conn := meta.(*FastlyClient).conn 771 772 // Update Name. No new verions is required for this 773 if d.HasChange("name") { 774 _, err := conn.UpdateService(&gofastly.UpdateServiceInput{ 775 ID: d.Id(), 776 Name: d.Get("name").(string), 777 }) 778 if err != nil { 779 return err 780 } 781 } 782 783 // Once activated, Versions are locked and become immutable. This is true for 784 // versions that are no longer active. For Domains, Backends, DefaultHost and 785 // DefaultTTL, a new Version must be created first, and updates posted to that 786 // Version. Loop these attributes and determine if we need to create a new version first 787 var needsChange bool 788 for _, v := range []string{ 789 "domain", 790 "backend", 791 "default_host", 792 "default_ttl", 793 "header", 794 "gzip", 795 "healthcheck", 796 "s3logging", 797 "papertrail", 798 "response_object", 799 "condition", 800 "request_setting", 801 "cache_setting", 802 "vcl", 803 } { 804 if d.HasChange(v) { 805 needsChange = true 806 } 807 } 808 809 if needsChange { 810 latestVersion := d.Get("active_version").(string) 811 if latestVersion == "" { 812 // If the service was just created, there is an empty Version 1 available 813 // that is unlocked and can be updated 814 latestVersion = "1" 815 } else { 816 // Clone the latest version, giving us an unlocked version we can modify 817 log.Printf("[DEBUG] Creating clone of version (%s) for updates", latestVersion) 818 newVersion, err := conn.CloneVersion(&gofastly.CloneVersionInput{ 819 Service: d.Id(), 820 Version: latestVersion, 821 }) 822 if err != nil { 823 return err 824 } 825 826 // The new version number is named "Number", but it's actually a string 827 latestVersion = newVersion.Number 828 829 // New versions are not immediately found in the API, or are not 830 // immediately mutable, so we need to sleep a few and let Fastly ready 831 // itself. Typically, 7 seconds is enough 832 log.Print("[DEBUG] Sleeping 7 seconds to allow Fastly Version to be available") 833 time.Sleep(7 * time.Second) 834 } 835 836 // update general settings 837 if d.HasChange("default_host") || d.HasChange("default_ttl") { 838 opts := gofastly.UpdateSettingsInput{ 839 Service: d.Id(), 840 Version: latestVersion, 841 // default_ttl has the same default value of 3600 that is provided by 842 // the Fastly API, so it's safe to include here 843 DefaultTTL: uint(d.Get("default_ttl").(int)), 844 } 845 846 if attr, ok := d.GetOk("default_host"); ok { 847 opts.DefaultHost = attr.(string) 848 } 849 850 log.Printf("[DEBUG] Update Settings opts: %#v", opts) 851 _, err := conn.UpdateSettings(&opts) 852 if err != nil { 853 return err 854 } 855 } 856 857 // Conditions need to be updated first, as they can be referenced by other 858 // configuraiton objects (Backends, Request Headers, etc) 859 860 // Find difference in Conditions 861 if d.HasChange("condition") { 862 // Note: we don't utilize the PUT endpoint to update these objects, we simply 863 // destroy any that have changed, and create new ones with the updated 864 // values. This is how Terraform works with nested sub resources, we only 865 // get the full diff not a partial set item diff. Because this is done 866 // on a new version of the Fastly Service configuration, this is considered safe 867 868 oc, nc := d.GetChange("condition") 869 if oc == nil { 870 oc = new(schema.Set) 871 } 872 if nc == nil { 873 nc = new(schema.Set) 874 } 875 876 ocs := oc.(*schema.Set) 877 ncs := nc.(*schema.Set) 878 removeConditions := ocs.Difference(ncs).List() 879 addConditions := ncs.Difference(ocs).List() 880 881 // DELETE old Conditions 882 for _, cRaw := range removeConditions { 883 cf := cRaw.(map[string]interface{}) 884 opts := gofastly.DeleteConditionInput{ 885 Service: d.Id(), 886 Version: latestVersion, 887 Name: cf["name"].(string), 888 } 889 890 log.Printf("[DEBUG] Fastly Conditions Removal opts: %#v", opts) 891 err := conn.DeleteCondition(&opts) 892 if err != nil { 893 return err 894 } 895 } 896 897 // POST new Conditions 898 for _, cRaw := range addConditions { 899 cf := cRaw.(map[string]interface{}) 900 opts := gofastly.CreateConditionInput{ 901 Service: d.Id(), 902 Version: latestVersion, 903 Name: cf["name"].(string), 904 Type: cf["type"].(string), 905 // need to trim leading/tailing spaces, incase the config has HEREDOC 906 // formatting and contains a trailing new line 907 Statement: strings.TrimSpace(cf["statement"].(string)), 908 Priority: cf["priority"].(int), 909 } 910 911 log.Printf("[DEBUG] Create Conditions Opts: %#v", opts) 912 _, err := conn.CreateCondition(&opts) 913 if err != nil { 914 return err 915 } 916 } 917 } 918 919 // Find differences in domains 920 if d.HasChange("domain") { 921 od, nd := d.GetChange("domain") 922 if od == nil { 923 od = new(schema.Set) 924 } 925 if nd == nil { 926 nd = new(schema.Set) 927 } 928 929 ods := od.(*schema.Set) 930 nds := nd.(*schema.Set) 931 932 remove := ods.Difference(nds).List() 933 add := nds.Difference(ods).List() 934 935 // Delete removed domains 936 for _, dRaw := range remove { 937 df := dRaw.(map[string]interface{}) 938 opts := gofastly.DeleteDomainInput{ 939 Service: d.Id(), 940 Version: latestVersion, 941 Name: df["name"].(string), 942 } 943 944 log.Printf("[DEBUG] Fastly Domain removal opts: %#v", opts) 945 err := conn.DeleteDomain(&opts) 946 if err != nil { 947 return err 948 } 949 } 950 951 // POST new Domains 952 for _, dRaw := range add { 953 df := dRaw.(map[string]interface{}) 954 opts := gofastly.CreateDomainInput{ 955 Service: d.Id(), 956 Version: latestVersion, 957 Name: df["name"].(string), 958 } 959 960 if v, ok := df["comment"]; ok { 961 opts.Comment = v.(string) 962 } 963 964 log.Printf("[DEBUG] Fastly Domain Addition opts: %#v", opts) 965 _, err := conn.CreateDomain(&opts) 966 if err != nil { 967 return err 968 } 969 } 970 } 971 972 // find difference in backends 973 if d.HasChange("backend") { 974 ob, nb := d.GetChange("backend") 975 if ob == nil { 976 ob = new(schema.Set) 977 } 978 if nb == nil { 979 nb = new(schema.Set) 980 } 981 982 obs := ob.(*schema.Set) 983 nbs := nb.(*schema.Set) 984 removeBackends := obs.Difference(nbs).List() 985 addBackends := nbs.Difference(obs).List() 986 987 // DELETE old Backends 988 for _, bRaw := range removeBackends { 989 bf := bRaw.(map[string]interface{}) 990 opts := gofastly.DeleteBackendInput{ 991 Service: d.Id(), 992 Version: latestVersion, 993 Name: bf["name"].(string), 994 } 995 996 log.Printf("[DEBUG] Fastly Backend removal opts: %#v", opts) 997 err := conn.DeleteBackend(&opts) 998 if err != nil { 999 return err 1000 } 1001 } 1002 1003 // Find and post new Backends 1004 for _, dRaw := range addBackends { 1005 df := dRaw.(map[string]interface{}) 1006 opts := gofastly.CreateBackendInput{ 1007 Service: d.Id(), 1008 Version: latestVersion, 1009 Name: df["name"].(string), 1010 Address: df["address"].(string), 1011 AutoLoadbalance: gofastly.CBool(df["auto_loadbalance"].(bool)), 1012 SSLCheckCert: gofastly.CBool(df["ssl_check_cert"].(bool)), 1013 SSLHostname: df["ssl_hostname"].(string), 1014 Shield: df["shield"].(string), 1015 Port: uint(df["port"].(int)), 1016 BetweenBytesTimeout: uint(df["between_bytes_timeout"].(int)), 1017 ConnectTimeout: uint(df["connect_timeout"].(int)), 1018 ErrorThreshold: uint(df["error_threshold"].(int)), 1019 FirstByteTimeout: uint(df["first_byte_timeout"].(int)), 1020 MaxConn: uint(df["max_conn"].(int)), 1021 Weight: uint(df["weight"].(int)), 1022 RequestCondition: df["request_condition"].(string), 1023 } 1024 1025 log.Printf("[DEBUG] Create Backend Opts: %#v", opts) 1026 _, err := conn.CreateBackend(&opts) 1027 if err != nil { 1028 return err 1029 } 1030 } 1031 } 1032 1033 if d.HasChange("header") { 1034 oh, nh := d.GetChange("header") 1035 if oh == nil { 1036 oh = new(schema.Set) 1037 } 1038 if nh == nil { 1039 nh = new(schema.Set) 1040 } 1041 1042 ohs := oh.(*schema.Set) 1043 nhs := nh.(*schema.Set) 1044 1045 remove := ohs.Difference(nhs).List() 1046 add := nhs.Difference(ohs).List() 1047 1048 // Delete removed headers 1049 for _, dRaw := range remove { 1050 df := dRaw.(map[string]interface{}) 1051 opts := gofastly.DeleteHeaderInput{ 1052 Service: d.Id(), 1053 Version: latestVersion, 1054 Name: df["name"].(string), 1055 } 1056 1057 log.Printf("[DEBUG] Fastly Header removal opts: %#v", opts) 1058 err := conn.DeleteHeader(&opts) 1059 if err != nil { 1060 return err 1061 } 1062 } 1063 1064 // POST new Headers 1065 for _, dRaw := range add { 1066 opts, err := buildHeader(dRaw.(map[string]interface{})) 1067 if err != nil { 1068 log.Printf("[DEBUG] Error building Header: %s", err) 1069 return err 1070 } 1071 opts.Service = d.Id() 1072 opts.Version = latestVersion 1073 1074 log.Printf("[DEBUG] Fastly Header Addition opts: %#v", opts) 1075 _, err = conn.CreateHeader(opts) 1076 if err != nil { 1077 return err 1078 } 1079 } 1080 } 1081 1082 // Find differences in Gzips 1083 if d.HasChange("gzip") { 1084 og, ng := d.GetChange("gzip") 1085 if og == nil { 1086 og = new(schema.Set) 1087 } 1088 if ng == nil { 1089 ng = new(schema.Set) 1090 } 1091 1092 ogs := og.(*schema.Set) 1093 ngs := ng.(*schema.Set) 1094 1095 remove := ogs.Difference(ngs).List() 1096 add := ngs.Difference(ogs).List() 1097 1098 // Delete removed gzip rules 1099 for _, dRaw := range remove { 1100 df := dRaw.(map[string]interface{}) 1101 opts := gofastly.DeleteGzipInput{ 1102 Service: d.Id(), 1103 Version: latestVersion, 1104 Name: df["name"].(string), 1105 } 1106 1107 log.Printf("[DEBUG] Fastly Gzip removal opts: %#v", opts) 1108 err := conn.DeleteGzip(&opts) 1109 if err != nil { 1110 return err 1111 } 1112 } 1113 1114 // POST new Gzips 1115 for _, dRaw := range add { 1116 df := dRaw.(map[string]interface{}) 1117 opts := gofastly.CreateGzipInput{ 1118 Service: d.Id(), 1119 Version: latestVersion, 1120 Name: df["name"].(string), 1121 CacheCondition: df["cache_condition"].(string), 1122 } 1123 1124 if v, ok := df["content_types"]; ok { 1125 if len(v.(*schema.Set).List()) > 0 { 1126 var cl []string 1127 for _, c := range v.(*schema.Set).List() { 1128 cl = append(cl, c.(string)) 1129 } 1130 opts.ContentTypes = strings.Join(cl, " ") 1131 } 1132 } 1133 1134 if v, ok := df["extensions"]; ok { 1135 if len(v.(*schema.Set).List()) > 0 { 1136 var el []string 1137 for _, e := range v.(*schema.Set).List() { 1138 el = append(el, e.(string)) 1139 } 1140 opts.Extensions = strings.Join(el, " ") 1141 } 1142 } 1143 1144 log.Printf("[DEBUG] Fastly Gzip Addition opts: %#v", opts) 1145 _, err := conn.CreateGzip(&opts) 1146 if err != nil { 1147 return err 1148 } 1149 } 1150 } 1151 1152 // find difference in Healthcheck 1153 if d.HasChange("healthcheck") { 1154 oh, nh := d.GetChange("healthcheck") 1155 if oh == nil { 1156 oh = new(schema.Set) 1157 } 1158 if nh == nil { 1159 nh = new(schema.Set) 1160 } 1161 1162 ohs := oh.(*schema.Set) 1163 nhs := nh.(*schema.Set) 1164 removeHealthCheck := ohs.Difference(nhs).List() 1165 addHealthCheck := nhs.Difference(ohs).List() 1166 1167 // DELETE old healthcheck configurations 1168 for _, hRaw := range removeHealthCheck { 1169 hf := hRaw.(map[string]interface{}) 1170 opts := gofastly.DeleteHealthCheckInput{ 1171 Service: d.Id(), 1172 Version: latestVersion, 1173 Name: hf["name"].(string), 1174 } 1175 1176 log.Printf("[DEBUG] Fastly Healthcheck removal opts: %#v", opts) 1177 err := conn.DeleteHealthCheck(&opts) 1178 if err != nil { 1179 return err 1180 } 1181 } 1182 1183 // POST new/updated Healthcheck 1184 for _, hRaw := range addHealthCheck { 1185 hf := hRaw.(map[string]interface{}) 1186 1187 opts := gofastly.CreateHealthCheckInput{ 1188 Service: d.Id(), 1189 Version: latestVersion, 1190 Name: hf["name"].(string), 1191 Host: hf["host"].(string), 1192 Path: hf["path"].(string), 1193 CheckInterval: uint(hf["check_interval"].(int)), 1194 ExpectedResponse: uint(hf["expected_response"].(int)), 1195 HTTPVersion: hf["http_version"].(string), 1196 Initial: uint(hf["initial"].(int)), 1197 Method: hf["method"].(string), 1198 Threshold: uint(hf["threshold"].(int)), 1199 Timeout: uint(hf["timeout"].(int)), 1200 Window: uint(hf["window"].(int)), 1201 } 1202 1203 log.Printf("[DEBUG] Create Healthcheck Opts: %#v", opts) 1204 _, err := conn.CreateHealthCheck(&opts) 1205 if err != nil { 1206 return err 1207 } 1208 } 1209 } 1210 1211 // find difference in s3logging 1212 if d.HasChange("s3logging") { 1213 os, ns := d.GetChange("s3logging") 1214 if os == nil { 1215 os = new(schema.Set) 1216 } 1217 if ns == nil { 1218 ns = new(schema.Set) 1219 } 1220 1221 oss := os.(*schema.Set) 1222 nss := ns.(*schema.Set) 1223 removeS3Logging := oss.Difference(nss).List() 1224 addS3Logging := nss.Difference(oss).List() 1225 1226 // DELETE old S3 Log configurations 1227 for _, sRaw := range removeS3Logging { 1228 sf := sRaw.(map[string]interface{}) 1229 opts := gofastly.DeleteS3Input{ 1230 Service: d.Id(), 1231 Version: latestVersion, 1232 Name: sf["name"].(string), 1233 } 1234 1235 log.Printf("[DEBUG] Fastly S3 Logging removal opts: %#v", opts) 1236 err := conn.DeleteS3(&opts) 1237 if err != nil { 1238 return err 1239 } 1240 } 1241 1242 // POST new/updated S3 Logging 1243 for _, sRaw := range addS3Logging { 1244 sf := sRaw.(map[string]interface{}) 1245 1246 // Fastly API will not error if these are omitted, so we throw an error 1247 // if any of these are empty 1248 for _, sk := range []string{"s3_access_key", "s3_secret_key"} { 1249 if sf[sk].(string) == "" { 1250 return fmt.Errorf("[ERR] No %s found for S3 Log stream setup for Service (%s)", sk, d.Id()) 1251 } 1252 } 1253 1254 opts := gofastly.CreateS3Input{ 1255 Service: d.Id(), 1256 Version: latestVersion, 1257 Name: sf["name"].(string), 1258 BucketName: sf["bucket_name"].(string), 1259 AccessKey: sf["s3_access_key"].(string), 1260 SecretKey: sf["s3_secret_key"].(string), 1261 Period: uint(sf["period"].(int)), 1262 GzipLevel: uint(sf["gzip_level"].(int)), 1263 Domain: sf["domain"].(string), 1264 Path: sf["path"].(string), 1265 Format: sf["format"].(string), 1266 FormatVersion: uint(sf["format_version"].(int)), 1267 TimestampFormat: sf["timestamp_format"].(string), 1268 ResponseCondition: sf["response_condition"].(string), 1269 } 1270 1271 log.Printf("[DEBUG] Create S3 Logging Opts: %#v", opts) 1272 _, err := conn.CreateS3(&opts) 1273 if err != nil { 1274 return err 1275 } 1276 } 1277 } 1278 1279 // find difference in Papertrail 1280 if d.HasChange("papertrail") { 1281 os, ns := d.GetChange("papertrail") 1282 if os == nil { 1283 os = new(schema.Set) 1284 } 1285 if ns == nil { 1286 ns = new(schema.Set) 1287 } 1288 1289 oss := os.(*schema.Set) 1290 nss := ns.(*schema.Set) 1291 removePapertrail := oss.Difference(nss).List() 1292 addPapertrail := nss.Difference(oss).List() 1293 1294 // DELETE old papertrail configurations 1295 for _, pRaw := range removePapertrail { 1296 pf := pRaw.(map[string]interface{}) 1297 opts := gofastly.DeletePapertrailInput{ 1298 Service: d.Id(), 1299 Version: latestVersion, 1300 Name: pf["name"].(string), 1301 } 1302 1303 log.Printf("[DEBUG] Fastly Papertrail removal opts: %#v", opts) 1304 err := conn.DeletePapertrail(&opts) 1305 if err != nil { 1306 return err 1307 } 1308 } 1309 1310 // POST new/updated Papertrail 1311 for _, pRaw := range addPapertrail { 1312 pf := pRaw.(map[string]interface{}) 1313 1314 opts := gofastly.CreatePapertrailInput{ 1315 Service: d.Id(), 1316 Version: latestVersion, 1317 Name: pf["name"].(string), 1318 Address: pf["address"].(string), 1319 Port: uint(pf["port"].(int)), 1320 Format: pf["format"].(string), 1321 ResponseCondition: pf["response_condition"].(string), 1322 } 1323 1324 log.Printf("[DEBUG] Create Papertrail Opts: %#v", opts) 1325 _, err := conn.CreatePapertrail(&opts) 1326 if err != nil { 1327 return err 1328 } 1329 } 1330 } 1331 1332 // find difference in Response Object 1333 if d.HasChange("response_object") { 1334 or, nr := d.GetChange("response_object") 1335 if or == nil { 1336 or = new(schema.Set) 1337 } 1338 if nr == nil { 1339 nr = new(schema.Set) 1340 } 1341 1342 ors := or.(*schema.Set) 1343 nrs := nr.(*schema.Set) 1344 removeResponseObject := ors.Difference(nrs).List() 1345 addResponseObject := nrs.Difference(ors).List() 1346 1347 // DELETE old response object configurations 1348 for _, rRaw := range removeResponseObject { 1349 rf := rRaw.(map[string]interface{}) 1350 opts := gofastly.DeleteResponseObjectInput{ 1351 Service: d.Id(), 1352 Version: latestVersion, 1353 Name: rf["name"].(string), 1354 } 1355 1356 log.Printf("[DEBUG] Fastly Response Object removal opts: %#v", opts) 1357 err := conn.DeleteResponseObject(&opts) 1358 if err != nil { 1359 return err 1360 } 1361 } 1362 1363 // POST new/updated Response Object 1364 for _, rRaw := range addResponseObject { 1365 rf := rRaw.(map[string]interface{}) 1366 1367 opts := gofastly.CreateResponseObjectInput{ 1368 Service: d.Id(), 1369 Version: latestVersion, 1370 Name: rf["name"].(string), 1371 Status: uint(rf["status"].(int)), 1372 Response: rf["response"].(string), 1373 Content: rf["content"].(string), 1374 ContentType: rf["content_type"].(string), 1375 RequestCondition: rf["request_condition"].(string), 1376 CacheCondition: rf["cache_condition"].(string), 1377 } 1378 1379 log.Printf("[DEBUG] Create Response Object Opts: %#v", opts) 1380 _, err := conn.CreateResponseObject(&opts) 1381 if err != nil { 1382 return err 1383 } 1384 } 1385 } 1386 1387 // find difference in request settings 1388 if d.HasChange("request_setting") { 1389 os, ns := d.GetChange("request_setting") 1390 if os == nil { 1391 os = new(schema.Set) 1392 } 1393 if ns == nil { 1394 ns = new(schema.Set) 1395 } 1396 1397 ors := os.(*schema.Set) 1398 nrs := ns.(*schema.Set) 1399 removeRequestSettings := ors.Difference(nrs).List() 1400 addRequestSettings := nrs.Difference(ors).List() 1401 1402 // DELETE old Request Settings configurations 1403 for _, sRaw := range removeRequestSettings { 1404 sf := sRaw.(map[string]interface{}) 1405 opts := gofastly.DeleteRequestSettingInput{ 1406 Service: d.Id(), 1407 Version: latestVersion, 1408 Name: sf["name"].(string), 1409 } 1410 1411 log.Printf("[DEBUG] Fastly Request Setting removal opts: %#v", opts) 1412 err := conn.DeleteRequestSetting(&opts) 1413 if err != nil { 1414 return err 1415 } 1416 } 1417 1418 // POST new/updated Request Setting 1419 for _, sRaw := range addRequestSettings { 1420 opts, err := buildRequestSetting(sRaw.(map[string]interface{})) 1421 if err != nil { 1422 log.Printf("[DEBUG] Error building Requset Setting: %s", err) 1423 return err 1424 } 1425 opts.Service = d.Id() 1426 opts.Version = latestVersion 1427 1428 log.Printf("[DEBUG] Create Request Setting Opts: %#v", opts) 1429 _, err = conn.CreateRequestSetting(opts) 1430 if err != nil { 1431 return err 1432 } 1433 } 1434 } 1435 1436 // Find differences in VCLs 1437 if d.HasChange("vcl") { 1438 // Note: as above with Gzip and S3 logging, we don't utilize the PUT 1439 // endpoint to update a VCL, we simply destroy it and create a new one. 1440 oldVCLVal, newVCLVal := d.GetChange("vcl") 1441 if oldVCLVal == nil { 1442 oldVCLVal = new(schema.Set) 1443 } 1444 if newVCLVal == nil { 1445 newVCLVal = new(schema.Set) 1446 } 1447 1448 oldVCLSet := oldVCLVal.(*schema.Set) 1449 newVCLSet := newVCLVal.(*schema.Set) 1450 1451 remove := oldVCLSet.Difference(newVCLSet).List() 1452 add := newVCLSet.Difference(oldVCLSet).List() 1453 1454 // Delete removed VCL configurations 1455 for _, dRaw := range remove { 1456 df := dRaw.(map[string]interface{}) 1457 opts := gofastly.DeleteVCLInput{ 1458 Service: d.Id(), 1459 Version: latestVersion, 1460 Name: df["name"].(string), 1461 } 1462 1463 log.Printf("[DEBUG] Fastly VCL Removal opts: %#v", opts) 1464 err := conn.DeleteVCL(&opts) 1465 if err != nil { 1466 return err 1467 } 1468 } 1469 // POST new VCL configurations 1470 for _, dRaw := range add { 1471 df := dRaw.(map[string]interface{}) 1472 opts := gofastly.CreateVCLInput{ 1473 Service: d.Id(), 1474 Version: latestVersion, 1475 Name: df["name"].(string), 1476 Content: df["content"].(string), 1477 } 1478 1479 log.Printf("[DEBUG] Fastly VCL Addition opts: %#v", opts) 1480 _, err := conn.CreateVCL(&opts) 1481 if err != nil { 1482 return err 1483 } 1484 1485 // if this new VCL is the main 1486 if df["main"].(bool) { 1487 opts := gofastly.ActivateVCLInput{ 1488 Service: d.Id(), 1489 Version: latestVersion, 1490 Name: df["name"].(string), 1491 } 1492 log.Printf("[DEBUG] Fastly VCL activation opts: %#v", opts) 1493 _, err := conn.ActivateVCL(&opts) 1494 if err != nil { 1495 return err 1496 } 1497 1498 } 1499 } 1500 } 1501 1502 // Find differences in Cache Settings 1503 if d.HasChange("cache_setting") { 1504 oc, nc := d.GetChange("cache_setting") 1505 if oc == nil { 1506 oc = new(schema.Set) 1507 } 1508 if nc == nil { 1509 nc = new(schema.Set) 1510 } 1511 1512 ocs := oc.(*schema.Set) 1513 ncs := nc.(*schema.Set) 1514 1515 remove := ocs.Difference(ncs).List() 1516 add := ncs.Difference(ocs).List() 1517 1518 // Delete removed Cache Settings 1519 for _, dRaw := range remove { 1520 df := dRaw.(map[string]interface{}) 1521 opts := gofastly.DeleteCacheSettingInput{ 1522 Service: d.Id(), 1523 Version: latestVersion, 1524 Name: df["name"].(string), 1525 } 1526 1527 log.Printf("[DEBUG] Fastly Cache Settings removal opts: %#v", opts) 1528 err := conn.DeleteCacheSetting(&opts) 1529 if err != nil { 1530 return err 1531 } 1532 } 1533 1534 // POST new Cache Settings 1535 for _, dRaw := range add { 1536 opts, err := buildCacheSetting(dRaw.(map[string]interface{})) 1537 if err != nil { 1538 log.Printf("[DEBUG] Error building Cache Setting: %s", err) 1539 return err 1540 } 1541 opts.Service = d.Id() 1542 opts.Version = latestVersion 1543 1544 log.Printf("[DEBUG] Fastly Cache Settings Addition opts: %#v", opts) 1545 _, err = conn.CreateCacheSetting(opts) 1546 if err != nil { 1547 return err 1548 } 1549 } 1550 } 1551 1552 // validate version 1553 log.Printf("[DEBUG] Validating Fastly Service (%s), Version (%s)", d.Id(), latestVersion) 1554 valid, msg, err := conn.ValidateVersion(&gofastly.ValidateVersionInput{ 1555 Service: d.Id(), 1556 Version: latestVersion, 1557 }) 1558 1559 if err != nil { 1560 return fmt.Errorf("[ERR] Error checking validation: %s", err) 1561 } 1562 1563 if !valid { 1564 return fmt.Errorf("[ERR] Invalid configuration for Fastly Service (%s): %s", d.Id(), msg) 1565 } 1566 1567 log.Printf("[DEBUG] Activating Fastly Service (%s), Version (%s)", d.Id(), latestVersion) 1568 _, err = conn.ActivateVersion(&gofastly.ActivateVersionInput{ 1569 Service: d.Id(), 1570 Version: latestVersion, 1571 }) 1572 if err != nil { 1573 return fmt.Errorf("[ERR] Error activating version (%s): %s", latestVersion, err) 1574 } 1575 1576 // Only if the version is valid and activated do we set the active_version. 1577 // This prevents us from getting stuck in cloning an invalid version 1578 d.Set("active_version", latestVersion) 1579 } 1580 1581 return resourceServiceV1Read(d, meta) 1582 } 1583 1584 func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error { 1585 conn := meta.(*FastlyClient).conn 1586 1587 // Find the Service. Discard the service because we need the ServiceDetails, 1588 // not just a Service record 1589 _, err := findService(d.Id(), meta) 1590 if err != nil { 1591 switch err { 1592 case fastlyNoServiceFoundErr: 1593 log.Printf("[WARN] %s for ID (%s)", err, d.Id()) 1594 d.SetId("") 1595 return nil 1596 default: 1597 return err 1598 } 1599 } 1600 1601 s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{ 1602 ID: d.Id(), 1603 }) 1604 1605 if err != nil { 1606 return err 1607 } 1608 1609 d.Set("name", s.Name) 1610 d.Set("active_version", s.ActiveVersion.Number) 1611 1612 // If CreateService succeeds, but initial updates to the Service fail, we'll 1613 // have an empty ActiveService version (no version is active, so we can't 1614 // query for information on it) 1615 if s.ActiveVersion.Number != "" { 1616 settingsOpts := gofastly.GetSettingsInput{ 1617 Service: d.Id(), 1618 Version: s.ActiveVersion.Number, 1619 } 1620 if settings, err := conn.GetSettings(&settingsOpts); err == nil { 1621 d.Set("default_host", settings.DefaultHost) 1622 d.Set("default_ttl", settings.DefaultTTL) 1623 } else { 1624 return fmt.Errorf("[ERR] Error looking up Version settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1625 } 1626 1627 // TODO: update go-fastly to support an ActiveVersion struct, which contains 1628 // domain and backend info in the response. Here we do 2 additional queries 1629 // to find out that info 1630 log.Printf("[DEBUG] Refreshing Domains for (%s)", d.Id()) 1631 domainList, err := conn.ListDomains(&gofastly.ListDomainsInput{ 1632 Service: d.Id(), 1633 Version: s.ActiveVersion.Number, 1634 }) 1635 1636 if err != nil { 1637 return fmt.Errorf("[ERR] Error looking up Domains for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1638 } 1639 1640 // Refresh Domains 1641 dl := flattenDomains(domainList) 1642 1643 if err := d.Set("domain", dl); err != nil { 1644 log.Printf("[WARN] Error setting Domains for (%s): %s", d.Id(), err) 1645 } 1646 1647 // Refresh Backends 1648 log.Printf("[DEBUG] Refreshing Backends for (%s)", d.Id()) 1649 backendList, err := conn.ListBackends(&gofastly.ListBackendsInput{ 1650 Service: d.Id(), 1651 Version: s.ActiveVersion.Number, 1652 }) 1653 1654 if err != nil { 1655 return fmt.Errorf("[ERR] Error looking up Backends for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1656 } 1657 1658 bl := flattenBackends(backendList) 1659 1660 if err := d.Set("backend", bl); err != nil { 1661 log.Printf("[WARN] Error setting Backends for (%s): %s", d.Id(), err) 1662 } 1663 1664 // refresh headers 1665 log.Printf("[DEBUG] Refreshing Headers for (%s)", d.Id()) 1666 headerList, err := conn.ListHeaders(&gofastly.ListHeadersInput{ 1667 Service: d.Id(), 1668 Version: s.ActiveVersion.Number, 1669 }) 1670 1671 if err != nil { 1672 return fmt.Errorf("[ERR] Error looking up Headers for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1673 } 1674 1675 hl := flattenHeaders(headerList) 1676 1677 if err := d.Set("header", hl); err != nil { 1678 log.Printf("[WARN] Error setting Headers for (%s): %s", d.Id(), err) 1679 } 1680 1681 // refresh gzips 1682 log.Printf("[DEBUG] Refreshing Gzips for (%s)", d.Id()) 1683 gzipsList, err := conn.ListGzips(&gofastly.ListGzipsInput{ 1684 Service: d.Id(), 1685 Version: s.ActiveVersion.Number, 1686 }) 1687 1688 if err != nil { 1689 return fmt.Errorf("[ERR] Error looking up Gzips for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1690 } 1691 1692 gl := flattenGzips(gzipsList) 1693 1694 if err := d.Set("gzip", gl); err != nil { 1695 log.Printf("[WARN] Error setting Gzips for (%s): %s", d.Id(), err) 1696 } 1697 1698 // refresh Healthcheck 1699 log.Printf("[DEBUG] Refreshing Healthcheck for (%s)", d.Id()) 1700 healthcheckList, err := conn.ListHealthChecks(&gofastly.ListHealthChecksInput{ 1701 Service: d.Id(), 1702 Version: s.ActiveVersion.Number, 1703 }) 1704 1705 if err != nil { 1706 return fmt.Errorf("[ERR] Error looking up Healthcheck for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1707 } 1708 1709 hcl := flattenHealthchecks(healthcheckList) 1710 1711 if err := d.Set("healthcheck", hcl); err != nil { 1712 log.Printf("[WARN] Error setting Healthcheck for (%s): %s", d.Id(), err) 1713 } 1714 1715 // refresh S3 Logging 1716 log.Printf("[DEBUG] Refreshing S3 Logging for (%s)", d.Id()) 1717 s3List, err := conn.ListS3s(&gofastly.ListS3sInput{ 1718 Service: d.Id(), 1719 Version: s.ActiveVersion.Number, 1720 }) 1721 1722 if err != nil { 1723 return fmt.Errorf("[ERR] Error looking up S3 Logging for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1724 } 1725 1726 sl := flattenS3s(s3List) 1727 1728 if err := d.Set("s3logging", sl); err != nil { 1729 log.Printf("[WARN] Error setting S3 Logging for (%s): %s", d.Id(), err) 1730 } 1731 1732 // refresh Papertrail Logging 1733 log.Printf("[DEBUG] Refreshing Papertrail for (%s)", d.Id()) 1734 papertrailList, err := conn.ListPapertrails(&gofastly.ListPapertrailsInput{ 1735 Service: d.Id(), 1736 Version: s.ActiveVersion.Number, 1737 }) 1738 1739 if err != nil { 1740 return fmt.Errorf("[ERR] Error looking up Papertrail for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1741 } 1742 1743 pl := flattenPapertrails(papertrailList) 1744 1745 if err := d.Set("papertrail", pl); err != nil { 1746 log.Printf("[WARN] Error setting Papertrail for (%s): %s", d.Id(), err) 1747 } 1748 1749 // refresh Response Objects 1750 log.Printf("[DEBUG] Refreshing Response Object for (%s)", d.Id()) 1751 responseObjectList, err := conn.ListResponseObjects(&gofastly.ListResponseObjectsInput{ 1752 Service: d.Id(), 1753 Version: s.ActiveVersion.Number, 1754 }) 1755 1756 if err != nil { 1757 return fmt.Errorf("[ERR] Error looking up Response Object for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1758 } 1759 1760 rol := flattenResponseObjects(responseObjectList) 1761 1762 if err := d.Set("response_object", rol); err != nil { 1763 log.Printf("[WARN] Error setting Response Object for (%s): %s", d.Id(), err) 1764 } 1765 1766 // refresh Conditions 1767 log.Printf("[DEBUG] Refreshing Conditions for (%s)", d.Id()) 1768 conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{ 1769 Service: d.Id(), 1770 Version: s.ActiveVersion.Number, 1771 }) 1772 1773 if err != nil { 1774 return fmt.Errorf("[ERR] Error looking up Conditions for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1775 } 1776 1777 cl := flattenConditions(conditionList) 1778 1779 if err := d.Set("condition", cl); err != nil { 1780 log.Printf("[WARN] Error setting Conditions for (%s): %s", d.Id(), err) 1781 } 1782 1783 // refresh Request Settings 1784 log.Printf("[DEBUG] Refreshing Request Settings for (%s)", d.Id()) 1785 rsList, err := conn.ListRequestSettings(&gofastly.ListRequestSettingsInput{ 1786 Service: d.Id(), 1787 Version: s.ActiveVersion.Number, 1788 }) 1789 1790 if err != nil { 1791 return fmt.Errorf("[ERR] Error looking up Request Settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1792 } 1793 1794 rl := flattenRequestSettings(rsList) 1795 1796 if err := d.Set("request_setting", rl); err != nil { 1797 log.Printf("[WARN] Error setting Request Settings for (%s): %s", d.Id(), err) 1798 } 1799 1800 // refresh VCLs 1801 log.Printf("[DEBUG] Refreshing VCLs for (%s)", d.Id()) 1802 vclList, err := conn.ListVCLs(&gofastly.ListVCLsInput{ 1803 Service: d.Id(), 1804 Version: s.ActiveVersion.Number, 1805 }) 1806 if err != nil { 1807 return fmt.Errorf("[ERR] Error looking up VCLs for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1808 } 1809 1810 vl := flattenVCLs(vclList) 1811 1812 if err := d.Set("vcl", vl); err != nil { 1813 log.Printf("[WARN] Error setting VCLs for (%s): %s", d.Id(), err) 1814 } 1815 1816 // refresh Cache Settings 1817 log.Printf("[DEBUG] Refreshing Cache Settings for (%s)", d.Id()) 1818 cslList, err := conn.ListCacheSettings(&gofastly.ListCacheSettingsInput{ 1819 Service: d.Id(), 1820 Version: s.ActiveVersion.Number, 1821 }) 1822 if err != nil { 1823 return fmt.Errorf("[ERR] Error looking up Cache Settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1824 } 1825 1826 csl := flattenCacheSettings(cslList) 1827 1828 if err := d.Set("cache_setting", csl); err != nil { 1829 log.Printf("[WARN] Error setting Cache Settings for (%s): %s", d.Id(), err) 1830 } 1831 1832 } else { 1833 log.Printf("[DEBUG] Active Version for Service (%s) is empty, no state to refresh", d.Id()) 1834 } 1835 1836 return nil 1837 } 1838 1839 func resourceServiceV1Delete(d *schema.ResourceData, meta interface{}) error { 1840 conn := meta.(*FastlyClient).conn 1841 1842 // Fastly will fail to delete any service with an Active Version. 1843 // If `force_destroy` is given, we deactivate the active version and then send 1844 // the DELETE call 1845 if d.Get("force_destroy").(bool) { 1846 s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{ 1847 ID: d.Id(), 1848 }) 1849 1850 if err != nil { 1851 return err 1852 } 1853 1854 if s.ActiveVersion.Number != "" { 1855 _, err := conn.DeactivateVersion(&gofastly.DeactivateVersionInput{ 1856 Service: d.Id(), 1857 Version: s.ActiveVersion.Number, 1858 }) 1859 if err != nil { 1860 return err 1861 } 1862 } 1863 } 1864 1865 err := conn.DeleteService(&gofastly.DeleteServiceInput{ 1866 ID: d.Id(), 1867 }) 1868 1869 if err != nil { 1870 return err 1871 } 1872 1873 _, err = findService(d.Id(), meta) 1874 if err != nil { 1875 switch err { 1876 // we expect no records to be found here 1877 case fastlyNoServiceFoundErr: 1878 d.SetId("") 1879 return nil 1880 default: 1881 return err 1882 } 1883 } 1884 1885 // findService above returned something and nil error, but shouldn't have 1886 return fmt.Errorf("[WARN] Tried deleting Service (%s), but was still found", d.Id()) 1887 1888 } 1889 1890 func flattenDomains(list []*gofastly.Domain) []map[string]interface{} { 1891 dl := make([]map[string]interface{}, 0, len(list)) 1892 1893 for _, d := range list { 1894 dl = append(dl, map[string]interface{}{ 1895 "name": d.Name, 1896 "comment": d.Comment, 1897 }) 1898 } 1899 1900 return dl 1901 } 1902 1903 func flattenBackends(backendList []*gofastly.Backend) []map[string]interface{} { 1904 var bl []map[string]interface{} 1905 for _, b := range backendList { 1906 // Convert Backend to a map for saving to state. 1907 nb := map[string]interface{}{ 1908 "name": b.Name, 1909 "address": b.Address, 1910 "auto_loadbalance": gofastly.CBool(b.AutoLoadbalance), 1911 "between_bytes_timeout": int(b.BetweenBytesTimeout), 1912 "connect_timeout": int(b.ConnectTimeout), 1913 "error_threshold": int(b.ErrorThreshold), 1914 "first_byte_timeout": int(b.FirstByteTimeout), 1915 "max_conn": int(b.MaxConn), 1916 "port": int(b.Port), 1917 "shield": b.Shield, 1918 "ssl_check_cert": gofastly.CBool(b.SSLCheckCert), 1919 "ssl_hostname": b.SSLHostname, 1920 "weight": int(b.Weight), 1921 "request_condition": b.RequestCondition, 1922 } 1923 1924 bl = append(bl, nb) 1925 } 1926 return bl 1927 } 1928 1929 // findService finds a Fastly Service via the ListServices endpoint, returning 1930 // the Service if found. 1931 // 1932 // Fastly API does not include any "deleted_at" type parameter to indicate 1933 // that a Service has been deleted. GET requests to a deleted Service will 1934 // return 200 OK and have the full output of the Service for an unknown time 1935 // (days, in my testing). In order to determine if a Service is deleted, we 1936 // need to hit /service and loop the returned Services, searching for the one 1937 // in question. This endpoint only returns active or "alive" services. If the 1938 // Service is not included, then it's "gone" 1939 // 1940 // Returns a fastlyNoServiceFoundErr error if the Service is not found in the 1941 // ListServices response. 1942 func findService(id string, meta interface{}) (*gofastly.Service, error) { 1943 conn := meta.(*FastlyClient).conn 1944 1945 l, err := conn.ListServices(&gofastly.ListServicesInput{}) 1946 if err != nil { 1947 return nil, fmt.Errorf("[WARN] Error listing services (%s): %s", id, err) 1948 } 1949 1950 for _, s := range l { 1951 if s.ID == id { 1952 log.Printf("[DEBUG] Found Service (%s)", id) 1953 return s, nil 1954 } 1955 } 1956 1957 return nil, fastlyNoServiceFoundErr 1958 } 1959 1960 func flattenHeaders(headerList []*gofastly.Header) []map[string]interface{} { 1961 var hl []map[string]interface{} 1962 for _, h := range headerList { 1963 // Convert Header to a map for saving to state. 1964 nh := map[string]interface{}{ 1965 "name": h.Name, 1966 "action": h.Action, 1967 "ignore_if_set": h.IgnoreIfSet, 1968 "type": h.Type, 1969 "destination": h.Destination, 1970 "source": h.Source, 1971 "regex": h.Regex, 1972 "substitution": h.Substitution, 1973 "priority": int(h.Priority), 1974 "request_condition": h.RequestCondition, 1975 "cache_condition": h.CacheCondition, 1976 "response_condition": h.ResponseCondition, 1977 } 1978 1979 for k, v := range nh { 1980 if v == "" { 1981 delete(nh, k) 1982 } 1983 } 1984 1985 hl = append(hl, nh) 1986 } 1987 return hl 1988 } 1989 1990 func buildHeader(headerMap interface{}) (*gofastly.CreateHeaderInput, error) { 1991 df := headerMap.(map[string]interface{}) 1992 opts := gofastly.CreateHeaderInput{ 1993 Name: df["name"].(string), 1994 IgnoreIfSet: gofastly.CBool(df["ignore_if_set"].(bool)), 1995 Destination: df["destination"].(string), 1996 Priority: uint(df["priority"].(int)), 1997 Source: df["source"].(string), 1998 Regex: df["regex"].(string), 1999 Substitution: df["substitution"].(string), 2000 RequestCondition: df["request_condition"].(string), 2001 CacheCondition: df["cache_condition"].(string), 2002 ResponseCondition: df["response_condition"].(string), 2003 } 2004 2005 act := strings.ToLower(df["action"].(string)) 2006 switch act { 2007 case "set": 2008 opts.Action = gofastly.HeaderActionSet 2009 case "append": 2010 opts.Action = gofastly.HeaderActionAppend 2011 case "delete": 2012 opts.Action = gofastly.HeaderActionDelete 2013 case "regex": 2014 opts.Action = gofastly.HeaderActionRegex 2015 case "regex_repeat": 2016 opts.Action = gofastly.HeaderActionRegexRepeat 2017 } 2018 2019 ty := strings.ToLower(df["type"].(string)) 2020 switch ty { 2021 case "request": 2022 opts.Type = gofastly.HeaderTypeRequest 2023 case "fetch": 2024 opts.Type = gofastly.HeaderTypeFetch 2025 case "cache": 2026 opts.Type = gofastly.HeaderTypeCache 2027 case "response": 2028 opts.Type = gofastly.HeaderTypeResponse 2029 } 2030 2031 return &opts, nil 2032 } 2033 2034 func buildCacheSetting(cacheMap interface{}) (*gofastly.CreateCacheSettingInput, error) { 2035 df := cacheMap.(map[string]interface{}) 2036 opts := gofastly.CreateCacheSettingInput{ 2037 Name: df["name"].(string), 2038 StaleTTL: uint(df["stale_ttl"].(int)), 2039 CacheCondition: df["cache_condition"].(string), 2040 } 2041 2042 if v, ok := df["ttl"]; ok { 2043 opts.TTL = uint(v.(int)) 2044 } 2045 2046 act := strings.ToLower(df["action"].(string)) 2047 switch act { 2048 case "cache": 2049 opts.Action = gofastly.CacheSettingActionCache 2050 case "pass": 2051 opts.Action = gofastly.CacheSettingActionPass 2052 case "restart": 2053 opts.Action = gofastly.CacheSettingActionRestart 2054 } 2055 2056 return &opts, nil 2057 } 2058 2059 func flattenGzips(gzipsList []*gofastly.Gzip) []map[string]interface{} { 2060 var gl []map[string]interface{} 2061 for _, g := range gzipsList { 2062 // Convert Gzip to a map for saving to state. 2063 ng := map[string]interface{}{ 2064 "name": g.Name, 2065 "cache_condition": g.CacheCondition, 2066 } 2067 2068 if g.Extensions != "" { 2069 e := strings.Split(g.Extensions, " ") 2070 var et []interface{} 2071 for _, ev := range e { 2072 et = append(et, ev) 2073 } 2074 ng["extensions"] = schema.NewSet(schema.HashString, et) 2075 } 2076 2077 if g.ContentTypes != "" { 2078 c := strings.Split(g.ContentTypes, " ") 2079 var ct []interface{} 2080 for _, cv := range c { 2081 ct = append(ct, cv) 2082 } 2083 ng["content_types"] = schema.NewSet(schema.HashString, ct) 2084 } 2085 2086 // prune any empty values that come from the default string value in structs 2087 for k, v := range ng { 2088 if v == "" { 2089 delete(ng, k) 2090 } 2091 } 2092 2093 gl = append(gl, ng) 2094 } 2095 2096 return gl 2097 } 2098 2099 func flattenHealthchecks(healthcheckList []*gofastly.HealthCheck) []map[string]interface{} { 2100 var hl []map[string]interface{} 2101 for _, h := range healthcheckList { 2102 // Convert HealthChecks to a map for saving to state. 2103 nh := map[string]interface{}{ 2104 "name": h.Name, 2105 "host": h.Host, 2106 "path": h.Path, 2107 "check_interval": h.CheckInterval, 2108 "expected_response": h.ExpectedResponse, 2109 "http_version": h.HTTPVersion, 2110 "initial": h.Initial, 2111 "method": h.Method, 2112 "threshold": h.Threshold, 2113 "timeout": h.Timeout, 2114 "window": h.Window, 2115 } 2116 2117 // prune any empty values that come from the default string value in structs 2118 for k, v := range nh { 2119 if v == "" { 2120 delete(nh, k) 2121 } 2122 } 2123 2124 hl = append(hl, nh) 2125 } 2126 2127 return hl 2128 } 2129 2130 func flattenS3s(s3List []*gofastly.S3) []map[string]interface{} { 2131 var sl []map[string]interface{} 2132 for _, s := range s3List { 2133 // Convert S3s to a map for saving to state. 2134 ns := map[string]interface{}{ 2135 "name": s.Name, 2136 "bucket_name": s.BucketName, 2137 "s3_access_key": s.AccessKey, 2138 "s3_secret_key": s.SecretKey, 2139 "path": s.Path, 2140 "period": s.Period, 2141 "domain": s.Domain, 2142 "gzip_level": s.GzipLevel, 2143 "format": s.Format, 2144 "format_version": s.FormatVersion, 2145 "timestamp_format": s.TimestampFormat, 2146 "response_condition": s.ResponseCondition, 2147 } 2148 2149 // prune any empty values that come from the default string value in structs 2150 for k, v := range ns { 2151 if v == "" { 2152 delete(ns, k) 2153 } 2154 } 2155 2156 sl = append(sl, ns) 2157 } 2158 2159 return sl 2160 } 2161 2162 func flattenPapertrails(papertrailList []*gofastly.Papertrail) []map[string]interface{} { 2163 var pl []map[string]interface{} 2164 for _, p := range papertrailList { 2165 // Convert S3s to a map for saving to state. 2166 ns := map[string]interface{}{ 2167 "name": p.Name, 2168 "address": p.Address, 2169 "port": p.Port, 2170 "format": p.Format, 2171 "response_condition": p.ResponseCondition, 2172 } 2173 2174 // prune any empty values that come from the default string value in structs 2175 for k, v := range ns { 2176 if v == "" { 2177 delete(ns, k) 2178 } 2179 } 2180 2181 pl = append(pl, ns) 2182 } 2183 2184 return pl 2185 } 2186 2187 func flattenResponseObjects(responseObjectList []*gofastly.ResponseObject) []map[string]interface{} { 2188 var rol []map[string]interface{} 2189 for _, ro := range responseObjectList { 2190 // Convert ResponseObjects to a map for saving to state. 2191 nro := map[string]interface{}{ 2192 "name": ro.Name, 2193 "status": ro.Status, 2194 "response": ro.Response, 2195 "content": ro.Content, 2196 "content_type": ro.ContentType, 2197 "request_condition": ro.RequestCondition, 2198 "cache_condition": ro.CacheCondition, 2199 } 2200 2201 // prune any empty values that come from the default string value in structs 2202 for k, v := range nro { 2203 if v == "" { 2204 delete(nro, k) 2205 } 2206 } 2207 2208 rol = append(rol, nro) 2209 } 2210 2211 return rol 2212 } 2213 2214 func flattenConditions(conditionList []*gofastly.Condition) []map[string]interface{} { 2215 var cl []map[string]interface{} 2216 for _, c := range conditionList { 2217 // Convert Conditions to a map for saving to state. 2218 nc := map[string]interface{}{ 2219 "name": c.Name, 2220 "statement": c.Statement, 2221 "type": c.Type, 2222 "priority": c.Priority, 2223 } 2224 2225 // prune any empty values that come from the default string value in structs 2226 for k, v := range nc { 2227 if v == "" { 2228 delete(nc, k) 2229 } 2230 } 2231 2232 cl = append(cl, nc) 2233 } 2234 2235 return cl 2236 } 2237 2238 func flattenRequestSettings(rsList []*gofastly.RequestSetting) []map[string]interface{} { 2239 var rl []map[string]interface{} 2240 for _, r := range rsList { 2241 // Convert Request Settings to a map for saving to state. 2242 nrs := map[string]interface{}{ 2243 "name": r.Name, 2244 "max_stale_age": r.MaxStaleAge, 2245 "force_miss": r.ForceMiss, 2246 "force_ssl": r.ForceSSL, 2247 "action": r.Action, 2248 "bypass_busy_wait": r.BypassBusyWait, 2249 "hash_keys": r.HashKeys, 2250 "xff": r.XForwardedFor, 2251 "timer_support": r.TimerSupport, 2252 "geo_headers": r.GeoHeaders, 2253 "default_host": r.DefaultHost, 2254 "request_condition": r.RequestCondition, 2255 } 2256 2257 // prune any empty values that come from the default string value in structs 2258 for k, v := range nrs { 2259 if v == "" { 2260 delete(nrs, k) 2261 } 2262 } 2263 2264 rl = append(rl, nrs) 2265 } 2266 2267 return rl 2268 } 2269 2270 func buildRequestSetting(requestSettingMap interface{}) (*gofastly.CreateRequestSettingInput, error) { 2271 df := requestSettingMap.(map[string]interface{}) 2272 opts := gofastly.CreateRequestSettingInput{ 2273 Name: df["name"].(string), 2274 MaxStaleAge: uint(df["max_stale_age"].(int)), 2275 ForceMiss: gofastly.CBool(df["force_miss"].(bool)), 2276 ForceSSL: gofastly.CBool(df["force_ssl"].(bool)), 2277 BypassBusyWait: gofastly.CBool(df["bypass_busy_wait"].(bool)), 2278 HashKeys: df["hash_keys"].(string), 2279 TimerSupport: gofastly.CBool(df["timer_support"].(bool)), 2280 GeoHeaders: gofastly.CBool(df["geo_headers"].(bool)), 2281 DefaultHost: df["default_host"].(string), 2282 RequestCondition: df["request_condition"].(string), 2283 } 2284 2285 act := strings.ToLower(df["action"].(string)) 2286 switch act { 2287 case "lookup": 2288 opts.Action = gofastly.RequestSettingActionLookup 2289 case "pass": 2290 opts.Action = gofastly.RequestSettingActionPass 2291 } 2292 2293 xff := strings.ToLower(df["xff"].(string)) 2294 switch xff { 2295 case "clear": 2296 opts.XForwardedFor = gofastly.RequestSettingXFFClear 2297 case "leave": 2298 opts.XForwardedFor = gofastly.RequestSettingXFFLeave 2299 case "append": 2300 opts.XForwardedFor = gofastly.RequestSettingXFFAppend 2301 case "append_all": 2302 opts.XForwardedFor = gofastly.RequestSettingXFFAppendAll 2303 case "overwrite": 2304 opts.XForwardedFor = gofastly.RequestSettingXFFOverwrite 2305 } 2306 2307 return &opts, nil 2308 } 2309 2310 func flattenCacheSettings(csList []*gofastly.CacheSetting) []map[string]interface{} { 2311 var csl []map[string]interface{} 2312 for _, cl := range csList { 2313 // Convert Cache Settings to a map for saving to state. 2314 clMap := map[string]interface{}{ 2315 "name": cl.Name, 2316 "action": cl.Action, 2317 "cache_condition": cl.CacheCondition, 2318 "stale_ttl": cl.StaleTTL, 2319 "ttl": cl.TTL, 2320 } 2321 2322 // prune any empty values that come from the default string value in structs 2323 for k, v := range clMap { 2324 if v == "" { 2325 delete(clMap, k) 2326 } 2327 } 2328 2329 csl = append(csl, clMap) 2330 } 2331 2332 return csl 2333 } 2334 2335 func flattenVCLs(vclList []*gofastly.VCL) []map[string]interface{} { 2336 var vl []map[string]interface{} 2337 for _, vcl := range vclList { 2338 // Convert VCLs to a map for saving to state. 2339 vclMap := map[string]interface{}{ 2340 "name": vcl.Name, 2341 "content": vcl.Content, 2342 "main": vcl.Main, 2343 } 2344 2345 // prune any empty values that come from the default string value in structs 2346 for k, v := range vclMap { 2347 if v == "" { 2348 delete(vclMap, k) 2349 } 2350 } 2351 2352 vl = append(vl, vclMap) 2353 } 2354 2355 return vl 2356 } 2357 2358 func validateVCLs(d *schema.ResourceData) error { 2359 // TODO: this would be nice to move into a resource/collection validation function, once that is available 2360 // (see https://github.com/hashicorp/terraform/pull/4348 and https://github.com/hashicorp/terraform/pull/6508) 2361 vcls, exists := d.GetOk("vcl") 2362 if !exists { 2363 return nil 2364 } 2365 2366 numberOfMainVCLs, numberOfIncludeVCLs := 0, 0 2367 for _, vclElem := range vcls.(*schema.Set).List() { 2368 vcl := vclElem.(map[string]interface{}) 2369 if mainVal, hasMain := vcl["main"]; hasMain && mainVal.(bool) { 2370 numberOfMainVCLs++ 2371 } else { 2372 numberOfIncludeVCLs++ 2373 } 2374 } 2375 if numberOfMainVCLs == 0 && numberOfIncludeVCLs > 0 { 2376 return errors.New("if you include VCL configurations, one of them should have main = true") 2377 } 2378 if numberOfMainVCLs > 1 { 2379 return errors.New("you cannot have more than one VCL configuration with main = true") 2380 } 2381 return nil 2382 }