diff --git a/internal/resources/metal/vlans/models.go b/internal/resources/metal/vlans/models.go index 5c8077e82..a60b3da14 100644 --- a/internal/resources/metal/vlans/models.go +++ b/internal/resources/metal/vlans/models.go @@ -62,24 +62,20 @@ type ResourceModel struct { func (m *ResourceModel) parse(vlan *packngo.VirtualNetwork) diag.Diagnostics { m.ID = types.StringValue(vlan.ID) + m.Description = types.StringValue(vlan.Description) + m.Vxlan = types.Int64Value(int64(vlan.VXLAN)) if vlan.Project.ID != "" { m.ProjectID = types.StringValue(vlan.Project.ID) } - m.Facility = types.StringNull() - if vlan.FacilityCode != "" { - m.Facility = types.StringValue(vlan.FacilityCode) + if vlan.Facility != nil { + m.Facility = types.StringValue(vlan.Facility.Code) + m.Metro = types.StringValue(strings.ToLower(vlan.Facility.Metro.Code)) } - m.Description = types.StringValue(vlan.Description) - m.Vxlan = types.Int64Value(int64(vlan.VXLAN)) - - // version of this resource. StateFunc doesn't exist in terraform and it requires implementation - // of bespoke logic before storing state. To ensure backward compatibility we ignore lower/upper - // case diff for now, but we may want to require input upper case - if !strings.EqualFold(m.Metro.ValueString(), vlan.MetroCode) { - m.Metro = types.StringValue(vlan.MetroCode) + if vlan.Metro != nil { + m.Metro = types.StringValue(strings.ToLower(vlan.Metro.Code)) } return nil } diff --git a/internal/resources/metal/vlans/resource.go b/internal/resources/metal/vlans/resource.go index c875d6453..143e23e04 100644 --- a/internal/resources/metal/vlans/resource.go +++ b/internal/resources/metal/vlans/resource.go @@ -11,6 +11,10 @@ import ( "github.com/packethost/packngo" ) +var ( + vlanDefaultIncludes = []string{"assigned_to", "facility", "metro"} +) + type Resource struct { framework.BaseResource framework.WithTimeouts @@ -79,6 +83,13 @@ func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, r return } + // get the current state of newly created vlan with default include fields + vlan, _, err = client.ProjectVirtualNetworks.Get(vlan.ID, &packngo.GetOptions{Includes: vlanDefaultIncludes}) + if err != nil { + response.Diagnostics.AddError("Error reading Vlan after create", equinix_errors.FriendlyError(err).Error()) + return + } + // Parse API response into the Terraform state response.Diagnostics.Append(data.parse(vlan)...) if response.Diagnostics.HasError() { @@ -102,7 +113,7 @@ func (r *Resource) Read(ctx context.Context, request resource.ReadRequest, respo vlan, _, err := client.ProjectVirtualNetworks.Get( data.ID.ValueString(), - &packngo.GetOptions{Includes: []string{"assigned_to"}}, + &packngo.GetOptions{Includes: vlanDefaultIncludes}, ) if err != nil { if equinix_errors.IsNotFound(err) { diff --git a/internal/resources/metal/vlans/resource_schema.go b/internal/resources/metal/vlans/resource_schema.go index 62edd3217..b1fcee622 100644 --- a/internal/resources/metal/vlans/resource_schema.go +++ b/internal/resources/metal/vlans/resource_schema.go @@ -40,50 +40,26 @@ func resourceSchema(ctx context.Context) schema.Schema { Description: "Facility where to create the VLAN", DeprecationMessage: "Use metro instead of facility. For more information, read the migration guide: https://registry.terraform.io/providers/equinix/equinix/latest/docs/guides/migration_guide_facilities_to_metros_devices", Optional: true, + Computed: true, Validators: []validator.String{ - stringvalidator.ConflictsWith(path.Expressions{ - path.MatchRoot("metro"), - }...), + stringvalidator.ConflictsWith(path.MatchRoot("metro")), }, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), stringplanmodifier.UseStateForUnknown(), }, - // TODO: aayushrangwala to check if this is needed with the framework changes - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // suppress diff when unsetting facility - //if len(old) > 0 && new == "" { - // return true - //} - //return old == new - //}, }, "metro": schema.StringAttribute{ Description: "Metro in which to create the VLAN", Optional: true, + Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, Validators: []validator.String{ - stringvalidator.ConflictsWith(path.Expressions{ - path.MatchRoot("facility"), - }...), + stringvalidator.ConflictsWith(path.MatchRoot("facility")), + stringvalidator.AtLeastOneOf(path.MatchRoot("facility"), path.MatchRoot("metro")), }, - // TODO: aayushrangwala to check if this is needed with the framework changes - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // _, facOk := d.GetOk("facility") - // new - new val from template - // old - old val from state - // - // suppress diff if metro is manually set for first time, and - // facility is already set - //if len(new) > 0 && old == "" && facOk { - // return facOk - //} - //return old == new - //}, - // TODO: add statefunc in framework - //StateFunc: converters.ToLowerIf, }, "vxlan": schema.Int64Attribute{ Description: "VLAN ID, must be unique in metro", diff --git a/internal/resources/metal/vlans/resource_test.go b/internal/resources/metal/vlans/resource_test.go index fdc417a75..61cf64b82 100644 --- a/internal/resources/metal/vlans/resource_test.go +++ b/internal/resources/metal/vlans/resource_test.go @@ -24,11 +24,23 @@ resource "equinix_metal_vlan" "foovlan" { project_id = equinix_metal_project.foobar.id metro = "%s" description = "%s" - vxlan = 5 } `, projSuffix, metro, desc) } +func testAccCheckMetalVlanConfig_facility(projSuffix, facility, desc string) string { + return fmt.Sprintf(` +resource "equinix_metal_project" "foobar" { + name = "tfacc-vlan-%s" +} +resource "equinix_metal_vlan" "foovlan" { + project_id = equinix_metal_project.foobar.id + facility = "%s" + description = "%s" +} +`, projSuffix, facility, desc) +} + func TestAccMetalVlan_metro(t *testing.T) { var vlan packngo.VirtualNetwork rs := acctest.RandString(10) @@ -117,6 +129,47 @@ func TestAccMetalVlan_importBasic(t *testing.T) { }) } +func TestAccMetalVlan_facility_to_metro(t *testing.T) { + var vlan packngo.VirtualNetwork + rs := acctest.RandString(10) + metro := "sv" + facility := "sv15" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheckMetal(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: testAccMetalVlanCheckDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccCheckMetalVlanConfig_facility(rs, facility, "tfacc-vlan"), + Check: resource.ComposeTestCheckFunc( + testAccCheckMetalVlanExists("equinix_metal_vlan.foovlan", &vlan), + resource.TestCheckResourceAttr( + "equinix_metal_vlan.foovlan", "description", "tfacc-vlan"), + resource.TestCheckResourceAttr( + "equinix_metal_vlan.foovlan", "metro", metro), + resource.TestCheckResourceAttr( + "equinix_metal_vlan.foovlan", "facility", facility), + ), + }, + { + Config: testAccCheckMetalVlanConfig_metro(rs, metro, "tfacc-vlan"), + ExpectNonEmptyPlan: false, + Check: resource.ComposeTestCheckFunc( + testAccCheckMetalVlanExists("equinix_metal_vlan.foovlan", &vlan), + resource.TestCheckResourceAttr( + "equinix_metal_vlan.foovlan", "description", "tfacc-vlan"), + resource.TestCheckResourceAttr( + "equinix_metal_vlan.foovlan", "metro", metro), + resource.TestCheckResourceAttr( + "equinix_metal_vlan.foovlan", "facility", facility), + ), + }, + }, + }) +} + func TestAccMetalVlan_metro_upgradeFromVersion(t *testing.T) { var vlan packngo.VirtualNetwork rs := acctest.RandString(10)