github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/fastly/resource_fastly_service_v1.go (about)

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