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