diff --git a/equinix/resource_fabric_connection.go b/equinix/resource_fabric_connection.go index 31540abcc..281703c8c 100644 --- a/equinix/resource_fabric_connection.go +++ b/equinix/resource_fabric_connection.go @@ -225,7 +225,7 @@ func accessPointSch() *schema.Resource { "type": { Type: schema.TypeString, Optional: true, - ValidateFunc: validation.StringInSlice([]string{"COLO", "VD", "VG", "SP", "IGW", "SUBNET", "CLOUD_ROUTER", "NETWORK"}, true), + ValidateFunc: validation.StringInSlice([]string{"COLO", "VD", "VG", "SP", "IGW", "SUBNET", "CLOUD_ROUTER", "NETWORK", "METAL_NETWORK"}, true), Description: "Access point type - COLO, VD, VG, SP, IGW, SUBNET, CLOUD_ROUTER, NETWORK", }, "account": { diff --git a/go.mod b/go.mod index 3c6b226f7..bc28209a3 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/antihax/optional v1.0.0 - github.com/equinix-labs/fabric-go v0.7.1 + github.com/equinix-labs/fabric-go v0.9.0 github.com/equinix/ecx-go/v2 v2.3.1 github.com/equinix/equinix-sdk-go v0.35.0 github.com/equinix/ne-go v1.16.0 diff --git a/go.sum b/go.sum index c1a9ac767..d4760e9c2 100644 --- a/go.sum +++ b/go.sum @@ -266,8 +266,8 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= -github.com/equinix-labs/fabric-go v0.7.1 h1:4yk0IKXMcc72rkRVbcYHokAEc1uUB06t6NXK+DtSsbs= -github.com/equinix-labs/fabric-go v0.7.1/go.mod h1:oqgGS3GOI8hHGPJKsAwDOEX0qRHl52sJGvwA/zMSd90= +github.com/equinix-labs/fabric-go v0.9.0 h1:BDJlVNaxpn1cSvjkTgy2zNr7srf8rF+7qOnLd2JxofU= +github.com/equinix-labs/fabric-go v0.9.0/go.mod h1:b/fvMHgVruNItuBIs7nZbcKc2ie4ZatkDHPM47DCF08= github.com/equinix/ecx-go/v2 v2.3.1 h1:gFcAIeyaEUw7S8ebqApmT7E/S7pC7Ac3wgScp89fkPU= github.com/equinix/ecx-go/v2 v2.3.1/go.mod h1:FvCdZ3jXU8Z4CPKig2DT+4J2HdwgRK17pIcznM7RXyk= github.com/equinix/equinix-sdk-go v0.35.0 h1:p/uwA8QPBAuNnKGc3mQkwjw+6++qUadLNnKFQ8jw+wg= diff --git a/internal/resources/metal/connection/datasource_schema.go b/internal/resources/metal/connection/datasource_schema.go index 1ab9d442a..9776c2bce 100644 --- a/internal/resources/metal/connection/datasource_schema.go +++ b/internal/resources/metal/connection/datasource_schema.go @@ -108,6 +108,10 @@ func dataSourceSchema(ctx context.Context) schema.Schema { ElementType: fwtypes.NewObjectTypeOf[ServiceTokenModel](ctx), Computed: true, }, + "authorization_code": schema.StringAttribute{ + Description: "Only used with Fabric Shared connection. Fabric uses this token to be able to give more detailed information about the Metal end of the network, when viewing resources from within Fabric.", + Computed: true, + }, }, } } diff --git a/internal/resources/metal/connection/datasource_test.go b/internal/resources/metal/connection/datasource_test.go index a95e9c613..d659ca1a6 100644 --- a/internal/resources/metal/connection/datasource_test.go +++ b/internal/resources/metal/connection/datasource_test.go @@ -76,6 +76,61 @@ func testDataSourceMetalConnectionConfig_withVlans(r int) string { r, r, r, r) } +func TestAccDataSourceMetalConnection_sharedPort(t *testing.T) { + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheckMetal(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: testAccMetalConnectionCheckDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceMetalConnectionConfig_SharedPort(rInt), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.equinix_metal_connection.test", "metro", "sv"), + resource.TestCheckResourceAttr( + "data.equinix_metal_connection.test", "contact_email", "tfacc@example.com"), + resource.TestCheckResourceAttrSet("data.equinix_metal_connection.test", "authorization_code"), + resource.TestCheckResourceAttrSet("data.equinix_metal_connection.test", "redundancy"), + ), + }, + }, + }) +} + +func testAccDataSourceMetalConnectionConfig_SharedPort(r int) string { + return fmt.Sprintf(` + resource "equinix_metal_project" "test" { + name = "tfacc-conn-pro-%d" + } + + resource "equinix_metal_vlan" "test1" { + description = "tfacc-conn-vlan1-%d" + metro = "sv" + project_id = equinix_metal_project.test.id + } + + resource "equinix_metal_connection" "test" { + name = "tfacc-conn-%d" + project_id = equinix_metal_project.test.id + type = "shared_port_vlan" + redundancy = "primary" + metro = "sv" + speed = "50Mbps" + contact_email = "tfacc@example.com" + vlans = [ + equinix_metal_vlan.test1.vxlan, + ] + } + + data "equinix_metal_connection" "test" { + connection_id = equinix_metal_connection.test.id + }`, + r, r, r) +} + // Test to verify that switching from SDKv2 to the Framework has not affected provider's behavior // TODO (ocobles): once migrated, this test may be removed func TestAccDataSourceMetalConnection_withVlans_upgradeFromVersion(t *testing.T) { diff --git a/internal/resources/metal/connection/models.go b/internal/resources/metal/connection/models.go index a9fe3aef4..0953dac10 100644 --- a/internal/resources/metal/connection/models.go +++ b/internal/resources/metal/connection/models.go @@ -15,50 +15,52 @@ import ( ) type ResourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Facility types.String `tfsdk:"facility"` - Metro types.String `tfsdk:"metro"` - Redundancy types.String `tfsdk:"redundancy"` - ContactEmail types.String `tfsdk:"contact_email"` - Type types.String `tfsdk:"type"` - ProjectID types.String `tfsdk:"project_id"` - Speed types.String `tfsdk:"speed"` - Description types.String `tfsdk:"description"` - Mode types.String `tfsdk:"mode"` - Tags types.List `tfsdk:"tags"` // List of strings - Vlans types.List `tfsdk:"vlans"` // List of ints - Vrfs types.List `tfsdk:"vrfs"` // List of strings - ServiceTokenType types.String `tfsdk:"service_token_type"` - OrganizationID types.String `tfsdk:"organization_id"` - Status types.String `tfsdk:"status"` - Token types.String `tfsdk:"token"` - Ports fwtypes.ListNestedObjectValueOf[PortModel] `tfsdk:"ports"` // List of Port - ServiceTokens fwtypes.ListNestedObjectValueOf[ServiceTokenModel] `tfsdk:"service_tokens"` // List of ServiceToken + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Facility types.String `tfsdk:"facility"` + Metro types.String `tfsdk:"metro"` + Redundancy types.String `tfsdk:"redundancy"` + ContactEmail types.String `tfsdk:"contact_email"` + Type types.String `tfsdk:"type"` + ProjectID types.String `tfsdk:"project_id"` + AuthorizationCode types.String `tfsdk:"authorization_code"` + Speed types.String `tfsdk:"speed"` + Description types.String `tfsdk:"description"` + Mode types.String `tfsdk:"mode"` + Tags types.List `tfsdk:"tags"` // List of strings + Vlans types.List `tfsdk:"vlans"` // List of ints + Vrfs types.List `tfsdk:"vrfs"` // List of strings + ServiceTokenType types.String `tfsdk:"service_token_type"` + OrganizationID types.String `tfsdk:"organization_id"` + Status types.String `tfsdk:"status"` + Token types.String `tfsdk:"token"` + Ports fwtypes.ListNestedObjectValueOf[PortModel] `tfsdk:"ports"` // List of Port + ServiceTokens fwtypes.ListNestedObjectValueOf[ServiceTokenModel] `tfsdk:"service_tokens"` // List of ServiceToken } type DataSourceModel struct { - ID types.String `tfsdk:"id"` - ConnectionID types.String `tfsdk:"connection_id"` - Name types.String `tfsdk:"name"` - Facility types.String `tfsdk:"facility"` - Metro types.String `tfsdk:"metro"` - Redundancy types.String `tfsdk:"redundancy"` - ContactEmail types.String `tfsdk:"contact_email"` - Type types.String `tfsdk:"type"` - ProjectID types.String `tfsdk:"project_id"` - Speed types.String `tfsdk:"speed"` - Description types.String `tfsdk:"description"` - Mode types.String `tfsdk:"mode"` - Tags types.List `tfsdk:"tags"` // List of strings - Vlans types.List `tfsdk:"vlans"` // List of ints - Vrfs types.List `tfsdk:"vrfs"` // List of strings - ServiceTokenType types.String `tfsdk:"service_token_type"` - OrganizationID types.String `tfsdk:"organization_id"` - Status types.String `tfsdk:"status"` - Token types.String `tfsdk:"token"` - Ports fwtypes.ListNestedObjectValueOf[PortModel] `tfsdk:"ports"` // List of Port - ServiceTokens fwtypes.ListNestedObjectValueOf[ServiceTokenModel] `tfsdk:"service_tokens"` // List of ServiceToken + ID types.String `tfsdk:"id"` + ConnectionID types.String `tfsdk:"connection_id"` + Name types.String `tfsdk:"name"` + Facility types.String `tfsdk:"facility"` + Metro types.String `tfsdk:"metro"` + Redundancy types.String `tfsdk:"redundancy"` + ContactEmail types.String `tfsdk:"contact_email"` + Type types.String `tfsdk:"type"` + ProjectID types.String `tfsdk:"project_id"` + AuthorizationCode types.String `tfsdk:"authorization_code"` + Speed types.String `tfsdk:"speed"` + Description types.String `tfsdk:"description"` + Mode types.String `tfsdk:"mode"` + Tags types.List `tfsdk:"tags"` // List of strings + Vlans types.List `tfsdk:"vlans"` // List of ints + Vrfs types.List `tfsdk:"vrfs"` // List of strings + ServiceTokenType types.String `tfsdk:"service_token_type"` + OrganizationID types.String `tfsdk:"organization_id"` + Status types.String `tfsdk:"status"` + Token types.String `tfsdk:"token"` + Ports fwtypes.ListNestedObjectValueOf[PortModel] `tfsdk:"ports"` // List of Port + ServiceTokens fwtypes.ListNestedObjectValueOf[ServiceTokenModel] `tfsdk:"service_tokens"` // List of ServiceToken } type PortModel struct { @@ -89,7 +91,7 @@ func (m *DataSourceModel) parse(ctx context.Context, conn *metalv1.Interconnecti &m.ID, &m.OrganizationID, &m.Name, &m.Facility, &m.Metro, &m.Description, &m.ContactEmail, &m.Status, &m.Redundancy, &m.Token, &m.Type, &m.Mode, &m.ServiceTokenType, &m.Speed, - &m.ProjectID, &m.Vlans, &m.Vrfs, &m.Ports, &m.ServiceTokens, + &m.ProjectID, &m.AuthorizationCode, &m.Vlans, &m.Vrfs, &m.Ports, &m.ServiceTokens, ) connTags, diags := types.ListValueFrom(ctx, types.StringType, conn.Tags) @@ -108,7 +110,7 @@ func (m *ResourceModel) parse(ctx context.Context, conn *metalv1.Interconnection &m.ID, &m.OrganizationID, &m.Name, &m.Facility, &m.Metro, &m.Description, &m.ContactEmail, &m.Status, &m.Redundancy, &m.Token, &m.Type, &m.Mode, &m.ServiceTokenType, &m.Speed, - &m.ProjectID, &m.Vlans, &m.Vrfs, &m.Ports, &m.ServiceTokens, + &m.ProjectID, &m.AuthorizationCode, &m.Vlans, &m.Vrfs, &m.Ports, &m.ServiceTokens, ) connTags, diags := types.ListValueFrom(ctx, types.StringType, conn.Tags) @@ -136,7 +138,7 @@ func parseConnection( ctx context.Context, conn *metalv1.Interconnection, id, orgID, name, facility, metro, description, contactEmail, status, redundancy, - token, typ, mode, serviceTokenType, speed, projectID *basetypes.StringValue, + token, typ, mode, serviceTokenType, speed, projectID, authorizationCode *basetypes.StringValue, vlans *basetypes.ListValue, vrfs *basetypes.ListValue, ports *fwtypes.ListNestedObjectValueOf[PortModel], @@ -154,6 +156,7 @@ func parseConnection( *redundancy = types.StringValue(string(conn.GetRedundancy())) *token = types.StringValue(conn.GetToken()) *typ = types.StringValue(string(conn.GetType())) + *authorizationCode = types.StringValue(conn.GetAuthorizationCode()) // TODO(ocobles) we were using "StateFunc: converters.ToLowerIf" for "metro" field in the sdkv2 // version of this resource. StateFunc doesn't exist in terraform and it requires implementation diff --git a/internal/resources/metal/connection/resource.go b/internal/resources/metal/connection/resource.go index c80ca7c7d..3588ba0ea 100644 --- a/internal/resources/metal/connection/resource.go +++ b/internal/resources/metal/connection/resource.go @@ -64,7 +64,7 @@ func (r *Resource) Create( var err error var conn *metalv1.Interconnection - if plan.Type.ValueString() == string(metalv1.INTERCONNECTIONTYPE_SHARED) { + if plan.Type.ValueString() == string(metalv1.INTERCONNECTIONTYPE_SHARED) || plan.Type.ValueString() == string(metalv1.INTERCONNECTIONTYPE_SHARED_PORT_VLAN) { request := client.InterconnectionsApi.CreateProjectInterconnection(ctx, projectID). CreateOrganizationInterconnectionRequest(createRequest) @@ -377,6 +377,36 @@ func buildVRFFabricVCCreateRequest(ctx context.Context, plan ResourceModel, req return diags } +func buildSharedPortVCVLANCreateRequest(ctx context.Context, plan ResourceModel, req *metalv1.CreateOrganizationInterconnectionRequest) diag.Diagnostics { + diags := validateSharedConnection(plan) + + project := plan.ProjectID.ValueString() + + req.SharedPortVCVlanCreateInput = &metalv1.SharedPortVCVlanCreateInput{ + Type: metalv1.SHAREDPORTVCVLANCREATEINPUTTYPE_SHARED_PORT_VLAN, + + Name: plan.Name.ValueString(), + Project: project, + Metro: plan.Metro.ValueString(), + Speed: plan.Speed.ValueStringPointer(), + } + + if email := plan.ContactEmail.ValueString(); email != "" { + req.SharedPortVCVlanCreateInput.ContactEmail = &email + } + if description := plan.Description.ValueString(); description != "" { + req.SharedPortVCVlanCreateInput.Description = &description + } + + vlansDiags := plan.Vlans.ElementsAs(ctx, &req.SharedPortVCVlanCreateInput.Vlans, true) + diags.Append(vlansDiags...) + + tagDiags := getPlanTags(ctx, plan, &req.SharedPortVCVlanCreateInput.Tags) + diags.Append(tagDiags...) + + return diags +} + func getPlanTags(ctx context.Context, plan ResourceModel, tags *[]string) diag.Diagnostics { if len(plan.Tags.Elements()) != 0 { return plan.Tags.ElementsAs(context.Background(), tags, false) @@ -432,6 +462,7 @@ func validateSharedConnection(plan ResourceModel) (diags diag.Diagnostics) { func buildCreateRequest(ctx context.Context, plan ResourceModel) (request metalv1.CreateOrganizationInterconnectionRequest, diags diag.Diagnostics) { hasVlans := len(plan.Vlans.Elements()) != 0 hasVrfs := len(plan.Vrfs.Elements()) != 0 + connType := metalv1.InterconnectionType(plan.Type.ValueString()) if hasVlans && hasVrfs { @@ -458,6 +489,14 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (request metalv "Shared connections must specify either vlans or vrfs", ) + return + } else if connType == metalv1.INTERCONNECTIONTYPE_SHARED_PORT_VLAN && !(hasVlans) { + diags.AddAttributeError( + path.Root("type"), + "Must specify vlans", + "Shared port connections must specify vlans", + ) + return } @@ -475,6 +514,9 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (request metalv var requestFunc func(context.Context, ResourceModel, *metalv1.CreateOrganizationInterconnectionRequest) diag.Diagnostics switch { + case hasVlans && connType == metalv1.INTERCONNECTIONTYPE_SHARED_PORT_VLAN: + requestFunc = buildSharedPortVCVLANCreateRequest + case hasVlans: requestFunc = buildVLANFabricVCCreateRequest diff --git a/internal/resources/metal/connection/resource_schema.go b/internal/resources/metal/connection/resource_schema.go index 9e72361cd..095549c76 100644 --- a/internal/resources/metal/connection/resource_schema.go +++ b/internal/resources/metal/connection/resource_schema.go @@ -73,7 +73,7 @@ func resourceSchema(ctx context.Context) schema.Schema { }, }, "type": schema.StringAttribute{ - Description: "Connection type - dedicated or shared", + Description: "Connection type - dedicated, shared or shared_port_vlan", Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), @@ -82,6 +82,7 @@ func resourceSchema(ctx context.Context) schema.Schema { stringvalidator.OneOf( string(metalv1.INTERCONNECTIONTYPE_DEDICATED), string(metalv1.INTERCONNECTIONTYPE_SHARED), + string(metalv1.INTERCONNECTIONTYPE_SHARED_PORT_VLAN), ), }, }, @@ -194,6 +195,10 @@ func resourceSchema(ctx context.Context) schema.Schema { listplanmodifier.UseStateForUnknown(), }, }, + "authorization_code": schema.StringAttribute{ + Description: "Only used with Fabric Shared connection. Fabric uses this token to be able to give more detailed information about the Metal end of the network, when viewing resources from within Fabric.", + Computed: true, + }, }, } } diff --git a/internal/resources/metal/connection/resource_test.go b/internal/resources/metal/connection/resource_test.go index 707c6c77c..1f7b79041 100644 --- a/internal/resources/metal/connection/resource_test.go +++ b/internal/resources/metal/connection/resource_test.go @@ -98,6 +98,31 @@ func testAccMetalConnectionConfig_SharedVlan(randstr string) string { randstr, randstr, randstr, randstr) } +func testAccMetalConnectionConfig_SharedPort(randstr string) string { + return fmt.Sprintf(` + resource "equinix_metal_project" "test" { + name = "tfacc-conn-pro-%s" + } + resource "equinix_metal_vlan" "test1" { + description = "tfacc-conn-vlan1-%s" + metro = "sv" + project_id = equinix_metal_project.test.id + } + resource "equinix_metal_connection" "test" { + name = "tfacc-conn-%s" + project_id = equinix_metal_project.test.id + type = "shared_port_vlan" + redundancy = "primary" + metro = "sv" + speed = "50Mbps" + contact_email = "tfacc@example.com" + vlans = [ + equinix_metal_vlan.test1.vxlan, + ] + }`, + randstr, randstr, randstr) +} + func testAccMetalConnectionConfig_SharedPrimaryVrf(randstr string) string { return fmt.Sprintf(` resource "equinix_metal_project" "test" { @@ -273,6 +298,36 @@ func TestAccMetalConnection_sharedVlan(t *testing.T) { }) } +func TestAccMetalConnection_sharedPort(t *testing.T) { + rs := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheckMetal(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: testAccMetalConnectionCheckDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccMetalConnectionConfig_SharedPort(rs), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "equinix_metal_connection.test", "metro", "sv"), + resource.TestCheckResourceAttr( + "equinix_metal_connection.test", "contact_email", "tfacc@example.com"), + resource.TestCheckResourceAttrSet("equinix_metal_connection.test", "authorization_code"), + resource.TestCheckResourceAttrSet("equinix_metal_connection.test", "redundancy"), + ), + }, + { + ResourceName: "equinix_metal_connection.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"project_id", "vlans"}, + }, + }, + }) +} + func TestAccMetalConnection_sharedRedundantVrf(t *testing.T) { rs := acctest.RandString(10)