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