terraform-provider-stackitp.../stackit/internal/services/iaasalpha/routingtable/shared/route.go
Ruben Hönle 9ff9b8f610
feat(iaas): add experimental support for routing tables and routes (#896)
* Merged PR 788126: feat(iaas): Onboard routing tables

feat(iaas): Onboard routing tables

Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>

* Merged PR 793350: fix(routingtable): region attribute is missing in scheme

fix(routingtable): region attribute is missing in scheme

Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>

* Merged PR 797968: feat(iaas): onboarding of routing table routes

relates to STACKITTPR-241

* use iaasalpha sdk from github

* resolve todos

* remove routes from routing table model

* restructure packages

* acc tests routing tables

* add acc tests for routes

* chore(iaas): mark routing table resources as experimental

* chore(deps): use iaasalpha sdk v0.1.19-alpha

* Review feedback

Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>

---------

Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>
Co-authored-by: Alexander Dahmen (EXT) <Alexander.Dahmen_ext@external.mail.schwarz>
Co-authored-by: Alexander Dahmen <alexander.dahmen@inovex.de>
2025-07-02 10:30:50 +02:00

213 lines
6.3 KiB
Go

package shared
import (
"context"
"fmt"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
)
type RouteReadModel struct {
RouteId types.String `tfsdk:"route_id"`
Destination types.Object `tfsdk:"destination"`
NextHop types.Object `tfsdk:"next_hop"`
Labels types.Map `tfsdk:"labels"`
CreatedAt types.String `tfsdk:"created_at"`
UpdatedAt types.String `tfsdk:"updated_at"`
}
func RouteReadModelTypes() map[string]attr.Type {
return map[string]attr.Type{
"route_id": types.StringType,
"destination": types.ObjectType{AttrTypes: RouteDestinationTypes},
"next_hop": types.ObjectType{AttrTypes: RouteNextHopTypes},
"labels": types.MapType{ElemType: types.StringType},
"created_at": types.StringType,
"updated_at": types.StringType,
}
}
type RouteModel struct {
RouteReadModel
Id types.String `tfsdk:"id"` // needed by TF
OrganizationId types.String `tfsdk:"organization_id"`
RoutingTableId types.String `tfsdk:"routing_table_id"`
NetworkAreaId types.String `tfsdk:"network_area_id"`
Region types.String `tfsdk:"region"`
}
func RouteModelTypes() map[string]attr.Type {
modelTypes := RouteReadModelTypes()
modelTypes["id"] = types.StringType
modelTypes["organization_id"] = types.StringType
modelTypes["routing_table_id"] = types.StringType
modelTypes["network_area_id"] = types.StringType
modelTypes["region"] = types.StringType
return modelTypes
}
// RouteDestination is the struct corresponding to RouteReadModel.Destination
type RouteDestination struct {
Type types.String `tfsdk:"type"`
Value types.String `tfsdk:"value"`
}
// RouteDestinationTypes Types corresponding to routeDestination
var RouteDestinationTypes = map[string]attr.Type{
"type": types.StringType,
"value": types.StringType,
}
// RouteNextHop is the struct corresponding to RouteReadModel.NextHop
type RouteNextHop struct {
Type types.String `tfsdk:"type"`
Value types.String `tfsdk:"value"`
}
// RouteNextHopTypes Types corresponding to routeNextHop
var RouteNextHopTypes = map[string]attr.Type{
"type": types.StringType,
"value": types.StringType,
}
func MapRouteModel(ctx context.Context, route *iaasalpha.Route, model *RouteModel, region string) error {
if route == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
err := MapRouteReadModel(ctx, route, &model.RouteReadModel)
if err != nil {
return err
}
idParts := []string{
model.OrganizationId.ValueString(),
region,
model.NetworkAreaId.ValueString(),
model.RoutingTableId.ValueString(),
model.RouteId.ValueString(),
}
model.Id = types.StringValue(
strings.Join(idParts, core.Separator),
)
model.Region = types.StringValue(region)
return nil
}
func MapRouteReadModel(ctx context.Context, route *iaasalpha.Route, model *RouteReadModel) error {
if route == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var routeId string
if model.RouteId.ValueString() != "" {
routeId = model.RouteId.ValueString()
} else if route.Id != nil {
routeId = *route.Id
} else {
return fmt.Errorf("routing table route id not present")
}
labels, err := iaasUtils.MapLabels(ctx, route.Labels, model.Labels)
if err != nil {
return err
}
// created at and updated at
createdAtTF, updatedAtTF := types.StringNull(), types.StringNull()
if route.CreatedAt != nil {
createdAtValue := *route.CreatedAt
createdAtTF = types.StringValue(createdAtValue.Format(time.RFC3339))
}
if route.UpdatedAt != nil {
updatedAtValue := *route.UpdatedAt
updatedAtTF = types.StringValue(updatedAtValue.Format(time.RFC3339))
}
// destination
model.Destination, err = MapRouteDestination(route)
if err != nil {
return fmt.Errorf("error mapping route destination: %w", err)
}
// next hop
model.NextHop, err = MapRouteNextHop(route)
if err != nil {
return fmt.Errorf("error mapping route next hop: %w", err)
}
model.RouteId = types.StringValue(routeId)
model.CreatedAt = createdAtTF
model.UpdatedAt = updatedAtTF
model.Labels = labels
return nil
}
func MapRouteNextHop(routeResp *iaasalpha.Route) (types.Object, error) {
if routeResp.Nexthop == nil {
return types.ObjectNull(RouteNextHopTypes), nil
}
nextHopMap := map[string]attr.Value{}
switch i := routeResp.Nexthop.GetActualInstance().(type) {
case *iaasalpha.NexthopIPv4:
nextHopMap["type"] = types.StringValue(*i.Type)
nextHopMap["value"] = types.StringPointerValue(i.Value)
case *iaasalpha.NexthopIPv6:
nextHopMap["type"] = types.StringValue(*i.Type)
nextHopMap["value"] = types.StringPointerValue(i.Value)
case *iaasalpha.NexthopBlackhole:
nextHopMap["type"] = types.StringValue(*i.Type)
nextHopMap["value"] = types.StringNull()
case *iaasalpha.NexthopInternet:
nextHopMap["type"] = types.StringValue(*i.Type)
nextHopMap["value"] = types.StringNull()
default:
return types.ObjectNull(RouteNextHopTypes), fmt.Errorf("unexpected Nexthop type: %T", i)
}
nextHopTF, diags := types.ObjectValue(RouteNextHopTypes, nextHopMap)
if diags.HasError() {
return types.ObjectNull(RouteNextHopTypes), core.DiagsToError(diags)
}
return nextHopTF, nil
}
func MapRouteDestination(routeResp *iaasalpha.Route) (types.Object, error) {
if routeResp.Destination == nil {
return types.ObjectNull(RouteDestinationTypes), nil
}
destinationMap := map[string]attr.Value{}
switch i := routeResp.Destination.GetActualInstance().(type) {
case *iaasalpha.DestinationCIDRv4:
destinationMap["type"] = types.StringValue(*i.Type)
destinationMap["value"] = types.StringPointerValue(i.Value)
case *iaasalpha.DestinationCIDRv6:
destinationMap["type"] = types.StringValue(*i.Type)
destinationMap["value"] = types.StringPointerValue(i.Value)
default:
return types.ObjectNull(RouteDestinationTypes), fmt.Errorf("unexpected Destionation type: %T", i)
}
destinationTF, diags := types.ObjectValue(RouteDestinationTypes, destinationMap)
if diags.HasError() {
return types.ObjectNull(RouteDestinationTypes), core.DiagsToError(diags)
}
return destinationTF, nil
}