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