Add new resource and datasource for Load Balancer credentials (#255)

* Implement and test credential resource and data source

* Fix descriptions in load balancer instance schema

* Extend acceptance tests

* Add acceptance test requirements to README

* Generate updated docs

* Fix linter issues

* Add examples and update docs

* Fix examples

* Improvements from review

* Remove credential data source
This commit is contained in:
João Palet 2024-02-08 11:49:05 +00:00 committed by GitHub
parent 996b4fbf20
commit d8734270f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 628 additions and 27 deletions

View file

@ -127,12 +127,19 @@ Terraform acceptance tests are run using the command `make test-acceptance-tf`.
- Authentication is set as usual.
- Optionally, the env var `TF_ACC_XXXXXX_CUSTOM_ENDPOINT` (where `XXXXXX` is the uppercase name of the service) can be set to use endpoints other than the default value.
Additionally, for the Resource Manager service,
Additionally:
- A service account with permissions to create and delete projects is required.
- The env var `TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_EMAIL` must be set as the email of the service account.
- The env var `TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_TOKEN` must be set as a valid token of the service account. Can also be set in the credentials file used by authentication (see [Authentication](#authentication) for more details)
- The env var `TF_ACC_PROJECT_ID` is ignored.
- For the Resource Manager service:
- A service account with permissions to create and delete projects is required
- The env var `TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_EMAIL` must be set as the email of the service account
- The env var `TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_TOKEN` must be set as a valid token of the service account. Can also be set in the credentials file used by authentication (see [Authentication](#authentication) for more details)
- The env var `TF_ACC_PROJECT_ID` is ignored
- For the Load Balancer service:
- OpenStack credentials are required, as the acceptance tests use the OpenStack provider to setup the supporting infrastructure
- These can be obtained after creating a user token through the [STACKIT Portal](https://portal.stackit.cloud/), in your project's Infrastructure API page
- The env var `TF_ACC_OS_USER_DOMAIN_NAME` must be set as the OpenStack user domain name
- The env var `TF_ACC_OS_USER_NAME` must be set as the OpenStack username
- The env var `TF_ACC_OS_PASSWORD` must be set as the OpenStack password
**WARNING:** Acceptance tests will create real resources, which may incur in costs.

View file

@ -3,12 +3,12 @@
page_title: "stackit_loadbalancer Data Source - stackit"
subcategory: ""
description: |-
Load Balancer resource schema.
Load Balancer data source schema. Must have a region specified in the provider configuration.
---
# stackit_loadbalancer (Data Source)
Load Balancer resource schema.
Load Balancer data source schema. Must have a `region` specified in the provider configuration.
## Example Usage

View file

@ -0,0 +1,37 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackit_loadbalancer_credential Resource - stackit"
subcategory: ""
description: |-
Load balancer credential resource schema. Must have a region specified in the provider configuration.
---
# stackit_loadbalancer_credential (Resource)
Load balancer credential resource schema. Must have a `region` specified in the provider configuration.
## Example Usage
```terraform
resource "stackit_loadbalancer_credential" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
display_name = "example-credentials"
username = "example-user"
password = "example-password"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `display_name` (String) Credential name.
- `password` (String) The password used for the ARGUS instance.
- `project_id` (String) STACKIT project ID to which the load balancer credential is associated.
- `username` (String) The username used for the ARGUS instance.
### Read-Only
- `credentials_ref` (String) The credentials reference can be used for observability of the Load Balancer.
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`","`credentials_ref`".

View file

@ -0,0 +1,6 @@
resource "stackit_loadbalancer_credential" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
display_name = "example-credentials"
username = "example-user"
password = "example-password"
}

View file

@ -0,0 +1,354 @@
package loadbalancer
import (
"context"
"fmt"
"strings"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"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/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer"
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer/wait"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &credentialResource{}
_ resource.ResourceWithConfigure = &credentialResource{}
_ resource.ResourceWithImportState = &credentialResource{}
)
type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
ProjectId types.String `tfsdk:"project_id"`
DisplayName types.String `tfsdk:"display_name"`
Username types.String `tfsdk:"username"`
Password types.String `tfsdk:"password"`
CredentialsRef types.String `tfsdk:"credentials_ref"`
}
// NewCredentialResource is a helper function to simplify the provider implementation.
func NewCredentialResource() resource.Resource {
return &credentialResource{}
}
// credentialResource is the resource implementation.
type credentialResource struct {
client *loadbalancer.APIClient
}
// Metadata returns the resource type name.
func (r *credentialResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_loadbalancer_credential"
}
// Configure adds the provider configured client to the resource.
func (r *credentialResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.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 != "" {
ctx = tflog.SetField(ctx, "loadbalancer_custom_endpoint", 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 resource configuration", err))
return
}
r.client = apiClient
tflog.Info(ctx, "Load Balancer client configured")
}
// Schema defines the schema for the resource.
func (r *credentialResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{
"main": "Load balancer credential resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"`credentials_ref`\".",
"credentials_ref": "The credentials reference can be used for observability of the Load Balancer.",
"project_id": "STACKIT project ID to which the load balancer credential is associated.",
"display_name": "Credential name.",
"username": "The username used for the ARGUS instance.",
"password": "The password used for the ARGUS instance.",
}
resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"credentials_ref": schema.StringAttribute{
Description: descriptions["credentials_ref"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.UUID(),
},
},
"display_name": schema.StringAttribute{
Description: descriptions["display_name"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"username": schema.StringAttribute{
Description: descriptions["username"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"password": schema.StringAttribute{
Description: descriptions["password"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
}
}
// Create creates the resource and sets the initial Terraform state.
func (r *credentialResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
var model Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
// Get status of load balancer functionality
statusResp, err := r.client.GetServiceStatus(ctx, projectId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error getting status of load balancer functionality", fmt.Sprintf("Calling API: %v", err))
return
}
// If load balancer functionality is not enabled, enable it
if *statusResp.Status != wait.FunctionalityStatusReady {
_, err = r.client.EnableService(ctx, projectId).XRequestID(uuid.NewString()).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error enabling load balancer functionality", fmt.Sprintf("Calling API: %v", err))
return
}
_, err := wait.EnableServiceWaitHandler(ctx, r.client, projectId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error enabling load balancer functionality", fmt.Sprintf("Waiting for enablement: %v", err))
return
}
}
// Generate API request body from model
payload, err := toCreatePayload(&model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Create new credentials
createResp, err := r.client.CreateCredentials(ctx, projectId).CreateCredentialsPayload(*payload).XRequestID(uuid.NewString()).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Calling API: %v", err))
return
}
ctx = tflog.SetField(ctx, "credentials_ref", createResp.Credential.CredentialsRef)
// Map response body to schema
err = mapFields(createResp.Credential, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set state to fully populated data
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Load balancer credential created")
}
// Read refreshes the Terraform state with the latest data.
func (r *credentialResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
credentialsRef := model.CredentialsRef.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "credentials_ref", credentialsRef)
// Get credentials
credResp, err := r.client.GetCredentials(ctx, projectId, credentialsRef).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Calling API: %v", err))
return
}
// Map response body to schema
err = mapFields(credResp.Credential, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", 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 credential read")
}
func (r *credentialResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
// Update shouldn't be called
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating credential", "Credential can't be updated")
}
// Delete deletes the resource and removes the Terraform state on success.
func (r *credentialResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
credentialsRef := model.CredentialsRef.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "credentials_ref", credentialsRef)
// Delete credentials
_, err := r.client.DeleteCredentials(ctx, projectId, credentialsRef).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting load balancer", fmt.Sprintf("Calling API: %v", err))
return
}
tflog.Info(ctx, "Load balancer credential deleted")
}
// ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,name
func (r *credentialResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing credential",
fmt.Sprintf("Expected import identifier with format: [project_id],[credentials_ref] Got: %q", req.ID),
)
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("credentials_ref"), idParts[1])...)
tflog.Info(ctx, "Load balancer credential state imported")
}
func toCreatePayload(model *Model) (*loadbalancer.CreateCredentialsPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
return &loadbalancer.CreateCredentialsPayload{
DisplayName: conversion.StringValueToPointer(model.DisplayName),
Username: conversion.StringValueToPointer(model.Username),
Password: conversion.StringValueToPointer(model.Password),
}, nil
}
func mapFields(cred *loadbalancer.CredentialsResponse, m *Model) error {
if cred == nil {
return fmt.Errorf("response input is nil")
}
if m == nil {
return fmt.Errorf("model input is nil")
}
var credentialsRef string
if m.CredentialsRef.ValueString() != "" {
credentialsRef = m.CredentialsRef.ValueString()
} else if cred.CredentialsRef != nil {
credentialsRef = *cred.CredentialsRef
} else {
return fmt.Errorf("credentials ref not present")
}
m.CredentialsRef = types.StringValue(credentialsRef)
m.DisplayName = types.StringPointerValue(cred.DisplayName)
var username string
if m.Username.ValueString() != "" {
username = m.Username.ValueString()
} else if cred.Username != nil {
username = *cred.Username
} else {
return fmt.Errorf("username not present")
}
m.Username = types.StringValue(username)
idParts := []string{
m.ProjectId.ValueString(),
m.CredentialsRef.ValueString(),
}
m.Id = types.StringValue(
strings.Join(idParts, core.Separator),
)
return nil
}

View file

@ -0,0 +1,152 @@
package loadbalancer
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer"
)
func TestToCreatePayload(t *testing.T) {
tests := []struct {
description string
input *Model
expected *loadbalancer.CreateCredentialsPayload
isValid bool
}{
{
"default_values_ok",
&Model{},
&loadbalancer.CreateCredentialsPayload{
DisplayName: nil,
Username: nil,
Password: nil,
},
true,
},
{
"simple_values_ok",
&Model{
DisplayName: types.StringValue("display_name"),
Username: types.StringValue("username"),
Password: types.StringValue("password"),
},
&loadbalancer.CreateCredentialsPayload{
DisplayName: utils.Ptr("display_name"),
Username: utils.Ptr("username"),
Password: utils.Ptr("password"),
},
true,
},
{
"nil_model",
nil,
nil,
false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := toCreatePayload(tt.input)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(output, tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
})
}
}
func TestMapFields(t *testing.T) {
tests := []struct {
description string
input *loadbalancer.CredentialsResponse
expected *Model
isValid bool
}{
{
"default_values_ok",
&loadbalancer.CredentialsResponse{
CredentialsRef: utils.Ptr("credentials_ref"),
Username: utils.Ptr("username"),
},
&Model{
Id: types.StringValue("pid,credentials_ref"),
ProjectId: types.StringValue("pid"),
CredentialsRef: types.StringValue("credentials_ref"),
Username: types.StringValue("username"),
},
true,
},
{
"simple_values_ok",
&loadbalancer.CredentialsResponse{
CredentialsRef: utils.Ptr("credentials_ref"),
DisplayName: utils.Ptr("display_name"),
Username: utils.Ptr("username"),
},
&Model{
Id: types.StringValue("pid,credentials_ref"),
ProjectId: types.StringValue("pid"),
CredentialsRef: types.StringValue("credentials_ref"),
DisplayName: types.StringValue("display_name"),
Username: types.StringValue("username"),
},
true,
},
{
"nil_response",
nil,
&Model{},
false,
},
{
"no_username",
&loadbalancer.CredentialsResponse{
CredentialsRef: utils.Ptr("credentials_ref"),
DisplayName: utils.Ptr("display_name"),
},
&Model{},
false,
},
{
"no_credentials_ref",
&loadbalancer.CredentialsResponse{
DisplayName: utils.Ptr("display_name"),
Username: utils.Ptr("username"),
},
&Model{},
false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
model := &Model{
ProjectId: tt.expected.ProjectId,
}
err := mapFields(tt.input, model)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(model, tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
})
}
}

View file

@ -78,7 +78,7 @@ func (r *loadBalancerDataSource) Configure(ctx context.Context, req datasource.C
// 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.",
"main": "Load Balancer data source schema. Must have a `region` specified in the provider configuration.",
"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.",

View file

@ -177,7 +177,7 @@ func (r *loadBalancerResource) Configure(ctx context.Context, req resource.Confi
// Schema defines the schema for the resource.
func (r *loadBalancerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{
"main": "Load Balancer resource schema.",
"main": "Load Balancer resource schema. Must have a `region` specified in the provider configuration.",
"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.",

View file

@ -16,23 +16,26 @@ import (
// Instance resource data
var loadBalancerResource = map[string]string{
"project_id": testutil.ProjectId,
"name": fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum)),
"target_pool_name": "example-target-pool",
"target_port": "5432",
"target_port_updated": "5431",
"target_display_name": "example-target",
"healthy_threshold": "3",
"interval": "10s",
"interval_jitter": "5s",
"timeout": "10s",
"unhealthy_threshold": "3",
"use_source_ip_address": "true",
"listener_display_name": "example-listener",
"listener_port": "5432",
"listener_protocol": "PROTOCOL_TCP",
"network_role": "ROLE_LISTENERS_AND_TARGETS",
"private_network_only": "true",
"project_id": testutil.ProjectId,
"name": fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum)),
"target_pool_name": "example-target-pool",
"target_port": "5432",
"target_port_updated": "5431",
"target_display_name": "example-target",
"healthy_threshold": "3",
"interval": "10s",
"interval_jitter": "5s",
"timeout": "10s",
"unhealthy_threshold": "3",
"use_source_ip_address": "true",
"listener_display_name": "example-listener",
"listener_port": "5432",
"listener_protocol": "PROTOCOL_TCP",
"network_role": "ROLE_LISTENERS_AND_TARGETS",
"private_network_only": "true",
"credential_display_name": fmt.Sprintf("tf-acc-cred%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum)),
"credential_username": "username",
"credential_password": "password",
}
func configResources(targetPort string) string {
@ -83,7 +86,14 @@ func configResources(targetPort string) string {
options = {
private_network_only = %s
}
}
}
resource "stackit_loadbalancer_credential" "credential" {
project_id = "%s"
display_name = "%s"
username = "%s"
password = "%s"
}
`,
supportingInfraResources(loadBalancerResource["name"], OpenStack{
userDomainName: testutil.OSUserDomainName,
@ -108,6 +118,10 @@ func configResources(targetPort string) string {
loadBalancerResource["target_pool_name"],
loadBalancerResource["network_role"],
loadBalancerResource["private_network_only"],
loadBalancerResource["project_id"],
loadBalancerResource["credential_display_name"],
loadBalancerResource["credential_username"],
loadBalancerResource["credential_password"],
)
}
@ -200,6 +214,7 @@ func TestAccLoadBalancerResource(t *testing.T) {
{
Config: configResources(loadBalancerResource["target_port"]),
Check: resource.ComposeAggregateTestCheckFunc(
// Load balancer instance
resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "project_id", loadBalancerResource["project_id"]),
resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "name", loadBalancerResource["name"]),
resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.name", loadBalancerResource["target_pool_name"]),
@ -219,6 +234,16 @@ func TestAccLoadBalancerResource(t *testing.T) {
resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "networks.0.network_id"),
resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "networks.0.role", loadBalancerResource["network_role"]),
resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "options.private_network_only", loadBalancerResource["private_network_only"]),
// Credential
resource.TestCheckResourceAttrPair(
"stackit_loadbalancer_credential.credential", "project_id",
"stackit_loadbalancer.loadbalancer", "project_id",
),
resource.TestCheckResourceAttrSet("stackit_loadbalancer_credential.credential", "credentials_ref"),
resource.TestCheckResourceAttr("stackit_loadbalancer_credential.credential", "display_name", loadBalancerResource["credential_display_name"]),
resource.TestCheckResourceAttr("stackit_loadbalancer_credential.credential", "username", loadBalancerResource["credential_username"]),
resource.TestCheckResourceAttr("stackit_loadbalancer_credential.credential", "password", loadBalancerResource["credential_password"]),
),
},
// Data source
@ -234,6 +259,7 @@ func TestAccLoadBalancerResource(t *testing.T) {
configResources(loadBalancerResource["target_port"]),
),
Check: resource.ComposeAggregateTestCheckFunc(
// Load balancer instance
resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "project_id", loadBalancerResource["project_id"]),
resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "name", loadBalancerResource["name"]),
resource.TestCheckResourceAttrPair(
@ -282,6 +308,23 @@ func TestAccLoadBalancerResource(t *testing.T) {
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "stackit_loadbalancer_credential.credential",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_loadbalancer_credential.credential"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_loadbalancer_credential.credential")
}
credentialsRef, ok := r.Primary.Attributes["credentials_ref"]
if !ok {
return "", fmt.Errorf("couldn't find attribute credentials_ref")
}
return fmt.Sprintf("%s,%s", testutil.ProjectId, credentialsRef), nil
},
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password"},
},
// Update
{
Config: configResources(loadBalancerResource["target_port_updated"]),

View file

@ -14,6 +14,7 @@ import (
argusScrapeConfig "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/argus/scrapeconfig"
dnsRecordSet "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/recordset"
dnsZone "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/zone"
loadBalancerCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/credential"
loadBalancer "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/loadbalancer"
logMeCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logme/credential"
logMeInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logme/instance"
@ -379,6 +380,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
dnsZone.NewZoneResource,
dnsRecordSet.NewRecordSetResource,
loadBalancer.NewLoadBalancerResource,
loadBalancerCredential.NewCredentialResource,
logMeInstance.NewInstanceResource,
logMeCredential.NewCredentialResource,
mariaDBInstance.NewInstanceResource,