terraform/builtin/providers/google/resource_compute_url_map.go

703 lines
18 KiB
Go
Raw Normal View History

package google
import (
"fmt"
"log"
"strconv"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
)
func resourceComputeUrlMap() *schema.Resource {
return &schema.Resource{
Create: resourceComputeUrlMapCreate,
Read: resourceComputeUrlMapRead,
Update: resourceComputeUrlMapUpdate,
Delete: resourceComputeUrlMapDelete,
Schema: map[string]*schema.Schema{
"default_service": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"fingerprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"host_rule": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
// TODO(evandbrown): Enable when lists support validation
//ValidateFunc: validateHostRules,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"hosts": &schema.Schema{
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"path_matcher": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
},
"id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"path_matcher": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"default_service": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"path_rule": &schema.Schema{
Type: schema.TypeList,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"paths": &schema.Schema{
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"service": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
},
},
},
},
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"self_link": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"test": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"host": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"path": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"service": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
},
},
}
}
func createHostRule(v interface{}) *compute.HostRule {
_hostRule := v.(map[string]interface{})
_hosts := _hostRule["hosts"].([]interface{})
hosts := make([]string, len(_hosts))
for i, v := range _hosts {
hosts[i] = v.(string)
}
pathMatcher := _hostRule["path_matcher"].(string)
hostRule := &compute.HostRule{
Hosts: hosts,
PathMatcher: pathMatcher,
}
if v, ok := _hostRule["description"]; ok {
hostRule.Description = v.(string)
}
return hostRule
}
func createPathMatcher(v interface{}) *compute.PathMatcher {
_pathMatcher := v.(map[string]interface{})
_pathRules := _pathMatcher["path_rule"].([]interface{})
pathRules := make([]*compute.PathRule, len(_pathRules))
for ip, vp := range _pathRules {
_pathRule := vp.(map[string]interface{})
_paths := _pathRule["paths"].([]interface{})
paths := make([]string, len(_paths))
for ipp, vpp := range _paths {
paths[ipp] = vpp.(string)
}
service := _pathRule["service"].(string)
pathRule := &compute.PathRule{
Paths: paths,
Service: service,
}
pathRules[ip] = pathRule
}
name := _pathMatcher["name"].(string)
defaultService := _pathMatcher["default_service"].(string)
pathMatcher := &compute.PathMatcher{
PathRules: pathRules,
Name: name,
DefaultService: defaultService,
}
if vp, okp := _pathMatcher["description"]; okp {
pathMatcher.Description = vp.(string)
}
return pathMatcher
}
func createUrlMapTest(v interface{}) *compute.UrlMapTest {
_test := v.(map[string]interface{})
host := _test["host"].(string)
path := _test["path"].(string)
service := _test["service"].(string)
test := &compute.UrlMapTest{
Host: host,
Path: path,
Service: service,
}
if vp, okp := _test["description"]; okp {
test.Description = vp.(string)
}
return test
}
func resourceComputeUrlMapCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
name := d.Get("name").(string)
defaultService := d.Get("default_service").(string)
urlMap := &compute.UrlMap{
Name: name,
DefaultService: defaultService,
}
if v, ok := d.GetOk("description"); ok {
urlMap.Description = v.(string)
}
_hostRules := d.Get("host_rule").(*schema.Set)
urlMap.HostRules = make([]*compute.HostRule, _hostRules.Len())
for i, v := range _hostRules.List() {
urlMap.HostRules[i] = createHostRule(v)
}
_pathMatchers := d.Get("path_matcher").([]interface{})
urlMap.PathMatchers = make([]*compute.PathMatcher, len(_pathMatchers))
for i, v := range _pathMatchers {
urlMap.PathMatchers[i] = createPathMatcher(v)
}
_tests := make([]interface{}, 0)
if v, ok := d.GetOk("test"); ok {
_tests = v.([]interface{})
}
urlMap.Tests = make([]*compute.UrlMapTest, len(_tests))
for i, v := range _tests {
urlMap.Tests[i] = createUrlMapTest(v)
}
op, err := config.clientCompute.UrlMaps.Insert(project, urlMap).Do()
if err != nil {
return fmt.Errorf("Error, failed to insert Url Map %s: %s", name, err)
}
err = computeOperationWaitGlobal(config, op, project, "Insert Url Map")
if err != nil {
return fmt.Errorf("Error, failed waitng to insert Url Map %s: %s", name, err)
}
return resourceComputeUrlMapRead(d, meta)
}
func resourceComputeUrlMapRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
name := d.Get("name").(string)
urlMap, err := config.clientCompute.UrlMaps.Get(project, name).Do()
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
log.Printf("[WARN] Removing URL Map %q because it's gone", d.Get("name").(string))
// The resource doesn't exist anymore
d.SetId("")
return nil
}
return fmt.Errorf("Error, failed to get Url Map %s: %s", name, err)
}
d.SetId(name)
d.Set("self_link", urlMap.SelfLink)
d.Set("id", strconv.FormatUint(urlMap.Id, 10))
d.Set("fingerprint", urlMap.Fingerprint)
hostRuleMap := make(map[string]*compute.HostRule)
for _, v := range urlMap.HostRules {
hostRuleMap[v.PathMatcher] = v
}
/* Only read host rules into our TF state that we have defined */
_hostRules := d.Get("host_rule").(*schema.Set).List()
_newHostRules := make([]interface{}, 0)
for _, v := range _hostRules {
_hostRule := v.(map[string]interface{})
_pathMatcher := _hostRule["path_matcher"].(string)
/* Delete local entries that are no longer found on the GCE server */
if hostRule, ok := hostRuleMap[_pathMatcher]; ok {
_newHostRule := make(map[string]interface{})
_newHostRule["path_matcher"] = _pathMatcher
hostsSet := make(map[string]bool)
for _, host := range hostRule.Hosts {
hostsSet[host] = true
}
/* Only store hosts we are keeping track of */
_newHosts := make([]interface{}, 0)
for _, vp := range _hostRule["hosts"].([]interface{}) {
if _, okp := hostsSet[vp.(string)]; okp {
_newHosts = append(_newHosts, vp)
}
}
_newHostRule["hosts"] = _newHosts
_newHostRule["description"] = hostRule.Description
_newHostRules = append(_newHostRules, _newHostRule)
}
}
d.Set("host_rule", _newHostRules)
pathMatcherMap := make(map[string]*compute.PathMatcher)
for _, v := range urlMap.PathMatchers {
pathMatcherMap[v.Name] = v
}
/* Only read path matchers into our TF state that we have defined */
_pathMatchers := d.Get("path_matcher").([]interface{})
_newPathMatchers := make([]interface{}, 0)
for _, v := range _pathMatchers {
_pathMatcher := v.(map[string]interface{})
_name := _pathMatcher["name"].(string)
if pathMatcher, ok := pathMatcherMap[_name]; ok {
_newPathMatcher := make(map[string]interface{})
_newPathMatcher["name"] = _name
_newPathMatcher["default_service"] = pathMatcher.DefaultService
_newPathMatcher["description"] = pathMatcher.Description
_newPathRules := make([]interface{}, len(pathMatcher.PathRules))
for ip, pathRule := range pathMatcher.PathRules {
_newPathRule := make(map[string]interface{})
_newPathRule["service"] = pathRule.Service
_paths := make([]interface{}, len(pathRule.Paths))
for ipp, vpp := range pathRule.Paths {
_paths[ipp] = vpp
}
_newPathRule["paths"] = _paths
_newPathRules[ip] = _newPathRule
}
_newPathMatcher["path_rule"] = _newPathRules
_newPathMatchers = append(_newPathMatchers, _newPathMatcher)
}
}
d.Set("path_matcher", _newPathMatchers)
testMap := make(map[string]*compute.UrlMapTest)
for _, v := range urlMap.Tests {
testMap[fmt.Sprintf("%s/%s", v.Host, v.Path)] = v
}
_tests := make([]interface{}, 0)
/* Only read tests into our TF state that we have defined */
if v, ok := d.GetOk("test"); ok {
_tests = v.([]interface{})
}
_newTests := make([]interface{}, 0)
for _, v := range _tests {
_test := v.(map[string]interface{})
_host := _test["host"].(string)
_path := _test["path"].(string)
/* Delete local entries that are no longer found on the GCE server */
if test, ok := testMap[fmt.Sprintf("%s/%s", _host, _path)]; ok {
_newTest := make(map[string]interface{})
_newTest["host"] = _host
_newTest["path"] = _path
_newTest["description"] = test.Description
_newTest["service"] = test.Service
_newTests = append(_newTests, _newTest)
}
}
d.Set("test", _newTests)
return nil
}
func resourceComputeUrlMapUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
name := d.Get("name").(string)
urlMap, err := config.clientCompute.UrlMaps.Get(project, name).Do()
if err != nil {
return fmt.Errorf("Error, failed to get Url Map %s: %s", name, err)
}
urlMap.DefaultService = d.Get("default_service").(string)
if v, ok := d.GetOk("description"); ok {
urlMap.Description = v.(string)
}
if d.HasChange("host_rule") {
_oldHostRules, _newHostRules := d.GetChange("host_rule")
_oldHostRulesMap := make(map[string]interface{})
_newHostRulesMap := make(map[string]interface{})
for _, v := range _oldHostRules.(*schema.Set).List() {
_hostRule := v.(map[string]interface{})
_oldHostRulesMap[_hostRule["path_matcher"].(string)] = v
}
for _, v := range _newHostRules.(*schema.Set).List() {
_hostRule := v.(map[string]interface{})
_newHostRulesMap[_hostRule["path_matcher"].(string)] = v
}
newHostRules := make([]*compute.HostRule, 0)
/* Decide which host rules to keep */
for _, v := range urlMap.HostRules {
/* If it's in the old state, we have ownership over the host rule */
if vOld, ok := _oldHostRulesMap[v.PathMatcher]; ok {
if vNew, ok := _newHostRulesMap[v.PathMatcher]; ok {
/* Adjust for any changes made to this rule */
_newHostRule := vNew.(map[string]interface{})
_oldHostRule := vOld.(map[string]interface{})
_newHostsSet := make(map[string]bool)
_oldHostsSet := make(map[string]bool)
hostRule := &compute.HostRule{
PathMatcher: v.PathMatcher,
}
for _, v := range _newHostRule["hosts"].([]interface{}) {
_newHostsSet[v.(string)] = true
}
for _, v := range _oldHostRule["hosts"].([]interface{}) {
_oldHostsSet[v.(string)] = true
}
/* Only add hosts that have been added locally or are new,
* not touching those from the GCE server state */
for _, host := range v.Hosts {
_, okNew := _newHostsSet[host]
_, okOld := _oldHostsSet[host]
/* Drop deleted hosts */
if okOld && !okNew {
continue
}
hostRule.Hosts = append(hostRule.Hosts, host)
/* Kep track of the fact that this host was added */
delete(_newHostsSet, host)
}
/* Now add in the brand new entries */
for host, _ := range _newHostsSet {
hostRule.Hosts = append(hostRule.Hosts, host)
}
if v, ok := _newHostRule["description"]; ok {
hostRule.Description = v.(string)
}
newHostRules = append(newHostRules, hostRule)
/* Record that we've include this host rule */
delete(_newHostRulesMap, v.PathMatcher)
} else {
/* It's been deleted */
continue
}
} else {
if vNew, ok := _newHostRulesMap[v.PathMatcher]; ok {
newHostRules = append(newHostRules, createHostRule(vNew))
/* Record that we've include this host rule */
delete(_newHostRulesMap, v.PathMatcher)
} else {
/* It wasn't created or modified locally */
newHostRules = append(newHostRules, v)
}
}
}
/* Record brand new host rules (ones not deleted above) */
for _, v := range _newHostRulesMap {
newHostRules = append(newHostRules, createHostRule(v))
}
urlMap.HostRules = newHostRules
}
if d.HasChange("path_matcher") {
_oldPathMatchers, _newPathMatchers := d.GetChange("path_matcher")
_oldPathMatchersMap := make(map[string]interface{})
_newPathMatchersMap := make(map[string]interface{})
for _, v := range _oldPathMatchers.([]interface{}) {
_pathMatcher := v.(map[string]interface{})
_oldPathMatchersMap[_pathMatcher["name"].(string)] = v
}
for _, v := range _newPathMatchers.([]interface{}) {
_pathMatcher := v.(map[string]interface{})
_newPathMatchersMap[_pathMatcher["name"].(string)] = v
}
newPathMatchers := make([]*compute.PathMatcher, 0)
/* Decide which path matchers to keep */
for _, v := range urlMap.PathMatchers {
/* If it's in the old state, we have ownership over the host rule */
_, okOld := _oldPathMatchersMap[v.Name]
vNew, okNew := _newPathMatchersMap[v.Name]
/* Drop deleted entries */
if okOld && !okNew {
continue
}
/* Don't change entries that don't belong to us */
if !okNew {
newPathMatchers = append(newPathMatchers, v)
} else {
newPathMatchers = append(newPathMatchers, createPathMatcher(vNew))
delete(_newPathMatchersMap, v.Name)
}
}
/* Record brand new host rules */
for _, v := range _newPathMatchersMap {
newPathMatchers = append(newPathMatchers, createPathMatcher(v))
}
urlMap.PathMatchers = newPathMatchers
}
if d.HasChange("tests") {
_oldTests, _newTests := d.GetChange("path_matcher")
_oldTestsMap := make(map[string]interface{})
_newTestsMap := make(map[string]interface{})
for _, v := range _oldTests.([]interface{}) {
_test := v.(map[string]interface{})
ident := fmt.Sprintf("%s/%s", _test["host"].(string), _test["path"].(string))
_oldTestsMap[ident] = v
}
for _, v := range _newTests.([]interface{}) {
_test := v.(map[string]interface{})
ident := fmt.Sprintf("%s/%s", _test["host"].(string), _test["path"].(string))
_newTestsMap[ident] = v
}
newTests := make([]*compute.UrlMapTest, 0)
/* Decide which path matchers to keep */
for _, v := range urlMap.Tests {
ident := fmt.Sprintf("%s/%s", v.Host, v.Path)
/* If it's in the old state, we have ownership over the host rule */
_, okOld := _oldTestsMap[ident]
vNew, okNew := _newTestsMap[ident]
/* Drop deleted entries */
if okOld && !okNew {
continue
}
/* Don't change entries that don't belong to us */
if !okNew {
newTests = append(newTests, v)
} else {
newTests = append(newTests, createUrlMapTest(vNew))
delete(_newTestsMap, ident)
}
}
/* Record brand new host rules */
for _, v := range _newTestsMap {
newTests = append(newTests, createUrlMapTest(v))
}
urlMap.Tests = newTests
}
op, err := config.clientCompute.UrlMaps.Update(project, urlMap.Name, urlMap).Do()
if err != nil {
return fmt.Errorf("Error, failed to update Url Map %s: %s", name, err)
}
err = computeOperationWaitGlobal(config, op, project, "Update Url Map")
if err != nil {
return fmt.Errorf("Error, failed waitng to update Url Map %s: %s", name, err)
}
return resourceComputeUrlMapRead(d, meta)
}
func resourceComputeUrlMapDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
name := d.Get("name").(string)
op, err := config.clientCompute.UrlMaps.Delete(project, name).Do()
if err != nil {
return fmt.Errorf("Error, failed to delete Url Map %s: %s", name, err)
}
err = computeOperationWaitGlobal(config, op, project, "Delete Url Map")
if err != nil {
return fmt.Errorf("Error, failed waitng to delete Url Map %s: %s", name, err)
}
return nil
}
func validateHostRules(v interface{}, k string) (ws []string, es []error) {
pathMatchers := make(map[string]bool)
hostRules := v.([]interface{})
for _, hri := range hostRules {
hr := hri.(map[string]interface{})
pm := hr["path_matcher"].(string)
if pathMatchers[pm] {
es = append(es, fmt.Errorf("Multiple host_rule entries with the same path_matcher are not allowed. Please collapse all hosts with the same path_matcher into one host_rule"))
return
}
pathMatchers[pm] = true
}
return
}