diff --git a/go.mod b/go.mod index b6543e6..2361234 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.10.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 github.com/hashicorp/terraform-plugin-testing v1.14.0 - github.com/oxidecomputer/oxide.go v0.7.1-0.20260127220329-e1ba7effd833 + github.com/oxidecomputer/oxide.go v0.7.1-0.20260129184855-ef797073771e github.com/stretchr/testify v1.11.1 ) diff --git a/go.sum b/go.sum index 1e2e2fc..4af7e65 100644 --- a/go.sum +++ b/go.sum @@ -621,10 +621,8 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/oxidecomputer/oxide.go v0.7.1-0.20260127143929-a6e47816d4f4 h1:Dkktvmc6O++01SQBjjjKYcsIYC2AW8dVvkF63gZt5Sk= -github.com/oxidecomputer/oxide.go v0.7.1-0.20260127143929-a6e47816d4f4/go.mod h1:ZlG5ri4a6ClA/yhDCbQN6m/F3d/m41XHx5s9WbfFLWc= -github.com/oxidecomputer/oxide.go v0.7.1-0.20260127220329-e1ba7effd833 h1:Gx1sq1gGDQ2Mf/R0lXlW8TV6tPM8tcA0tDoXyuovYAQ= -github.com/oxidecomputer/oxide.go v0.7.1-0.20260127220329-e1ba7effd833/go.mod h1:ZlG5ri4a6ClA/yhDCbQN6m/F3d/m41XHx5s9WbfFLWc= +github.com/oxidecomputer/oxide.go v0.7.1-0.20260129184855-ef797073771e h1:B0o8R4UUTSK9au6GE4pns6wBK9GvOCZcdeyP67N10b0= +github.com/oxidecomputer/oxide.go v0.7.1-0.20260129184855-ef797073771e/go.mod h1:ZlG5ri4a6ClA/yhDCbQN6m/F3d/m41XHx5s9WbfFLWc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= diff --git a/internal/provider/resource_ip_pool.go b/internal/provider/resource_ip_pool.go index e4d8931..49cad6a 100644 --- a/internal/provider/resource_ip_pool.go +++ b/internal/provider/resource_ip_pool.go @@ -7,7 +7,6 @@ package provider import ( "context" "fmt" - "reflect" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -272,22 +271,20 @@ func (r *ipPoolResource) Read( for index, item := range ipPoolRanges.Items { ipPoolRange := ipPoolResourceRangeModel{} - // TODO: For the time being we are using interfaces for nested allOf within oneOf objects in - // the OpenAPI spec. When we come up with a better approach this should be edited to reflect - // that. - switch item.Range.(type) { - case map[string]interface{}: - rs := item.Range.(map[string]interface{}) - ipPoolRange.FirstAddress = types.StringValue(rs["first"].(string)) - ipPoolRange.LastAddress = types.StringValue(rs["last"].(string)) + // Extract first/last addresses from the IpRange variant + switch v := item.Range.Value.(type) { + case *oxide.Ipv4Range: + ipPoolRange.FirstAddress = types.StringValue(v.First) + ipPoolRange.LastAddress = types.StringValue(v.Last) + case *oxide.Ipv6Range: + ipPoolRange.FirstAddress = types.StringValue(v.First) + ipPoolRange.LastAddress = types.StringValue(v.Last) default: - // Theoretically this should never happen. Just in case though! resp.Diagnostics.AddError( "Unable to read IP Pool ranges:", fmt.Sprintf( - "internal error: %v is not map[string]interface{}. Debugging content: %+v. If you hit this bug, please contact support", - reflect.TypeOf(item.Range), - item.Range, + "internal error: unexpected IpRange variant type %T. If you hit this bug, please contact support", + item.Range.Value, ), ) return @@ -431,36 +428,10 @@ func (r *ipPoolResource) Delete( ) for _, item := range ranges.Items { - var ipRange oxide.IpRange - rs := item.Range.(map[string]interface{}) - if isIPv4(rs["first"].(string)) { - ipRange = oxide.Ipv4Range{ - First: rs["first"].(string), - Last: rs["last"].(string), - } - } else if isIPv6(rs["first"].(string)) { - ipRange = oxide.Ipv6Range{ - First: rs["first"].(string), - Last: rs["last"].(string), - } - } else { - // This should never happen as we are retrieving information from Nexus. If we do - // encounter - // this error we have a huge problem. - resp.Diagnostics.AddError( - "Unable to read IP Pool ranges:", - fmt.Sprintf( - "internal error: %v is not map[string]interface{}. Debugging content: %+v. If you hit this bug, please contact support", - reflect.TypeOf(item.Range), - item.Range, - ), - ) - return - } - + // item.Range is now a struct with a Value field containing the variant params := oxide.IpPoolRangeRemoveParams{ Pool: oxide.NameOrId(state.ID.ValueString()), - Body: &ipRange, + Body: &item.Range, } if err := r.client.IpPoolRangeRemove(ctx, params); err != nil { if !is404(err) { @@ -472,9 +443,8 @@ func (r *ipPoolResource) Delete( } } tflog.Trace(ctx, fmt.Sprintf( - "removed IP pool range %v - %v from IP pool with ID: %v", - rs["first"].(string), - rs["last"].(string), + "removed IP pool range %v from IP pool with ID: %v", + item.Range.String(), state.ID.ValueString(), ), map[string]any{"success": true}) } @@ -508,26 +478,14 @@ func addRanges( var diags diag.Diagnostics for _, ipPoolRange := range ranges { - var body oxide.IpRange - firstAddress := ipPoolRange.FirstAddress.ValueString() lastAddress := ipPoolRange.LastAddress.ValueString() - if isIPv4(firstAddress) { - body = oxide.Ipv4Range{ - First: firstAddress, - Last: lastAddress, - } - } else if isIPv6(firstAddress) { - body = oxide.Ipv6Range{ - First: firstAddress, - Last: lastAddress, - } - } else { + body, err := oxide.NewIpRange(firstAddress, lastAddress) + if err != nil { diags.AddError( "Error creating range within IP Pool", - fmt.Errorf("%s is neither a valid IPv4 or IPv6", - firstAddress).Error(), + err.Error(), ) return diags } @@ -569,26 +527,14 @@ func removeRanges( var diags diag.Diagnostics for _, ipPoolRange := range ranges { - var body oxide.IpRange - firstAddress := ipPoolRange.FirstAddress.ValueString() lastAddress := ipPoolRange.LastAddress.ValueString() - if isIPv4(firstAddress) { - body = oxide.Ipv4Range{ - First: firstAddress, - Last: lastAddress, - } - } else if isIPv6(firstAddress) { - body = oxide.Ipv6Range{ - First: firstAddress, - Last: lastAddress, - } - } else { + body, err := oxide.NewIpRange(firstAddress, lastAddress) + if err != nil { diags.AddError( "Error removing range within IP Pool", - fmt.Errorf("%s is neither a valid IPv4 or IPv6", - firstAddress).Error(), + err.Error(), ) return diags } @@ -598,7 +544,7 @@ func removeRanges( Body: &body, } - err := client.IpPoolRangeRemove(ctx, params) + err = client.IpPoolRangeRemove(ctx, params) if err != nil { diags.AddError( "Error removing range within IP Pool", diff --git a/internal/provider/resource_switch_port_settings.go b/internal/provider/resource_switch_port_settings.go index 22f9df5..6c140cf 100644 --- a/internal/provider/resource_switch_port_settings.go +++ b/internal/provider/resource_switch_port_settings.go @@ -827,7 +827,7 @@ func toSwitchPortSettingsModel( } addressModel := switchPortSettingsAddressAddressModel{ - Address: types.StringValue(address.Address.(string)), + Address: types.StringValue(address.Address.String()), AddressLotID: types.StringValue(string(address.AddressLotId)), VlanID: func() types.Int32 { if address.VlanId == nil { @@ -922,7 +922,7 @@ func toSwitchPortSettingsModel( } res := make([]types.String, 0) for _, elem := range bgpPeer.AllowedExport.Value { - res = append(res, types.StringValue(elem.(string))) + res = append(res, types.StringValue(elem.String())) } return res }(), @@ -936,7 +936,7 @@ func toSwitchPortSettingsModel( } res := make([]types.String, 0) for _, elem := range bgpPeer.AllowedImport.Value { - res = append(res, types.StringValue(elem.(string))) + res = append(res, types.StringValue(elem.String())) } return res }(), @@ -1098,7 +1098,7 @@ func toSwitchPortSettingsModel( } routeModel := switchPortSettingsRouteRouteModel{ - Dst: types.StringValue(route.Dst.(string)), + Dst: types.StringValue(route.Dst.String()), GW: types.StringValue(route.Gw), RIBPriority: func() types.Int32 { if route.RibPriority != nil { @@ -1154,12 +1154,22 @@ func toNetworkingSwitchPortSettingsCreateParams( // // Addresses // + var diags diag.Diagnostics addressConfigs := make([]oxide.AddressConfig, 0) for _, addressModel := range model.Addresses { addresses := make([]oxide.Address, 0) for _, addressModelNested := range addressModel.Addresses { + ipNet, err := oxide.NewIpNet(addressModelNested.Address.ValueString()) + if err != nil { + diags.AddError( + "Invalid IP network address", + fmt.Sprintf("Failed to parse address %q: %s", + addressModelNested.Address.ValueString(), err.Error()), + ) + return params, diags + } address := oxide.Address{ - Address: oxide.IpNet(addressModelNested.Address.ValueString()), + Address: ipNet, AddressLot: oxide.NameOrId(addressModelNested.AddressLotID.ValueString()), VlanId: func() *int { if addressModelNested.VlanID.IsNull() { @@ -1231,36 +1241,46 @@ func toNetworkingSwitchPortSettingsCreateParams( }(), } - bgpPeer.AllowedExport = oxide.ImportExportPolicy{ - Type: oxide.ImportExportPolicyType(bgpModelNested.AllowedExport.Type.ValueString()), - Value: func() []oxide.IpNet { - if len(bgpModelNested.AllowedExport.Value) == 0 { - return nil + // Parse AllowedExport values + var allowedExportValues []oxide.IpNet + if len(bgpModelNested.AllowedExport.Value) > 0 { + allowedExportValues = make([]oxide.IpNet, 0, len(bgpModelNested.AllowedExport.Value)) + for _, value := range bgpModelNested.AllowedExport.Value { + ipNet, err := oxide.NewIpNet(value.ValueString()) + if err != nil { + diags.AddError( + "Invalid IP network in allowed_export", + fmt.Sprintf("Failed to parse %q: %s", value.ValueString(), err.Error()), + ) + return params, diags } + allowedExportValues = append(allowedExportValues, ipNet) + } + } + bgpPeer.AllowedExport = oxide.ImportExportPolicy{ + Type: oxide.ImportExportPolicyType(bgpModelNested.AllowedExport.Type.ValueString()), + Value: allowedExportValues, + } - values := make([]oxide.IpNet, 0) - for _, value := range bgpModelNested.AllowedExport.Value { - values = append(values, oxide.IpNet(value.ValueString())) + // Parse AllowedImport values + var allowedImportValues []oxide.IpNet + if len(bgpModelNested.AllowedImport.Value) > 0 { + allowedImportValues = make([]oxide.IpNet, 0, len(bgpModelNested.AllowedImport.Value)) + for _, value := range bgpModelNested.AllowedImport.Value { + ipNet, err := oxide.NewIpNet(value.ValueString()) + if err != nil { + diags.AddError( + "Invalid IP network in allowed_import", + fmt.Sprintf("Failed to parse %q: %s", value.ValueString(), err.Error()), + ) + return params, diags } - - return values - }(), + allowedImportValues = append(allowedImportValues, ipNet) + } } - bgpPeer.AllowedImport = oxide.ImportExportPolicy{ - Type: oxide.ImportExportPolicyType(bgpModelNested.AllowedImport.Type.ValueString()), - Value: func() []oxide.IpNet { - if len(bgpModelNested.AllowedImport.Value) == 0 { - return nil - } - - values := make([]oxide.IpNet, 0) - for _, value := range bgpModelNested.AllowedImport.Value { - values = append(values, oxide.IpNet(value.ValueString())) - } - - return values - }(), + Type: oxide.ImportExportPolicyType(bgpModelNested.AllowedImport.Type.ValueString()), + Value: allowedImportValues, } bgpPeer.Communities = func() []int { @@ -1351,8 +1371,17 @@ func toNetworkingSwitchPortSettingsCreateParams( for _, routeModel := range model.Routes { routes := make([]oxide.Route, 0) for _, routeModel := range routeModel.Routes { + dst, err := oxide.NewIpNet(routeModel.Dst.ValueString()) + if err != nil { + diags.AddError( + "Invalid route destination", + fmt.Sprintf("Failed to parse destination %q: %s", + routeModel.Dst.ValueString(), err.Error()), + ) + return params, diags + } route := oxide.Route{ - Dst: oxide.IpNet(routeModel.Dst.ValueString()), + Dst: dst, Gw: routeModel.GW.ValueString(), RibPriority: func() *int { if routeModel.RIBPriority.IsNull() {