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