diff --git a/examples/megaport_aws_vxc_basic_update/main.tf b/examples/megaport_aws_vxc_basic_update/main.tf index 356771e..15c51e1 100644 --- a/examples/megaport_aws_vxc_basic_update/main.tf +++ b/examples/megaport_aws_vxc_basic_update/main.tf @@ -30,9 +30,13 @@ resource "megaport_aws_vxc" "foo" { } b_end { - product_uid = data.megaport_partner_port.aws.id - aws_account_id = "{{ .aws_account_id }}" - customer_asn = {{ .customer_asn }} - type = "private" + product_uid = data.megaport_partner_port.aws.id + aws_account_id = "{{ .aws_account_id }}" + aws_connection_name = "{{ .uid }}" + aws_ip_address = "{{ .aws_ip_address }}" + bgp_auth_key = "{{ .uid }}" + customer_asn = {{ .customer_asn }} + customer_ip_address = "{{ .customer_ip_address }}" + type = "private" } } diff --git a/megaport/api/vxc.go b/megaport/api/vxc.go index b51be0d..094d885 100644 --- a/megaport/api/vxc.go +++ b/megaport/api/vxc.go @@ -132,11 +132,13 @@ func (v *PrivateVxcCreateInput) toPayload() ([]byte, error) { } type vxcUpdatePayload struct { - AEndVlan *uint64 `json:"aEndVlan,omitempty"` - BEndVlan *uint64 `json:"bEndVlan,omitempty"` - CostCentre *string `json:"costCentre,omitempty"` - Name *string `json:"name,omitempty"` - RateLimit *uint64 `json:"rateLimit,omitempty"` + AEndVlan *uint64 `json:"aEndVlan,omitempty"` + BEndVlan *uint64 `json:"bEndVlan,omitempty"` + CostCentre *string `json:"costCentre,omitempty"` + Name *string `json:"name,omitempty"` + BEndConfig interface{} `json:"bEndConfig,omitempty"` + RateLimit *uint64 `json:"rateLimit,omitempty"` + // SecondaryName *string `json:"secondaryName,omitempty"` } type PrivateVxcUpdateInput struct { @@ -219,15 +221,16 @@ func (v *PartnerConfigAWS) toPayload() interface{} { } type vxcCreatePayloadPartnerConfigAWS struct { - // AmazonAsn *uint64 `json:",omitempty"` - AmazonIpAddress *string `json:"amazonIpAddress,omitempty"` - Asn *uint64 `json:"asn,omitempty"` - AuthKey *string `json:"authKey,omitempty"` - ConnectType *string `json:"connectType,omitempty"` + // AmazonAsn *uint64 `json:",omitempty"` + AmazonIpAddress *string `json:"amazonIpAddress,omitempty"` + Asn *uint64 `json:"asn,omitempty"` + AuthKey *string `json:"authKey,omitempty"` + ConnectType *string `json:"connectType,omitempty"` + // Complete *bool `json:"complete,omitempty"` CustomerIpAddress *string `json:"customerIpAddress,omitempty"` Name *string `json:"name,omitempty"` OwnerAccount *string `json:"ownerAccount,omitempty"` - // Prefixes *string `json:",omitempty"` + // Prefixes *string `json:",omitempty"` Type *string `json:"type,omitempty"` } @@ -270,6 +273,7 @@ type CloudVxcUpdateInput struct { InvoiceReference *string Name *string ProductUid *string + PartnerConfig PartnerConfig RateLimit *uint64 VlanA *uint64 } @@ -283,6 +287,7 @@ func (v *CloudVxcUpdateInput) toPayload() ([]byte, error) { AEndVlan: v.VlanA, CostCentre: v.InvoiceReference, Name: v.Name, + BEndConfig: v.PartnerConfig.toPayload(), RateLimit: v.RateLimit, } return json.Marshal(payload) diff --git a/megaport/common.go b/megaport/common.go index 74ecaf7..9e56736 100644 --- a/megaport/common.go +++ b/megaport/common.go @@ -76,3 +76,11 @@ func isResourceDeleted(provisioningStatus string) bool { return false } } + +func compareNillableStrings(a *string, b string) bool { + return a == nil || *a == b +} + +func compareNillableUints(a *uint64, b uint64) bool { + return a == nil || *a == b +} diff --git a/megaport/resource_megaport_aws_vxc.go b/megaport/resource_megaport_aws_vxc.go index d74e702..3e8de0d 100644 --- a/megaport/resource_megaport_aws_vxc.go +++ b/megaport/resource_megaport_aws_vxc.go @@ -113,6 +113,27 @@ func flattenVxcEndAws(configProductUid string, v api.ProductAssociatedVxcEnd, r }} } +func expandVxcEndAws(e map[string]interface{}) *api.PartnerConfigAWS { + pc := &api.PartnerConfigAWS{ + AWSAccountID: api.String(e["aws_account_id"]), + CustomerASN: api.Uint64FromInt(e["customer_asn"]), + Type: api.String(e["type"]), + } + if v := e["aws_connection_name"]; v != "" { + pc.AWSConnectionName = api.String(v) + } + if v := e["aws_ip_address"]; v != "" { + pc.AmazonIPAddress = api.String(v) + } + if v := e["bgp_auth_key"]; v != "" { + pc.BGPAuthKey = api.String(v) + } + if v := e["customer_ip_address"]; v != "" { + pc.CustomerIPAddress = api.String(v) + } + return pc +} + func resourceMegaportAwsVxcRead(d *schema.ResourceData, m interface{}) error { cfg := m.(*Config) p, err := cfg.Client.GetCloudVxc(d.Id()) @@ -149,10 +170,11 @@ func resourceMegaportAwsVxcCreate(d *schema.ResourceData, m interface{}) error { a := d.Get("a_end").([]interface{})[0].(map[string]interface{}) b := d.Get("b_end").([]interface{})[0].(map[string]interface{}) input := &api.CloudVxcCreateInput{ - ProductUidA: api.String(a["product_uid"]), - ProductUidB: api.String(b["product_uid"]), - Name: api.String(d.Get("name")), - RateLimit: api.Uint64FromInt(d.Get("rate_limit")), + ProductUidA: api.String(a["product_uid"]), + ProductUidB: api.String(b["product_uid"]), + Name: api.String(d.Get("name")), + PartnerConfig: expandVxcEndAws(b), + RateLimit: api.Uint64FromInt(d.Get("rate_limit")), } if v, ok := d.GetOk("invoice_reference"); ok { input.InvoiceReference = api.String(v) @@ -160,30 +182,12 @@ func resourceMegaportAwsVxcCreate(d *schema.ResourceData, m interface{}) error { if v := a["vlan"]; v != 0 { input.VlanA = api.Uint64FromInt(a["vlan"]) } - inputPartnerConfig := &api.PartnerConfigAWS{ - AWSAccountID: api.String(b["aws_account_id"]), - CustomerASN: api.Uint64FromInt(b["customer_asn"]), - Type: api.String(b["type"]), - } - if v := b["aws_connection_name"]; v != "" { - inputPartnerConfig.AWSConnectionName = api.String(v) - } - if v := b["amazon_ip_address"]; v != "" { - inputPartnerConfig.AmazonIPAddress = api.String(v) - } - if v := b["bgp_auth_key"]; v != "" { - inputPartnerConfig.BGPAuthKey = api.String(v) - } - if v := b["customer_ip_address"]; v != "" { - inputPartnerConfig.CustomerIPAddress = api.String(v) - } - input.PartnerConfig = inputPartnerConfig uid, err := cfg.Client.CreateCloudVxc(input) if err != nil { return err } d.SetId(*uid) - if err := waitUntilVxcIsConfigured(cfg.Client, *uid, 5*time.Minute); err != nil { + if err := waitUntilAwsVxcIsConfigured(cfg.Client, *uid, 5*time.Minute); err != nil { return err } return resourceMegaportAwsVxcRead(d, m) @@ -192,10 +196,11 @@ func resourceMegaportAwsVxcCreate(d *schema.ResourceData, m interface{}) error { func resourceMegaportAwsVxcUpdate(d *schema.ResourceData, m interface{}) error { cfg := m.(*Config) a := d.Get("a_end").([]interface{})[0].(map[string]interface{}) - //b := d.Get("b_end").([]interface{})[0].(map[string]interface{}) // TODO + b := d.Get("b_end").([]interface{})[0].(map[string]interface{}) input := &api.CloudVxcUpdateInput{ InvoiceReference: api.String(d.Get("invoice_reference")), Name: api.String(d.Get("name")), + PartnerConfig: expandVxcEndAws(b), ProductUid: api.String(d.Id()), RateLimit: api.Uint64FromInt(d.Get("rate_limit")), VlanA: api.Uint64FromInt(a["vlan"]), @@ -203,10 +208,10 @@ func resourceMegaportAwsVxcUpdate(d *schema.ResourceData, m interface{}) error { if err := cfg.Client.UpdateCloudVxc(input); err != nil { return err } - if err := waitUntilVxcIsConfigured(cfg.Client, d.Id(), 5*time.Minute); err != nil { + if err := waitUntilAwsVxcIsConfigured(cfg.Client, d.Id(), 5*time.Minute); err != nil { return err } - if err := waitUntilVxcIsUpdated(cfg.Client, input, 5*time.Minute); err != nil { + if err := waitUntilAwsVxcIsUpdated(cfg.Client, input, 5*time.Minute); err != nil { return err } return resourceMegaportAwsVxcRead(d, m) @@ -224,11 +229,21 @@ func resourceMegaportAwsVxcDelete(d *schema.ResourceData, m interface{}) error { return nil } -func waitUntilVxcIsConfigured(client *api.Client, productUid string, timeout time.Duration) error { +func waitUntilAwsVxcIsConfigured(client *api.Client, productUid string, timeout time.Duration) error { scc := &resource.StateChangeConf{ - Pending: []string{api.ProductStatusDeployable}, - Target: []string{api.ProductStatusConfigured, api.ProductStatusLive}, - Refresh: resourceMegaportAwsVxcStateRefreshFunc(client, productUid), + Pending: []string{api.ProductStatusDeployable}, + Target: []string{api.ProductStatusConfigured, api.ProductStatusLive}, + Refresh: func() (interface{}, string, error) { + v, err := client.GetCloudVxc(productUid) // TODO we can probably use this for any kind of VXC + if err != nil { + log.Printf("Error retrieving VXC while waiting for setup to finish: %v", err) + return nil, "", err + } + if v == nil { + return nil, "", nil + } + return v, v.ProvisioningStatus, nil + }, Timeout: timeout, MinTimeout: 10 * time.Second, Delay: 5 * time.Second, @@ -238,21 +253,7 @@ func waitUntilVxcIsConfigured(client *api.Client, productUid string, timeout tim return err } -func resourceMegaportAwsVxcStateRefreshFunc(client *api.Client, uid string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - v, err := client.GetCloudVxc(uid) - if err != nil { - log.Printf("Error retrieving VXC while waiting for setup to finish: %v", err) - return nil, "", err - } - if v == nil { - return nil, "", nil - } - return v, v.ProvisioningStatus, nil - } -} - -func waitUntilVxcIsUpdated(client *api.Client, input *api.CloudVxcUpdateInput, timeout time.Duration) error { +func waitUntilAwsVxcIsUpdated(client *api.Client, input *api.CloudVxcUpdateInput, timeout time.Duration) error { scc := &resource.StateChangeConf{ Pending: []string{"PENDING"}, Target: []string{"UPDATED"}, @@ -265,16 +266,38 @@ func waitUntilVxcIsUpdated(client *api.Client, input *api.CloudVxcUpdateInput, t if v == nil { return nil, "", nil } - if v.CostCentre != *input.InvoiceReference { + if !compareNillableStrings(input.InvoiceReference, v.CostCentre) { + return nil, "PENDING", nil + } + if !compareNillableStrings(input.Name, v.ProductName) { + return nil, "PENDING", nil + } + if !compareNillableUints(input.RateLimit, v.RateLimit) { + return nil, "PENDING", nil + } + if !compareNillableUints(input.VlanA, v.AEnd.Vlan) { + return nil, "PENDING", nil + } + pc := input.PartnerConfig.(*api.PartnerConfigAWS) + if !compareNillableStrings(pc.AmazonIPAddress, v.Resources.AwsVirtualInterface.AmazonIpAddress) { + return nil, "PENDING", nil + } + if !compareNillableStrings(pc.AWSAccountID, v.Resources.AwsVirtualInterface.OwnerAccount) { + return nil, "PENDING", nil + } + if !compareNillableStrings(pc.AWSConnectionName, v.Resources.AwsVirtualInterface.Name) { + return nil, "PENDING", nil + } + if !compareNillableStrings(pc.BGPAuthKey, v.Resources.AwsVirtualInterface.AuthKey) { return nil, "PENDING", nil } - if v.ProductName != *input.Name { + if !compareNillableUints(pc.CustomerASN, v.Resources.AwsVirtualInterface.Asn) { return nil, "PENDING", nil } - if v.RateLimit != *input.RateLimit { + if !compareNillableStrings(pc.CustomerIPAddress, v.Resources.AwsVirtualInterface.CustomerIpAddress) { return nil, "PENDING", nil } - if v.AEnd.Vlan != *input.VlanA { + if !compareNillableStrings(pc.Type, strings.ToLower(v.Resources.AwsVirtualInterface.Type)) { return nil, "PENDING", nil } return v, "UPDATED", nil diff --git a/megaport/resource_megaport_aws_vxc_test.go b/megaport/resource_megaport_aws_vxc_test.go index 075cdfb..994ab97 100644 --- a/megaport/resource_megaport_aws_vxc_test.go +++ b/megaport/resource_megaport_aws_vxc_test.go @@ -1,8 +1,11 @@ package megaport import ( + "math/rand" + "net" "strconv" "testing" + "time" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" @@ -10,16 +13,28 @@ import ( ) func TestAccMegaportAwsVxc_basic(t *testing.T) { - var vxcBefore api.ProductAssociatedVxc + var ( + vxc, vxcUpdated api.ProductAssociatedVxc + port api.Product + ) rName := "t" + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) rId := acctest.RandStringFromCharSet(12, "012346789") rAsn := uint64(acctest.RandIntRange(1, 65535)) - + rand.Seed(time.Now().UnixNano()) + n := &net.IPNet{ + IP: net.IPv4(169, 254, byte(rand.Intn(255)), byte(rand.Intn(63)*4+1)), + Mask: net.CIDRMask(30, 32), + } + ipA := n.String() + n.IP[len(n.IP)-1]++ + ipB := n.String() configValues := map[string]interface{}{ - "uid": rName, - "location": "Equinix LD5", - "aws_account_id": rId, - "customer_asn": rAsn, + "uid": rName, + "location": "Equinix LD5", + "aws_account_id": rId, + "customer_asn": rAsn, + "aws_ip_address": ipA, + "customer_ip_address": ipB, } cfg, err := testAccGetConfig("megaport_aws_vxc_basic", configValues, 0) if err != nil { @@ -38,35 +53,48 @@ func TestAccMegaportAwsVxc_basic(t *testing.T) { { Config: cfg, Check: resource.ComposeTestCheckFunc( - testAccCheckResourceExists("megaport_port.foo", &vxcBefore), - testAccCheckResourceExists("megaport_aws_vxc.foo", &vxcBefore), + testAccCheckResourceExists("megaport_port.foo", &port), + testAccCheckResourceExists("megaport_aws_vxc.foo", &vxc), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "name", "terraform_acctest_"+rName), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "rate_limit", "100"), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "invoice_reference", ""), resource.TestCheckResourceAttrPair("megaport_aws_vxc.foo", "a_end.0.product_uid", "megaport_port.foo", "id"), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "a_end.0.vlan", "567"), resource.TestCheckResourceAttrPair("megaport_aws_vxc.foo", "b_end.0.product_uid", "data.megaport_partner_port.aws", "id"), + resource.TestCheckResourceAttrSet("megaport_aws_vxc.foo", "b_end.0.connected_product_uid"), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.aws_account_id", rId), + resource.TestCheckResourceAttrSet("megaport_aws_vxc.foo", "b_end.0.aws_connection_name"), + resource.TestCheckResourceAttrSet("megaport_aws_vxc.foo", "b_end.0.aws_ip_address"), + resource.TestCheckResourceAttrSet("megaport_aws_vxc.foo", "b_end.0.bgp_auth_key"), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.customer_asn", strconv.Itoa(int(rAsn))), + resource.TestCheckResourceAttrSet("megaport_aws_vxc.foo", "b_end.0.customer_ip_address"), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.type", "private"), ), }, { Config: cfgUpdate, Check: resource.ComposeTestCheckFunc( - testAccCheckResourceExists("megaport_port.foo", &vxcBefore), - testAccCheckResourceExists("megaport_aws_vxc.foo", &vxcBefore), + testAccCheckResourceExists("megaport_port.foo", &port), + testAccCheckResourceExists("megaport_aws_vxc.foo", &vxcUpdated), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "name", "terraform_acctest_"+rName), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "rate_limit", "1000"), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "invoice_reference", rName), resource.TestCheckResourceAttrPair("megaport_aws_vxc.foo", "a_end.0.product_uid", "megaport_port.foo", "id"), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "a_end.0.vlan", "568"), resource.TestCheckResourceAttrPair("megaport_aws_vxc.foo", "b_end.0.product_uid", "data.megaport_partner_port.aws", "id"), + resource.TestCheckResourceAttrSet("megaport_aws_vxc.foo", "b_end.0.connected_product_uid"), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.aws_account_id", rId), + resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.aws_connection_name", rName), + resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.aws_ip_address", ipA), + resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.bgp_auth_key", rName), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.customer_asn", strconv.Itoa(int(rAsn))), + resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.customer_ip_address", ipB), resource.TestCheckResourceAttr("megaport_aws_vxc.foo", "b_end.0.type", "private"), ), }, }, }) + if vxc.ProductUid != vxcUpdated.ProductUid { + t.Errorf("TestAccMegaportAwsVxc_basic: expected the VXC to be updated but the resource ids differ") + } }