github.com/jdextraze/terraform@v0.6.17-0.20160511153921-e33847c8a8af/builtin/providers/fastly/resource_fastly_service_v1.go (about)

     1  package fastly
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  	gofastly "github.com/sethvargo/go-fastly"
    12  )
    13  
    14  var fastlyNoServiceFoundErr = errors.New("No matching Fastly Service found")
    15  
    16  func resourceServiceV1() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceServiceV1Create,
    19  		Read:   resourceServiceV1Read,
    20  		Update: resourceServiceV1Update,
    21  		Delete: resourceServiceV1Delete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"name": &schema.Schema{
    25  				Type:        schema.TypeString,
    26  				Required:    true,
    27  				Description: "Unique name for this Service",
    28  			},
    29  
    30  			// Active Version represents the currently activated version in Fastly. In
    31  			// Terraform, we abstract this number away from the users and manage
    32  			// creating and activating. It's used internally, but also exported for
    33  			// users to see.
    34  			"active_version": &schema.Schema{
    35  				Type:     schema.TypeString,
    36  				Computed: true,
    37  			},
    38  
    39  			"domain": &schema.Schema{
    40  				Type:     schema.TypeSet,
    41  				Required: true,
    42  				Elem: &schema.Resource{
    43  					Schema: map[string]*schema.Schema{
    44  						"name": &schema.Schema{
    45  							Type:        schema.TypeString,
    46  							Required:    true,
    47  							Description: "The domain that this Service will respond to",
    48  						},
    49  
    50  						"comment": &schema.Schema{
    51  							Type:     schema.TypeString,
    52  							Optional: true,
    53  						},
    54  					},
    55  				},
    56  			},
    57  
    58  			"condition": &schema.Schema{
    59  				Type:     schema.TypeSet,
    60  				Optional: true,
    61  				Elem: &schema.Resource{
    62  					Schema: map[string]*schema.Schema{
    63  						"name": &schema.Schema{
    64  							Type:     schema.TypeString,
    65  							Required: true,
    66  						},
    67  						"statement": &schema.Schema{
    68  							Type:        schema.TypeString,
    69  							Required:    true,
    70  							Description: "The statement used to determine if the condition is met",
    71  							StateFunc: func(v interface{}) string {
    72  								value := v.(string)
    73  								// Trim newlines and spaces, to match Fastly API
    74  								return strings.TrimSpace(value)
    75  							},
    76  						},
    77  						"priority": &schema.Schema{
    78  							Type:        schema.TypeInt,
    79  							Required:    true,
    80  							Description: "A number used to determine the order in which multiple conditions execute. Lower numbers execute first",
    81  						},
    82  						"type": &schema.Schema{
    83  							Type:        schema.TypeString,
    84  							Required:    true,
    85  							Description: "Type of the condition, either `REQUEST`, `RESPONSE`, or `CACHE`",
    86  						},
    87  					},
    88  				},
    89  			},
    90  
    91  			"default_ttl": &schema.Schema{
    92  				Type:        schema.TypeInt,
    93  				Optional:    true,
    94  				Default:     3600,
    95  				Description: "The default Time-to-live (TTL) for the version",
    96  			},
    97  
    98  			"default_host": &schema.Schema{
    99  				Type:        schema.TypeString,
   100  				Optional:    true,
   101  				Computed:    true,
   102  				Description: "The default hostname for the version",
   103  			},
   104  
   105  			"backend": &schema.Schema{
   106  				Type:     schema.TypeSet,
   107  				Required: true,
   108  				Elem: &schema.Resource{
   109  					Schema: map[string]*schema.Schema{
   110  						// required fields
   111  						"name": &schema.Schema{
   112  							Type:        schema.TypeString,
   113  							Required:    true,
   114  							Description: "A name for this Backend",
   115  						},
   116  						"address": &schema.Schema{
   117  							Type:        schema.TypeString,
   118  							Required:    true,
   119  							Description: "An IPv4, hostname, or IPv6 address for the Backend",
   120  						},
   121  						// Optional fields, defaults where they exist
   122  						"auto_loadbalance": &schema.Schema{
   123  							Type:        schema.TypeBool,
   124  							Optional:    true,
   125  							Default:     true,
   126  							Description: "Should this Backend be load balanced",
   127  						},
   128  						"between_bytes_timeout": &schema.Schema{
   129  							Type:        schema.TypeInt,
   130  							Optional:    true,
   131  							Default:     10000,
   132  							Description: "How long to wait between bytes in milliseconds",
   133  						},
   134  						"connect_timeout": &schema.Schema{
   135  							Type:        schema.TypeInt,
   136  							Optional:    true,
   137  							Default:     1000,
   138  							Description: "How long to wait for a timeout in milliseconds",
   139  						},
   140  						"error_threshold": &schema.Schema{
   141  							Type:        schema.TypeInt,
   142  							Optional:    true,
   143  							Default:     0,
   144  							Description: "Number of errors to allow before the Backend is marked as down",
   145  						},
   146  						"first_byte_timeout": &schema.Schema{
   147  							Type:        schema.TypeInt,
   148  							Optional:    true,
   149  							Default:     15000,
   150  							Description: "How long to wait for the first bytes in milliseconds",
   151  						},
   152  						"max_conn": &schema.Schema{
   153  							Type:        schema.TypeInt,
   154  							Optional:    true,
   155  							Default:     200,
   156  							Description: "Maximum number of connections for this Backend",
   157  						},
   158  						"port": &schema.Schema{
   159  							Type:        schema.TypeInt,
   160  							Optional:    true,
   161  							Default:     80,
   162  							Description: "The port number Backend responds on. Default 80",
   163  						},
   164  						"ssl_check_cert": &schema.Schema{
   165  							Type:        schema.TypeBool,
   166  							Optional:    true,
   167  							Default:     true,
   168  							Description: "Be strict on checking SSL certs",
   169  						},
   170  						// UseSSL is something we want to support in the future, but
   171  						// requires SSL setup we don't yet have
   172  						// TODO: Provide all SSL fields from https://docs.fastly.com/api/config#backend
   173  						// "use_ssl": &schema.Schema{
   174  						// 	Type:        schema.TypeBool,
   175  						// 	Optional:    true,
   176  						// 	Default:     false,
   177  						// 	Description: "Whether or not to use SSL to reach the Backend",
   178  						// },
   179  						"weight": &schema.Schema{
   180  							Type:        schema.TypeInt,
   181  							Optional:    true,
   182  							Default:     100,
   183  							Description: "The portion of traffic to send to a specific origins. Each origin receives weight/total of the traffic.",
   184  						},
   185  					},
   186  				},
   187  			},
   188  
   189  			"force_destroy": &schema.Schema{
   190  				Type:     schema.TypeBool,
   191  				Optional: true,
   192  			},
   193  
   194  			"gzip": &schema.Schema{
   195  				Type:     schema.TypeSet,
   196  				Optional: true,
   197  				Elem: &schema.Resource{
   198  					Schema: map[string]*schema.Schema{
   199  						// required fields
   200  						"name": &schema.Schema{
   201  							Type:        schema.TypeString,
   202  							Required:    true,
   203  							Description: "A name to refer to this gzip condition",
   204  						},
   205  						// optional fields
   206  						"content_types": &schema.Schema{
   207  							Type:        schema.TypeSet,
   208  							Optional:    true,
   209  							Description: "Content types to apply automatic gzip to",
   210  							Elem:        &schema.Schema{Type: schema.TypeString},
   211  						},
   212  						"extensions": &schema.Schema{
   213  							Type:        schema.TypeSet,
   214  							Optional:    true,
   215  							Description: "File extensions to apply automatic gzip to. Do not include '.'",
   216  							Elem:        &schema.Schema{Type: schema.TypeString},
   217  						},
   218  						// These fields represent Fastly options that Terraform does not
   219  						// currently support
   220  						"cache_condition": &schema.Schema{
   221  							Type:        schema.TypeString,
   222  							Computed:    true,
   223  							Description: "Optional name of a CacheCondition to apply.",
   224  						},
   225  					},
   226  				},
   227  			},
   228  
   229  			"header": &schema.Schema{
   230  				Type:     schema.TypeSet,
   231  				Optional: true,
   232  				Elem: &schema.Resource{
   233  					Schema: map[string]*schema.Schema{
   234  						// required fields
   235  						"name": &schema.Schema{
   236  							Type:        schema.TypeString,
   237  							Required:    true,
   238  							Description: "A name to refer to this Header object",
   239  						},
   240  						"action": &schema.Schema{
   241  							Type:        schema.TypeString,
   242  							Required:    true,
   243  							Description: "One of set, append, delete, regex, or regex_repeat",
   244  							ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   245  								var found bool
   246  								for _, t := range []string{"set", "append", "delete", "regex", "regex_repeat"} {
   247  									if v.(string) == t {
   248  										found = true
   249  									}
   250  								}
   251  								if !found {
   252  									es = append(es, fmt.Errorf(
   253  										"Fastly Header action is case sensitive and must be one of 'set', 'append', 'delete', 'regex', or 'regex_repeat'; found: %s", v.(string)))
   254  								}
   255  								return
   256  							},
   257  						},
   258  						"type": &schema.Schema{
   259  							Type:        schema.TypeString,
   260  							Required:    true,
   261  							Description: "Type to manipulate: request, fetch, cache, response",
   262  							ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   263  								var found bool
   264  								for _, t := range []string{"request", "fetch", "cache", "response"} {
   265  									if v.(string) == t {
   266  										found = true
   267  									}
   268  								}
   269  								if !found {
   270  									es = append(es, fmt.Errorf(
   271  										"Fastly Header type is case sensitive and must be one of 'request', 'fetch', 'cache', or 'response'; found: %s", v.(string)))
   272  								}
   273  								return
   274  							},
   275  						},
   276  						"destination": &schema.Schema{
   277  							Type:        schema.TypeString,
   278  							Required:    true,
   279  							Description: "Header this affects",
   280  						},
   281  						// Optional fields, defaults where they exist
   282  						"ignore_if_set": &schema.Schema{
   283  							Type:        schema.TypeBool,
   284  							Optional:    true,
   285  							Default:     false,
   286  							Description: "Don't add the header if it is already. (Only applies to 'set' action.). Default `false`",
   287  						},
   288  						"source": &schema.Schema{
   289  							Type:        schema.TypeString,
   290  							Optional:    true,
   291  							Computed:    true,
   292  							Description: "Variable to be used as a source for the header content (Does not apply to 'delete' action.)",
   293  						},
   294  						"regex": &schema.Schema{
   295  							Type:        schema.TypeString,
   296  							Optional:    true,
   297  							Computed:    true,
   298  							Description: "Regular expression to use (Only applies to 'regex' and 'regex_repeat' actions.)",
   299  						},
   300  						"substitution": &schema.Schema{
   301  							Type:        schema.TypeString,
   302  							Optional:    true,
   303  							Computed:    true,
   304  							Description: "Value to substitute in place of regular expression. (Only applies to 'regex' and 'regex_repeat'.)",
   305  						},
   306  						"priority": &schema.Schema{
   307  							Type:        schema.TypeInt,
   308  							Optional:    true,
   309  							Default:     100,
   310  							Description: "Lower priorities execute first. (Default: 100.)",
   311  						},
   312  						// These fields represent Fastly options that Terraform does not
   313  						// currently support
   314  						"request_condition": &schema.Schema{
   315  							Type:        schema.TypeString,
   316  							Computed:    true,
   317  							Description: "Optional name of a RequestCondition to apply.",
   318  						},
   319  						"cache_condition": &schema.Schema{
   320  							Type:        schema.TypeString,
   321  							Computed:    true,
   322  							Description: "Optional name of a CacheCondition to apply.",
   323  						},
   324  						"response_condition": &schema.Schema{
   325  							Type:        schema.TypeString,
   326  							Computed:    true,
   327  							Description: "Optional name of a ResponseCondition to apply.",
   328  						},
   329  					},
   330  				},
   331  			},
   332  
   333  			"s3logging": &schema.Schema{
   334  				Type:     schema.TypeSet,
   335  				Optional: true,
   336  				Elem: &schema.Resource{
   337  					Schema: map[string]*schema.Schema{
   338  						// Required fields
   339  						"name": &schema.Schema{
   340  							Type:        schema.TypeString,
   341  							Required:    true,
   342  							Description: "Unique name to refer to this logging setup",
   343  						},
   344  						"bucket_name": &schema.Schema{
   345  							Type:        schema.TypeString,
   346  							Required:    true,
   347  							Description: "S3 Bucket name to store logs in",
   348  						},
   349  						"s3_access_key": &schema.Schema{
   350  							Type:        schema.TypeString,
   351  							Optional:    true,
   352  							DefaultFunc: schema.EnvDefaultFunc("FASTLY_S3_ACCESS_KEY", ""),
   353  							Description: "AWS Access Key",
   354  						},
   355  						"s3_secret_key": &schema.Schema{
   356  							Type:        schema.TypeString,
   357  							Optional:    true,
   358  							DefaultFunc: schema.EnvDefaultFunc("FASTLY_S3_SECRET_KEY", ""),
   359  							Description: "AWS Secret Key",
   360  						},
   361  						// Optional fields
   362  						"path": &schema.Schema{
   363  							Type:        schema.TypeString,
   364  							Optional:    true,
   365  							Description: "Path to store the files. Must end with a trailing slash",
   366  						},
   367  						"domain": &schema.Schema{
   368  							Type:        schema.TypeString,
   369  							Optional:    true,
   370  							Description: "Bucket endpoint",
   371  						},
   372  						"gzip_level": &schema.Schema{
   373  							Type:        schema.TypeInt,
   374  							Optional:    true,
   375  							Default:     0,
   376  							Description: "Gzip Compression level",
   377  						},
   378  						"period": &schema.Schema{
   379  							Type:        schema.TypeInt,
   380  							Optional:    true,
   381  							Default:     3600,
   382  							Description: "How frequently the logs should be transferred, in seconds (Default 3600)",
   383  						},
   384  						"format": &schema.Schema{
   385  							Type:        schema.TypeString,
   386  							Optional:    true,
   387  							Default:     "%h %l %u %t %r %>s",
   388  							Description: "Apache-style string or VCL variables to use for log formatting",
   389  						},
   390  						"timestamp_format": &schema.Schema{
   391  							Type:        schema.TypeString,
   392  							Optional:    true,
   393  							Default:     "%Y-%m-%dT%H:%M:%S.000",
   394  							Description: "specified timestamp formatting (default `%Y-%m-%dT%H:%M:%S.000`)",
   395  						},
   396  					},
   397  				},
   398  			},
   399  		},
   400  	}
   401  }
   402  
   403  func resourceServiceV1Create(d *schema.ResourceData, meta interface{}) error {
   404  	conn := meta.(*FastlyClient).conn
   405  	service, err := conn.CreateService(&gofastly.CreateServiceInput{
   406  		Name:    d.Get("name").(string),
   407  		Comment: "Managed by Terraform",
   408  	})
   409  
   410  	if err != nil {
   411  		return err
   412  	}
   413  
   414  	d.SetId(service.ID)
   415  	return resourceServiceV1Update(d, meta)
   416  }
   417  
   418  func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
   419  	conn := meta.(*FastlyClient).conn
   420  
   421  	// Update Name. No new verions is required for this
   422  	if d.HasChange("name") {
   423  		_, err := conn.UpdateService(&gofastly.UpdateServiceInput{
   424  			ID:   d.Id(),
   425  			Name: d.Get("name").(string),
   426  		})
   427  		if err != nil {
   428  			return err
   429  		}
   430  	}
   431  
   432  	// Once activated, Versions are locked and become immutable. This is true for
   433  	// versions that are no longer active. For Domains, Backends, DefaultHost and
   434  	// DefaultTTL, a new Version must be created first, and updates posted to that
   435  	// Version. Loop these attributes and determine if we need to create a new version first
   436  	var needsChange bool
   437  	for _, v := range []string{
   438  		"domain",
   439  		"backend",
   440  		"default_host",
   441  		"default_ttl",
   442  		"header",
   443  		"gzip",
   444  		"s3logging",
   445  		"condition",
   446  	} {
   447  		if d.HasChange(v) {
   448  			needsChange = true
   449  		}
   450  	}
   451  
   452  	if needsChange {
   453  		latestVersion := d.Get("active_version").(string)
   454  		if latestVersion == "" {
   455  			// If the service was just created, there is an empty Version 1 available
   456  			// that is unlocked and can be updated
   457  			latestVersion = "1"
   458  		} else {
   459  			// Clone the latest version, giving us an unlocked version we can modify
   460  			log.Printf("[DEBUG] Creating clone of version (%s) for updates", latestVersion)
   461  			newVersion, err := conn.CloneVersion(&gofastly.CloneVersionInput{
   462  				Service: d.Id(),
   463  				Version: latestVersion,
   464  			})
   465  			if err != nil {
   466  				return err
   467  			}
   468  
   469  			// The new version number is named "Number", but it's actually a string
   470  			latestVersion = newVersion.Number
   471  
   472  			// New versions are not immediately found in the API, or are not
   473  			// immediately mutable, so we need to sleep a few and let Fastly ready
   474  			// itself. Typically, 7 seconds is enough
   475  			log.Printf("[DEBUG] Sleeping 7 seconds to allow Fastly Version to be available")
   476  			time.Sleep(7 * time.Second)
   477  		}
   478  
   479  		// update general settings
   480  		if d.HasChange("default_host") || d.HasChange("default_ttl") {
   481  			opts := gofastly.UpdateSettingsInput{
   482  				Service: d.Id(),
   483  				Version: latestVersion,
   484  				// default_ttl has the same default value of 3600 that is provided by
   485  				// the Fastly API, so it's safe to include here
   486  				DefaultTTL: uint(d.Get("default_ttl").(int)),
   487  			}
   488  
   489  			if attr, ok := d.GetOk("default_host"); ok {
   490  				opts.DefaultHost = attr.(string)
   491  			}
   492  
   493  			log.Printf("[DEBUG] Update Settings opts: %#v", opts)
   494  			_, err := conn.UpdateSettings(&opts)
   495  			if err != nil {
   496  				return err
   497  			}
   498  		}
   499  
   500  		// Conditions need to be updated first, as they can be referenced by other
   501  		// configuraiton objects (Backends, Request Headers, etc)
   502  
   503  		// Find difference in Conditions
   504  		if d.HasChange("condition") {
   505  			// Note: we don't utilize the PUT endpoint to update these objects, we simply
   506  			// destroy any that have changed, and create new ones with the updated
   507  			// values. This is how Terraform works with nested sub resources, we only
   508  			// get the full diff not a partial set item diff. Because this is done
   509  			// on a new version of the Fastly Service configuration, this is considered safe
   510  
   511  			oc, nc := d.GetChange("condition")
   512  			if oc == nil {
   513  				oc = new(schema.Set)
   514  			}
   515  			if nc == nil {
   516  				nc = new(schema.Set)
   517  			}
   518  
   519  			ocs := oc.(*schema.Set)
   520  			ncs := nc.(*schema.Set)
   521  			removeConditions := ocs.Difference(ncs).List()
   522  			addConditions := ncs.Difference(ocs).List()
   523  
   524  			// DELETE old Conditions
   525  			for _, cRaw := range removeConditions {
   526  				cf := cRaw.(map[string]interface{})
   527  				opts := gofastly.DeleteConditionInput{
   528  					Service: d.Id(),
   529  					Version: latestVersion,
   530  					Name:    cf["name"].(string),
   531  				}
   532  
   533  				log.Printf("[DEBUG] Fastly Conditions Removal opts: %#v", opts)
   534  				err := conn.DeleteCondition(&opts)
   535  				if err != nil {
   536  					return err
   537  				}
   538  			}
   539  
   540  			// POST new Conditions
   541  			for _, cRaw := range addConditions {
   542  				cf := cRaw.(map[string]interface{})
   543  				opts := gofastly.CreateConditionInput{
   544  					Service: d.Id(),
   545  					Version: latestVersion,
   546  					Name:    cf["name"].(string),
   547  					Type:    cf["type"].(string),
   548  					// need to trim leading/tailing spaces, incase the config has HEREDOC
   549  					// formatting and contains a trailing new line
   550  					Statement: strings.TrimSpace(cf["statement"].(string)),
   551  					Priority:  cf["priority"].(int),
   552  				}
   553  
   554  				log.Printf("[DEBUG] Create Conditions Opts: %#v", opts)
   555  				_, err := conn.CreateCondition(&opts)
   556  				if err != nil {
   557  					return err
   558  				}
   559  			}
   560  		}
   561  
   562  		// Find differences in domains
   563  		if d.HasChange("domain") {
   564  			od, nd := d.GetChange("domain")
   565  			if od == nil {
   566  				od = new(schema.Set)
   567  			}
   568  			if nd == nil {
   569  				nd = new(schema.Set)
   570  			}
   571  
   572  			ods := od.(*schema.Set)
   573  			nds := nd.(*schema.Set)
   574  
   575  			remove := ods.Difference(nds).List()
   576  			add := nds.Difference(ods).List()
   577  
   578  			// Delete removed domains
   579  			for _, dRaw := range remove {
   580  				df := dRaw.(map[string]interface{})
   581  				opts := gofastly.DeleteDomainInput{
   582  					Service: d.Id(),
   583  					Version: latestVersion,
   584  					Name:    df["name"].(string),
   585  				}
   586  
   587  				log.Printf("[DEBUG] Fastly Domain Removal opts: %#v", opts)
   588  				err := conn.DeleteDomain(&opts)
   589  				if err != nil {
   590  					return err
   591  				}
   592  			}
   593  
   594  			// POST new Domains
   595  			for _, dRaw := range add {
   596  				df := dRaw.(map[string]interface{})
   597  				opts := gofastly.CreateDomainInput{
   598  					Service: d.Id(),
   599  					Version: latestVersion,
   600  					Name:    df["name"].(string),
   601  				}
   602  
   603  				if v, ok := df["comment"]; ok {
   604  					opts.Comment = v.(string)
   605  				}
   606  
   607  				log.Printf("[DEBUG] Fastly Domain Addition opts: %#v", opts)
   608  				_, err := conn.CreateDomain(&opts)
   609  				if err != nil {
   610  					return err
   611  				}
   612  			}
   613  		}
   614  
   615  		// find difference in backends
   616  		if d.HasChange("backend") {
   617  			ob, nb := d.GetChange("backend")
   618  			if ob == nil {
   619  				ob = new(schema.Set)
   620  			}
   621  			if nb == nil {
   622  				nb = new(schema.Set)
   623  			}
   624  
   625  			obs := ob.(*schema.Set)
   626  			nbs := nb.(*schema.Set)
   627  			removeBackends := obs.Difference(nbs).List()
   628  			addBackends := nbs.Difference(obs).List()
   629  
   630  			// DELETE old Backends
   631  			for _, bRaw := range removeBackends {
   632  				bf := bRaw.(map[string]interface{})
   633  				opts := gofastly.DeleteBackendInput{
   634  					Service: d.Id(),
   635  					Version: latestVersion,
   636  					Name:    bf["name"].(string),
   637  				}
   638  
   639  				log.Printf("[DEBUG] Fastly Backend Removal opts: %#v", opts)
   640  				err := conn.DeleteBackend(&opts)
   641  				if err != nil {
   642  					return err
   643  				}
   644  			}
   645  
   646  			// Find and post new Backends
   647  			for _, dRaw := range addBackends {
   648  				df := dRaw.(map[string]interface{})
   649  				opts := gofastly.CreateBackendInput{
   650  					Service:             d.Id(),
   651  					Version:             latestVersion,
   652  					Name:                df["name"].(string),
   653  					Address:             df["address"].(string),
   654  					AutoLoadbalance:     df["auto_loadbalance"].(bool),
   655  					SSLCheckCert:        df["ssl_check_cert"].(bool),
   656  					Port:                uint(df["port"].(int)),
   657  					BetweenBytesTimeout: uint(df["between_bytes_timeout"].(int)),
   658  					ConnectTimeout:      uint(df["connect_timeout"].(int)),
   659  					ErrorThreshold:      uint(df["error_threshold"].(int)),
   660  					FirstByteTimeout:    uint(df["first_byte_timeout"].(int)),
   661  					MaxConn:             uint(df["max_conn"].(int)),
   662  					Weight:              uint(df["weight"].(int)),
   663  				}
   664  
   665  				log.Printf("[DEBUG] Create Backend Opts: %#v", opts)
   666  				_, err := conn.CreateBackend(&opts)
   667  				if err != nil {
   668  					return err
   669  				}
   670  			}
   671  		}
   672  
   673  		if d.HasChange("header") {
   674  			oh, nh := d.GetChange("header")
   675  			if oh == nil {
   676  				oh = new(schema.Set)
   677  			}
   678  			if nh == nil {
   679  				nh = new(schema.Set)
   680  			}
   681  
   682  			ohs := oh.(*schema.Set)
   683  			nhs := nh.(*schema.Set)
   684  
   685  			remove := ohs.Difference(nhs).List()
   686  			add := nhs.Difference(ohs).List()
   687  
   688  			// Delete removed headers
   689  			for _, dRaw := range remove {
   690  				df := dRaw.(map[string]interface{})
   691  				opts := gofastly.DeleteHeaderInput{
   692  					Service: d.Id(),
   693  					Version: latestVersion,
   694  					Name:    df["name"].(string),
   695  				}
   696  
   697  				log.Printf("[DEBUG] Fastly Header Removal opts: %#v", opts)
   698  				err := conn.DeleteHeader(&opts)
   699  				if err != nil {
   700  					return err
   701  				}
   702  			}
   703  
   704  			// POST new Headers
   705  			for _, dRaw := range add {
   706  				opts, err := buildHeader(dRaw.(map[string]interface{}))
   707  				if err != nil {
   708  					log.Printf("[DEBUG] Error building Header: %s", err)
   709  					return err
   710  				}
   711  				opts.Service = d.Id()
   712  				opts.Version = latestVersion
   713  
   714  				log.Printf("[DEBUG] Fastly Header Addition opts: %#v", opts)
   715  				_, err = conn.CreateHeader(opts)
   716  				if err != nil {
   717  					return err
   718  				}
   719  			}
   720  		}
   721  
   722  		// Find differences in Gzips
   723  		if d.HasChange("gzip") {
   724  			og, ng := d.GetChange("gzip")
   725  			if og == nil {
   726  				og = new(schema.Set)
   727  			}
   728  			if ng == nil {
   729  				ng = new(schema.Set)
   730  			}
   731  
   732  			ogs := og.(*schema.Set)
   733  			ngs := ng.(*schema.Set)
   734  
   735  			remove := ogs.Difference(ngs).List()
   736  			add := ngs.Difference(ogs).List()
   737  
   738  			// Delete removed gzip rules
   739  			for _, dRaw := range remove {
   740  				df := dRaw.(map[string]interface{})
   741  				opts := gofastly.DeleteGzipInput{
   742  					Service: d.Id(),
   743  					Version: latestVersion,
   744  					Name:    df["name"].(string),
   745  				}
   746  
   747  				log.Printf("[DEBUG] Fastly Gzip Removal opts: %#v", opts)
   748  				err := conn.DeleteGzip(&opts)
   749  				if err != nil {
   750  					return err
   751  				}
   752  			}
   753  
   754  			// POST new Gzips
   755  			for _, dRaw := range add {
   756  				df := dRaw.(map[string]interface{})
   757  				opts := gofastly.CreateGzipInput{
   758  					Service: d.Id(),
   759  					Version: latestVersion,
   760  					Name:    df["name"].(string),
   761  				}
   762  
   763  				if v, ok := df["content_types"]; ok {
   764  					if len(v.(*schema.Set).List()) > 0 {
   765  						var cl []string
   766  						for _, c := range v.(*schema.Set).List() {
   767  							cl = append(cl, c.(string))
   768  						}
   769  						opts.ContentTypes = strings.Join(cl, " ")
   770  					}
   771  				}
   772  
   773  				if v, ok := df["extensions"]; ok {
   774  					if len(v.(*schema.Set).List()) > 0 {
   775  						var el []string
   776  						for _, e := range v.(*schema.Set).List() {
   777  							el = append(el, e.(string))
   778  						}
   779  						opts.Extensions = strings.Join(el, " ")
   780  					}
   781  				}
   782  
   783  				log.Printf("[DEBUG] Fastly Gzip Addition opts: %#v", opts)
   784  				_, err := conn.CreateGzip(&opts)
   785  				if err != nil {
   786  					return err
   787  				}
   788  			}
   789  		}
   790  
   791  		// find difference in s3logging
   792  		if d.HasChange("s3logging") {
   793  			os, ns := d.GetChange("s3logging")
   794  			if os == nil {
   795  				os = new(schema.Set)
   796  			}
   797  			if ns == nil {
   798  				ns = new(schema.Set)
   799  			}
   800  
   801  			oss := os.(*schema.Set)
   802  			nss := ns.(*schema.Set)
   803  			removeS3Logging := oss.Difference(nss).List()
   804  			addS3Logging := nss.Difference(oss).List()
   805  
   806  			// DELETE old S3 Log configurations
   807  			for _, sRaw := range removeS3Logging {
   808  				sf := sRaw.(map[string]interface{})
   809  				opts := gofastly.DeleteS3Input{
   810  					Service: d.Id(),
   811  					Version: latestVersion,
   812  					Name:    sf["name"].(string),
   813  				}
   814  
   815  				log.Printf("[DEBUG] Fastly S3 Logging Removal opts: %#v", opts)
   816  				err := conn.DeleteS3(&opts)
   817  				if err != nil {
   818  					return err
   819  				}
   820  			}
   821  
   822  			// POST new/updated S3 Logging
   823  			for _, sRaw := range addS3Logging {
   824  				sf := sRaw.(map[string]interface{})
   825  
   826  				// Fastly API will not error if these are omitted, so we throw an error
   827  				// if any of these are empty
   828  				for _, sk := range []string{"s3_access_key", "s3_secret_key"} {
   829  					if sf[sk].(string) == "" {
   830  						return fmt.Errorf("[ERR] No %s found for S3 Log stream setup for Service (%s)", sk, d.Id())
   831  					}
   832  				}
   833  
   834  				opts := gofastly.CreateS3Input{
   835  					Service:         d.Id(),
   836  					Version:         latestVersion,
   837  					Name:            sf["name"].(string),
   838  					BucketName:      sf["bucket_name"].(string),
   839  					AccessKey:       sf["s3_access_key"].(string),
   840  					SecretKey:       sf["s3_secret_key"].(string),
   841  					Period:          uint(sf["period"].(int)),
   842  					GzipLevel:       uint(sf["gzip_level"].(int)),
   843  					Domain:          sf["domain"].(string),
   844  					Path:            sf["path"].(string),
   845  					Format:          sf["format"].(string),
   846  					TimestampFormat: sf["timestamp_format"].(string),
   847  				}
   848  
   849  				log.Printf("[DEBUG] Create S3 Logging Opts: %#v", opts)
   850  				_, err := conn.CreateS3(&opts)
   851  				if err != nil {
   852  					return err
   853  				}
   854  			}
   855  		}
   856  
   857  		// validate version
   858  		log.Printf("[DEBUG] Validating Fastly Service (%s), Version (%s)", d.Id(), latestVersion)
   859  		valid, msg, err := conn.ValidateVersion(&gofastly.ValidateVersionInput{
   860  			Service: d.Id(),
   861  			Version: latestVersion,
   862  		})
   863  
   864  		if err != nil {
   865  			return fmt.Errorf("[ERR] Error checking validation: %s", err)
   866  		}
   867  
   868  		if !valid {
   869  			return fmt.Errorf("[ERR] Invalid configuration for Fastly Service (%s): %s", d.Id(), msg)
   870  		}
   871  
   872  		log.Printf("[DEBUG] Activating Fastly Service (%s), Version (%s)", d.Id(), latestVersion)
   873  		_, err = conn.ActivateVersion(&gofastly.ActivateVersionInput{
   874  			Service: d.Id(),
   875  			Version: latestVersion,
   876  		})
   877  		if err != nil {
   878  			return fmt.Errorf("[ERR] Error activating version (%s): %s", latestVersion, err)
   879  		}
   880  
   881  		// Only if the version is valid and activated do we set the active_version.
   882  		// This prevents us from getting stuck in cloning an invalid version
   883  		d.Set("active_version", latestVersion)
   884  	}
   885  
   886  	return resourceServiceV1Read(d, meta)
   887  }
   888  
   889  func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error {
   890  	conn := meta.(*FastlyClient).conn
   891  
   892  	// Find the Service. Discard the service because we need the ServiceDetails,
   893  	// not just a Service record
   894  	_, err := findService(d.Id(), meta)
   895  	if err != nil {
   896  		switch err {
   897  		case fastlyNoServiceFoundErr:
   898  			log.Printf("[WARN] %s for ID (%s)", err, d.Id())
   899  			d.SetId("")
   900  			return nil
   901  		default:
   902  			return err
   903  		}
   904  	}
   905  
   906  	s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{
   907  		ID: d.Id(),
   908  	})
   909  
   910  	if err != nil {
   911  		return err
   912  	}
   913  
   914  	d.Set("name", s.Name)
   915  	d.Set("active_version", s.ActiveVersion.Number)
   916  
   917  	// If CreateService succeeds, but initial updates to the Service fail, we'll
   918  	// have an empty ActiveService version (no version is active, so we can't
   919  	// query for information on it)
   920  	if s.ActiveVersion.Number != "" {
   921  		settingsOpts := gofastly.GetSettingsInput{
   922  			Service: d.Id(),
   923  			Version: s.ActiveVersion.Number,
   924  		}
   925  		if settings, err := conn.GetSettings(&settingsOpts); err == nil {
   926  			d.Set("default_host", settings.DefaultHost)
   927  			d.Set("default_ttl", settings.DefaultTTL)
   928  		} else {
   929  			return fmt.Errorf("[ERR] Error looking up Version settings for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
   930  		}
   931  
   932  		// TODO: update go-fastly to support an ActiveVersion struct, which contains
   933  		// domain and backend info in the response. Here we do 2 additional queries
   934  		// to find out that info
   935  		log.Printf("[DEBUG] Refreshing Domains for (%s)", d.Id())
   936  		domainList, err := conn.ListDomains(&gofastly.ListDomainsInput{
   937  			Service: d.Id(),
   938  			Version: s.ActiveVersion.Number,
   939  		})
   940  
   941  		if err != nil {
   942  			return fmt.Errorf("[ERR] Error looking up Domains for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
   943  		}
   944  
   945  		// Refresh Domains
   946  		dl := flattenDomains(domainList)
   947  
   948  		if err := d.Set("domain", dl); err != nil {
   949  			log.Printf("[WARN] Error setting Domains for (%s): %s", d.Id(), err)
   950  		}
   951  
   952  		// Refresh Backends
   953  		log.Printf("[DEBUG] Refreshing Backends for (%s)", d.Id())
   954  		backendList, err := conn.ListBackends(&gofastly.ListBackendsInput{
   955  			Service: d.Id(),
   956  			Version: s.ActiveVersion.Number,
   957  		})
   958  
   959  		if err != nil {
   960  			return fmt.Errorf("[ERR] Error looking up Backends for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
   961  		}
   962  
   963  		bl := flattenBackends(backendList)
   964  
   965  		if err := d.Set("backend", bl); err != nil {
   966  			log.Printf("[WARN] Error setting Backends for (%s): %s", d.Id(), err)
   967  		}
   968  
   969  		// refresh headers
   970  		log.Printf("[DEBUG] Refreshing Headers for (%s)", d.Id())
   971  		headerList, err := conn.ListHeaders(&gofastly.ListHeadersInput{
   972  			Service: d.Id(),
   973  			Version: s.ActiveVersion.Number,
   974  		})
   975  
   976  		if err != nil {
   977  			return fmt.Errorf("[ERR] Error looking up Headers for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
   978  		}
   979  
   980  		hl := flattenHeaders(headerList)
   981  
   982  		if err := d.Set("header", hl); err != nil {
   983  			log.Printf("[WARN] Error setting Headers for (%s): %s", d.Id(), err)
   984  		}
   985  
   986  		// refresh gzips
   987  		log.Printf("[DEBUG] Refreshing Gzips for (%s)", d.Id())
   988  		gzipsList, err := conn.ListGzips(&gofastly.ListGzipsInput{
   989  			Service: d.Id(),
   990  			Version: s.ActiveVersion.Number,
   991  		})
   992  
   993  		if err != nil {
   994  			return fmt.Errorf("[ERR] Error looking up Gzips for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
   995  		}
   996  
   997  		gl := flattenGzips(gzipsList)
   998  
   999  		if err := d.Set("gzip", gl); err != nil {
  1000  			log.Printf("[WARN] Error setting Gzips for (%s): %s", d.Id(), err)
  1001  		}
  1002  
  1003  		// refresh S3 Logging
  1004  		log.Printf("[DEBUG] Refreshing S3 Logging for (%s)", d.Id())
  1005  		s3List, err := conn.ListS3s(&gofastly.ListS3sInput{
  1006  			Service: d.Id(),
  1007  			Version: s.ActiveVersion.Number,
  1008  		})
  1009  
  1010  		if err != nil {
  1011  			return fmt.Errorf("[ERR] Error looking up S3 Logging for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1012  		}
  1013  
  1014  		sl := flattenS3s(s3List)
  1015  
  1016  		if err := d.Set("s3logging", sl); err != nil {
  1017  			log.Printf("[WARN] Error setting S3 Logging for (%s): %s", d.Id(), err)
  1018  		}
  1019  
  1020  		// refresh Conditions
  1021  		log.Printf("[DEBUG] Refreshing Conditions for (%s)", d.Id())
  1022  		conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{
  1023  			Service: d.Id(),
  1024  			Version: s.ActiveVersion.Number,
  1025  		})
  1026  
  1027  		if err != nil {
  1028  			return fmt.Errorf("[ERR] Error looking up Conditions for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
  1029  		}
  1030  
  1031  		cl := flattenConditions(conditionList)
  1032  
  1033  		if err := d.Set("condition", cl); err != nil {
  1034  			log.Printf("[WARN] Error setting Conditions for (%s): %s", d.Id(), err)
  1035  		}
  1036  
  1037  	} else {
  1038  		log.Printf("[DEBUG] Active Version for Service (%s) is empty, no state to refresh", d.Id())
  1039  	}
  1040  
  1041  	return nil
  1042  }
  1043  
  1044  func resourceServiceV1Delete(d *schema.ResourceData, meta interface{}) error {
  1045  	conn := meta.(*FastlyClient).conn
  1046  
  1047  	// Fastly will fail to delete any service with an Active Version.
  1048  	// If `force_destroy` is given, we deactivate the active version and then send
  1049  	// the DELETE call
  1050  	if d.Get("force_destroy").(bool) {
  1051  		s, err := conn.GetServiceDetails(&gofastly.GetServiceInput{
  1052  			ID: d.Id(),
  1053  		})
  1054  
  1055  		if err != nil {
  1056  			return err
  1057  		}
  1058  
  1059  		if s.ActiveVersion.Number != "" {
  1060  			_, err := conn.DeactivateVersion(&gofastly.DeactivateVersionInput{
  1061  				Service: d.Id(),
  1062  				Version: s.ActiveVersion.Number,
  1063  			})
  1064  			if err != nil {
  1065  				return err
  1066  			}
  1067  		}
  1068  	}
  1069  
  1070  	err := conn.DeleteService(&gofastly.DeleteServiceInput{
  1071  		ID: d.Id(),
  1072  	})
  1073  
  1074  	if err != nil {
  1075  		return err
  1076  	}
  1077  
  1078  	_, err = findService(d.Id(), meta)
  1079  	if err != nil {
  1080  		switch err {
  1081  		// we expect no records to be found here
  1082  		case fastlyNoServiceFoundErr:
  1083  			d.SetId("")
  1084  			return nil
  1085  		default:
  1086  			return err
  1087  		}
  1088  	}
  1089  
  1090  	// findService above returned something and nil error, but shouldn't have
  1091  	return fmt.Errorf("[WARN] Tried deleting Service (%s), but was still found", d.Id())
  1092  
  1093  }
  1094  
  1095  func flattenDomains(list []*gofastly.Domain) []map[string]interface{} {
  1096  	dl := make([]map[string]interface{}, 0, len(list))
  1097  
  1098  	for _, d := range list {
  1099  		dl = append(dl, map[string]interface{}{
  1100  			"name":    d.Name,
  1101  			"comment": d.Comment,
  1102  		})
  1103  	}
  1104  
  1105  	return dl
  1106  }
  1107  
  1108  func flattenBackends(backendList []*gofastly.Backend) []map[string]interface{} {
  1109  	var bl []map[string]interface{}
  1110  	for _, b := range backendList {
  1111  		// Convert Backend to a map for saving to state.
  1112  		nb := map[string]interface{}{
  1113  			"name":                  b.Name,
  1114  			"address":               b.Address,
  1115  			"auto_loadbalance":      b.AutoLoadbalance,
  1116  			"between_bytes_timeout": int(b.BetweenBytesTimeout),
  1117  			"connect_timeout":       int(b.ConnectTimeout),
  1118  			"error_threshold":       int(b.ErrorThreshold),
  1119  			"first_byte_timeout":    int(b.FirstByteTimeout),
  1120  			"max_conn":              int(b.MaxConn),
  1121  			"port":                  int(b.Port),
  1122  			"ssl_check_cert":        b.SSLCheckCert,
  1123  			"weight":                int(b.Weight),
  1124  		}
  1125  
  1126  		bl = append(bl, nb)
  1127  	}
  1128  	return bl
  1129  }
  1130  
  1131  // findService finds a Fastly Service via the ListServices endpoint, returning
  1132  // the Service if found.
  1133  //
  1134  // Fastly API does not include any "deleted_at" type parameter to indicate
  1135  // that a Service has been deleted. GET requests to a deleted Service will
  1136  // return 200 OK and have the full output of the Service for an unknown time
  1137  // (days, in my testing). In order to determine if a Service is deleted, we
  1138  // need to hit /service and loop the returned Services, searching for the one
  1139  // in question. This endpoint only returns active or "alive" services. If the
  1140  // Service is not included, then it's "gone"
  1141  //
  1142  // Returns a fastlyNoServiceFoundErr error if the Service is not found in the
  1143  // ListServices response.
  1144  func findService(id string, meta interface{}) (*gofastly.Service, error) {
  1145  	conn := meta.(*FastlyClient).conn
  1146  
  1147  	l, err := conn.ListServices(&gofastly.ListServicesInput{})
  1148  	if err != nil {
  1149  		return nil, fmt.Errorf("[WARN] Error listing services when deleting Fastly Service (%s): %s", id, err)
  1150  	}
  1151  
  1152  	for _, s := range l {
  1153  		if s.ID == id {
  1154  			log.Printf("[DEBUG] Found Service (%s)", id)
  1155  			return s, nil
  1156  		}
  1157  	}
  1158  
  1159  	return nil, fastlyNoServiceFoundErr
  1160  }
  1161  
  1162  func flattenHeaders(headerList []*gofastly.Header) []map[string]interface{} {
  1163  	var hl []map[string]interface{}
  1164  	for _, h := range headerList {
  1165  		// Convert Header to a map for saving to state.
  1166  		nh := map[string]interface{}{
  1167  			"name":               h.Name,
  1168  			"action":             h.Action,
  1169  			"ignore_if_set":      h.IgnoreIfSet,
  1170  			"type":               h.Type,
  1171  			"destination":        h.Destination,
  1172  			"source":             h.Source,
  1173  			"regex":              h.Regex,
  1174  			"substitution":       h.Substitution,
  1175  			"priority":           int(h.Priority),
  1176  			"request_condition":  h.RequestCondition,
  1177  			"cache_condition":    h.CacheCondition,
  1178  			"response_condition": h.ResponseCondition,
  1179  		}
  1180  
  1181  		for k, v := range nh {
  1182  			if v == "" {
  1183  				delete(nh, k)
  1184  			}
  1185  		}
  1186  
  1187  		hl = append(hl, nh)
  1188  	}
  1189  	return hl
  1190  }
  1191  
  1192  func buildHeader(headerMap interface{}) (*gofastly.CreateHeaderInput, error) {
  1193  	df := headerMap.(map[string]interface{})
  1194  	opts := gofastly.CreateHeaderInput{
  1195  		Name:              df["name"].(string),
  1196  		IgnoreIfSet:       df["ignore_if_set"].(bool),
  1197  		Destination:       df["destination"].(string),
  1198  		Priority:          uint(df["priority"].(int)),
  1199  		Source:            df["source"].(string),
  1200  		Regex:             df["regex"].(string),
  1201  		Substitution:      df["substitution"].(string),
  1202  		RequestCondition:  df["request_condition"].(string),
  1203  		CacheCondition:    df["cache_condition"].(string),
  1204  		ResponseCondition: df["response_condition"].(string),
  1205  	}
  1206  
  1207  	act := strings.ToLower(df["action"].(string))
  1208  	switch act {
  1209  	case "set":
  1210  		opts.Action = gofastly.HeaderActionSet
  1211  	case "append":
  1212  		opts.Action = gofastly.HeaderActionAppend
  1213  	case "delete":
  1214  		opts.Action = gofastly.HeaderActionDelete
  1215  	case "regex":
  1216  		opts.Action = gofastly.HeaderActionRegex
  1217  	case "regex_repeat":
  1218  		opts.Action = gofastly.HeaderActionRegexRepeat
  1219  	}
  1220  
  1221  	ty := strings.ToLower(df["type"].(string))
  1222  	switch ty {
  1223  	case "request":
  1224  		opts.Type = gofastly.HeaderTypeRequest
  1225  	case "fetch":
  1226  		opts.Type = gofastly.HeaderTypeFetch
  1227  	case "cache":
  1228  		opts.Type = gofastly.HeaderTypeCache
  1229  	case "response":
  1230  		opts.Type = gofastly.HeaderTypeResponse
  1231  	}
  1232  
  1233  	return &opts, nil
  1234  }
  1235  
  1236  func flattenGzips(gzipsList []*gofastly.Gzip) []map[string]interface{} {
  1237  	var gl []map[string]interface{}
  1238  	for _, g := range gzipsList {
  1239  		// Convert Gzip to a map for saving to state.
  1240  		ng := map[string]interface{}{
  1241  			"name":            g.Name,
  1242  			"cache_condition": g.CacheCondition,
  1243  		}
  1244  
  1245  		if g.Extensions != "" {
  1246  			e := strings.Split(g.Extensions, " ")
  1247  			var et []interface{}
  1248  			for _, ev := range e {
  1249  				et = append(et, ev)
  1250  			}
  1251  			ng["extensions"] = schema.NewSet(schema.HashString, et)
  1252  		}
  1253  
  1254  		if g.ContentTypes != "" {
  1255  			c := strings.Split(g.ContentTypes, " ")
  1256  			var ct []interface{}
  1257  			for _, cv := range c {
  1258  				ct = append(ct, cv)
  1259  			}
  1260  			ng["content_types"] = schema.NewSet(schema.HashString, ct)
  1261  		}
  1262  
  1263  		// prune any empty values that come from the default string value in structs
  1264  		for k, v := range ng {
  1265  			if v == "" {
  1266  				delete(ng, k)
  1267  			}
  1268  		}
  1269  
  1270  		gl = append(gl, ng)
  1271  	}
  1272  
  1273  	return gl
  1274  }
  1275  
  1276  func flattenS3s(s3List []*gofastly.S3) []map[string]interface{} {
  1277  	var sl []map[string]interface{}
  1278  	for _, s := range s3List {
  1279  		// Convert S3s to a map for saving to state.
  1280  		ns := map[string]interface{}{
  1281  			"name":             s.Name,
  1282  			"bucket_name":      s.BucketName,
  1283  			"s3_access_key":    s.AccessKey,
  1284  			"s3_secret_key":    s.SecretKey,
  1285  			"path":             s.Path,
  1286  			"period":           s.Period,
  1287  			"domain":           s.Domain,
  1288  			"gzip_level":       s.GzipLevel,
  1289  			"format":           s.Format,
  1290  			"timestamp_format": s.TimestampFormat,
  1291  		}
  1292  
  1293  		// prune any empty values that come from the default string value in structs
  1294  		for k, v := range ns {
  1295  			if v == "" {
  1296  				delete(ns, k)
  1297  			}
  1298  		}
  1299  
  1300  		sl = append(sl, ns)
  1301  	}
  1302  
  1303  	return sl
  1304  }
  1305  
  1306  func flattenConditions(conditionList []*gofastly.Condition) []map[string]interface{} {
  1307  	var cl []map[string]interface{}
  1308  	for _, c := range conditionList {
  1309  		// Convert Conditions to a map for saving to state.
  1310  		nc := map[string]interface{}{
  1311  			"name":      c.Name,
  1312  			"statement": c.Statement,
  1313  			"type":      c.Type,
  1314  			"priority":  c.Priority,
  1315  		}
  1316  
  1317  		// prune any empty values that come from the default string value in structs
  1318  		for k, v := range nc {
  1319  			if v == "" {
  1320  				delete(nc, k)
  1321  			}
  1322  		}
  1323  
  1324  		cl = append(cl, nc)
  1325  	}
  1326  
  1327  	return cl
  1328  }