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