github.com/cbroglie/terraform@v0.7.0-rc3.0.20170410193827-735dfc416d46/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: validateLoggingFormatVersion, 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 "sumologic": { 598 Type: schema.TypeSet, 599 Optional: true, 600 Elem: &schema.Resource{ 601 Schema: map[string]*schema.Schema{ 602 // Required fields 603 "name": { 604 Type: schema.TypeString, 605 Required: true, 606 Description: "Unique name to refer to this logging setup", 607 }, 608 "url": { 609 Type: schema.TypeString, 610 Required: true, 611 Description: "The URL to POST to.", 612 }, 613 // Optional fields 614 "format": { 615 Type: schema.TypeString, 616 Optional: true, 617 Default: "%h %l %u %t %r %>s", 618 Description: "Apache-style string or VCL variables to use for log formatting", 619 }, 620 "format_version": { 621 Type: schema.TypeInt, 622 Optional: true, 623 Default: 1, 624 Description: "The version of the custom logging format used for the configured endpoint. Can be either 1 or 2. (Default: 1)", 625 ValidateFunc: validateLoggingFormatVersion, 626 }, 627 "response_condition": { 628 Type: schema.TypeString, 629 Optional: true, 630 Default: "", 631 Description: "Name of a condition to apply this logging.", 632 }, 633 "message_type": { 634 Type: schema.TypeString, 635 Optional: true, 636 Default: "classic", 637 Description: "How the message should be formatted.", 638 ValidateFunc: validateLoggingMessageType, 639 }, 640 }, 641 }, 642 }, 643 644 "response_object": { 645 Type: schema.TypeSet, 646 Optional: true, 647 Elem: &schema.Resource{ 648 Schema: map[string]*schema.Schema{ 649 // Required 650 "name": { 651 Type: schema.TypeString, 652 Required: true, 653 Description: "Unique name to refer to this request object", 654 }, 655 // Optional fields 656 "status": { 657 Type: schema.TypeInt, 658 Optional: true, 659 Default: 200, 660 Description: "The HTTP Status Code of the object", 661 }, 662 "response": { 663 Type: schema.TypeString, 664 Optional: true, 665 Default: "OK", 666 Description: "The HTTP Response of the object", 667 }, 668 "content": { 669 Type: schema.TypeString, 670 Optional: true, 671 Default: "", 672 Description: "The content to deliver for the response object", 673 }, 674 "content_type": { 675 Type: schema.TypeString, 676 Optional: true, 677 Default: "", 678 Description: "The MIME type of the content", 679 }, 680 "request_condition": { 681 Type: schema.TypeString, 682 Optional: true, 683 Default: "", 684 Description: "Name of the condition to be checked during the request phase to see if the object should be delivered", 685 }, 686 "cache_condition": { 687 Type: schema.TypeString, 688 Optional: true, 689 Default: "", 690 Description: "Name of the condition checked after we have retrieved an object. If the condition passes then deliver this Request Object instead.", 691 }, 692 }, 693 }, 694 }, 695 696 "request_setting": { 697 Type: schema.TypeSet, 698 Optional: true, 699 Elem: &schema.Resource{ 700 Schema: map[string]*schema.Schema{ 701 // Required fields 702 "name": { 703 Type: schema.TypeString, 704 Required: true, 705 Description: "Unique name to refer to this Request Setting", 706 }, 707 "request_condition": { 708 Type: schema.TypeString, 709 Required: true, 710 Description: "Name of a request condition to apply.", 711 }, 712 // Optional fields 713 "max_stale_age": { 714 Type: schema.TypeInt, 715 Optional: true, 716 Default: 60, 717 Description: "How old an object is allowed to be, in seconds. Default `60`", 718 }, 719 "force_miss": { 720 Type: schema.TypeBool, 721 Optional: true, 722 Description: "Force a cache miss for the request", 723 }, 724 "force_ssl": { 725 Type: schema.TypeBool, 726 Optional: true, 727 Description: "Forces the request use SSL", 728 }, 729 "action": { 730 Type: schema.TypeString, 731 Optional: true, 732 Description: "Allows you to terminate request handling and immediately perform an action", 733 }, 734 "bypass_busy_wait": { 735 Type: schema.TypeBool, 736 Optional: true, 737 Description: "Disable collapsed forwarding", 738 }, 739 "hash_keys": { 740 Type: schema.TypeString, 741 Optional: true, 742 Description: "Comma separated list of varnish request object fields that should be in the hash key", 743 }, 744 "xff": { 745 Type: schema.TypeString, 746 Optional: true, 747 Default: "append", 748 Description: "X-Forwarded-For options", 749 }, 750 "timer_support": { 751 Type: schema.TypeBool, 752 Optional: true, 753 Description: "Injects the X-Timer info into the request", 754 }, 755 "geo_headers": { 756 Type: schema.TypeBool, 757 Optional: true, 758 Description: "Inject Fastly-Geo-Country, Fastly-Geo-City, and Fastly-Geo-Region", 759 }, 760 "default_host": { 761 Type: schema.TypeString, 762 Optional: true, 763 Description: "the host header", 764 }, 765 }, 766 }, 767 }, 768 "vcl": { 769 Type: schema.TypeSet, 770 Optional: true, 771 Elem: &schema.Resource{ 772 Schema: map[string]*schema.Schema{ 773 "name": { 774 Type: schema.TypeString, 775 Required: true, 776 Description: "A name to refer to this VCL configuration", 777 }, 778 "content": { 779 Type: schema.TypeString, 780 Required: true, 781 Description: "The contents of this VCL configuration", 782 StateFunc: func(v interface{}) string { 783 switch v.(type) { 784 case string: 785 hash := sha1.Sum([]byte(v.(string))) 786 return hex.EncodeToString(hash[:]) 787 default: 788 return "" 789 } 790 }, 791 }, 792 "main": { 793 Type: schema.TypeBool, 794 Optional: true, 795 Default: false, 796 Description: "Should this VCL configuration be the main configuration", 797 }, 798 }, 799 }, 800 }, 801 }, 802 } 803 } 804 805 func resourceServiceV1Create(d *schema.ResourceData, meta interface{}) error { 806 if err := validateVCLs(d); err != nil { 807 return err 808 } 809 810 conn := meta.(*FastlyClient).conn 811 service, err := conn.CreateService(&gofastly.CreateServiceInput{ 812 Name: d.Get("name").(string), 813 Comment: "Managed by Terraform", 814 }) 815 816 if err != nil { 817 return err 818 } 819 820 d.SetId(service.ID) 821 return resourceServiceV1Update(d, meta) 822 } 823 824 func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error { 825 if err := validateVCLs(d); err != nil { 826 return err 827 } 828 829 conn := meta.(*FastlyClient).conn 830 831 // Update Name. No new verions is required for this 832 if d.HasChange("name") { 833 _, err := conn.UpdateService(&gofastly.UpdateServiceInput{ 834 ID: d.Id(), 835 Name: d.Get("name").(string), 836 }) 837 if err != nil { 838 return err 839 } 840 } 841 842 // Once activated, Versions are locked and become immutable. This is true for 843 // versions that are no longer active. For Domains, Backends, DefaultHost and 844 // DefaultTTL, a new Version must be created first, and updates posted to that 845 // Version. Loop these attributes and determine if we need to create a new version first 846 var needsChange bool 847 for _, v := range []string{ 848 "domain", 849 "backend", 850 "default_host", 851 "default_ttl", 852 "header", 853 "gzip", 854 "healthcheck", 855 "s3logging", 856 "papertrail", 857 "response_object", 858 "condition", 859 "request_setting", 860 "cache_setting", 861 "vcl", 862 } { 863 if d.HasChange(v) { 864 needsChange = true 865 } 866 } 867 868 if needsChange { 869 latestVersion := d.Get("active_version").(string) 870 if latestVersion == "" { 871 // If the service was just created, there is an empty Version 1 available 872 // that is unlocked and can be updated 873 latestVersion = "1" 874 } else { 875 // Clone the latest version, giving us an unlocked version we can modify 876 log.Printf("[DEBUG] Creating clone of version (%s) for updates", latestVersion) 877 newVersion, err := conn.CloneVersion(&gofastly.CloneVersionInput{ 878 Service: d.Id(), 879 Version: latestVersion, 880 }) 881 if err != nil { 882 return err 883 } 884 885 // The new version number is named "Number", but it's actually a string 886 latestVersion = newVersion.Number 887 888 // New versions are not immediately found in the API, or are not 889 // immediately mutable, so we need to sleep a few and let Fastly ready 890 // itself. Typically, 7 seconds is enough 891 log.Print("[DEBUG] Sleeping 7 seconds to allow Fastly Version to be available") 892 time.Sleep(7 * time.Second) 893 } 894 895 // update general settings 896 if d.HasChange("default_host") || d.HasChange("default_ttl") { 897 opts := gofastly.UpdateSettingsInput{ 898 Service: d.Id(), 899 Version: latestVersion, 900 // default_ttl has the same default value of 3600 that is provided by 901 // the Fastly API, so it's safe to include here 902 DefaultTTL: uint(d.Get("default_ttl").(int)), 903 } 904 905 if attr, ok := d.GetOk("default_host"); ok { 906 opts.DefaultHost = attr.(string) 907 } 908 909 log.Printf("[DEBUG] Update Settings opts: %#v", opts) 910 _, err := conn.UpdateSettings(&opts) 911 if err != nil { 912 return err 913 } 914 } 915 916 // Conditions need to be updated first, as they can be referenced by other 917 // configuraiton objects (Backends, Request Headers, etc) 918 919 // Find difference in Conditions 920 if d.HasChange("condition") { 921 // Note: we don't utilize the PUT endpoint to update these objects, we simply 922 // destroy any that have changed, and create new ones with the updated 923 // values. This is how Terraform works with nested sub resources, we only 924 // get the full diff not a partial set item diff. Because this is done 925 // on a new version of the Fastly Service configuration, this is considered safe 926 927 oc, nc := d.GetChange("condition") 928 if oc == nil { 929 oc = new(schema.Set) 930 } 931 if nc == nil { 932 nc = new(schema.Set) 933 } 934 935 ocs := oc.(*schema.Set) 936 ncs := nc.(*schema.Set) 937 removeConditions := ocs.Difference(ncs).List() 938 addConditions := ncs.Difference(ocs).List() 939 940 // DELETE old Conditions 941 for _, cRaw := range removeConditions { 942 cf := cRaw.(map[string]interface{}) 943 opts := gofastly.DeleteConditionInput{ 944 Service: d.Id(), 945 Version: latestVersion, 946 Name: cf["name"].(string), 947 } 948 949 log.Printf("[DEBUG] Fastly Conditions Removal opts: %#v", opts) 950 err := conn.DeleteCondition(&opts) 951 if err != nil { 952 return err 953 } 954 } 955 956 // POST new Conditions 957 for _, cRaw := range addConditions { 958 cf := cRaw.(map[string]interface{}) 959 opts := gofastly.CreateConditionInput{ 960 Service: d.Id(), 961 Version: latestVersion, 962 Name: cf["name"].(string), 963 Type: cf["type"].(string), 964 // need to trim leading/tailing spaces, incase the config has HEREDOC 965 // formatting and contains a trailing new line 966 Statement: strings.TrimSpace(cf["statement"].(string)), 967 Priority: cf["priority"].(int), 968 } 969 970 log.Printf("[DEBUG] Create Conditions Opts: %#v", opts) 971 _, err := conn.CreateCondition(&opts) 972 if err != nil { 973 return err 974 } 975 } 976 } 977 978 // Find differences in domains 979 if d.HasChange("domain") { 980 od, nd := d.GetChange("domain") 981 if od == nil { 982 od = new(schema.Set) 983 } 984 if nd == nil { 985 nd = new(schema.Set) 986 } 987 988 ods := od.(*schema.Set) 989 nds := nd.(*schema.Set) 990 991 remove := ods.Difference(nds).List() 992 add := nds.Difference(ods).List() 993 994 // Delete removed domains 995 for _, dRaw := range remove { 996 df := dRaw.(map[string]interface{}) 997 opts := gofastly.DeleteDomainInput{ 998 Service: d.Id(), 999 Version: latestVersion, 1000 Name: df["name"].(string), 1001 } 1002 1003 log.Printf("[DEBUG] Fastly Domain removal opts: %#v", opts) 1004 err := conn.DeleteDomain(&opts) 1005 if err != nil { 1006 return err 1007 } 1008 } 1009 1010 // POST new Domains 1011 for _, dRaw := range add { 1012 df := dRaw.(map[string]interface{}) 1013 opts := gofastly.CreateDomainInput{ 1014 Service: d.Id(), 1015 Version: latestVersion, 1016 Name: df["name"].(string), 1017 } 1018 1019 if v, ok := df["comment"]; ok { 1020 opts.Comment = v.(string) 1021 } 1022 1023 log.Printf("[DEBUG] Fastly Domain Addition opts: %#v", opts) 1024 _, err := conn.CreateDomain(&opts) 1025 if err != nil { 1026 return err 1027 } 1028 } 1029 } 1030 1031 // find difference in backends 1032 if d.HasChange("backend") { 1033 ob, nb := d.GetChange("backend") 1034 if ob == nil { 1035 ob = new(schema.Set) 1036 } 1037 if nb == nil { 1038 nb = new(schema.Set) 1039 } 1040 1041 obs := ob.(*schema.Set) 1042 nbs := nb.(*schema.Set) 1043 removeBackends := obs.Difference(nbs).List() 1044 addBackends := nbs.Difference(obs).List() 1045 1046 // DELETE old Backends 1047 for _, bRaw := range removeBackends { 1048 bf := bRaw.(map[string]interface{}) 1049 opts := gofastly.DeleteBackendInput{ 1050 Service: d.Id(), 1051 Version: latestVersion, 1052 Name: bf["name"].(string), 1053 } 1054 1055 log.Printf("[DEBUG] Fastly Backend removal opts: %#v", opts) 1056 err := conn.DeleteBackend(&opts) 1057 if err != nil { 1058 return err 1059 } 1060 } 1061 1062 // Find and post new Backends 1063 for _, dRaw := range addBackends { 1064 df := dRaw.(map[string]interface{}) 1065 opts := gofastly.CreateBackendInput{ 1066 Service: d.Id(), 1067 Version: latestVersion, 1068 Name: df["name"].(string), 1069 Address: df["address"].(string), 1070 AutoLoadbalance: gofastly.CBool(df["auto_loadbalance"].(bool)), 1071 SSLCheckCert: gofastly.CBool(df["ssl_check_cert"].(bool)), 1072 SSLHostname: df["ssl_hostname"].(string), 1073 SSLCertHostname: df["ssl_cert_hostname"].(string), 1074 SSLSNIHostname: df["ssl_sni_hostname"].(string), 1075 Shield: df["shield"].(string), 1076 Port: uint(df["port"].(int)), 1077 BetweenBytesTimeout: uint(df["between_bytes_timeout"].(int)), 1078 ConnectTimeout: uint(df["connect_timeout"].(int)), 1079 ErrorThreshold: uint(df["error_threshold"].(int)), 1080 FirstByteTimeout: uint(df["first_byte_timeout"].(int)), 1081 MaxConn: uint(df["max_conn"].(int)), 1082 Weight: uint(df["weight"].(int)), 1083 RequestCondition: df["request_condition"].(string), 1084 } 1085 1086 log.Printf("[DEBUG] Create Backend Opts: %#v", opts) 1087 _, err := conn.CreateBackend(&opts) 1088 if err != nil { 1089 return err 1090 } 1091 } 1092 } 1093 1094 if d.HasChange("header") { 1095 oh, nh := d.GetChange("header") 1096 if oh == nil { 1097 oh = new(schema.Set) 1098 } 1099 if nh == nil { 1100 nh = new(schema.Set) 1101 } 1102 1103 ohs := oh.(*schema.Set) 1104 nhs := nh.(*schema.Set) 1105 1106 remove := ohs.Difference(nhs).List() 1107 add := nhs.Difference(ohs).List() 1108 1109 // Delete removed headers 1110 for _, dRaw := range remove { 1111 df := dRaw.(map[string]interface{}) 1112 opts := gofastly.DeleteHeaderInput{ 1113 Service: d.Id(), 1114 Version: latestVersion, 1115 Name: df["name"].(string), 1116 } 1117 1118 log.Printf("[DEBUG] Fastly Header removal opts: %#v", opts) 1119 err := conn.DeleteHeader(&opts) 1120 if err != nil { 1121 return err 1122 } 1123 } 1124 1125 // POST new Headers 1126 for _, dRaw := range add { 1127 opts, err := buildHeader(dRaw.(map[string]interface{})) 1128 if err != nil { 1129 log.Printf("[DEBUG] Error building Header: %s", err) 1130 return err 1131 } 1132 opts.Service = d.Id() 1133 opts.Version = latestVersion 1134 1135 log.Printf("[DEBUG] Fastly Header Addition opts: %#v", opts) 1136 _, err = conn.CreateHeader(opts) 1137 if err != nil { 1138 return err 1139 } 1140 } 1141 } 1142 1143 // Find differences in Gzips 1144 if d.HasChange("gzip") { 1145 og, ng := d.GetChange("gzip") 1146 if og == nil { 1147 og = new(schema.Set) 1148 } 1149 if ng == nil { 1150 ng = new(schema.Set) 1151 } 1152 1153 ogs := og.(*schema.Set) 1154 ngs := ng.(*schema.Set) 1155 1156 remove := ogs.Difference(ngs).List() 1157 add := ngs.Difference(ogs).List() 1158 1159 // Delete removed gzip rules 1160 for _, dRaw := range remove { 1161 df := dRaw.(map[string]interface{}) 1162 opts := gofastly.DeleteGzipInput{ 1163 Service: d.Id(), 1164 Version: latestVersion, 1165 Name: df["name"].(string), 1166 } 1167 1168 log.Printf("[DEBUG] Fastly Gzip removal opts: %#v", opts) 1169 err := conn.DeleteGzip(&opts) 1170 if err != nil { 1171 return err 1172 } 1173 } 1174 1175 // POST new Gzips 1176 for _, dRaw := range add { 1177 df := dRaw.(map[string]interface{}) 1178 opts := gofastly.CreateGzipInput{ 1179 Service: d.Id(), 1180 Version: latestVersion, 1181 Name: df["name"].(string), 1182 CacheCondition: df["cache_condition"].(string), 1183 } 1184 1185 if v, ok := df["content_types"]; ok { 1186 if len(v.(*schema.Set).List()) > 0 { 1187 var cl []string 1188 for _, c := range v.(*schema.Set).List() { 1189 cl = append(cl, c.(string)) 1190 } 1191 opts.ContentTypes = strings.Join(cl, " ") 1192 } 1193 } 1194 1195 if v, ok := df["extensions"]; ok { 1196 if len(v.(*schema.Set).List()) > 0 { 1197 var el []string 1198 for _, e := range v.(*schema.Set).List() { 1199 el = append(el, e.(string)) 1200 } 1201 opts.Extensions = strings.Join(el, " ") 1202 } 1203 } 1204 1205 log.Printf("[DEBUG] Fastly Gzip Addition opts: %#v", opts) 1206 _, err := conn.CreateGzip(&opts) 1207 if err != nil { 1208 return err 1209 } 1210 } 1211 } 1212 1213 // find difference in Healthcheck 1214 if d.HasChange("healthcheck") { 1215 oh, nh := d.GetChange("healthcheck") 1216 if oh == nil { 1217 oh = new(schema.Set) 1218 } 1219 if nh == nil { 1220 nh = new(schema.Set) 1221 } 1222 1223 ohs := oh.(*schema.Set) 1224 nhs := nh.(*schema.Set) 1225 removeHealthCheck := ohs.Difference(nhs).List() 1226 addHealthCheck := nhs.Difference(ohs).List() 1227 1228 // DELETE old healthcheck configurations 1229 for _, hRaw := range removeHealthCheck { 1230 hf := hRaw.(map[string]interface{}) 1231 opts := gofastly.DeleteHealthCheckInput{ 1232 Service: d.Id(), 1233 Version: latestVersion, 1234 Name: hf["name"].(string), 1235 } 1236 1237 log.Printf("[DEBUG] Fastly Healthcheck removal opts: %#v", opts) 1238 err := conn.DeleteHealthCheck(&opts) 1239 if err != nil { 1240 return err 1241 } 1242 } 1243 1244 // POST new/updated Healthcheck 1245 for _, hRaw := range addHealthCheck { 1246 hf := hRaw.(map[string]interface{}) 1247 1248 opts := gofastly.CreateHealthCheckInput{ 1249 Service: d.Id(), 1250 Version: latestVersion, 1251 Name: hf["name"].(string), 1252 Host: hf["host"].(string), 1253 Path: hf["path"].(string), 1254 CheckInterval: uint(hf["check_interval"].(int)), 1255 ExpectedResponse: uint(hf["expected_response"].(int)), 1256 HTTPVersion: hf["http_version"].(string), 1257 Initial: uint(hf["initial"].(int)), 1258 Method: hf["method"].(string), 1259 Threshold: uint(hf["threshold"].(int)), 1260 Timeout: uint(hf["timeout"].(int)), 1261 Window: uint(hf["window"].(int)), 1262 } 1263 1264 log.Printf("[DEBUG] Create Healthcheck Opts: %#v", opts) 1265 _, err := conn.CreateHealthCheck(&opts) 1266 if err != nil { 1267 return err 1268 } 1269 } 1270 } 1271 1272 // find difference in s3logging 1273 if d.HasChange("s3logging") { 1274 os, ns := d.GetChange("s3logging") 1275 if os == nil { 1276 os = new(schema.Set) 1277 } 1278 if ns == nil { 1279 ns = new(schema.Set) 1280 } 1281 1282 oss := os.(*schema.Set) 1283 nss := ns.(*schema.Set) 1284 removeS3Logging := oss.Difference(nss).List() 1285 addS3Logging := nss.Difference(oss).List() 1286 1287 // DELETE old S3 Log configurations 1288 for _, sRaw := range removeS3Logging { 1289 sf := sRaw.(map[string]interface{}) 1290 opts := gofastly.DeleteS3Input{ 1291 Service: d.Id(), 1292 Version: latestVersion, 1293 Name: sf["name"].(string), 1294 } 1295 1296 log.Printf("[DEBUG] Fastly S3 Logging removal opts: %#v", opts) 1297 err := conn.DeleteS3(&opts) 1298 if err != nil { 1299 return err 1300 } 1301 } 1302 1303 // POST new/updated S3 Logging 1304 for _, sRaw := range addS3Logging { 1305 sf := sRaw.(map[string]interface{}) 1306 1307 // Fastly API will not error if these are omitted, so we throw an error 1308 // if any of these are empty 1309 for _, sk := range []string{"s3_access_key", "s3_secret_key"} { 1310 if sf[sk].(string) == "" { 1311 return fmt.Errorf("[ERR] No %s found for S3 Log stream setup for Service (%s)", sk, d.Id()) 1312 } 1313 } 1314 1315 opts := gofastly.CreateS3Input{ 1316 Service: d.Id(), 1317 Version: latestVersion, 1318 Name: sf["name"].(string), 1319 BucketName: sf["bucket_name"].(string), 1320 AccessKey: sf["s3_access_key"].(string), 1321 SecretKey: sf["s3_secret_key"].(string), 1322 Period: uint(sf["period"].(int)), 1323 GzipLevel: uint(sf["gzip_level"].(int)), 1324 Domain: sf["domain"].(string), 1325 Path: sf["path"].(string), 1326 Format: sf["format"].(string), 1327 FormatVersion: uint(sf["format_version"].(int)), 1328 TimestampFormat: sf["timestamp_format"].(string), 1329 ResponseCondition: sf["response_condition"].(string), 1330 } 1331 1332 log.Printf("[DEBUG] Create S3 Logging Opts: %#v", opts) 1333 _, err := conn.CreateS3(&opts) 1334 if err != nil { 1335 return err 1336 } 1337 } 1338 } 1339 1340 // find difference in Papertrail 1341 if d.HasChange("papertrail") { 1342 os, ns := d.GetChange("papertrail") 1343 if os == nil { 1344 os = new(schema.Set) 1345 } 1346 if ns == nil { 1347 ns = new(schema.Set) 1348 } 1349 1350 oss := os.(*schema.Set) 1351 nss := ns.(*schema.Set) 1352 removePapertrail := oss.Difference(nss).List() 1353 addPapertrail := nss.Difference(oss).List() 1354 1355 // DELETE old papertrail configurations 1356 for _, pRaw := range removePapertrail { 1357 pf := pRaw.(map[string]interface{}) 1358 opts := gofastly.DeletePapertrailInput{ 1359 Service: d.Id(), 1360 Version: latestVersion, 1361 Name: pf["name"].(string), 1362 } 1363 1364 log.Printf("[DEBUG] Fastly Papertrail removal opts: %#v", opts) 1365 err := conn.DeletePapertrail(&opts) 1366 if err != nil { 1367 return err 1368 } 1369 } 1370 1371 // POST new/updated Papertrail 1372 for _, pRaw := range addPapertrail { 1373 pf := pRaw.(map[string]interface{}) 1374 1375 opts := gofastly.CreatePapertrailInput{ 1376 Service: d.Id(), 1377 Version: latestVersion, 1378 Name: pf["name"].(string), 1379 Address: pf["address"].(string), 1380 Port: uint(pf["port"].(int)), 1381 Format: pf["format"].(string), 1382 ResponseCondition: pf["response_condition"].(string), 1383 } 1384 1385 log.Printf("[DEBUG] Create Papertrail Opts: %#v", opts) 1386 _, err := conn.CreatePapertrail(&opts) 1387 if err != nil { 1388 return err 1389 } 1390 } 1391 } 1392 1393 // find difference in Sumologic 1394 if d.HasChange("sumologic") { 1395 os, ns := d.GetChange("sumologic") 1396 if os == nil { 1397 os = new(schema.Set) 1398 } 1399 if ns == nil { 1400 ns = new(schema.Set) 1401 } 1402 1403 oss := os.(*schema.Set) 1404 nss := ns.(*schema.Set) 1405 removeSumologic := oss.Difference(nss).List() 1406 addSumologic := nss.Difference(oss).List() 1407 1408 // DELETE old sumologic configurations 1409 for _, pRaw := range removeSumologic { 1410 sf := pRaw.(map[string]interface{}) 1411 opts := gofastly.DeleteSumologicInput{ 1412 Service: d.Id(), 1413 Version: latestVersion, 1414 Name: sf["name"].(string), 1415 } 1416 1417 log.Printf("[DEBUG] Fastly Sumologic removal opts: %#v", opts) 1418 err := conn.DeleteSumologic(&opts) 1419 if err != nil { 1420 return err 1421 } 1422 } 1423 1424 // POST new/updated Sumologic 1425 for _, pRaw := range addSumologic { 1426 sf := pRaw.(map[string]interface{}) 1427 opts := gofastly.CreateSumologicInput{ 1428 Service: d.Id(), 1429 Version: latestVersion, 1430 Name: sf["name"].(string), 1431 URL: sf["url"].(string), 1432 Format: sf["format"].(string), 1433 FormatVersion: sf["format_version"].(int), 1434 ResponseCondition: sf["response_condition"].(string), 1435 MessageType: sf["message_type"].(string), 1436 } 1437 1438 log.Printf("[DEBUG] Create Sumologic Opts: %#v", opts) 1439 _, err := conn.CreateSumologic(&opts) 1440 if err != nil { 1441 return err 1442 } 1443 } 1444 } 1445 1446 // find difference in Response Object 1447 if d.HasChange("response_object") { 1448 or, nr := d.GetChange("response_object") 1449 if or == nil { 1450 or = new(schema.Set) 1451 } 1452 if nr == nil { 1453 nr = new(schema.Set) 1454 } 1455 1456 ors := or.(*schema.Set) 1457 nrs := nr.(*schema.Set) 1458 removeResponseObject := ors.Difference(nrs).List() 1459 addResponseObject := nrs.Difference(ors).List() 1460 1461 // DELETE old response object configurations 1462 for _, rRaw := range removeResponseObject { 1463 rf := rRaw.(map[string]interface{}) 1464 opts := gofastly.DeleteResponseObjectInput{ 1465 Service: d.Id(), 1466 Version: latestVersion, 1467 Name: rf["name"].(string), 1468 } 1469 1470 log.Printf("[DEBUG] Fastly Response Object removal opts: %#v", opts) 1471 err := conn.DeleteResponseObject(&opts) 1472 if err != nil { 1473 return err 1474 } 1475 } 1476 1477 // POST new/updated Response Object 1478 for _, rRaw := range addResponseObject { 1479 rf := rRaw.(map[string]interface{}) 1480 1481 opts := gofastly.CreateResponseObjectInput{ 1482 Service: d.Id(), 1483 Version: latestVersion, 1484 Name: rf["name"].(string), 1485 Status: uint(rf["status"].(int)), 1486 Response: rf["response"].(string), 1487 Content: rf["content"].(string), 1488 ContentType: rf["content_type"].(string), 1489 RequestCondition: rf["request_condition"].(string), 1490 CacheCondition: rf["cache_condition"].(string), 1491 } 1492 1493 log.Printf("[DEBUG] Create Response Object Opts: %#v", opts) 1494 _, err := conn.CreateResponseObject(&opts) 1495 if err != nil { 1496 return err 1497 } 1498 } 1499 } 1500 1501 // find difference in request settings 1502 if d.HasChange("request_setting") { 1503 os, ns := d.GetChange("request_setting") 1504 if os == nil { 1505 os = new(schema.Set) 1506 } 1507 if ns == nil { 1508 ns = new(schema.Set) 1509 } 1510 1511 ors := os.(*schema.Set) 1512 nrs := ns.(*schema.Set) 1513 removeRequestSettings := ors.Difference(nrs).List() 1514 addRequestSettings := nrs.Difference(ors).List() 1515 1516 // DELETE old Request Settings configurations 1517 for _, sRaw := range removeRequestSettings { 1518 sf := sRaw.(map[string]interface{}) 1519 opts := gofastly.DeleteRequestSettingInput{ 1520 Service: d.Id(), 1521 Version: latestVersion, 1522 Name: sf["name"].(string), 1523 } 1524 1525 log.Printf("[DEBUG] Fastly Request Setting removal opts: %#v", opts) 1526 err := conn.DeleteRequestSetting(&opts) 1527 if err != nil { 1528 return err 1529 } 1530 } 1531 1532 // POST new/updated Request Setting 1533 for _, sRaw := range addRequestSettings { 1534 opts, err := buildRequestSetting(sRaw.(map[string]interface{})) 1535 if err != nil { 1536 log.Printf("[DEBUG] Error building Requset Setting: %s", err) 1537 return err 1538 } 1539 opts.Service = d.Id() 1540 opts.Version = latestVersion 1541 1542 log.Printf("[DEBUG] Create Request Setting Opts: %#v", opts) 1543 _, err = conn.CreateRequestSetting(opts) 1544 if err != nil { 1545 return err 1546 } 1547 } 1548 } 1549 1550 // Find differences in VCLs 1551 if d.HasChange("vcl") { 1552 // Note: as above with Gzip and S3 logging, we don't utilize the PUT 1553 // endpoint to update a VCL, we simply destroy it and create a new one. 1554 oldVCLVal, newVCLVal := d.GetChange("vcl") 1555 if oldVCLVal == nil { 1556 oldVCLVal = new(schema.Set) 1557 } 1558 if newVCLVal == nil { 1559 newVCLVal = new(schema.Set) 1560 } 1561 1562 oldVCLSet := oldVCLVal.(*schema.Set) 1563 newVCLSet := newVCLVal.(*schema.Set) 1564 1565 remove := oldVCLSet.Difference(newVCLSet).List() 1566 add := newVCLSet.Difference(oldVCLSet).List() 1567 1568 // Delete removed VCL configurations 1569 for _, dRaw := range remove { 1570 df := dRaw.(map[string]interface{}) 1571 opts := gofastly.DeleteVCLInput{ 1572 Service: d.Id(), 1573 Version: latestVersion, 1574 Name: df["name"].(string), 1575 } 1576 1577 log.Printf("[DEBUG] Fastly VCL Removal opts: %#v", opts) 1578 err := conn.DeleteVCL(&opts) 1579 if err != nil { 1580 return err 1581 } 1582 } 1583 // POST new VCL configurations 1584 for _, dRaw := range add { 1585 df := dRaw.(map[string]interface{}) 1586 opts := gofastly.CreateVCLInput{ 1587 Service: d.Id(), 1588 Version: latestVersion, 1589 Name: df["name"].(string), 1590 Content: df["content"].(string), 1591 } 1592 1593 log.Printf("[DEBUG] Fastly VCL Addition opts: %#v", opts) 1594 _, err := conn.CreateVCL(&opts) 1595 if err != nil { 1596 return err 1597 } 1598 1599 // if this new VCL is the main 1600 if df["main"].(bool) { 1601 opts := gofastly.ActivateVCLInput{ 1602 Service: d.Id(), 1603 Version: latestVersion, 1604 Name: df["name"].(string), 1605 } 1606 log.Printf("[DEBUG] Fastly VCL activation opts: %#v", opts) 1607 _, err := conn.ActivateVCL(&opts) 1608 if err != nil { 1609 return err 1610 } 1611 1612 } 1613 } 1614 } 1615 1616 // Find differences in Cache Settings 1617 if d.HasChange("cache_setting") { 1618 oc, nc := d.GetChange("cache_setting") 1619 if oc == nil { 1620 oc = new(schema.Set) 1621 } 1622 if nc == nil { 1623 nc = new(schema.Set) 1624 } 1625 1626 ocs := oc.(*schema.Set) 1627 ncs := nc.(*schema.Set) 1628 1629 remove := ocs.Difference(ncs).List() 1630 add := ncs.Difference(ocs).List() 1631 1632 // Delete removed Cache Settings 1633 for _, dRaw := range remove { 1634 df := dRaw.(map[string]interface{}) 1635 opts := gofastly.DeleteCacheSettingInput{ 1636 Service: d.Id(), 1637 Version: latestVersion, 1638 Name: df["name"].(string), 1639 } 1640 1641 log.Printf("[DEBUG] Fastly Cache Settings removal opts: %#v", opts) 1642 err := conn.DeleteCacheSetting(&opts) 1643 if err != nil { 1644 return err 1645 } 1646 } 1647 1648 // POST new Cache Settings 1649 for _, dRaw := range add { 1650 opts, err := buildCacheSetting(dRaw.(map[string]interface{})) 1651 if err != nil { 1652 log.Printf("[DEBUG] Error building Cache Setting: %s", err) 1653 return err 1654 } 1655 opts.Service = d.Id() 1656 opts.Version = latestVersion 1657 1658 log.Printf("[DEBUG] Fastly Cache Settings Addition opts: %#v", opts) 1659 _, err = conn.CreateCacheSetting(opts) 1660 if err != nil { 1661 return err 1662 } 1663 } 1664 } 1665 1666 // validate version 1667 log.Printf("[DEBUG] Validating Fastly Service (%s), Version (%s)", d.Id(), latestVersion) 1668 valid, msg, err := conn.ValidateVersion(&gofastly.ValidateVersionInput{ 1669 Service: d.Id(), 1670 Version: latestVersion, 1671 }) 1672 1673 if err != nil { 1674 return fmt.Errorf("[ERR] Error checking validation: %s", err) 1675 } 1676 1677 if !valid { 1678 return fmt.Errorf("[ERR] Invalid configuration for Fastly Service (%s): %s", d.Id(), msg) 1679 } 1680 1681 log.Printf("[DEBUG] Activating Fastly Service (%s), Version (%s)", d.Id(), latestVersion) 1682 _, err = conn.ActivateVersion(&gofastly.ActivateVersionInput{ 1683 Service: d.Id(), 1684 Version: latestVersion, 1685 }) 1686 if err != nil { 1687 return fmt.Errorf("[ERR] Error activating version (%s): %s", latestVersion, err) 1688 } 1689 1690 // Only if the version is valid and activated do we set the active_version. 1691 // This prevents us from getting stuck in cloning an invalid version 1692 d.Set("active_version", latestVersion) 1693 } 1694 1695 return resourceServiceV1Read(d, meta) 1696 } 1697 1698 func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error { 1699 conn := meta.(*FastlyClient).conn 1700 1701 // Find the Service. Discard the service because we need the ServiceDetails, 1702 // not just a Service record 1703 _, err := findService(d.Id(), meta) 1704 if err != nil { 1705 switch err { 1706 case fastlyNoServiceFoundErr: 1707 log.Printf("[WARN] %s for ID (%s)", err, d.Id()) 1708 d.SetId("") 1709 return nil 1710 default: 1711 return err 1712 } 1713 } 1714 1715 s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{ 1716 ID: d.Id(), 1717 }) 1718 1719 if err != nil { 1720 return err 1721 } 1722 1723 d.Set("name", s.Name) 1724 d.Set("active_version", s.ActiveVersion.Number) 1725 1726 // If CreateService succeeds, but initial updates to the Service fail, we'll 1727 // have an empty ActiveService version (no version is active, so we can't 1728 // query for information on it) 1729 if s.ActiveVersion.Number != "" { 1730 settingsOpts := gofastly.GetSettingsInput{ 1731 Service: d.Id(), 1732 Version: s.ActiveVersion.Number, 1733 } 1734 if settings, err := conn.GetSettings(&settingsOpts); err == nil { 1735 d.Set("default_host", settings.DefaultHost) 1736 d.Set("default_ttl", settings.DefaultTTL) 1737 } else { 1738 return fmt.Errorf("[ERR] Error looking up Version settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1739 } 1740 1741 // TODO: update go-fastly to support an ActiveVersion struct, which contains 1742 // domain and backend info in the response. Here we do 2 additional queries 1743 // to find out that info 1744 log.Printf("[DEBUG] Refreshing Domains for (%s)", d.Id()) 1745 domainList, err := conn.ListDomains(&gofastly.ListDomainsInput{ 1746 Service: d.Id(), 1747 Version: s.ActiveVersion.Number, 1748 }) 1749 1750 if err != nil { 1751 return fmt.Errorf("[ERR] Error looking up Domains for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1752 } 1753 1754 // Refresh Domains 1755 dl := flattenDomains(domainList) 1756 1757 if err := d.Set("domain", dl); err != nil { 1758 log.Printf("[WARN] Error setting Domains for (%s): %s", d.Id(), err) 1759 } 1760 1761 // Refresh Backends 1762 log.Printf("[DEBUG] Refreshing Backends for (%s)", d.Id()) 1763 backendList, err := conn.ListBackends(&gofastly.ListBackendsInput{ 1764 Service: d.Id(), 1765 Version: s.ActiveVersion.Number, 1766 }) 1767 1768 if err != nil { 1769 return fmt.Errorf("[ERR] Error looking up Backends for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1770 } 1771 1772 bl := flattenBackends(backendList) 1773 1774 if err := d.Set("backend", bl); err != nil { 1775 log.Printf("[WARN] Error setting Backends for (%s): %s", d.Id(), err) 1776 } 1777 1778 // refresh headers 1779 log.Printf("[DEBUG] Refreshing Headers for (%s)", d.Id()) 1780 headerList, err := conn.ListHeaders(&gofastly.ListHeadersInput{ 1781 Service: d.Id(), 1782 Version: s.ActiveVersion.Number, 1783 }) 1784 1785 if err != nil { 1786 return fmt.Errorf("[ERR] Error looking up Headers for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1787 } 1788 1789 hl := flattenHeaders(headerList) 1790 1791 if err := d.Set("header", hl); err != nil { 1792 log.Printf("[WARN] Error setting Headers for (%s): %s", d.Id(), err) 1793 } 1794 1795 // refresh gzips 1796 log.Printf("[DEBUG] Refreshing Gzips for (%s)", d.Id()) 1797 gzipsList, err := conn.ListGzips(&gofastly.ListGzipsInput{ 1798 Service: d.Id(), 1799 Version: s.ActiveVersion.Number, 1800 }) 1801 1802 if err != nil { 1803 return fmt.Errorf("[ERR] Error looking up Gzips for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1804 } 1805 1806 gl := flattenGzips(gzipsList) 1807 1808 if err := d.Set("gzip", gl); err != nil { 1809 log.Printf("[WARN] Error setting Gzips for (%s): %s", d.Id(), err) 1810 } 1811 1812 // refresh Healthcheck 1813 log.Printf("[DEBUG] Refreshing Healthcheck for (%s)", d.Id()) 1814 healthcheckList, err := conn.ListHealthChecks(&gofastly.ListHealthChecksInput{ 1815 Service: d.Id(), 1816 Version: s.ActiveVersion.Number, 1817 }) 1818 1819 if err != nil { 1820 return fmt.Errorf("[ERR] Error looking up Healthcheck for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1821 } 1822 1823 hcl := flattenHealthchecks(healthcheckList) 1824 1825 if err := d.Set("healthcheck", hcl); err != nil { 1826 log.Printf("[WARN] Error setting Healthcheck for (%s): %s", d.Id(), err) 1827 } 1828 1829 // refresh S3 Logging 1830 log.Printf("[DEBUG] Refreshing S3 Logging for (%s)", d.Id()) 1831 s3List, err := conn.ListS3s(&gofastly.ListS3sInput{ 1832 Service: d.Id(), 1833 Version: s.ActiveVersion.Number, 1834 }) 1835 1836 if err != nil { 1837 return fmt.Errorf("[ERR] Error looking up S3 Logging for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1838 } 1839 1840 sl := flattenS3s(s3List) 1841 1842 if err := d.Set("s3logging", sl); err != nil { 1843 log.Printf("[WARN] Error setting S3 Logging for (%s): %s", d.Id(), err) 1844 } 1845 1846 // refresh Papertrail Logging 1847 log.Printf("[DEBUG] Refreshing Papertrail for (%s)", d.Id()) 1848 papertrailList, err := conn.ListPapertrails(&gofastly.ListPapertrailsInput{ 1849 Service: d.Id(), 1850 Version: s.ActiveVersion.Number, 1851 }) 1852 1853 if err != nil { 1854 return fmt.Errorf("[ERR] Error looking up Papertrail for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1855 } 1856 1857 pl := flattenPapertrails(papertrailList) 1858 1859 if err := d.Set("papertrail", pl); err != nil { 1860 log.Printf("[WARN] Error setting Papertrail for (%s): %s", d.Id(), err) 1861 } 1862 1863 // refresh Sumologic Logging 1864 log.Printf("[DEBUG] Refreshing Sumologic for (%s)", d.Id()) 1865 sumologicList, err := conn.ListSumologics(&gofastly.ListSumologicsInput{ 1866 Service: d.Id(), 1867 Version: s.ActiveVersion.Number, 1868 }) 1869 1870 if err != nil { 1871 return fmt.Errorf("[ERR] Error looking up Sumologic for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1872 } 1873 1874 sul := flattenSumologics(sumologicList) 1875 if err := d.Set("sumologic", sul); err != nil { 1876 log.Printf("[WARN] Error setting Sumologic for (%s): %s", d.Id(), err) 1877 } 1878 1879 // refresh Response Objects 1880 log.Printf("[DEBUG] Refreshing Response Object for (%s)", d.Id()) 1881 responseObjectList, err := conn.ListResponseObjects(&gofastly.ListResponseObjectsInput{ 1882 Service: d.Id(), 1883 Version: s.ActiveVersion.Number, 1884 }) 1885 1886 if err != nil { 1887 return fmt.Errorf("[ERR] Error looking up Response Object for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1888 } 1889 1890 rol := flattenResponseObjects(responseObjectList) 1891 1892 if err := d.Set("response_object", rol); err != nil { 1893 log.Printf("[WARN] Error setting Response Object for (%s): %s", d.Id(), err) 1894 } 1895 1896 // refresh Conditions 1897 log.Printf("[DEBUG] Refreshing Conditions for (%s)", d.Id()) 1898 conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{ 1899 Service: d.Id(), 1900 Version: s.ActiveVersion.Number, 1901 }) 1902 1903 if err != nil { 1904 return fmt.Errorf("[ERR] Error looking up Conditions for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1905 } 1906 1907 cl := flattenConditions(conditionList) 1908 1909 if err := d.Set("condition", cl); err != nil { 1910 log.Printf("[WARN] Error setting Conditions for (%s): %s", d.Id(), err) 1911 } 1912 1913 // refresh Request Settings 1914 log.Printf("[DEBUG] Refreshing Request Settings for (%s)", d.Id()) 1915 rsList, err := conn.ListRequestSettings(&gofastly.ListRequestSettingsInput{ 1916 Service: d.Id(), 1917 Version: s.ActiveVersion.Number, 1918 }) 1919 1920 if err != nil { 1921 return fmt.Errorf("[ERR] Error looking up Request Settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1922 } 1923 1924 rl := flattenRequestSettings(rsList) 1925 1926 if err := d.Set("request_setting", rl); err != nil { 1927 log.Printf("[WARN] Error setting Request Settings for (%s): %s", d.Id(), err) 1928 } 1929 1930 // refresh VCLs 1931 log.Printf("[DEBUG] Refreshing VCLs for (%s)", d.Id()) 1932 vclList, err := conn.ListVCLs(&gofastly.ListVCLsInput{ 1933 Service: d.Id(), 1934 Version: s.ActiveVersion.Number, 1935 }) 1936 if err != nil { 1937 return fmt.Errorf("[ERR] Error looking up VCLs for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1938 } 1939 1940 vl := flattenVCLs(vclList) 1941 1942 if err := d.Set("vcl", vl); err != nil { 1943 log.Printf("[WARN] Error setting VCLs for (%s): %s", d.Id(), err) 1944 } 1945 1946 // refresh Cache Settings 1947 log.Printf("[DEBUG] Refreshing Cache Settings for (%s)", d.Id()) 1948 cslList, err := conn.ListCacheSettings(&gofastly.ListCacheSettingsInput{ 1949 Service: d.Id(), 1950 Version: s.ActiveVersion.Number, 1951 }) 1952 if err != nil { 1953 return fmt.Errorf("[ERR] Error looking up Cache Settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) 1954 } 1955 1956 csl := flattenCacheSettings(cslList) 1957 1958 if err := d.Set("cache_setting", csl); err != nil { 1959 log.Printf("[WARN] Error setting Cache Settings for (%s): %s", d.Id(), err) 1960 } 1961 1962 } else { 1963 log.Printf("[DEBUG] Active Version for Service (%s) is empty, no state to refresh", d.Id()) 1964 } 1965 1966 return nil 1967 } 1968 1969 func resourceServiceV1Delete(d *schema.ResourceData, meta interface{}) error { 1970 conn := meta.(*FastlyClient).conn 1971 1972 // Fastly will fail to delete any service with an Active Version. 1973 // If `force_destroy` is given, we deactivate the active version and then send 1974 // the DELETE call 1975 if d.Get("force_destroy").(bool) { 1976 s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{ 1977 ID: d.Id(), 1978 }) 1979 1980 if err != nil { 1981 return err 1982 } 1983 1984 if s.ActiveVersion.Number != "" { 1985 _, err := conn.DeactivateVersion(&gofastly.DeactivateVersionInput{ 1986 Service: d.Id(), 1987 Version: s.ActiveVersion.Number, 1988 }) 1989 if err != nil { 1990 return err 1991 } 1992 } 1993 } 1994 1995 err := conn.DeleteService(&gofastly.DeleteServiceInput{ 1996 ID: d.Id(), 1997 }) 1998 1999 if err != nil { 2000 return err 2001 } 2002 2003 _, err = findService(d.Id(), meta) 2004 if err != nil { 2005 switch err { 2006 // we expect no records to be found here 2007 case fastlyNoServiceFoundErr: 2008 d.SetId("") 2009 return nil 2010 default: 2011 return err 2012 } 2013 } 2014 2015 // findService above returned something and nil error, but shouldn't have 2016 return fmt.Errorf("[WARN] Tried deleting Service (%s), but was still found", d.Id()) 2017 2018 } 2019 2020 func flattenDomains(list []*gofastly.Domain) []map[string]interface{} { 2021 dl := make([]map[string]interface{}, 0, len(list)) 2022 2023 for _, d := range list { 2024 dl = append(dl, map[string]interface{}{ 2025 "name": d.Name, 2026 "comment": d.Comment, 2027 }) 2028 } 2029 2030 return dl 2031 } 2032 2033 func flattenBackends(backendList []*gofastly.Backend) []map[string]interface{} { 2034 var bl []map[string]interface{} 2035 for _, b := range backendList { 2036 // Convert Backend to a map for saving to state. 2037 nb := map[string]interface{}{ 2038 "name": b.Name, 2039 "address": b.Address, 2040 "auto_loadbalance": b.AutoLoadbalance, 2041 "between_bytes_timeout": int(b.BetweenBytesTimeout), 2042 "connect_timeout": int(b.ConnectTimeout), 2043 "error_threshold": int(b.ErrorThreshold), 2044 "first_byte_timeout": int(b.FirstByteTimeout), 2045 "max_conn": int(b.MaxConn), 2046 "port": int(b.Port), 2047 "shield": b.Shield, 2048 "ssl_check_cert": b.SSLCheckCert, 2049 "ssl_hostname": b.SSLHostname, 2050 "ssl_cert_hostname": b.SSLCertHostname, 2051 "ssl_sni_hostname": b.SSLSNIHostname, 2052 "weight": int(b.Weight), 2053 "request_condition": b.RequestCondition, 2054 } 2055 2056 bl = append(bl, nb) 2057 } 2058 return bl 2059 } 2060 2061 // findService finds a Fastly Service via the ListServices endpoint, returning 2062 // the Service if found. 2063 // 2064 // Fastly API does not include any "deleted_at" type parameter to indicate 2065 // that a Service has been deleted. GET requests to a deleted Service will 2066 // return 200 OK and have the full output of the Service for an unknown time 2067 // (days, in my testing). In order to determine if a Service is deleted, we 2068 // need to hit /service and loop the returned Services, searching for the one 2069 // in question. This endpoint only returns active or "alive" services. If the 2070 // Service is not included, then it's "gone" 2071 // 2072 // Returns a fastlyNoServiceFoundErr error if the Service is not found in the 2073 // ListServices response. 2074 func findService(id string, meta interface{}) (*gofastly.Service, error) { 2075 conn := meta.(*FastlyClient).conn 2076 2077 l, err := conn.ListServices(&gofastly.ListServicesInput{}) 2078 if err != nil { 2079 return nil, fmt.Errorf("[WARN] Error listing services (%s): %s", id, err) 2080 } 2081 2082 for _, s := range l { 2083 if s.ID == id { 2084 log.Printf("[DEBUG] Found Service (%s)", id) 2085 return s, nil 2086 } 2087 } 2088 2089 return nil, fastlyNoServiceFoundErr 2090 } 2091 2092 func flattenHeaders(headerList []*gofastly.Header) []map[string]interface{} { 2093 var hl []map[string]interface{} 2094 for _, h := range headerList { 2095 // Convert Header to a map for saving to state. 2096 nh := map[string]interface{}{ 2097 "name": h.Name, 2098 "action": h.Action, 2099 "ignore_if_set": h.IgnoreIfSet, 2100 "type": h.Type, 2101 "destination": h.Destination, 2102 "source": h.Source, 2103 "regex": h.Regex, 2104 "substitution": h.Substitution, 2105 "priority": int(h.Priority), 2106 "request_condition": h.RequestCondition, 2107 "cache_condition": h.CacheCondition, 2108 "response_condition": h.ResponseCondition, 2109 } 2110 2111 for k, v := range nh { 2112 if v == "" { 2113 delete(nh, k) 2114 } 2115 } 2116 2117 hl = append(hl, nh) 2118 } 2119 return hl 2120 } 2121 2122 func buildHeader(headerMap interface{}) (*gofastly.CreateHeaderInput, error) { 2123 df := headerMap.(map[string]interface{}) 2124 opts := gofastly.CreateHeaderInput{ 2125 Name: df["name"].(string), 2126 IgnoreIfSet: gofastly.CBool(df["ignore_if_set"].(bool)), 2127 Destination: df["destination"].(string), 2128 Priority: uint(df["priority"].(int)), 2129 Source: df["source"].(string), 2130 Regex: df["regex"].(string), 2131 Substitution: df["substitution"].(string), 2132 RequestCondition: df["request_condition"].(string), 2133 CacheCondition: df["cache_condition"].(string), 2134 ResponseCondition: df["response_condition"].(string), 2135 } 2136 2137 act := strings.ToLower(df["action"].(string)) 2138 switch act { 2139 case "set": 2140 opts.Action = gofastly.HeaderActionSet 2141 case "append": 2142 opts.Action = gofastly.HeaderActionAppend 2143 case "delete": 2144 opts.Action = gofastly.HeaderActionDelete 2145 case "regex": 2146 opts.Action = gofastly.HeaderActionRegex 2147 case "regex_repeat": 2148 opts.Action = gofastly.HeaderActionRegexRepeat 2149 } 2150 2151 ty := strings.ToLower(df["type"].(string)) 2152 switch ty { 2153 case "request": 2154 opts.Type = gofastly.HeaderTypeRequest 2155 case "fetch": 2156 opts.Type = gofastly.HeaderTypeFetch 2157 case "cache": 2158 opts.Type = gofastly.HeaderTypeCache 2159 case "response": 2160 opts.Type = gofastly.HeaderTypeResponse 2161 } 2162 2163 return &opts, nil 2164 } 2165 2166 func buildCacheSetting(cacheMap interface{}) (*gofastly.CreateCacheSettingInput, error) { 2167 df := cacheMap.(map[string]interface{}) 2168 opts := gofastly.CreateCacheSettingInput{ 2169 Name: df["name"].(string), 2170 StaleTTL: uint(df["stale_ttl"].(int)), 2171 CacheCondition: df["cache_condition"].(string), 2172 } 2173 2174 if v, ok := df["ttl"]; ok { 2175 opts.TTL = uint(v.(int)) 2176 } 2177 2178 act := strings.ToLower(df["action"].(string)) 2179 switch act { 2180 case "cache": 2181 opts.Action = gofastly.CacheSettingActionCache 2182 case "pass": 2183 opts.Action = gofastly.CacheSettingActionPass 2184 case "restart": 2185 opts.Action = gofastly.CacheSettingActionRestart 2186 } 2187 2188 return &opts, nil 2189 } 2190 2191 func flattenGzips(gzipsList []*gofastly.Gzip) []map[string]interface{} { 2192 var gl []map[string]interface{} 2193 for _, g := range gzipsList { 2194 // Convert Gzip to a map for saving to state. 2195 ng := map[string]interface{}{ 2196 "name": g.Name, 2197 "cache_condition": g.CacheCondition, 2198 } 2199 2200 if g.Extensions != "" { 2201 e := strings.Split(g.Extensions, " ") 2202 var et []interface{} 2203 for _, ev := range e { 2204 et = append(et, ev) 2205 } 2206 ng["extensions"] = schema.NewSet(schema.HashString, et) 2207 } 2208 2209 if g.ContentTypes != "" { 2210 c := strings.Split(g.ContentTypes, " ") 2211 var ct []interface{} 2212 for _, cv := range c { 2213 ct = append(ct, cv) 2214 } 2215 ng["content_types"] = schema.NewSet(schema.HashString, ct) 2216 } 2217 2218 // prune any empty values that come from the default string value in structs 2219 for k, v := range ng { 2220 if v == "" { 2221 delete(ng, k) 2222 } 2223 } 2224 2225 gl = append(gl, ng) 2226 } 2227 2228 return gl 2229 } 2230 2231 func flattenHealthchecks(healthcheckList []*gofastly.HealthCheck) []map[string]interface{} { 2232 var hl []map[string]interface{} 2233 for _, h := range healthcheckList { 2234 // Convert HealthChecks to a map for saving to state. 2235 nh := map[string]interface{}{ 2236 "name": h.Name, 2237 "host": h.Host, 2238 "path": h.Path, 2239 "check_interval": h.CheckInterval, 2240 "expected_response": h.ExpectedResponse, 2241 "http_version": h.HTTPVersion, 2242 "initial": h.Initial, 2243 "method": h.Method, 2244 "threshold": h.Threshold, 2245 "timeout": h.Timeout, 2246 "window": h.Window, 2247 } 2248 2249 // prune any empty values that come from the default string value in structs 2250 for k, v := range nh { 2251 if v == "" { 2252 delete(nh, k) 2253 } 2254 } 2255 2256 hl = append(hl, nh) 2257 } 2258 2259 return hl 2260 } 2261 2262 func flattenS3s(s3List []*gofastly.S3) []map[string]interface{} { 2263 var sl []map[string]interface{} 2264 for _, s := range s3List { 2265 // Convert S3s to a map for saving to state. 2266 ns := map[string]interface{}{ 2267 "name": s.Name, 2268 "bucket_name": s.BucketName, 2269 "s3_access_key": s.AccessKey, 2270 "s3_secret_key": s.SecretKey, 2271 "path": s.Path, 2272 "period": s.Period, 2273 "domain": s.Domain, 2274 "gzip_level": s.GzipLevel, 2275 "format": s.Format, 2276 "format_version": s.FormatVersion, 2277 "timestamp_format": s.TimestampFormat, 2278 "response_condition": s.ResponseCondition, 2279 } 2280 2281 // prune any empty values that come from the default string value in structs 2282 for k, v := range ns { 2283 if v == "" { 2284 delete(ns, k) 2285 } 2286 } 2287 2288 sl = append(sl, ns) 2289 } 2290 2291 return sl 2292 } 2293 2294 func flattenPapertrails(papertrailList []*gofastly.Papertrail) []map[string]interface{} { 2295 var pl []map[string]interface{} 2296 for _, p := range papertrailList { 2297 // Convert Papertrails to a map for saving to state. 2298 ns := map[string]interface{}{ 2299 "name": p.Name, 2300 "address": p.Address, 2301 "port": p.Port, 2302 "format": p.Format, 2303 "response_condition": p.ResponseCondition, 2304 } 2305 2306 // prune any empty values that come from the default string value in structs 2307 for k, v := range ns { 2308 if v == "" { 2309 delete(ns, k) 2310 } 2311 } 2312 2313 pl = append(pl, ns) 2314 } 2315 2316 return pl 2317 } 2318 2319 func flattenSumologics(sumologicList []*gofastly.Sumologic) []map[string]interface{} { 2320 var l []map[string]interface{} 2321 for _, p := range sumologicList { 2322 // Convert Sumologic to a map for saving to state. 2323 ns := map[string]interface{}{ 2324 "name": p.Name, 2325 "url": p.URL, 2326 "format": p.Format, 2327 "response_condition": p.ResponseCondition, 2328 "message_type": p.MessageType, 2329 "format_version": int(p.FormatVersion), 2330 } 2331 2332 // prune any empty values that come from the default string value in structs 2333 for k, v := range ns { 2334 if v == "" { 2335 delete(ns, k) 2336 } 2337 } 2338 2339 l = append(l, ns) 2340 } 2341 2342 return l 2343 } 2344 2345 func flattenResponseObjects(responseObjectList []*gofastly.ResponseObject) []map[string]interface{} { 2346 var rol []map[string]interface{} 2347 for _, ro := range responseObjectList { 2348 // Convert ResponseObjects to a map for saving to state. 2349 nro := map[string]interface{}{ 2350 "name": ro.Name, 2351 "status": ro.Status, 2352 "response": ro.Response, 2353 "content": ro.Content, 2354 "content_type": ro.ContentType, 2355 "request_condition": ro.RequestCondition, 2356 "cache_condition": ro.CacheCondition, 2357 } 2358 2359 // prune any empty values that come from the default string value in structs 2360 for k, v := range nro { 2361 if v == "" { 2362 delete(nro, k) 2363 } 2364 } 2365 2366 rol = append(rol, nro) 2367 } 2368 2369 return rol 2370 } 2371 2372 func flattenConditions(conditionList []*gofastly.Condition) []map[string]interface{} { 2373 var cl []map[string]interface{} 2374 for _, c := range conditionList { 2375 // Convert Conditions to a map for saving to state. 2376 nc := map[string]interface{}{ 2377 "name": c.Name, 2378 "statement": c.Statement, 2379 "type": c.Type, 2380 "priority": c.Priority, 2381 } 2382 2383 // prune any empty values that come from the default string value in structs 2384 for k, v := range nc { 2385 if v == "" { 2386 delete(nc, k) 2387 } 2388 } 2389 2390 cl = append(cl, nc) 2391 } 2392 2393 return cl 2394 } 2395 2396 func flattenRequestSettings(rsList []*gofastly.RequestSetting) []map[string]interface{} { 2397 var rl []map[string]interface{} 2398 for _, r := range rsList { 2399 // Convert Request Settings to a map for saving to state. 2400 nrs := map[string]interface{}{ 2401 "name": r.Name, 2402 "max_stale_age": r.MaxStaleAge, 2403 "force_miss": r.ForceMiss, 2404 "force_ssl": r.ForceSSL, 2405 "action": r.Action, 2406 "bypass_busy_wait": r.BypassBusyWait, 2407 "hash_keys": r.HashKeys, 2408 "xff": r.XForwardedFor, 2409 "timer_support": r.TimerSupport, 2410 "geo_headers": r.GeoHeaders, 2411 "default_host": r.DefaultHost, 2412 "request_condition": r.RequestCondition, 2413 } 2414 2415 // prune any empty values that come from the default string value in structs 2416 for k, v := range nrs { 2417 if v == "" { 2418 delete(nrs, k) 2419 } 2420 } 2421 2422 rl = append(rl, nrs) 2423 } 2424 2425 return rl 2426 } 2427 2428 func buildRequestSetting(requestSettingMap interface{}) (*gofastly.CreateRequestSettingInput, error) { 2429 df := requestSettingMap.(map[string]interface{}) 2430 opts := gofastly.CreateRequestSettingInput{ 2431 Name: df["name"].(string), 2432 MaxStaleAge: uint(df["max_stale_age"].(int)), 2433 ForceMiss: gofastly.CBool(df["force_miss"].(bool)), 2434 ForceSSL: gofastly.CBool(df["force_ssl"].(bool)), 2435 BypassBusyWait: gofastly.CBool(df["bypass_busy_wait"].(bool)), 2436 HashKeys: df["hash_keys"].(string), 2437 TimerSupport: gofastly.CBool(df["timer_support"].(bool)), 2438 GeoHeaders: gofastly.CBool(df["geo_headers"].(bool)), 2439 DefaultHost: df["default_host"].(string), 2440 RequestCondition: df["request_condition"].(string), 2441 } 2442 2443 act := strings.ToLower(df["action"].(string)) 2444 switch act { 2445 case "lookup": 2446 opts.Action = gofastly.RequestSettingActionLookup 2447 case "pass": 2448 opts.Action = gofastly.RequestSettingActionPass 2449 } 2450 2451 xff := strings.ToLower(df["xff"].(string)) 2452 switch xff { 2453 case "clear": 2454 opts.XForwardedFor = gofastly.RequestSettingXFFClear 2455 case "leave": 2456 opts.XForwardedFor = gofastly.RequestSettingXFFLeave 2457 case "append": 2458 opts.XForwardedFor = gofastly.RequestSettingXFFAppend 2459 case "append_all": 2460 opts.XForwardedFor = gofastly.RequestSettingXFFAppendAll 2461 case "overwrite": 2462 opts.XForwardedFor = gofastly.RequestSettingXFFOverwrite 2463 } 2464 2465 return &opts, nil 2466 } 2467 2468 func flattenCacheSettings(csList []*gofastly.CacheSetting) []map[string]interface{} { 2469 var csl []map[string]interface{} 2470 for _, cl := range csList { 2471 // Convert Cache Settings to a map for saving to state. 2472 clMap := map[string]interface{}{ 2473 "name": cl.Name, 2474 "action": cl.Action, 2475 "cache_condition": cl.CacheCondition, 2476 "stale_ttl": cl.StaleTTL, 2477 "ttl": cl.TTL, 2478 } 2479 2480 // prune any empty values that come from the default string value in structs 2481 for k, v := range clMap { 2482 if v == "" { 2483 delete(clMap, k) 2484 } 2485 } 2486 2487 csl = append(csl, clMap) 2488 } 2489 2490 return csl 2491 } 2492 2493 func flattenVCLs(vclList []*gofastly.VCL) []map[string]interface{} { 2494 var vl []map[string]interface{} 2495 for _, vcl := range vclList { 2496 // Convert VCLs to a map for saving to state. 2497 vclMap := map[string]interface{}{ 2498 "name": vcl.Name, 2499 "content": vcl.Content, 2500 "main": vcl.Main, 2501 } 2502 2503 // prune any empty values that come from the default string value in structs 2504 for k, v := range vclMap { 2505 if v == "" { 2506 delete(vclMap, k) 2507 } 2508 } 2509 2510 vl = append(vl, vclMap) 2511 } 2512 2513 return vl 2514 } 2515 2516 func validateVCLs(d *schema.ResourceData) error { 2517 // TODO: this would be nice to move into a resource/collection validation function, once that is available 2518 // (see https://github.com/hashicorp/terraform/pull/4348 and https://github.com/hashicorp/terraform/pull/6508) 2519 vcls, exists := d.GetOk("vcl") 2520 if !exists { 2521 return nil 2522 } 2523 2524 numberOfMainVCLs, numberOfIncludeVCLs := 0, 0 2525 for _, vclElem := range vcls.(*schema.Set).List() { 2526 vcl := vclElem.(map[string]interface{}) 2527 if mainVal, hasMain := vcl["main"]; hasMain && mainVal.(bool) { 2528 numberOfMainVCLs++ 2529 } else { 2530 numberOfIncludeVCLs++ 2531 } 2532 } 2533 if numberOfMainVCLs == 0 && numberOfIncludeVCLs > 0 { 2534 return errors.New("if you include VCL configurations, one of them should have main = true") 2535 } 2536 if numberOfMainVCLs > 1 { 2537 return errors.New("you cannot have more than one VCL configuration with main = true") 2538 } 2539 return nil 2540 }