Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
312 changes: 312 additions & 0 deletions stackit/internal/services/loadbalancer/loadbalancer/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
package loadbalancer

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"

"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer"
)

// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &loadBalancerDataSource{}
)

// NewLoadBalancerDataSource is a helper function to simplify the provider implementation.
func NewLoadBalancerDataSource() datasource.DataSource {
return &loadBalancerDataSource{}
}

// loadBalancerDataSource is the data source implementation.
type loadBalancerDataSource struct {
client *loadbalancer.APIClient
}

// Metadata returns the data source type name.
func (r *loadBalancerDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_loadbalancer"
}

// Configure adds the provider configured client to the data source.
func (r *loadBalancerDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

providerData, ok := req.ProviderData.(core.ProviderData)
if !ok {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Expected configure type stackit.ProviderData, got %T", req.ProviderData))
return
}

var apiClient *loadbalancer.APIClient
var err error
if providerData.LoadBalancerCustomEndpoint != "" {
apiClient, err = loadbalancer.NewAPIClient(
config.WithCustomAuth(providerData.RoundTripper),
config.WithEndpoint(providerData.LoadBalancerCustomEndpoint),
)
} else {
apiClient, err = loadbalancer.NewAPIClient(
config.WithCustomAuth(providerData.RoundTripper),
config.WithRegion(providerData.Region),
)
}

if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the data source configuration", err))
return
}

r.client = apiClient
tflog.Info(ctx, "Load balancer client configured")
}

// Schema defines the schema for the data source.
func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{
"main": "Load Balancer resource schema.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"`name`\".",
"project_id": "STACKIT project ID to which the Load Balancer is associated.",
"external_address": "External Load Balancer IP address where this Load Balancer is exposed.",
"listeners": "List of all listeners which will accept traffic. Limited to 20.",
"port": "Port number where we listen for traffic.",
"protocol": "Protocol is the highest network protocol we understand to load balance.",
"target_pool": "Reference target pool by target pool name.",
"name": "Load balancer name.",
"networks": "List of networks that listeners and targets reside in.",
"network_id": "Openstack network ID.",
"role": "The role defines how the load balancer is using the network.",
"options": "Defines any optional functionality you want to have enabled on your load balancer.",
"acl": "Load Balancer is accessible only from an IP address in this range.",
"private_network_only": "If true, Load Balancer is accessible only via a private network IP address.",
"private_address": "Transient private Load Balancer IP address. It can change any time.",
"target_pools": "List of all target pools which will be used in the Load Balancer. Limited to 20.",
"healthy_threshold": "Healthy threshold of the health checking.",
"interval": "Interval duration of health checking in seconds.",
"interval_jitter": "Interval duration threshold of the health checking in seconds.",
"timeout": "Active health checking timeout duration in seconds.",
"unhealthy_threshold": "Unhealthy threshold of the health checking.",
"target_pools.name": "Target pool name.",
"target_port": "Identical port number where each target listens for traffic.",
"targets": "List of all targets which will be used in the pool. Limited to 250.",
"targets.display_name": "Target display name",
"ip": "Target IP",
}

resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
},
},
"external_address": schema.StringAttribute{
Description: descriptions["external_address"],
Computed: true,
},
"listeners": schema.ListNestedAttribute{
Description: descriptions["listeners"],
Computed: true,
Validators: []validator.List{
listvalidator.SizeBetween(1, 20),
},
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"display_name": schema.StringAttribute{
Description: descriptions["listeners.display_name"],
Computed: true,
},
"port": schema.Int64Attribute{
Description: descriptions["port"],
Computed: true,
},
"protocol": schema.StringAttribute{
Description: descriptions["protocol"],
Computed: true,
},
"target_pool": schema.StringAttribute{
Description: descriptions["target_pool"],
Computed: true,
},
},
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.LengthAtMost(63),
validate.NoSeparator(),
},
},
"networks": schema.ListNestedAttribute{
Description: descriptions["networks"],
Computed: true,
Validators: []validator.List{
listvalidator.SizeBetween(1, 1),
},
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"network_id": schema.StringAttribute{
Description: descriptions["network_id"],
Computed: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"role": schema.StringAttribute{
Description: descriptions["role"],
Computed: true,
},
},
},
},
"options": schema.SingleNestedAttribute{
Description: descriptions["options"],
Computed: true,
Attributes: map[string]schema.Attribute{
"acl": schema.SetAttribute{
Description: descriptions["acl"],
ElementType: types.StringType,
Computed: true,
Validators: []validator.Set{
setvalidator.ValueStringsAre(
validate.CIDR(),
),
},
},
"private_network_only": schema.BoolAttribute{
Description: descriptions["private_network_only"],
Computed: true,
},
},
},
"private_address": schema.StringAttribute{
Description: descriptions["private_address"],
Computed: true,
},
"target_pools": schema.ListNestedAttribute{
Description: descriptions["target_pools"],
Computed: true,
Validators: []validator.List{
listvalidator.SizeBetween(1, 20),
},
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"active_health_check": schema.SingleNestedAttribute{
Description: descriptions["active_health_check"],
Computed: true,
Attributes: map[string]schema.Attribute{
"healthy_threshold": schema.Int64Attribute{
Description: descriptions["healthy_threshold"],
Computed: true,
},
"interval": schema.StringAttribute{
Description: descriptions["interval"],
Computed: true,
},
"interval_jitter": schema.StringAttribute{
Description: descriptions["interval_jitter"],
Computed: true,
},
"timeout": schema.StringAttribute{
Description: descriptions["timeout"],
Computed: true,
},
"unhealthy_threshold": schema.Int64Attribute{
Description: descriptions["unhealthy_threshold"],
Computed: true,
},
},
},
"name": schema.StringAttribute{
Description: descriptions["target_pools.name"],
Computed: true,
},
"target_port": schema.Int64Attribute{
Description: descriptions["target_port"],
Computed: true,
},
"targets": schema.ListNestedAttribute{
Description: descriptions["targets"],
Computed: true,
Validators: []validator.List{
listvalidator.SizeBetween(1, 250),
},
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"display_name": schema.StringAttribute{
Description: descriptions["targets.display_name"],
Computed: true,
},
"ip": schema.StringAttribute{
Description: descriptions["ip"],
Computed: true,
},
},
},
},
},
},
},
},
}
}

// Read refreshes the Terraform state with the latest data.
func (r *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
name := model.Name.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "name", name)

lbResp, err := r.client.GetLoadBalancer(ctx, projectId, name).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading load balancer", fmt.Sprintf("Calling API: %v", err))
return
}

// Map response body to schema
err = mapFields(ctx, lbResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading load balancer", fmt.Sprintf("Processing API payload: %v", err))
return
}

// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Load balancer read")
}
Loading