Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: migrate resource and datasource equinix_metal_project to framework #604

Merged
merged 25 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b9f854c
created model schema and create func
Feb 29, 2024
87beaf9
delete function
Mar 1, 2024
31e5fbb
read and update functions
Mar 1, 2024
31edb96
project datasource migrated
Mar 1, 2024
b67f340
update tests to use metal sdk
Mar 1, 2024
717a0be
fix project datasource read function
Mar 1, 2024
3516abf
fixup! update tests to use metal sdk
Mar 1, 2024
0fd8e5d
fixup! update tests to use metal sdk
Mar 1, 2024
5aa5551
remove empty organization id from create request and update test to u…
Mar 4, 2024
73d2045
update mock tests
Mar 4, 2024
02dc882
refactor mockProviderFactories function
Mar 5, 2024
d0b1823
workaround - API returns an empty bgp object instead of nil
Mar 5, 2024
502185f
fetch always bgp to fix resource import
Mar 5, 2024
83da010
fix test where provider_meta is defined and specific version is set i…
Mar 5, 2024
53364d8
let updating both project and bgp at once
Mar 5, 2024
15f9af8
fix update logic and schema, bgp can be updated but new asn should fo…
Mar 5, 2024
480c592
Update internal/resources/metal/project/resource_test.go
ocobles Mar 18, 2024
7fedee8
BGP ASN required only if bgp was previosuly configured
Mar 5, 2024
cd4b119
custom plan modifier for asn
Mar 18, 2024
1d304a0
fixup! custom plan modifier for asn
Mar 18, 2024
a9cddcc
fixup! custom plan modifier for asn
Mar 18, 2024
55fc51c
custom plan modifier for bgp_config
Mar 18, 2024
ec85ea8
remove unused constant
ctreatma Mar 20, 2024
92e2f47
add missing test case for immutable list plan modifier
ctreatma Mar 20, 2024
30e64cc
fix project update behavior
ctreatma Mar 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions equinix/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import (
"time"

"github.com/equinix/terraform-provider-equinix/internal/config"
metal_project "github.com/equinix/terraform-provider-equinix/internal/resources/metal/project"
"github.com/equinix/terraform-provider-equinix/internal/resources/metal/vrf"

"github.com/equinix/ecx-go/v2"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -106,7 +104,6 @@ func Provider() *schema.Provider {
"equinix_metal_device_bgp_neighbors": dataSourceMetalDeviceBGPNeighbors(),
"equinix_metal_plans": dataSourceMetalPlans(),
"equinix_metal_port": dataSourceMetalPort(),
"equinix_metal_project": metal_project.DataSource(),
"equinix_metal_reserved_ip_block": dataSourceMetalReservedIPBlock(),
"equinix_metal_spot_market_request": dataSourceMetalSpotMarketRequest(),
"equinix_metal_virtual_circuit": dataSourceMetalVirtualCircuit(),
Expand All @@ -133,7 +130,6 @@ func Provider() *schema.Provider {
"equinix_metal_device": resourceMetalDevice(),
"equinix_metal_device_network_type": resourceMetalDeviceNetworkType(),
"equinix_metal_port": resourceMetalPort(),
"equinix_metal_project": metal_project.Resource(),
"equinix_metal_reserved_ip_block": resourceMetalReservedIPBlock(),
"equinix_metal_ip_attachment": resourceMetalIPAttachment(),
"equinix_metal_spot_market_request": resourceMetalSpotMarketRequest(),
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-retryablehttp v0.7.5
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/terraform-plugin-docs v0.18.0
github.com/hashicorp/terraform-plugin-framework v1.6.1
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1
Expand Down Expand Up @@ -69,7 +70,6 @@ require (
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hc-install v0.6.3 // indirect
github.com/hashicorp/hcl/v2 v2.19.1 // indirect
Expand Down
46 changes: 46 additions & 0 deletions internal/planmodifiers/immutable_int64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package planmodifiers

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func ImmutableInt64() planmodifier.Int64 {
return &immutableInt64PlanModifier{}
}

type immutableInt64PlanModifier struct{}

func (d *immutableInt64PlanModifier) PlanModifyInt64(ctx context.Context, request planmodifier.Int64Request, response *planmodifier.Int64Response) {
if request.StateValue.IsNull() && request.PlanValue.IsUnknown() {
return
}

oldValue := request.StateValue.ValueInt64()
newValue := request.PlanValue.ValueInt64()

if oldValue != 0 && newValue != oldValue {
response.Diagnostics.AddAttributeError(
request.Path,
"Change not allowed",
fmt.Sprintf(
"Cannot modify the value of the `%s` field. Resource recreation would be required.",
request.Path.String(),
),
)
return
}

response.PlanValue = types.Int64Value(newValue)
}

func (d *immutableInt64PlanModifier) Description(ctx context.Context) string {
return "Prevents modification of a int64 value if the old value is not null."
}

func (d *immutableInt64PlanModifier) MarkdownDescription(ctx context.Context) string {
return d.Description(ctx)
}
61 changes: 61 additions & 0 deletions internal/planmodifiers/immutable_int64_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package planmodifiers

import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestImmutableStringSet(t *testing.T) {
testCases := []struct {
Old, New, Expected int64
ExpectError bool
}{
{
Old: 0,
New: 1234,
Expected: 1234,
ExpectError: false,
},
{
Old: 1234,
New: 4321,
Expected: 0,
ExpectError: true,
},
}

testPlanModifier := ImmutableInt64()

for i, testCase := range testCases {
stateValue := types.Int64Value(testCase.Old)
planValue := types.Int64Value(testCase.New)
expectedValue := types.Int64Null()
if testCase.Expected != 0 {
expectedValue = types.Int64Value(testCase.Expected)
}

req := planmodifier.Int64Request{
StateValue: stateValue,
PlanValue: planValue,
Path: path.Root("test"),
}

var resp planmodifier.Int64Response

testPlanModifier.PlanModifyInt64(context.Background(), req, &resp)

if resp.Diagnostics.HasError() {
if testCase.ExpectError == false {
t.Fatalf("%d: got error modifying plan: %v", i, resp.Diagnostics.Errors())
}
}

if !resp.PlanValue.Equal(expectedValue) {
t.Fatalf("%d: output plan value does not equal expected. Want %d plan value, got %d", i, expectedValue, resp.PlanValue.ValueInt64())
}
}
}
43 changes: 43 additions & 0 deletions internal/planmodifiers/immutable_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package planmodifiers

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
)

func ImmutableList() planmodifier.List {
return &immutableListPlanModifier{}
}

type immutableListPlanModifier struct{}

func (d *immutableListPlanModifier) PlanModifyList(ctx context.Context, request planmodifier.ListRequest, response *planmodifier.ListResponse) {

if request.StateValue.IsNull() && request.PlanValue.IsNull() {
return
}

if request.PlanValue.IsNull() && len(request.StateValue.Elements()) > 0 {
response.Diagnostics.AddAttributeError(
request.Path,
"Change not allowed",
fmt.Sprintf(
"Elements of the `%s` list field can not be removed. Resource recreation would be required.",
request.Path.String(),
),
)
return
}

response.PlanValue = request.PlanValue
}

func (d *immutableListPlanModifier) Description(ctx context.Context) string {
return "Allows adding elements to a list if it was initially empty and permits modifications, but disallows removals, requiring resource recreation."
}

func (d *immutableListPlanModifier) MarkdownDescription(ctx context.Context) string {
return d.Description(ctx)
}
65 changes: 65 additions & 0 deletions internal/planmodifiers/immutable_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package planmodifiers

import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestImmutableListSet(t *testing.T) {
testCases := []struct {
Old, New, Expected []string
ExpectError bool
}{
{
Old: []string{},
New: []string{"test"},
Expected: []string{"test"},
ExpectError: false,
},
{
Old: []string{"test"},
New: []string{},
Expected: []string{},
ExpectError: true,
},
{
Old: []string{"foo"},
New: []string{"bar"},
Expected: []string{"bar"},
ExpectError: true,
},
}

testPlanModifier := ImmutableList()

for i, testCase := range testCases {
stateValue, _ := types.ListValueFrom(context.Background(), types.StringType, testCase.Old)
planValue, _ := types.ListValueFrom(context.Background(), types.StringType, testCase.New)
expectedValue, _ := types.ListValueFrom(context.Background(), types.StringType, testCase.Expected)

req := planmodifier.ListRequest{
StateValue: stateValue,
PlanValue: planValue,
Path: path.Root("test"),
}

var resp planmodifier.ListResponse

testPlanModifier.PlanModifyList(context.Background(), req, &resp)

if resp.Diagnostics.HasError() {
if testCase.ExpectError == false {
t.Fatalf("%d: got error modifying plan: %v", i, resp.Diagnostics.Errors())
}
}

if !resp.PlanValue.Equal(expectedValue) {
value, _ := resp.PlanValue.ToListValue(context.Background())
t.Fatalf("%d: output plan value does not equal expected. Want %v plan value, got %v", i, expectedValue, value)
}
}
}
3 changes: 3 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
metalgateway "github.com/equinix/terraform-provider-equinix/internal/resources/metal/gateway"
metalorganization "github.com/equinix/terraform-provider-equinix/internal/resources/metal/organization"
metalorganizationmember "github.com/equinix/terraform-provider-equinix/internal/resources/metal/organization_member"
metalproject "github.com/equinix/terraform-provider-equinix/internal/resources/metal/project"
metalprojectsshkey "github.com/equinix/terraform-provider-equinix/internal/resources/metal/project_ssh_key"
metalsshkey "github.com/equinix/terraform-provider-equinix/internal/resources/metal/ssh_key"
"github.com/equinix/terraform-provider-equinix/internal/resources/metal/vlan"
Expand Down Expand Up @@ -114,6 +115,7 @@ func (p *FrameworkProvider) MetaSchema(
func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
metalgateway.NewResource,
metalproject.NewResource,
metalprojectsshkey.NewResource,
metalsshkey.NewResource,
metalconnection.NewResource,
Expand All @@ -126,6 +128,7 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res
func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
metalgateway.NewDataSource,
metalproject.NewDataSource,
metalprojectsshkey.NewDataSource,
metalconnection.NewDataSource,
metalorganization.NewDataSource,
Expand Down
86 changes: 86 additions & 0 deletions internal/resources/metal/project/bgp_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package project

import (
"context"

"github.com/equinix/equinix-sdk-go/services/metalv1"
equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors"
fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types"

"github.com/hashicorp/terraform-plugin-framework/diag"
)

func fetchBGPConfig(ctx context.Context, client *metalv1.APIClient, projectID string) (*metalv1.BgpConfig, diag.Diagnostics) {
var diags diag.Diagnostics

bgpConfig, _, err := client.BGPApi.FindBgpConfigByProject(ctx, projectID).Execute()
if err != nil {
friendlyErr := equinix_errors.FriendlyError(err)
diags.AddError(
"Error reading BGP configuration",
"Could not read BGP configuration for project with ID "+projectID+": "+friendlyErr.Error(),
)
return nil, diags
}

return bgpConfig, diags
}

func expandBGPConfig(ctx context.Context, bgpConfig fwtypes.ListNestedObjectValueOf[BGPConfigModel]) (*metalv1.BgpConfigRequestInput, error) {
bgpConfigModel, _ := bgpConfig.ToSlice(ctx)
bgpDeploymentType, err := metalv1.NewBgpConfigRequestInputDeploymentTypeFromValue(bgpConfigModel[0].DeploymentType.ValueString())
if err != nil {
return nil, err
}
bgpCreateRequest := metalv1.BgpConfigRequestInput{
DeploymentType: *bgpDeploymentType,
Asn: int32(bgpConfigModel[0].ASN.ValueInt64()),
}
if !bgpConfigModel[0].MD5.IsNull() {
bgpCreateRequest.Md5 = bgpConfigModel[0].MD5.ValueStringPointer()
}

return &bgpCreateRequest, nil
}

func handleBGPConfigChanges(ctx context.Context, client *metalv1.APIClient, plan, state *ResourceModel, projectID string) (*metalv1.BgpConfig, diag.Diagnostics) {
var diags diag.Diagnostics
var bgpConfig *metalv1.BgpConfig

if plan.BGPConfig.IsNull() && state.BGPConfig.IsNull() {
return bgpConfig, nil
}

bgpAdded := !plan.BGPConfig.IsNull() && state.BGPConfig.IsNull()
bgpChanged := !plan.BGPConfig.IsNull() && !state.BGPConfig.IsNull() && !plan.BGPConfig.Equal(state.BGPConfig)

if bgpAdded || bgpChanged {
// Create BGP Config
bgpCreateRequest, err := expandBGPConfig(ctx, plan.BGPConfig)
if err != nil {
diags.AddError(
"Error creating project",
"Could not validate BGP Config: "+err.Error(),
)
return nil, diags
}
createResp, err := client.BGPApi.RequestBgpConfig(ctx, projectID).BgpConfigRequestInput(*bgpCreateRequest).Execute()
if err != nil {
err = equinix_errors.FriendlyErrorForMetalGo(err, createResp)
diags.AddError(
"Error creating BGP configuration",
"Could not create BGP configuration for project: "+err.Error(),
)
return nil, diags
}
// Fetch the newly created BGP Config
bgpConfig, diags = fetchBGPConfig(ctx, client, projectID)
diags.Append(diags...)
} else { // assuming already exists
// Fetch the existing BGP Config
bgpConfig, diags = fetchBGPConfig(ctx, client, projectID)
diags.Append(diags...)
}

return bgpConfig, diags
}
Loading
Loading