-
Notifications
You must be signed in to change notification settings - Fork 630
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'ewilde-spectrum' of github.com:ewilde/terraform-provide…
…r-cloudflare into ewilde-spectrum
- Loading branch information
Showing
1 changed file
with
352 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,352 @@ | ||
package cloudflare | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"strings" | ||
|
||
"time" | ||
|
||
"github.com/cloudflare/cloudflare-go" | ||
"github.com/hashicorp/terraform/helper/schema" | ||
"github.com/hashicorp/terraform/helper/validation" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
func resourceCloudflareSpectrumApp() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceCloudflareSpectrumAppCreate, | ||
Read: resourceCloudflareSpectrumAppRead, | ||
Update: resourceCloudflareSpectrumAppUpdate, | ||
Delete: resourceCloudflareSpectrumAppDelete, | ||
Importer: &schema.ResourceImporter{ | ||
State: resourceCloudflareSpectrumAppImport, | ||
}, | ||
|
||
SchemaVersion: 0, | ||
Schema: map[string]*schema.Schema{ | ||
"protocol": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
|
||
"dns": { | ||
Type: schema.TypeList, | ||
Required: true, | ||
MaxItems: 1, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"type": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
"name": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
|
||
"origin_direct": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
}, | ||
|
||
|
||
|
||
"origin_dns": { | ||
Type: schema.TypeList, | ||
Required: true, | ||
MaxItems: 1, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"name": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
|
||
"tls": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
ValidateFunc: validation.StringInSlice([]string{ | ||
cloudflare., | ||
ecs.ScopeTask, | ||
}, false), | ||
}, | ||
|
||
"created_on": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
|
||
"modified_on": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
var popPoolElem = &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"pop": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
// let the api handle validating pops | ||
}, | ||
|
||
"pool_ids": { | ||
Type: schema.TypeList, | ||
Required: true, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
ValidateFunc: validation.StringLenBetween(1, 32), | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
var regionPoolElem = &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"region": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
// let the api handle validating regions | ||
}, | ||
|
||
"pool_ids": { | ||
Type: schema.TypeList, | ||
Required: true, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
ValidateFunc: validation.StringLenBetween(1, 32), | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
var localPoolElems = map[string]*schema.Resource{ | ||
"pop": popPoolElem, | ||
"region": regionPoolElem, | ||
} | ||
|
||
func resourceCloudflareSpectrumAppCreate(d *schema.ResourceData, meta interface{}) error { | ||
client := meta.(*cloudflare.API) | ||
|
||
newSpectrumApp := cloudflare.SpectrumApp{ | ||
Name: d.Get("name").(string), | ||
FallbackPool: d.Get("fallback_pool_id").(string), | ||
DefaultPools: expandInterfaceToStringList(d.Get("default_pool_ids")), | ||
Proxied: d.Get("proxied").(bool), | ||
TTL: d.Get("ttl").(int), | ||
SteeringPolicy: d.Get("steering_policy").(string), | ||
Persistence: d.Get("session_affinity").(string), | ||
} | ||
|
||
if description, ok := d.GetOk("description"); ok { | ||
newSpectrumApp.Description = description.(string) | ||
} | ||
|
||
if ttl, ok := d.GetOk("ttl"); ok { | ||
newSpectrumApp.TTL = ttl.(int) | ||
} | ||
|
||
if regionPools, ok := d.GetOk("region_pools"); ok { | ||
expandedRegionPools, err := expandGeoPools(regionPools, "region") | ||
if err != nil { | ||
return err | ||
} | ||
newSpectrumApp.RegionPools = expandedRegionPools | ||
} | ||
|
||
if popPools, ok := d.GetOk("pop_pools"); ok { | ||
expandedPopPools, err := expandGeoPools(popPools, "pop") | ||
if err != nil { | ||
return err | ||
} | ||
newSpectrumApp.PopPools = expandedPopPools | ||
} | ||
|
||
zoneName := d.Get("zone").(string) | ||
zoneID, err := client.ZoneIDByName(zoneName) | ||
if err != nil { | ||
return fmt.Errorf("error finding zone %q: %s", zoneName, err) | ||
} | ||
d.Set("zone_id", zoneID) | ||
|
||
log.Printf("[INFO] Creating Cloudflare Load Balancer from struct: %+v", newSpectrumApp) | ||
|
||
r, err := client.CreateSpectrumApp(zoneID, newSpectrumApp) | ||
if err != nil { | ||
return errors.Wrap(err, "error creating load balancer for zone") | ||
} | ||
|
||
if r.ID == "" { | ||
return fmt.Errorf("failed to find id in Create response; resource was empty") | ||
} | ||
|
||
d.SetId(r.ID) | ||
|
||
log.Printf("[INFO] Cloudflare Load Balancer ID: %s", d.Id()) | ||
|
||
return resourceCloudflareSpectrumAppRead(d, meta) | ||
} | ||
|
||
func resourceCloudflareSpectrumAppUpdate(d *schema.ResourceData, meta interface{}) error { | ||
// since api only supports replace, update looks a lot like create... | ||
client := meta.(*cloudflare.API) | ||
zoneID := d.Get("zone_id").(string) | ||
|
||
loadBalancer := cloudflare.SpectrumApp{ | ||
ID: d.Id(), | ||
Name: d.Get("name").(string), | ||
FallbackPool: d.Get("fallback_pool_id").(string), | ||
DefaultPools: expandInterfaceToStringList(d.Get("default_pool_ids")), | ||
Proxied: d.Get("proxied").(bool), | ||
TTL: d.Get("ttl").(int), | ||
SteeringPolicy: d.Get("steering_policy").(string), | ||
Persistence: d.Get("session_affinity").(string), | ||
} | ||
|
||
if description, ok := d.GetOk("description"); ok { | ||
loadBalancer.Description = description.(string) | ||
} | ||
|
||
if regionPools, ok := d.GetOk("region_pools"); ok { | ||
expandedRegionPools, err := expandGeoPools(regionPools, "region") | ||
if err != nil { | ||
return err | ||
} | ||
loadBalancer.RegionPools = expandedRegionPools | ||
} | ||
|
||
if popPools, ok := d.GetOk("pop_pools"); ok { | ||
expandedPopPools, err := expandGeoPools(popPools, "pop") | ||
if err != nil { | ||
return err | ||
} | ||
loadBalancer.PopPools = expandedPopPools | ||
} | ||
|
||
log.Printf("[INFO] Updating Cloudflare Load Balancer from struct: %+v", loadBalancer) | ||
|
||
_, err := client.ModifySpectrumApp(zoneID, loadBalancer) | ||
if err != nil { | ||
return errors.Wrap(err, "error creating load balancer for zone") | ||
} | ||
|
||
return resourceCloudflareSpectrumAppRead(d, meta) | ||
} | ||
|
||
func expandGeoPools(pool interface{}, geoType string) (map[string][]string, error) { | ||
cfg := pool.(*schema.Set).List() | ||
expanded := make(map[string][]string) | ||
for _, v := range cfg { | ||
locationConfig := v.(map[string]interface{}) | ||
// lists are of type interface{} by default | ||
location := locationConfig[geoType].(string) | ||
if _, present := expanded[location]; !present { | ||
expanded[location] = expandInterfaceToStringList(locationConfig["pool_ids"]) | ||
} else { | ||
return nil, fmt.Errorf("duplicate entry specified for %s pool in location %q. each location must only be specified once", geoType, location) | ||
} | ||
} | ||
return expanded, nil | ||
} | ||
|
||
func resourceCloudflareSpectrumAppRead(d *schema.ResourceData, meta interface{}) error { | ||
client := meta.(*cloudflare.API) | ||
zoneID := d.Get("zone_id").(string) | ||
loadBalancerID := d.Id() | ||
|
||
loadBalancer, err := client.SpectrumAppDetails(zoneID, loadBalancerID) | ||
if err != nil { | ||
if strings.Contains(err.Error(), "HTTP status 404") { | ||
log.Printf("[INFO] Load balancer %s in zone %s not found", loadBalancerID, zoneID) | ||
d.SetId("") | ||
return nil | ||
} | ||
return errors.Wrap(err, | ||
fmt.Sprintf("Error reading load balancer resource from API for resource %s in zone %s", zoneID, loadBalancerID)) | ||
} | ||
|
||
d.Set("name", loadBalancer.Name) | ||
d.Set("fallback_pool_id", loadBalancer.FallbackPool) | ||
d.Set("proxied", loadBalancer.Proxied) | ||
d.Set("description", loadBalancer.Description) | ||
d.Set("ttl", loadBalancer.TTL) | ||
d.Set("steering_policy", loadBalancer.SteeringPolicy) | ||
d.Set("created_on", loadBalancer.CreatedOn.Format(time.RFC3339Nano)) | ||
d.Set("modified_on", loadBalancer.ModifiedOn.Format(time.RFC3339Nano)) | ||
|
||
if err := d.Set("default_pool_ids", loadBalancer.DefaultPools); err != nil { | ||
log.Printf("[WARN] Error setting default_pool_ids on load balancer %q: %s", d.Id(), err) | ||
} | ||
|
||
if err := d.Set("pop_pools", flattenGeoPools(loadBalancer.PopPools, "pop")); err != nil { | ||
log.Printf("[WARN] Error setting pop_pools on load balancer %q: %s", d.Id(), err) | ||
} | ||
|
||
if err := d.Set("region_pools", flattenGeoPools(loadBalancer.RegionPools, "region")); err != nil { | ||
log.Printf("[WARN] Error setting region_pools on load balancer %q: %s", d.Id(), err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func flattenGeoPools(pools map[string][]string, geoType string) *schema.Set { | ||
flattened := make([]interface{}, 0) | ||
for k, v := range pools { | ||
geoConf := map[string]interface{}{ | ||
geoType: k, | ||
"pool_ids": flattenStringList(v), | ||
} | ||
flattened = append(flattened, geoConf) | ||
} | ||
return schema.NewSet(schema.HashResource(localPoolElems[geoType]), flattened) | ||
} | ||
|
||
func resourceCloudflareSpectrumAppDelete(d *schema.ResourceData, meta interface{}) error { | ||
client := meta.(*cloudflare.API) | ||
zoneID := d.Get("zone_id").(string) | ||
loadBalancerID := d.Id() | ||
|
||
log.Printf("[INFO] Deleting Cloudflare Load Balancer: %s in zone: %s", loadBalancerID, zoneID) | ||
|
||
err := client.DeleteSpectrumApp(zoneID, loadBalancerID) | ||
if err != nil { | ||
return fmt.Errorf("error deleting Cloudflare Load Balancer: %s", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceCloudflareSpectrumAppImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { | ||
client := meta.(*cloudflare.API) | ||
|
||
// split the id so we can lookup | ||
idAttr := strings.SplitN(d.Id(), "/", 2) | ||
var zoneName string | ||
var loadBalancerID string | ||
if len(idAttr) == 2 { | ||
zoneName = idAttr[0] | ||
loadBalancerID = idAttr[1] | ||
} else { | ||
return nil, fmt.Errorf("invalid id (\"%s\") specified, should be in format \"zoneName/loadBalancerID\"", d.Id()) | ||
} | ||
zoneID, err := client.ZoneIDByName(zoneName) | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("error finding zoneName %q: %s", zoneName, err) | ||
} | ||
|
||
d.Set("zone", zoneName) | ||
d.Set("zone_id", zoneID) | ||
d.SetId(loadBalancerID) | ||
return []*schema.ResourceData{d}, nil | ||
} |