github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/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  
    25  		Schema: map[string]*schema.Schema{
    26  			"name": &schema.Schema{
    27  				Type:        schema.TypeString,
    28  				Required:    true,
    29  				Description: "Unique name for this Service",
    30  			},
    31  
    32  			// Active Version represents the currently activated version in Fastly. In
    33  			// Terraform, we abstract this number away from the users and manage
    34  			// creating and activating. It's used internally, but also exported for
    35  			// users to see.
    36  			"active_version": &schema.Schema{
    37  				Type:     schema.TypeString,
    38  				Computed: true,
    39  			},
    40  
    41  			"domain": &schema.Schema{
    42  				Type:     schema.TypeSet,
    43  				Required: true,
    44  				Elem: &schema.Resource{
    45  					Schema: map[string]*schema.Schema{
    46  						"name": &schema.Schema{
    47  							Type:        schema.TypeString,
    48  							Required:    true,
    49  							Description: "The domain that this Service will respond to",
    50  						},
    51  
    52  						"comment": &schema.Schema{
    53  							Type:     schema.TypeString,
    54  							Optional: true,
    55  						},
    56  					},
    57  				},
    58  			},
    59  
    60  			"condition": &schema.Schema{
    61  				Type:     schema.TypeSet,
    62  				Optional: true,
    63  				Elem: &schema.Resource{
    64  					Schema: map[string]*schema.Schema{
    65  						"name": &schema.Schema{
    66  							Type:     schema.TypeString,
    67  							Required: true,
    68  						},
    69  						"statement": &schema.Schema{
    70  							Type:        schema.TypeString,
    71  							Required:    true,
    72  							Description: "The statement used to determine if the condition is met",
    73  							StateFunc: func(v interface{}) string {
    74  								value := v.(string)
    75  								// Trim newlines and spaces, to match Fastly API
    76  								return strings.TrimSpace(value)
    77  							},
    78  						},
    79  						"priority": &schema.Schema{
    80  							Type:        schema.TypeInt,
    81  							Required:    true,
    82  							Description: "A number used to determine the order in which multiple conditions execute. Lower numbers execute first",
    83  						},
    84  						"type": &schema.Schema{
    85  							Type:        schema.TypeString,
    86  							Required:    true,
    87  							Description: "Type of the condition, either `REQUEST`, `RESPONSE`, or `CACHE`",
    88  						},
    89  					},
    90  				},
    91  			},
    92  
    93  			"default_ttl": &schema.Schema{
    94  				Type:        schema.TypeInt,
    95  				Optional:    true,
    96  				Default:     3600,
    97  				Description: "The default Time-to-live (TTL) for the version",
    98  			},
    99  
   100  			"default_host": &schema.Schema{
   101  				Type:        schema.TypeString,
   102  				Optional:    true,
   103  				Computed:    true,
   104  				Description: "The default hostname for the version",
   105  			},
   106  
   107  			"backend": &schema.Schema{
   108  				Type:     schema.TypeSet,
   109  				Required: true,
   110  				Elem: &schema.Resource{
   111  					Schema: map[string]*schema.Schema{
   112  						// required fields
   113  						"name": &schema.Schema{
   114  							Type:        schema.TypeString,
   115  							Required:    true,
   116  							Description: "A name for this Backend",
   117  						},
   118  						"address": &schema.Schema{
   119  							Type:        schema.TypeString,
   120  							Required:    true,
   121  							Description: "An IPv4, hostname, or IPv6 address for the Backend",
   122  						},
   123  						// Optional fields, defaults where they exist
   124  						"auto_loadbalance": &schema.Schema{
   125  							Type:        schema.TypeBool,
   126  							Optional:    true,
   127  							Default:     true,
   128  							Description: "Should this Backend be load balanced",
   129  						},
   130  						"between_bytes_timeout": &schema.Schema{
   131  							Type:        schema.TypeInt,
   132  							Optional:    true,
   133  							Default:     10000,
   134  							Description: "How long to wait between bytes in milliseconds",
   135  						},
   136  						"connect_timeout": &schema.Schema{
   137  							Type:        schema.TypeInt,
   138  							Optional:    true,
   139  							Default:     1000,
   140  							Description: "How long to wait for a timeout in milliseconds",
   141  						},
   142  						"error_threshold": &schema.Schema{
   143  							Type:        schema.TypeInt,
   144  							Optional:    true,
   145  							Default:     0,
   146  							Description: "Number of errors to allow before the Backend is marked as down",
   147  						},
   148  						"first_byte_timeout": &schema.Schema{
   149  							Type:        schema.TypeInt,
   150  							Optional:    true,
   151  							Default:     15000,
   152  							Description: "How long to wait for the first bytes in milliseconds",
   153  						},
   154  						"max_conn": &schema.Schema{
   155  							Type:        schema.TypeInt,
   156  							Optional:    true,
   157  							Default:     200,
   158  							Description: "Maximum number of connections for this Backend",
   159  						},
   160  						"port": &schema.Schema{
   161  							Type:        schema.TypeInt,
   162  							Optional:    true,
   163  							Default:     80,
   164  							Description: "The port number Backend responds on. Default 80",
   165  						},
   166  						"ssl_check_cert": &schema.Schema{
   167  							Type:        schema.TypeBool,
   168  							Optional:    true,
   169  							Default:     true,
   170  							Description: "Be strict on checking SSL certs",
   171  						},
   172  						// UseSSL is something we want to support in the future, but
   173  						// requires SSL setup we don't yet have
   174  						// TODO: Provide all SSL fields from https://docs.fastly.com/api/config#backend
   175  						// "use_ssl": &schema.Schema{
   176  						// 	Type:        schema.TypeBool,
   177  						// 	Optional:    true,
   178  						// 	Default:     false,
   179  						// 	Description: "Whether or not to use SSL to reach the Backend",
   180  						// },
   181  						"weight": &schema.Schema{
   182  							Type:        schema.TypeInt,
   183  							Optional:    true,
   184  							Default:     100,
   185  							Description: "The portion of traffic to send to a specific origins. Each origin receives weight/total of the traffic.",
   186  						},
   187  					},
   188  				},
   189  			},
   190  
   191  			"force_destroy": &schema.Schema{
   192  				Type:     schema.TypeBool,
   193  				Optional: true,
   194  			},
   195  
   196  			"gzip": &schema.Schema{
   197  				Type:     schema.TypeSet,
   198  				Optional: true,
   199  				Elem: &schema.Resource{
   200  					Schema: map[string]*schema.Schema{
   201  						// required fields
   202  						"name": &schema.Schema{
   203  							Type:        schema.TypeString,
   204  							Required:    true,
   205  							Description: "A name to refer to this gzip condition",
   206  						},
   207  						// optional fields
   208  						"content_types": &schema.Schema{
   209  							Type:        schema.TypeSet,
   210  							Optional:    true,
   211  							Description: "Content types to apply automatic gzip to",
   212  							Elem:        &schema.Schema{Type: schema.TypeString},
   213  						},
   214  						"extensions": &schema.Schema{
   215  							Type:        schema.TypeSet,
   216  							Optional:    true,
   217  							Description: "File extensions to apply automatic gzip to. Do not include '.'",
   218  							Elem:        &schema.Schema{Type: schema.TypeString},
   219  						},
   220  						// These fields represent Fastly options that Terraform does not
   221  						// currently support
   222  						"cache_condition": &schema.Schema{
   223  							Type:        schema.TypeString,
   224  							Computed:    true,
   225  							Description: "Optional name of a CacheCondition to apply.",
   226  						},
   227  					},
   228  				},
   229  			},
   230  
   231  			"header": &schema.Schema{
   232  				Type:     schema.TypeSet,
   233  				Optional: true,
   234  				Elem: &schema.Resource{
   235  					Schema: map[string]*schema.Schema{
   236  						// required fields
   237  						"name": &schema.Schema{
   238  							Type:        schema.TypeString,
   239  							Required:    true,
   240  							Description: "A name to refer to this Header object",
   241  						},
   242  						"action": &schema.Schema{
   243  							Type:        schema.TypeString,
   244  							Required:    true,
   245  							Description: "One of set, append, delete, regex, or regex_repeat",
   246  							ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   247  								var found bool
   248  								for _, t := range []string{"set", "append", "delete", "regex", "regex_repeat"} {
   249  									if v.(string) == t {
   250  										found = true
   251  									}
   252  								}
   253  								if !found {
   254  									es = append(es, fmt.Errorf(
   255  										"Fastly Header action is case sensitive and must be one of 'set', 'append', 'delete', 'regex', or 'regex_repeat'; found: %s", v.(string)))
   256  								}
   257  								return
   258  							},
   259  						},
   260  						"type": &schema.Schema{
   261  							Type:        schema.TypeString,
   262  							Required:    true,
   263  							Description: "Type to manipulate: request, fetch, cache, response",
   264  							ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   265  								var found bool
   266  								for _, t := range []string{"request", "fetch", "cache", "response"} {
   267  									if v.(string) == t {
   268  										found = true
   269  									}
   270  								}
   271  								if !found {
   272  									es = append(es, fmt.Errorf(
   273  										"Fastly Header type is case sensitive and must be one of 'request', 'fetch', 'cache', or 'response'; found: %s", v.(string)))
   274  								}
   275  								return
   276  							},
   277  						},
   278  						"destination": &schema.Schema{
   279  							Type:        schema.TypeString,
   280  							Required:    true,
   281  							Description: "Header this affects",
   282  						},
   283  						// Optional fields, defaults where they exist
   284  						"ignore_if_set": &schema.Schema{
   285  							Type:        schema.TypeBool,
   286  							Optional:    true,
   287  							Default:     false,
   288  							Description: "Don't add the header if it is already. (Only applies to 'set' action.). Default `false`",
   289  						},
   290  						"source": &schema.Schema{
   291  							Type:        schema.TypeString,
   292  							Optional:    true,
   293  							Computed:    true,
   294  							Description: "Variable to be used as a source for the header content (Does not apply to 'delete' action.)",
   295  						},
   296  						"regex": &schema.Schema{
   297  							Type:        schema.TypeString,
   298  							Optional:    true,
   299  							Computed:    true,
   300  							Description: "Regular expression to use (Only applies to 'regex' and 'regex_repeat' actions.)",
   301  						},
   302  						"substitution": &schema.Schema{
   303  							Type:        schema.TypeString,
   304  							Optional:    true,
   305  							Computed:    true,
   306  							Description: "Value to substitute in place of regular expression. (Only applies to 'regex' and 'regex_repeat'.)",
   307  						},
   308  						"priority": &schema.Schema{
   309  							Type:        schema.TypeInt,
   310  							Optional:    true,
   311  							Default:     100,
   312  							Description: "Lower priorities execute first. (Default: 100.)",
   313  						},
   314  						// These fields represent Fastly options that Terraform does not
   315  						// currently support
   316  						"request_condition": &schema.Schema{
   317  							Type:        schema.TypeString,
   318  							Computed:    true,
   319  							Description: "Optional name of a RequestCondition to apply.",
   320  						},
   321  						"cache_condition": &schema.Schema{
   322  							Type:        schema.TypeString,
   323  							Computed:    true,
   324  							Description: "Optional name of a CacheCondition to apply.",
   325  						},
   326  						"response_condition": &schema.Schema{
   327  							Type:        schema.TypeString,
   328  							Computed:    true,
   329  							Description: "Optional name of a ResponseCondition to apply.",
   330  						},
   331  					},
   332  				},
   333  			},
   334  
   335  			"s3logging": &schema.Schema{
   336  				Type:     schema.TypeSet,
   337  				Optional: true,
   338  				Elem: &schema.Resource{
   339  					Schema: map[string]*schema.Schema{
   340  						// Required fields
   341  						"name": &schema.Schema{
   342  							Type:        schema.TypeString,
   343  							Required:    true,
   344  							Description: "Unique name to refer to this logging setup",
   345  						},
   346  						"bucket_name": &schema.Schema{
   347  							Type:        schema.TypeString,
   348  							Required:    true,
   349  							Description: "S3 Bucket name to store logs in",
   350  						},
   351  						"s3_access_key": &schema.Schema{
   352  							Type:        schema.TypeString,
   353  							Optional:    true,
   354  							DefaultFunc: schema.EnvDefaultFunc("FASTLY_S3_ACCESS_KEY", ""),
   355  							Description: "AWS Access Key",
   356  						},
   357  						"s3_secret_key": &schema.Schema{
   358  							Type:        schema.TypeString,
   359  							Optional:    true,
   360  							DefaultFunc: schema.EnvDefaultFunc("FASTLY_S3_SECRET_KEY", ""),
   361  							Description: "AWS Secret Key",
   362  						},
   363  						// Optional fields
   364  						"path": &schema.Schema{
   365  							Type:        schema.TypeString,
   366  							Optional:    true,
   367  							Description: "Path to store the files. Must end with a trailing slash",
   368  						},
   369  						"domain": &schema.Schema{
   370  							Type:        schema.TypeString,
   371  							Optional:    true,
   372  							Description: "Bucket endpoint",
   373  						},
   374  						"gzip_level": &schema.Schema{
   375  							Type:        schema.TypeInt,
   376  							Optional:    true,
   377  							Default:     0,
   378  							Description: "Gzip Compression level",
   379  						},
   380  						"period": &schema.Schema{
   381  							Type:        schema.TypeInt,
   382  							Optional:    true,
   383  							Default:     3600,
   384  							Description: "How frequently the logs should be transferred, in seconds (Default 3600)",
   385  						},
   386  						"format": &schema.Schema{
   387  							Type:        schema.TypeString,
   388  							Optional:    true,
   389  							Default:     "%h %l %u %t %r %>s",
   390  							Description: "Apache-style string or VCL variables to use for log formatting",
   391  						},
   392  						"timestamp_format": &schema.Schema{
   393  							Type:        schema.TypeString,
   394  							Optional:    true,
   395  							Default:     "%Y-%m-%dT%H:%M:%S.000",
   396  							Description: "specified timestamp formatting (default `%Y-%m-%dT%H:%M:%S.000`)",
   397  						},
   398  					},
   399  				},
   400  			},
   401  
   402  			"request_setting": &schema.Schema{
   403  				Type:     schema.TypeSet,
   404  				Optional: true,
   405  				Elem: &schema.Resource{
   406  					Schema: map[string]*schema.Schema{
   407  						// Required fields
   408  						"name": &schema.Schema{
   409  							Type:        schema.TypeString,
   410  							Required:    true,
   411  							Description: "Unique name to refer to this Request Setting",
   412  						},
   413  						"request_condition": &schema.Schema{
   414  							Type:        schema.TypeString,
   415  							Required:    true,
   416  							Description: "Name of a RequestCondition to apply.",
   417  						},
   418  						// Optional fields
   419  						"max_stale_age": &schema.Schema{
   420  							Type:        schema.TypeInt,
   421  							Optional:    true,
   422  							Default:     60,
   423  							Description: "How old an object is allowed to be, in seconds. Default `60`",
   424  						},
   425  						"force_miss": &schema.Schema{
   426  							Type:        schema.TypeBool,
   427  							Optional:    true,
   428  							Description: "Force a cache miss for the request",
   429  						},
   430  						"force_ssl": &schema.Schema{
   431  							Type:        schema.TypeBool,
   432  							Optional:    true,
   433  							Description: "Forces the request use SSL",
   434  						},
   435  						"action": &schema.Schema{
   436  							Type:        schema.TypeString,
   437  							Optional:    true,
   438  							Description: "Allows you to terminate request handling and immediately perform an action",
   439  						},
   440  						"bypass_busy_wait": &schema.Schema{
   441  							Type:        schema.TypeBool,
   442  							Optional:    true,
   443  							Description: "Disable collapsed forwarding",
   444  						},
   445  						"hash_keys": &schema.Schema{
   446  							Type:        schema.TypeString,
   447  							Optional:    true,
   448  							Description: "Comma separated list of varnish request object fields that should be in the hash key",
   449  						},
   450  						"xff": &schema.Schema{
   451  							Type:        schema.TypeString,
   452  							Optional:    true,
   453  							Default:     "append",
   454  							Description: "X-Forwarded-For options",
   455  						},
   456  						"timer_support": &schema.Schema{
   457  							Type:        schema.TypeBool,
   458  							Optional:    true,
   459  							Description: "Injects the X-Timer info into the request",
   460  						},
   461  						"geo_headers": &schema.Schema{
   462  							Type:        schema.TypeBool,
   463  							Optional:    true,
   464  							Description: "Inject Fastly-Geo-Country, Fastly-Geo-City, and Fastly-Geo-Region",
   465  						},
   466  						"default_host": &schema.Schema{
   467  							Type:        schema.TypeString,
   468  							Optional:    true,
   469  							Description: "the host header",
   470  						},
   471  					},
   472  				},
   473  			},
   474  			"vcl": &schema.Schema{
   475  				Type:     schema.TypeSet,
   476  				Optional: true,
   477  				Elem: &schema.Resource{
   478  					Schema: map[string]*schema.Schema{
   479  						"name": &schema.Schema{
   480  							Type:        schema.TypeString,
   481  							Required:    true,
   482  							Description: "A name to refer to this VCL configuration",
   483  						},
   484  						"content": &schema.Schema{
   485  							Type:        schema.TypeString,
   486  							Required:    true,
   487  							Description: "The contents of this VCL configuration",
   488  							StateFunc: func(v interface{}) string {
   489  								switch v.(type) {
   490  								case string:
   491  									hash := sha1.Sum([]byte(v.(string)))
   492  									return hex.EncodeToString(hash[:])
   493  								default:
   494  									return ""
   495  								}
   496  							},
   497  						},
   498  						"main": &schema.Schema{
   499  							Type:        schema.TypeBool,
   500  							Optional:    true,
   501  							Default:     false,
   502  							Description: "Should this VCL configuation be the main configuration",
   503  						},
   504  					},
   505  				},
   506  			},
   507  		},
   508  	}
   509  }
   510  
   511  func resourceServiceV1Create(d *schema.ResourceData, meta interface{}) error {
   512  	if err := validateVCLs(d); err != nil {
   513  		return err
   514  	}
   515  
   516  	conn := meta.(*FastlyClient).conn
   517  	service, err := conn.CreateService(&gofastly.CreateServiceInput{
   518  		Name:    d.Get("name").(string),
   519  		Comment: "Managed by Terraform",
   520  	})
   521  
   522  	if err != nil {
   523  		return err
   524  	}
   525  
   526  	d.SetId(service.ID)
   527  	return resourceServiceV1Update(d, meta)
   528  }
   529  
   530  func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
   531  	if err := validateVCLs(d); err != nil {
   532  		return err
   533  	}
   534  
   535  	conn := meta.(*FastlyClient).conn
   536  
   537  	// Update Name. No new verions is required for this
   538  	if d.HasChange("name") {
   539  		_, err := conn.UpdateService(&gofastly.UpdateServiceInput{
   540  			ID:   d.Id(),
   541  			Name: d.Get("name").(string),
   542  		})
   543  		if err != nil {
   544  			return err
   545  		}
   546  	}
   547  
   548  	// Once activated, Versions are locked and become immutable. This is true for
   549  	// versions that are no longer active. For Domains, Backends, DefaultHost and
   550  	// DefaultTTL, a new Version must be created first, and updates posted to that
   551  	// Version. Loop these attributes and determine if we need to create a new version first
   552  	var needsChange bool
   553  	for _, v := range []string{
   554  		"domain",
   555  		"backend",
   556  		"default_host",
   557  		"default_ttl",
   558  		"header",
   559  		"gzip",
   560  		"s3logging",
   561  		"condition",
   562  		"request_setting",
   563  		"vcl",
   564  	} {
   565  		if d.HasChange(v) {
   566  			needsChange = true
   567  		}
   568  	}
   569  
   570  	if needsChange {
   571  		latestVersion := d.Get("active_version").(string)
   572  		if latestVersion == "" {
   573  			// If the service was just created, there is an empty Version 1 available
   574  			// that is unlocked and can be updated
   575  			latestVersion = "1"
   576  		} else {
   577  			// Clone the latest version, giving us an unlocked version we can modify
   578  			log.Printf("[DEBUG] Creating clone of version (%s) for updates", latestVersion)
   579  			newVersion, err := conn.CloneVersion(&gofastly.CloneVersionInput{
   580  				Service: d.Id(),
   581  				Version: latestVersion,
   582  			})
   583  			if err != nil {
   584  				return err
   585  			}
   586  
   587  			// The new version number is named "Number", but it's actually a string
   588  			latestVersion = newVersion.Number
   589  
   590  			// New versions are not immediately found in the API, or are not
   591  			// immediately mutable, so we need to sleep a few and let Fastly ready
   592  			// itself. Typically, 7 seconds is enough
   593  			log.Printf("[DEBUG] Sleeping 7 seconds to allow Fastly Version to be available")
   594  			time.Sleep(7 * time.Second)
   595  		}
   596  
   597  		// update general settings
   598  		if d.HasChange("default_host") || d.HasChange("default_ttl") {
   599  			opts := gofastly.UpdateSettingsInput{
   600  				Service: d.Id(),
   601  				Version: latestVersion,
   602  				// default_ttl has the same default value of 3600 that is provided by
   603  				// the Fastly API, so it's safe to include here
   604  				DefaultTTL: uint(d.Get("default_ttl").(int)),
   605  			}
   606  
   607  			if attr, ok := d.GetOk("default_host"); ok {
   608  				opts.DefaultHost = attr.(string)
   609  			}
   610  
   611  			log.Printf("[DEBUG] Update Settings opts: %#v", opts)
   612  			_, err := conn.UpdateSettings(&opts)
   613  			if err != nil {
   614  				return err
   615  			}
   616  		}
   617  
   618  		// Conditions need to be updated first, as they can be referenced by other
   619  		// configuraiton objects (Backends, Request Headers, etc)
   620  
   621  		// Find difference in Conditions
   622  		if d.HasChange("condition") {
   623  			// Note: we don't utilize the PUT endpoint to update these objects, we simply
   624  			// destroy any that have changed, and create new ones with the updated
   625  			// values. This is how Terraform works with nested sub resources, we only
   626  			// get the full diff not a partial set item diff. Because this is done
   627  			// on a new version of the Fastly Service configuration, this is considered safe
   628  
   629  			oc, nc := d.GetChange("condition")
   630  			if oc == nil {
   631  				oc = new(schema.Set)
   632  			}
   633  			if nc == nil {
   634  				nc = new(schema.Set)
   635  			}
   636  
   637  			ocs := oc.(*schema.Set)
   638  			ncs := nc.(*schema.Set)
   639  			removeConditions := ocs.Difference(ncs).List()
   640  			addConditions := ncs.Difference(ocs).List()
   641  
   642  			// DELETE old Conditions
   643  			for _, cRaw := range removeConditions {
   644  				cf := cRaw.(map[string]interface{})
   645  				opts := gofastly.DeleteConditionInput{
   646  					Service: d.Id(),
   647  					Version: latestVersion,
   648  					Name:    cf["name"].(string),
   649  				}
   650  
   651  				log.Printf("[DEBUG] Fastly Conditions Removal opts: %#v", opts)
   652  				err := conn.DeleteCondition(&opts)
   653  				if err != nil {
   654  					return err
   655  				}
   656  			}
   657  
   658  			// POST new Conditions
   659  			for _, cRaw := range addConditions {
   660  				cf := cRaw.(map[string]interface{})
   661  				opts := gofastly.CreateConditionInput{
   662  					Service: d.Id(),
   663  					Version: latestVersion,
   664  					Name:    cf["name"].(string),
   665  					Type:    cf["type"].(string),
   666  					// need to trim leading/tailing spaces, incase the config has HEREDOC
   667  					// formatting and contains a trailing new line
   668  					Statement: strings.TrimSpace(cf["statement"].(string)),
   669  					Priority:  cf["priority"].(int),
   670  				}
   671  
   672  				log.Printf("[DEBUG] Create Conditions Opts: %#v", opts)
   673  				_, err := conn.CreateCondition(&opts)
   674  				if err != nil {
   675  					return err
   676  				}
   677  			}
   678  		}
   679  
   680  		// Find differences in domains
   681  		if d.HasChange("domain") {
   682  			od, nd := d.GetChange("domain")
   683  			if od == nil {
   684  				od = new(schema.Set)
   685  			}
   686  			if nd == nil {
   687  				nd = new(schema.Set)
   688  			}
   689  
   690  			ods := od.(*schema.Set)
   691  			nds := nd.(*schema.Set)
   692  
   693  			remove := ods.Difference(nds).List()
   694  			add := nds.Difference(ods).List()
   695  
   696  			// Delete removed domains
   697  			for _, dRaw := range remove {
   698  				df := dRaw.(map[string]interface{})
   699  				opts := gofastly.DeleteDomainInput{
   700  					Service: d.Id(),
   701  					Version: latestVersion,
   702  					Name:    df["name"].(string),
   703  				}
   704  
   705  				log.Printf("[DEBUG] Fastly Domain removal opts: %#v", opts)
   706  				err := conn.DeleteDomain(&opts)
   707  				if err != nil {
   708  					return err
   709  				}
   710  			}
   711  
   712  			// POST new Domains
   713  			for _, dRaw := range add {
   714  				df := dRaw.(map[string]interface{})
   715  				opts := gofastly.CreateDomainInput{
   716  					Service: d.Id(),
   717  					Version: latestVersion,
   718  					Name:    df["name"].(string),
   719  				}
   720  
   721  				if v, ok := df["comment"]; ok {
   722  					opts.Comment = v.(string)
   723  				}
   724  
   725  				log.Printf("[DEBUG] Fastly Domain Addition opts: %#v", opts)
   726  				_, err := conn.CreateDomain(&opts)
   727  				if err != nil {
   728  					return err
   729  				}
   730  			}
   731  		}
   732  
   733  		// find difference in backends
   734  		if d.HasChange("backend") {
   735  			ob, nb := d.GetChange("backend")
   736  			if ob == nil {
   737  				ob = new(schema.Set)
   738  			}
   739  			if nb == nil {
   740  				nb = new(schema.Set)
   741  			}
   742  
   743  			obs := ob.(*schema.Set)
   744  			nbs := nb.(*schema.Set)
   745  			removeBackends := obs.Difference(nbs).List()
   746  			addBackends := nbs.Difference(obs).List()
   747  
   748  			// DELETE old Backends
   749  			for _, bRaw := range removeBackends {
   750  				bf := bRaw.(map[string]interface{})
   751  				opts := gofastly.DeleteBackendInput{
   752  					Service: d.Id(),
   753  					Version: latestVersion,
   754  					Name:    bf["name"].(string),
   755  				}
   756  
   757  				log.Printf("[DEBUG] Fastly Backend removal opts: %#v", opts)
   758  				err := conn.DeleteBackend(&opts)
   759  				if err != nil {
   760  					return err
   761  				}
   762  			}
   763  
   764  			// Find and post new Backends
   765  			for _, dRaw := range addBackends {
   766  				df := dRaw.(map[string]interface{})
   767  				opts := gofastly.CreateBackendInput{
   768  					Service:             d.Id(),
   769  					Version:             latestVersion,
   770  					Name:                df["name"].(string),
   771  					Address:             df["address"].(string),
   772  					AutoLoadbalance:     df["auto_loadbalance"].(bool),
   773  					SSLCheckCert:        df["ssl_check_cert"].(bool),
   774  					Port:                uint(df["port"].(int)),
   775  					BetweenBytesTimeout: uint(df["between_bytes_timeout"].(int)),
   776  					ConnectTimeout:      uint(df["connect_timeout"].(int)),
   777  					ErrorThreshold:      uint(df["error_threshold"].(int)),
   778  					FirstByteTimeout:    uint(df["first_byte_timeout"].(int)),
   779  					MaxConn:             uint(df["max_conn"].(int)),
   780  					Weight:              uint(df["weight"].(int)),
   781  				}
   782  
   783  				log.Printf("[DEBUG] Create Backend Opts: %#v", opts)
   784  				_, err := conn.CreateBackend(&opts)
   785  				if err != nil {
   786  					return err
   787  				}
   788  			}
   789  		}
   790  
   791  		if d.HasChange("header") {
   792  			oh, nh := d.GetChange("header")
   793  			if oh == nil {
   794  				oh = new(schema.Set)
   795  			}
   796  			if nh == nil {
   797  				nh = new(schema.Set)
   798  			}
   799  
   800  			ohs := oh.(*schema.Set)
   801  			nhs := nh.(*schema.Set)
   802  
   803  			remove := ohs.Difference(nhs).List()
   804  			add := nhs.Difference(ohs).List()
   805  
   806  			// Delete removed headers
   807  			for _, dRaw := range remove {
   808  				df := dRaw.(map[string]interface{})
   809  				opts := gofastly.DeleteHeaderInput{
   810  					Service: d.Id(),
   811  					Version: latestVersion,
   812  					Name:    df["name"].(string),
   813  				}
   814  
   815  				log.Printf("[DEBUG] Fastly Header removal opts: %#v", opts)
   816  				err := conn.DeleteHeader(&opts)
   817  				if err != nil {
   818  					return err
   819  				}
   820  			}
   821  
   822  			// POST new Headers
   823  			for _, dRaw := range add {
   824  				opts, err := buildHeader(dRaw.(map[string]interface{}))
   825  				if err != nil {
   826  					log.Printf("[DEBUG] Error building Header: %s", err)
   827  					return err
   828  				}
   829  				opts.Service = d.Id()
   830  				opts.Version = latestVersion
   831  
   832  				log.Printf("[DEBUG] Fastly Header Addition opts: %#v", opts)
   833  				_, err = conn.CreateHeader(opts)
   834  				if err != nil {
   835  					return err
   836  				}
   837  			}
   838  		}
   839  
   840  		// Find differences in Gzips
   841  		if d.HasChange("gzip") {
   842  			og, ng := d.GetChange("gzip")
   843  			if og == nil {
   844  				og = new(schema.Set)
   845  			}
   846  			if ng == nil {
   847  				ng = new(schema.Set)
   848  			}
   849  
   850  			ogs := og.(*schema.Set)
   851  			ngs := ng.(*schema.Set)
   852  
   853  			remove := ogs.Difference(ngs).List()
   854  			add := ngs.Difference(ogs).List()
   855  
   856  			// Delete removed gzip rules
   857  			for _, dRaw := range remove {
   858  				df := dRaw.(map[string]interface{})
   859  				opts := gofastly.DeleteGzipInput{
   860  					Service: d.Id(),
   861  					Version: latestVersion,
   862  					Name:    df["name"].(string),
   863  				}
   864  
   865  				log.Printf("[DEBUG] Fastly Gzip removal opts: %#v", opts)
   866  				err := conn.DeleteGzip(&opts)
   867  				if err != nil {
   868  					return err
   869  				}
   870  			}
   871  
   872  			// POST new Gzips
   873  			for _, dRaw := range add {
   874  				df := dRaw.(map[string]interface{})
   875  				opts := gofastly.CreateGzipInput{
   876  					Service: d.Id(),
   877  					Version: latestVersion,
   878  					Name:    df["name"].(string),
   879  				}
   880  
   881  				if v, ok := df["content_types"]; ok {
   882  					if len(v.(*schema.Set).List()) > 0 {
   883  						var cl []string
   884  						for _, c := range v.(*schema.Set).List() {
   885  							cl = append(cl, c.(string))
   886  						}
   887  						opts.ContentTypes = strings.Join(cl, " ")
   888  					}
   889  				}
   890  
   891  				if v, ok := df["extensions"]; ok {
   892  					if len(v.(*schema.Set).List()) > 0 {
   893  						var el []string
   894  						for _, e := range v.(*schema.Set).List() {
   895  							el = append(el, e.(string))
   896  						}
   897  						opts.Extensions = strings.Join(el, " ")
   898  					}
   899  				}
   900  
   901  				log.Printf("[DEBUG] Fastly Gzip Addition opts: %#v", opts)
   902  				_, err := conn.CreateGzip(&opts)
   903  				if err != nil {
   904  					return err
   905  				}
   906  			}
   907  		}
   908  
   909  		// find difference in s3logging
   910  		if d.HasChange("s3logging") {
   911  			os, ns := d.GetChange("s3logging")
   912  			if os == nil {
   913  				os = new(schema.Set)
   914  			}
   915  			if ns == nil {
   916  				ns = new(schema.Set)
   917  			}
   918  
   919  			oss := os.(*schema.Set)
   920  			nss := ns.(*schema.Set)
   921  			removeS3Logging := oss.Difference(nss).List()
   922  			addS3Logging := nss.Difference(oss).List()
   923  
   924  			// DELETE old S3 Log configurations
   925  			for _, sRaw := range removeS3Logging {
   926  				sf := sRaw.(map[string]interface{})
   927  				opts := gofastly.DeleteS3Input{
   928  					Service: d.Id(),
   929  					Version: latestVersion,
   930  					Name:    sf["name"].(string),
   931  				}
   932  
   933  				log.Printf("[DEBUG] Fastly S3 Logging removal opts: %#v", opts)
   934  				err := conn.DeleteS3(&opts)
   935  				if err != nil {
   936  					return err
   937  				}
   938  			}
   939  
   940  			// POST new/updated S3 Logging
   941  			for _, sRaw := range addS3Logging {
   942  				sf := sRaw.(map[string]interface{})
   943  
   944  				// Fastly API will not error if these are omitted, so we throw an error
   945  				// if any of these are empty
   946  				for _, sk := range []string{"s3_access_key", "s3_secret_key"} {
   947  					if sf[sk].(string) == "" {
   948  						return fmt.Errorf("[ERR] No %s found for S3 Log stream setup for Service (%s)", sk, d.Id())
   949  					}
   950  				}
   951  
   952  				opts := gofastly.CreateS3Input{
   953  					Service:         d.Id(),
   954  					Version:         latestVersion,
   955  					Name:            sf["name"].(string),
   956  					BucketName:      sf["bucket_name"].(string),
   957  					AccessKey:       sf["s3_access_key"].(string),
   958  					SecretKey:       sf["s3_secret_key"].(string),
   959  					Period:          uint(sf["period"].(int)),
   960  					GzipLevel:       uint(sf["gzip_level"].(int)),
   961  					Domain:          sf["domain"].(string),
   962  					Path:            sf["path"].(string),
   963  					Format:          sf["format"].(string),
   964  					TimestampFormat: sf["timestamp_format"].(string),
   965  				}
   966  
   967  				log.Printf("[DEBUG] Create S3 Logging Opts: %#v", opts)
   968  				_, err := conn.CreateS3(&opts)
   969  				if err != nil {
   970  					return err
   971  				}
   972  			}
   973  		}
   974  
   975  		// find difference in request settings
   976  		if d.HasChange("request_setting") {
   977  			os, ns := d.GetChange("request_setting")
   978  			if os == nil {
   979  				os = new(schema.Set)
   980  			}
   981  			if ns == nil {
   982  				ns = new(schema.Set)
   983  			}
   984  
   985  			ors := os.(*schema.Set)
   986  			nrs := ns.(*schema.Set)
   987  			removeRequestSettings := ors.Difference(nrs).List()
   988  			addRequestSettings := nrs.Difference(ors).List()
   989  
   990  			// DELETE old Request Settings configurations
   991  			for _, sRaw := range removeRequestSettings {
   992  				sf := sRaw.(map[string]interface{})
   993  				opts := gofastly.DeleteRequestSettingInput{
   994  					Service: d.Id(),
   995  					Version: latestVersion,
   996  					Name:    sf["name"].(string),
   997  				}
   998  
   999  				log.Printf("[DEBUG] Fastly Request Setting removal opts: %#v", opts)
  1000  				err := conn.DeleteRequestSetting(&opts)
  1001  				if err != nil {
  1002  					return err
  1003  				}
  1004  			}
  1005  
  1006  			// POST new/updated Request Setting
  1007  			for _, sRaw := range addRequestSettings {
  1008  				opts, err := buildRequestSetting(sRaw.(map[string]interface{}))
  1009  				if err != nil {
  1010  					log.Printf("[DEBUG] Error building Requset Setting: %s", err)
  1011  					return err
  1012  				}
  1013  				opts.Service = d.Id()
  1014  				opts.Version = latestVersion
  1015  
  1016  				log.Printf("[DEBUG] Create Request Setting Opts: %#v", opts)
  1017  				_, err = conn.CreateRequestSetting(opts)
  1018  				if err != nil {
  1019  					return err
  1020  				}
  1021  			}
  1022  		}
  1023  		// Find differences in VCLs
  1024  		if d.HasChange("vcl") {
  1025  			// Note: as above with Gzip and S3 logging, we don't utilize the PUT
  1026  			// endpoint to update a VCL, we simply destroy it and create a new one.
  1027  			oldVCLVal, newVCLVal := d.GetChange("vcl")
  1028  			if oldVCLVal == nil {
  1029  				oldVCLVal = new(schema.Set)
  1030  			}
  1031  			if newVCLVal == nil {
  1032  				newVCLVal = new(schema.Set)
  1033  			}
  1034  
  1035  			oldVCLSet := oldVCLVal.(*schema.Set)
  1036  			newVCLSet := newVCLVal.(*schema.Set)
  1037  
  1038  			remove := oldVCLSet.Difference(newVCLSet).List()
  1039  			add := newVCLSet.Difference(oldVCLSet).List()
  1040  
  1041  			// Delete removed VCL configurations
  1042  			for _, dRaw := range remove {
  1043  				df := dRaw.(map[string]interface{})
  1044  				opts := gofastly.DeleteVCLInput{
  1045  					Service: d.Id(),
  1046  					Version: latestVersion,
  1047  					Name:    df["name"].(string),
  1048  				}
  1049  
  1050  				log.Printf("[DEBUG] Fastly VCL Removal opts: %#v", opts)
  1051  				err := conn.DeleteVCL(&opts)
  1052  				if err != nil {
  1053  					return err
  1054  				}
  1055  			}
  1056  			// POST new VCL configurations
  1057  			for _, dRaw := range add {
  1058  				df := dRaw.(map[string]interface{})
  1059  				opts := gofastly.CreateVCLInput{
  1060  					Service: d.Id(),
  1061  					Version: latestVersion,
  1062  					Name:    df["name"].(string),
  1063  					Content: df["content"].(string),
  1064  				}
  1065  
  1066  				log.Printf("[DEBUG] Fastly VCL Addition opts: %#v", opts)
  1067  				_, err := conn.CreateVCL(&opts)
  1068  				if err != nil {
  1069  					return err
  1070  				}
  1071  
  1072  				// if this new VCL is the main
  1073  				if df["main"].(bool) {
  1074  					opts := gofastly.ActivateVCLInput{
  1075  						Service: d.Id(),
  1076  						Version: latestVersion,
  1077  						Name:    df["name"].(string),
  1078  					}
  1079  					log.Printf("[DEBUG] Fastly VCL activation opts: %#v", opts)
  1080  					_, err := conn.ActivateVCL(&opts)
  1081  					if err != nil {
  1082  						return err
  1083  					}
  1084  
  1085  				}
  1086  			}
  1087  		}
  1088  
  1089  		// validate version
  1090  		log.Printf("[DEBUG] Validating Fastly Service (%s), Version (%s)", d.Id(), latestVersion)
  1091  		valid, msg, err := conn.ValidateVersion(&gofastly.ValidateVersionInput{
  1092  			Service: d.Id(),
  1093  			Version: latestVersion,
  1094  		})
  1095  
  1096  		if err != nil {
  1097  			return fmt.Errorf("[ERR] Error checking validation: %s", err)
  1098  		}
  1099  
  1100  		if !valid {
  1101  			return fmt.Errorf("[ERR] Invalid configuration for Fastly Service (%s): %s", d.Id(), msg)
  1102  		}
  1103  
  1104  		log.Printf("[DEBUG] Activating Fastly Service (%s), Version (%s)", d.Id(), latestVersion)
  1105  		_, err = conn.ActivateVersion(&gofastly.ActivateVersionInput{
  1106  			Service: d.Id(),
  1107  			Version: latestVersion,
  1108  		})
  1109  		if err != nil {
  1110  			return fmt.Errorf("[ERR] Error activating version (%s): %s", latestVersion, err)
  1111  		}
  1112  
  1113  		// Only if the version is valid and activated do we set the active_version.
  1114  		// This prevents us from getting stuck in cloning an invalid version
  1115  		d.Set("active_version", latestVersion)
  1116  	}
  1117  
  1118  	return resourceServiceV1Read(d, meta)
  1119  }
  1120  
  1121  func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error {
  1122  	conn := meta.(*FastlyClient).conn
  1123  
  1124  	// Find the Service. Discard the service because we need the ServiceDetails,
  1125  	// not just a Service record
  1126  	_, err := findService(d.Id(), meta)
  1127  	if err != nil {
  1128  		switch err {
  1129  		case fastlyNoServiceFoundErr:
  1130  			log.Printf("[WARN] %s for ID (%s)", err, d.Id())
  1131  			d.SetId("")
  1132  			return nil
  1133  		default:
  1134  			return err
  1135  		}
  1136  	}
  1137  
  1138  	s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{
  1139  		ID: d.Id(),
  1140  	})
  1141  
  1142  	if err != nil {
  1143  		return err
  1144  	}
  1145  
  1146  	d.Set("name", s.Name)
  1147  	d.Set("active_version", s.ActiveVersion.Number)
  1148  
  1149  	// If CreateService succeeds, but initial updates to the Service fail, we'll
  1150  	// have an empty ActiveService version (no version is active, so we can't
  1151  	// query for information on it)
  1152  	if s.ActiveVersion.Number != "" {
  1153  		settingsOpts := gofastly.GetSettingsInput{
  1154  			Service: d.Id(),
  1155  			Version: s.ActiveVersion.Number,
  1156  		}
  1157  		if settings, err := conn.GetSettings(&settingsOpts); err == nil {
  1158  			d.Set("default_host", settings.DefaultHost)
  1159  			d.Set("default_ttl", settings.DefaultTTL)
  1160  		} else {
  1161  			return fmt.Errorf("[ERR] Error looking up Version settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1162  		}
  1163  
  1164  		// TODO: update go-fastly to support an ActiveVersion struct, which contains
  1165  		// domain and backend info in the response. Here we do 2 additional queries
  1166  		// to find out that info
  1167  		log.Printf("[DEBUG] Refreshing Domains for (%s)", d.Id())
  1168  		domainList, err := conn.ListDomains(&gofastly.ListDomainsInput{
  1169  			Service: d.Id(),
  1170  			Version: s.ActiveVersion.Number,
  1171  		})
  1172  
  1173  		if err != nil {
  1174  			return fmt.Errorf("[ERR] Error looking up Domains for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1175  		}
  1176  
  1177  		// Refresh Domains
  1178  		dl := flattenDomains(domainList)
  1179  
  1180  		if err := d.Set("domain", dl); err != nil {
  1181  			log.Printf("[WARN] Error setting Domains for (%s): %s", d.Id(), err)
  1182  		}
  1183  
  1184  		// Refresh Backends
  1185  		log.Printf("[DEBUG] Refreshing Backends for (%s)", d.Id())
  1186  		backendList, err := conn.ListBackends(&gofastly.ListBackendsInput{
  1187  			Service: d.Id(),
  1188  			Version: s.ActiveVersion.Number,
  1189  		})
  1190  
  1191  		if err != nil {
  1192  			return fmt.Errorf("[ERR] Error looking up Backends for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1193  		}
  1194  
  1195  		bl := flattenBackends(backendList)
  1196  
  1197  		if err := d.Set("backend", bl); err != nil {
  1198  			log.Printf("[WARN] Error setting Backends for (%s): %s", d.Id(), err)
  1199  		}
  1200  
  1201  		// refresh headers
  1202  		log.Printf("[DEBUG] Refreshing Headers for (%s)", d.Id())
  1203  		headerList, err := conn.ListHeaders(&gofastly.ListHeadersInput{
  1204  			Service: d.Id(),
  1205  			Version: s.ActiveVersion.Number,
  1206  		})
  1207  
  1208  		if err != nil {
  1209  			return fmt.Errorf("[ERR] Error looking up Headers for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1210  		}
  1211  
  1212  		hl := flattenHeaders(headerList)
  1213  
  1214  		if err := d.Set("header", hl); err != nil {
  1215  			log.Printf("[WARN] Error setting Headers for (%s): %s", d.Id(), err)
  1216  		}
  1217  
  1218  		// refresh gzips
  1219  		log.Printf("[DEBUG] Refreshing Gzips for (%s)", d.Id())
  1220  		gzipsList, err := conn.ListGzips(&gofastly.ListGzipsInput{
  1221  			Service: d.Id(),
  1222  			Version: s.ActiveVersion.Number,
  1223  		})
  1224  
  1225  		if err != nil {
  1226  			return fmt.Errorf("[ERR] Error looking up Gzips for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1227  		}
  1228  
  1229  		gl := flattenGzips(gzipsList)
  1230  
  1231  		if err := d.Set("gzip", gl); err != nil {
  1232  			log.Printf("[WARN] Error setting Gzips for (%s): %s", d.Id(), err)
  1233  		}
  1234  
  1235  		// refresh S3 Logging
  1236  		log.Printf("[DEBUG] Refreshing S3 Logging for (%s)", d.Id())
  1237  		s3List, err := conn.ListS3s(&gofastly.ListS3sInput{
  1238  			Service: d.Id(),
  1239  			Version: s.ActiveVersion.Number,
  1240  		})
  1241  
  1242  		if err != nil {
  1243  			return fmt.Errorf("[ERR] Error looking up S3 Logging for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1244  		}
  1245  
  1246  		sl := flattenS3s(s3List)
  1247  
  1248  		if err := d.Set("s3logging", sl); err != nil {
  1249  			log.Printf("[WARN] Error setting S3 Logging for (%s): %s", d.Id(), err)
  1250  		}
  1251  
  1252  		// refresh Conditions
  1253  		log.Printf("[DEBUG] Refreshing Conditions for (%s)", d.Id())
  1254  		conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{
  1255  			Service: d.Id(),
  1256  			Version: s.ActiveVersion.Number,
  1257  		})
  1258  
  1259  		if err != nil {
  1260  			return fmt.Errorf("[ERR] Error looking up Conditions for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1261  		}
  1262  
  1263  		cl := flattenConditions(conditionList)
  1264  
  1265  		if err := d.Set("condition", cl); err != nil {
  1266  			log.Printf("[WARN] Error setting Conditions for (%s): %s", d.Id(), err)
  1267  		}
  1268  
  1269  		// refresh Request Settings
  1270  		log.Printf("[DEBUG] Refreshing Request Settings for (%s)", d.Id())
  1271  		rsList, err := conn.ListRequestSettings(&gofastly.ListRequestSettingsInput{
  1272  			Service: d.Id(),
  1273  			Version: s.ActiveVersion.Number,
  1274  		})
  1275  
  1276  		if err != nil {
  1277  			return fmt.Errorf("[ERR] Error looking up Request Settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1278  		}
  1279  
  1280  		rl := flattenRequestSettings(rsList)
  1281  
  1282  		if err := d.Set("request_setting", rl); err != nil {
  1283  			log.Printf("[WARN] Error setting Request Settings for (%s): %s", d.Id(), err)
  1284  		}
  1285  		// refresh VCLs
  1286  		log.Printf("[DEBUG] Refreshing VCLs for (%s)", d.Id())
  1287  		vclList, err := conn.ListVCLs(&gofastly.ListVCLsInput{
  1288  			Service: d.Id(),
  1289  			Version: s.ActiveVersion.Number,
  1290  		})
  1291  		if err != nil {
  1292  			return fmt.Errorf("[ERR] Error looking up VCLs for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1293  		}
  1294  
  1295  		vl := flattenVCLs(vclList)
  1296  
  1297  		if err := d.Set("vcl", vl); err != nil {
  1298  			log.Printf("[WARN] Error setting VCLs for (%s): %s", d.Id(), err)
  1299  		}
  1300  
  1301  	} else {
  1302  		log.Printf("[DEBUG] Active Version for Service (%s) is empty, no state to refresh", d.Id())
  1303  	}
  1304  
  1305  	return nil
  1306  }
  1307  
  1308  func resourceServiceV1Delete(d *schema.ResourceData, meta interface{}) error {
  1309  	conn := meta.(*FastlyClient).conn
  1310  
  1311  	// Fastly will fail to delete any service with an Active Version.
  1312  	// If `force_destroy` is given, we deactivate the active version and then send
  1313  	// the DELETE call
  1314  	if d.Get("force_destroy").(bool) {
  1315  		s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{
  1316  			ID: d.Id(),
  1317  		})
  1318  
  1319  		if err != nil {
  1320  			return err
  1321  		}
  1322  
  1323  		if s.ActiveVersion.Number != "" {
  1324  			_, err := conn.DeactivateVersion(&gofastly.DeactivateVersionInput{
  1325  				Service: d.Id(),
  1326  				Version: s.ActiveVersion.Number,
  1327  			})
  1328  			if err != nil {
  1329  				return err
  1330  			}
  1331  		}
  1332  	}
  1333  
  1334  	err := conn.DeleteService(&gofastly.DeleteServiceInput{
  1335  		ID: d.Id(),
  1336  	})
  1337  
  1338  	if err != nil {
  1339  		return err
  1340  	}
  1341  
  1342  	_, err = findService(d.Id(), meta)
  1343  	if err != nil {
  1344  		switch err {
  1345  		// we expect no records to be found here
  1346  		case fastlyNoServiceFoundErr:
  1347  			d.SetId("")
  1348  			return nil
  1349  		default:
  1350  			return err
  1351  		}
  1352  	}
  1353  
  1354  	// findService above returned something and nil error, but shouldn't have
  1355  	return fmt.Errorf("[WARN] Tried deleting Service (%s), but was still found", d.Id())
  1356  
  1357  }
  1358  
  1359  func flattenDomains(list []*gofastly.Domain) []map[string]interface{} {
  1360  	dl := make([]map[string]interface{}, 0, len(list))
  1361  
  1362  	for _, d := range list {
  1363  		dl = append(dl, map[string]interface{}{
  1364  			"name":    d.Name,
  1365  			"comment": d.Comment,
  1366  		})
  1367  	}
  1368  
  1369  	return dl
  1370  }
  1371  
  1372  func flattenBackends(backendList []*gofastly.Backend) []map[string]interface{} {
  1373  	var bl []map[string]interface{}
  1374  	for _, b := range backendList {
  1375  		// Convert Backend to a map for saving to state.
  1376  		nb := map[string]interface{}{
  1377  			"name":                  b.Name,
  1378  			"address":               b.Address,
  1379  			"auto_loadbalance":      b.AutoLoadbalance,
  1380  			"between_bytes_timeout": int(b.BetweenBytesTimeout),
  1381  			"connect_timeout":       int(b.ConnectTimeout),
  1382  			"error_threshold":       int(b.ErrorThreshold),
  1383  			"first_byte_timeout":    int(b.FirstByteTimeout),
  1384  			"max_conn":              int(b.MaxConn),
  1385  			"port":                  int(b.Port),
  1386  			"ssl_check_cert":        b.SSLCheckCert,
  1387  			"weight":                int(b.Weight),
  1388  		}
  1389  
  1390  		bl = append(bl, nb)
  1391  	}
  1392  	return bl
  1393  }
  1394  
  1395  // findService finds a Fastly Service via the ListServices endpoint, returning
  1396  // the Service if found.
  1397  //
  1398  // Fastly API does not include any "deleted_at" type parameter to indicate
  1399  // that a Service has been deleted. GET requests to a deleted Service will
  1400  // return 200 OK and have the full output of the Service for an unknown time
  1401  // (days, in my testing). In order to determine if a Service is deleted, we
  1402  // need to hit /service and loop the returned Services, searching for the one
  1403  // in question. This endpoint only returns active or "alive" services. If the
  1404  // Service is not included, then it's "gone"
  1405  //
  1406  // Returns a fastlyNoServiceFoundErr error if the Service is not found in the
  1407  // ListServices response.
  1408  func findService(id string, meta interface{}) (*gofastly.Service, error) {
  1409  	conn := meta.(*FastlyClient).conn
  1410  
  1411  	l, err := conn.ListServices(&gofastly.ListServicesInput{})
  1412  	if err != nil {
  1413  		return nil, fmt.Errorf("[WARN] Error listing services when deleting Fastly Service (%s): %s", id, err)
  1414  	}
  1415  
  1416  	for _, s := range l {
  1417  		if s.ID == id {
  1418  			log.Printf("[DEBUG] Found Service (%s)", id)
  1419  			return s, nil
  1420  		}
  1421  	}
  1422  
  1423  	return nil, fastlyNoServiceFoundErr
  1424  }
  1425  
  1426  func flattenHeaders(headerList []*gofastly.Header) []map[string]interface{} {
  1427  	var hl []map[string]interface{}
  1428  	for _, h := range headerList {
  1429  		// Convert Header to a map for saving to state.
  1430  		nh := map[string]interface{}{
  1431  			"name":               h.Name,
  1432  			"action":             h.Action,
  1433  			"ignore_if_set":      h.IgnoreIfSet,
  1434  			"type":               h.Type,
  1435  			"destination":        h.Destination,
  1436  			"source":             h.Source,
  1437  			"regex":              h.Regex,
  1438  			"substitution":       h.Substitution,
  1439  			"priority":           int(h.Priority),
  1440  			"request_condition":  h.RequestCondition,
  1441  			"cache_condition":    h.CacheCondition,
  1442  			"response_condition": h.ResponseCondition,
  1443  		}
  1444  
  1445  		for k, v := range nh {
  1446  			if v == "" {
  1447  				delete(nh, k)
  1448  			}
  1449  		}
  1450  
  1451  		hl = append(hl, nh)
  1452  	}
  1453  	return hl
  1454  }
  1455  
  1456  func buildHeader(headerMap interface{}) (*gofastly.CreateHeaderInput, error) {
  1457  	df := headerMap.(map[string]interface{})
  1458  	opts := gofastly.CreateHeaderInput{
  1459  		Name:              df["name"].(string),
  1460  		IgnoreIfSet:       df["ignore_if_set"].(bool),
  1461  		Destination:       df["destination"].(string),
  1462  		Priority:          uint(df["priority"].(int)),
  1463  		Source:            df["source"].(string),
  1464  		Regex:             df["regex"].(string),
  1465  		Substitution:      df["substitution"].(string),
  1466  		RequestCondition:  df["request_condition"].(string),
  1467  		CacheCondition:    df["cache_condition"].(string),
  1468  		ResponseCondition: df["response_condition"].(string),
  1469  	}
  1470  
  1471  	act := strings.ToLower(df["action"].(string))
  1472  	switch act {
  1473  	case "set":
  1474  		opts.Action = gofastly.HeaderActionSet
  1475  	case "append":
  1476  		opts.Action = gofastly.HeaderActionAppend
  1477  	case "delete":
  1478  		opts.Action = gofastly.HeaderActionDelete
  1479  	case "regex":
  1480  		opts.Action = gofastly.HeaderActionRegex
  1481  	case "regex_repeat":
  1482  		opts.Action = gofastly.HeaderActionRegexRepeat
  1483  	}
  1484  
  1485  	ty := strings.ToLower(df["type"].(string))
  1486  	switch ty {
  1487  	case "request":
  1488  		opts.Type = gofastly.HeaderTypeRequest
  1489  	case "fetch":
  1490  		opts.Type = gofastly.HeaderTypeFetch
  1491  	case "cache":
  1492  		opts.Type = gofastly.HeaderTypeCache
  1493  	case "response":
  1494  		opts.Type = gofastly.HeaderTypeResponse
  1495  	}
  1496  
  1497  	return &opts, nil
  1498  }
  1499  
  1500  func flattenGzips(gzipsList []*gofastly.Gzip) []map[string]interface{} {
  1501  	var gl []map[string]interface{}
  1502  	for _, g := range gzipsList {
  1503  		// Convert Gzip to a map for saving to state.
  1504  		ng := map[string]interface{}{
  1505  			"name":            g.Name,
  1506  			"cache_condition": g.CacheCondition,
  1507  		}
  1508  
  1509  		if g.Extensions != "" {
  1510  			e := strings.Split(g.Extensions, " ")
  1511  			var et []interface{}
  1512  			for _, ev := range e {
  1513  				et = append(et, ev)
  1514  			}
  1515  			ng["extensions"] = schema.NewSet(schema.HashString, et)
  1516  		}
  1517  
  1518  		if g.ContentTypes != "" {
  1519  			c := strings.Split(g.ContentTypes, " ")
  1520  			var ct []interface{}
  1521  			for _, cv := range c {
  1522  				ct = append(ct, cv)
  1523  			}
  1524  			ng["content_types"] = schema.NewSet(schema.HashString, ct)
  1525  		}
  1526  
  1527  		// prune any empty values that come from the default string value in structs
  1528  		for k, v := range ng {
  1529  			if v == "" {
  1530  				delete(ng, k)
  1531  			}
  1532  		}
  1533  
  1534  		gl = append(gl, ng)
  1535  	}
  1536  
  1537  	return gl
  1538  }
  1539  
  1540  func flattenS3s(s3List []*gofastly.S3) []map[string]interface{} {
  1541  	var sl []map[string]interface{}
  1542  	for _, s := range s3List {
  1543  		// Convert S3s to a map for saving to state.
  1544  		ns := map[string]interface{}{
  1545  			"name":             s.Name,
  1546  			"bucket_name":      s.BucketName,
  1547  			"s3_access_key":    s.AccessKey,
  1548  			"s3_secret_key":    s.SecretKey,
  1549  			"path":             s.Path,
  1550  			"period":           s.Period,
  1551  			"domain":           s.Domain,
  1552  			"gzip_level":       s.GzipLevel,
  1553  			"format":           s.Format,
  1554  			"timestamp_format": s.TimestampFormat,
  1555  		}
  1556  
  1557  		// prune any empty values that come from the default string value in structs
  1558  		for k, v := range ns {
  1559  			if v == "" {
  1560  				delete(ns, k)
  1561  			}
  1562  		}
  1563  
  1564  		sl = append(sl, ns)
  1565  	}
  1566  
  1567  	return sl
  1568  }
  1569  
  1570  func flattenConditions(conditionList []*gofastly.Condition) []map[string]interface{} {
  1571  	var cl []map[string]interface{}
  1572  	for _, c := range conditionList {
  1573  		// Convert Conditions to a map for saving to state.
  1574  		nc := map[string]interface{}{
  1575  			"name":      c.Name,
  1576  			"statement": c.Statement,
  1577  			"type":      c.Type,
  1578  			"priority":  c.Priority,
  1579  		}
  1580  
  1581  		// prune any empty values that come from the default string value in structs
  1582  		for k, v := range nc {
  1583  			if v == "" {
  1584  				delete(nc, k)
  1585  			}
  1586  		}
  1587  
  1588  		cl = append(cl, nc)
  1589  	}
  1590  
  1591  	return cl
  1592  }
  1593  
  1594  func flattenRequestSettings(rsList []*gofastly.RequestSetting) []map[string]interface{} {
  1595  	var rl []map[string]interface{}
  1596  	for _, r := range rsList {
  1597  		// Convert Request Settings to a map for saving to state.
  1598  		nrs := map[string]interface{}{
  1599  			"name":              r.Name,
  1600  			"max_stale_age":     r.MaxStaleAge,
  1601  			"force_miss":        r.ForceMiss,
  1602  			"force_ssl":         r.ForceSSL,
  1603  			"action":            r.Action,
  1604  			"bypass_busy_wait":  r.BypassBusyWait,
  1605  			"hash_keys":         r.HashKeys,
  1606  			"xff":               r.XForwardedFor,
  1607  			"timer_support":     r.TimerSupport,
  1608  			"geo_headers":       r.GeoHeaders,
  1609  			"default_host":      r.DefaultHost,
  1610  			"request_condition": r.RequestCondition,
  1611  		}
  1612  
  1613  		// prune any empty values that come from the default string value in structs
  1614  		for k, v := range nrs {
  1615  			if v == "" {
  1616  				delete(nrs, k)
  1617  			}
  1618  		}
  1619  
  1620  		rl = append(rl, nrs)
  1621  	}
  1622  
  1623  	return rl
  1624  }
  1625  
  1626  func buildRequestSetting(requestSettingMap interface{}) (*gofastly.CreateRequestSettingInput, error) {
  1627  	df := requestSettingMap.(map[string]interface{})
  1628  	opts := gofastly.CreateRequestSettingInput{
  1629  		Name:             df["name"].(string),
  1630  		MaxStaleAge:      uint(df["max_stale_age"].(int)),
  1631  		ForceMiss:        gofastly.Compatibool(df["force_miss"].(bool)),
  1632  		ForceSSL:         gofastly.Compatibool(df["force_ssl"].(bool)),
  1633  		BypassBusyWait:   gofastly.Compatibool(df["bypass_busy_wait"].(bool)),
  1634  		HashKeys:         df["hash_keys"].(string),
  1635  		TimerSupport:     gofastly.Compatibool(df["timer_support"].(bool)),
  1636  		GeoHeaders:       gofastly.Compatibool(df["geo_headers"].(bool)),
  1637  		DefaultHost:      df["default_host"].(string),
  1638  		RequestCondition: df["request_condition"].(string),
  1639  	}
  1640  
  1641  	act := strings.ToLower(df["action"].(string))
  1642  	switch act {
  1643  	case "lookup":
  1644  		opts.Action = gofastly.RequestSettingActionLookup
  1645  	case "pass":
  1646  		opts.Action = gofastly.RequestSettingActionPass
  1647  	}
  1648  
  1649  	xff := strings.ToLower(df["xff"].(string))
  1650  	switch xff {
  1651  	case "clear":
  1652  		opts.XForwardedFor = gofastly.RequestSettingXFFClear
  1653  	case "leave":
  1654  		opts.XForwardedFor = gofastly.RequestSettingXFFLeave
  1655  	case "append":
  1656  		opts.XForwardedFor = gofastly.RequestSettingXFFAppend
  1657  	case "append_all":
  1658  		opts.XForwardedFor = gofastly.RequestSettingXFFAppendAll
  1659  	case "overwrite":
  1660  		opts.XForwardedFor = gofastly.RequestSettingXFFOverwrite
  1661  	}
  1662  
  1663  	return &opts, nil
  1664  }
  1665  func flattenVCLs(vclList []*gofastly.VCL) []map[string]interface{} {
  1666  	var vl []map[string]interface{}
  1667  	for _, vcl := range vclList {
  1668  		// Convert VCLs to a map for saving to state.
  1669  		vclMap := map[string]interface{}{
  1670  			"name":    vcl.Name,
  1671  			"content": vcl.Content,
  1672  			"main":    vcl.Main,
  1673  		}
  1674  
  1675  		// prune any empty values that come from the default string value in structs
  1676  		for k, v := range vclMap {
  1677  			if v == "" {
  1678  				delete(vclMap, k)
  1679  			}
  1680  		}
  1681  
  1682  		vl = append(vl, vclMap)
  1683  	}
  1684  
  1685  	return vl
  1686  }
  1687  
  1688  func validateVCLs(d *schema.ResourceData) error {
  1689  	// TODO: this would be nice to move into a resource/collection validation function, once that is available
  1690  	// (see https://github.com/hashicorp/terraform/pull/4348 and https://github.com/hashicorp/terraform/pull/6508)
  1691  	vcls, exists := d.GetOk("vcl")
  1692  	if !exists {
  1693  		return nil
  1694  	}
  1695  
  1696  	numberOfMainVCLs, numberOfIncludeVCLs := 0, 0
  1697  	for _, vclElem := range vcls.(*schema.Set).List() {
  1698  		vcl := vclElem.(map[string]interface{})
  1699  		if mainVal, hasMain := vcl["main"]; hasMain && mainVal.(bool) {
  1700  			numberOfMainVCLs++
  1701  		} else {
  1702  			numberOfIncludeVCLs++
  1703  		}
  1704  	}
  1705  	if numberOfMainVCLs == 0 && numberOfIncludeVCLs > 0 {
  1706  		return fmt.Errorf("if you include VCL configurations, one of them should have main = true")
  1707  	}
  1708  	if numberOfMainVCLs > 1 {
  1709  		return fmt.Errorf("you cannot have more than one VCL configuration with main = true")
  1710  	}
  1711  	return nil
  1712  }