github.com/cbroglie/terraform@v0.7.0-rc3.0.20170410193827-735dfc416d46/builtin/providers/fastly/resource_fastly_service_v1.go (about)

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