From 52dd346d57698a91e49582e432536dc58d5d7810 Mon Sep 17 00:00:00 2001 From: Devansh Thakur Date: Mon, 8 Dec 2025 00:05:21 +0100 Subject: [PATCH 01/41] Resolve conflicts # Conflicts: # go.mod # go.sum # stackit/internal/testutil/testutil.go # stackit/provider.go # Conflicts: # stackit/internal/testutil/testutil.go # Conflicts: # stackit/internal/testutil/testutil.go --- docs/index.md | 1 + docs/resources/intake_runner.md | 34 ++ stackit/internal/core/core.go | 1 + .../services/intake/runner/resource.go | 529 ++++++++++++++++++ .../intake/runner/resource_acc_test.go | 160 ++++++ .../services/intake/runner/resource_test.go | 254 +++++++++ .../internal/services/intake/utils/utils.go | 31 + stackit/internal/testutil/testutil.go | 1 + stackit/provider.go | 8 + 9 files changed, 1019 insertions(+) create mode 100644 docs/resources/intake_runner.md create mode 100644 stackit/internal/services/intake/runner/resource.go create mode 100644 stackit/internal/services/intake/runner/resource_acc_test.go create mode 100644 stackit/internal/services/intake/runner/resource_test.go create mode 100644 stackit/internal/services/intake/utils/utils.go diff --git a/docs/index.md b/docs/index.md index ebf478219..0b10f72ce 100644 --- a/docs/index.md +++ b/docs/index.md @@ -175,6 +175,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de - `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: iam, routing-tables, network - `git_custom_endpoint` (String) Custom endpoint for the Git service - `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service +- `intake_custom_endpoint` (String) - `kms_custom_endpoint` (String) Custom endpoint for the KMS service - `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service - `logme_custom_endpoint` (String) Custom endpoint for the LogMe service diff --git a/docs/resources/intake_runner.md b/docs/resources/intake_runner.md new file mode 100644 index 000000000..65a5c3206 --- /dev/null +++ b/docs/resources/intake_runner.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_intake_runner Resource - stackit" +subcategory: "" +description: |- + Manages STACKIT Intake Runner. +--- + +# stackit_intake_runner (Resource) + +Manages STACKIT Intake Runner. + + + + +## Schema + +### Required + +- `max_message_size_kib` (Number) The maximum message size in KiB. +- `max_messages_per_hour` (Number) The maximum number of messages per hour. +- `name` (String) The name of the runner. +- `project_id` (String) STACKIT Project ID to which the runner is associated. + +### Optional + +- `description` (String) The description of the runner. +- `labels` (Map of String) User-defined labels. +- `region` (String) The resource region. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`runner_id`". +- `runner_id` (String) The runner ID. diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go index f8fa8f9f0..3ecf92de1 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -48,6 +48,7 @@ type ProviderData struct { EdgeCloudCustomEndpoint string GitCustomEndpoint string IaaSCustomEndpoint string + IntakeCustomEndpoint string KMSCustomEndpoint string LoadBalancerCustomEndpoint string LogMeCustomEndpoint string diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go new file mode 100644 index 000000000..d9de6667a --- /dev/null +++ b/stackit/internal/services/intake/runner/resource.go @@ -0,0 +1,529 @@ +package runner + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" + "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/oapierror" + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + intakeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/intake/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &runnerResource{} + _ resource.ResourceWithConfigure = &runnerResource{} + _ resource.ResourceWithImportState = &runnerResource{} + _ resource.ResourceWithModifyPlan = &runnerResource{} +) + +// Model is the internal model of the terraform resource +type Model struct { + Id types.String `tfsdk:"id"` // needed by TF + ProjectId types.String `tfsdk:"project_id"` + RunnerId types.String `tfsdk:"runner_id"` + Region types.String `tfsdk:"region"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Labels types.Map `tfsdk:"labels"` + MaxMessageSizeKiB types.Int64 `tfsdk:"max_message_size_kib"` + MaxMessagesPerHour types.Int64 `tfsdk:"max_messages_per_hour"` +} + +// NewRunnerResource is a helper function to simplify the provider implementation. +func NewRunnerResource() resource.Resource { + return &runnerResource{} +} + +// runnerResource is the resource implementation. +type runnerResource struct { + client *intake.APIClient + providerData core.ProviderData +} + +// Metadata returns the resource type name. +func (r *runnerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_intake_runner" +} + +// Configure adds the provider configured client to the resource. +func (r *runnerResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := intakeUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + r.client = apiClient + tflog.Info(ctx, "Intake runner client configured") +} + +// ModifyPlan implements resource.ResourceWithModifyPlan. +// Use the modifier to set the effective region in the current plan. +func (r *runnerResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform + var configModel Model + // skip initial empty configuration to avoid follow-up errors + if req.Config.Raw.IsNull() { + return + } + resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) + if resp.Diagnostics.HasError() { + return + } + + var planModel Model + resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) + if resp.Diagnostics.HasError() { + return + } + + utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...) + if resp.Diagnostics.HasError() { + return + } +} + +// Schema defines the schema for the resource. +func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + descriptions := map[string]string{ + "main": "Manages STACKIT Intake Runner.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`runner_id`\".", + "project_id": "STACKIT Project ID to which the runner is associated.", + "runner_id": "The runner ID.", + "name": "The name of the runner.", + "region": "The resource region. If not defined, the provider region is used.", + "description": "The description of the runner.", + "labels": "User-defined labels.", + "max_message_size_kib": "The maximum message size in KiB.", + "max_messages_per_hour": "The maximum number of messages per hour.", + } + + 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, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "runner_id": schema.StringAttribute{ + Description: descriptions["runner_id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: descriptions["name"], + Required: true, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "labels": schema.MapAttribute{ + Description: descriptions["labels"], + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.UseStateForUnknown(), + }, + }, + "max_message_size_kib": schema.Int64Attribute{ + Description: descriptions["max_message_size_kib"], + Required: true, + }, + "max_messages_per_hour": schema.Int64Attribute{ + Description: descriptions["max_messages_per_hour"], + Required: true, + }, + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: descriptions["region"], + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf("eu01"), // Currently Intake supports only EU01 region + }, + }, + }, + } +} + +// Create creates the resource and sets the initial Terraform state. +func (r *runnerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + // prepare the payload struct for the create bar request + 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 bar + runnerResp, err := r.client.CreateIntakeRunner(ctx, projectId, region).CreateIntakeRunnerPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating runner", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + // Wait for creation of intake runner + _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, r.client, projectId, region, runnerResp.GetId()).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating runner", fmt.Sprintf("Intake runner creation waiting: %v", err)) + return + } + + err = mapFields(runnerResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating runner", fmt.Sprintf("Processing API payload: %v", err)) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Intake runner created") +} + +// Read refreshes the Terraform state with the latest data. +func (r *runnerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + resp.Diagnostics.Append(req.State.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + runnerId := model.RunnerId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "runner_id", runnerId) + + runnerResp, err := r.client.GetIntakeRunner(ctx, projectId, region, runnerId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + // Map response body to schema + err = mapFields(runnerResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Intake runner read") +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *runnerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform + var model, state Model + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + runnerId := model.RunnerId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "runner_id", runnerId) + ctx = tflog.SetField(ctx, "region", region) + + payload, err := toUpdatePayload(&model, &state) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating runner", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + // Update runner + runnerResp, err := r.client.UpdateIntakeRunner(ctx, projectId, region, runnerId).UpdateIntakeRunnerPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating runner", fmt.Sprintf("Calling API: %v", err)) + return + } + + // Wait for update + _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, r.client, projectId, region, runnerId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating runner", fmt.Sprintf("Runner update waiting: %v", err)) + return + } + + // Map response body to schema + err = mapFields(runnerResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating runner", fmt.Sprintf("Processing API response: %v", err)) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Intake runner updated") +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *runnerResource) 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 + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() + runnerId := model.RunnerId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "runner_id", runnerId) + + // Delete existing bar + err := r.client.DeleteIntakeRunner(ctx, projectId, region, runnerId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { + tflog.Info(ctx, "Intake runner already deleted") + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting runner", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + // Wait for the delete operation to complete + _, err = wait.DeleteIntakeRunnerWaitHandler(ctx, r.client, projectId, region, runnerId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting runner", fmt.Sprintf("Runner deletion waiting: %v", err)) + return + } + + tflog.Info(ctx, "Intake runner deleted") +} + +// ImportState imports a resource into the Terraform state on success. +// The expected format of the Intake runner resource import identifier is: [project_id],[region],[runner_id] +func (r *runnerResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, core.Separator) + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, + "Error importing intake runner", + fmt.Sprintf("Expected import identifier with format [project_id],[region],[runner_id], got %q", req.ID), + ) + return + } + + utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": idParts[0], + "region": idParts[1], + "runner_id": idParts[2], + }) + + tflog.Info(ctx, "Intake runner state imported") +} + +// Maps runner fields to the provider internal model +func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model) error { + if runnerResp == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + var runnerId string + if runnerResp.Id != nil { + runnerId = *runnerResp.Id + } + + model.Id = utils.BuildInternalTerraformId( + model.ProjectId.ValueString(), + model.Region.ValueString(), + runnerId, + ) + + if runnerResp.Labels == nil { + model.Labels = types.MapValueMust(types.StringType, map[string]attr.Value{}) + } else { + labels, diags := types.MapValueFrom(context.Background(), types.StringType, runnerResp.Labels) + if diags.HasError() { + return fmt.Errorf("converting labels: %w", core.DiagsToError(diags)) + } + model.Labels = labels + } + + if runnerResp.Id != nil && *runnerResp.Id == "" { + model.RunnerId = types.StringNull() + } else { + model.RunnerId = types.StringPointerValue(runnerResp.Id) + } + model.Name = types.StringPointerValue(runnerResp.DisplayName) + if runnerResp.Description == nil { + model.Description = types.StringValue("") + } else { + model.Description = types.StringPointerValue(runnerResp.Description) + } + model.MaxMessageSizeKiB = types.Int64PointerValue(runnerResp.MaxMessageSizeKiB) + model.MaxMessagesPerHour = types.Int64PointerValue(runnerResp.MaxMessagesPerHour) + return nil +} + +// Build CreateBarPayload from provider's model +func toCreatePayload(model *Model) (*intake.CreateIntakeRunnerPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + + var labels map[string]string + if !model.Labels.IsNull() && !model.Labels.IsUnknown() { + diags := model.Labels.ElementsAs(context.Background(), &labels, false) + if diags.HasError() { + return nil, fmt.Errorf("converting labels: %w", core.DiagsToError(diags)) + } + } + + var labelsPtr *map[string]string + if len(labels) > 0 { + labelsPtr = &labels + } + + return &intake.CreateIntakeRunnerPayload{ + Description: conversion.StringValueToPointer(model.Description), + DisplayName: conversion.StringValueToPointer(model.Name), + Labels: labelsPtr, + MaxMessageSizeKiB: conversion.Int64ValueToPointer(model.MaxMessageSizeKiB), + MaxMessagesPerHour: conversion.Int64ValueToPointer(model.MaxMessagesPerHour), + }, nil +} + +func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, error) { + if model == nil { + return nil, fmt.Errorf("model is nil") + } + if state == nil { + return nil, fmt.Errorf("state is nil") + } + + payload := &intake.UpdateIntakeRunnerPayload{} + + if !model.Name.IsUnknown() { + payload.DisplayName = conversion.StringValueToPointer(model.Name) + } + + if !model.MaxMessageSizeKiB.IsUnknown() { + payload.MaxMessageSizeKiB = conversion.Int64ValueToPointer(model.MaxMessageSizeKiB) + } + + if !model.MaxMessagesPerHour.IsUnknown() { + payload.MaxMessagesPerHour = conversion.Int64ValueToPointer(model.MaxMessagesPerHour) + } + + // Handle optional fields + if !model.Description.IsUnknown() || model.Description.IsNull() { + if model.Description.IsNull() { + payload.Description = sdkUtils.Ptr("") + } else { + payload.Description = conversion.StringValueToPointer(model.Description) + } + } + + var labels map[string]string + if !model.Labels.IsUnknown() { + if model.Labels.IsNull() { + labels = map[string]string{} + payload.Labels = &labels + } else { + diags := model.Labels.ElementsAs(context.Background(), &labels, false) + if diags.HasError() { + return nil, fmt.Errorf("failed to convert labels: %w", core.DiagsToError(diags)) + } + payload.Labels = &labels + } + } + + return payload, nil +} diff --git a/stackit/internal/services/intake/runner/resource_acc_test.go b/stackit/internal/services/intake/runner/resource_acc_test.go new file mode 100644 index 000000000..7a65bc10a --- /dev/null +++ b/stackit/internal/services/intake/runner/resource_acc_test.go @@ -0,0 +1,160 @@ +package runner_test + +import ( + "context" + "errors" + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +// intakeRunnerResource is the name of the test resource +const intakeRunnerResource = "stackit_intake_runner.example" + +func TestAccIntakeRunner(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckIntakeRunnerDestroy, + Steps: []resource.TestStep{ + // create the runner + { + Config: testutil.IntakeProviderConfig() + testAccIntakeRunnerConfigMinimal("example-runner-minimal"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr(intakeRunnerResource, "name", "example-runner-minimal"), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", "eu01"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), + resource.TestCheckResourceAttr(intakeRunnerResource, "description", ""), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "0"), + ), + }, + // update the runner + { + Config: testutil.IntakeProviderConfig() + testAccIntakeRunnerConfigFull("example-runner-full", "An example runner for Intake", 1024, 1100), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(intakeRunnerResource, "name", "example-runner-full"), + resource.TestCheckResourceAttr(intakeRunnerResource, "description", "An example runner for Intake"), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1100"), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "2"), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.env", "development"), + ), + }, + // importing the runner + { + ResourceName: intakeRunnerResource, + ImportState: true, + ImportStateVerify: true, + }, + // update to remove optional attributes + { + Config: testutil.IntakeProviderConfig() + testAccIntakeRunnerConfigUpdated("example-runner-updated", 1024, 1100), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(intakeRunnerResource, "name", "example-runner-updated"), + resource.TestCheckResourceAttr(intakeRunnerResource, "description", ""), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "0"), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1100"), + ), + }, + }, + }) +} + +func testAccIntakeRunnerConfigMinimal(name string) string { + return fmt.Sprintf(` + resource "stackit_intake_runner" "example" { + project_id = "%s" + name = "%s" + region = "eu01" + max_message_size_kib = 1024 + max_messages_per_hour = 1000 + } + `, + testutil.ProjectId, + name, + ) +} + +func testAccIntakeRunnerConfigFull(name, description string, maxKib, maxPerHour int) string { + return fmt.Sprintf(` + resource "stackit_intake_runner" "example" { + project_id = "%s" + name = "%s" + description = "%s" + max_message_size_kib = %d + max_messages_per_hour = %d + labels = { + "created_by" = "terraform-provider-stackit" + "env" = "development" + } + region = "eu01" + } + `, + testutil.ProjectId, + name, + description, + maxKib, + maxPerHour, + ) +} + +func testAccIntakeRunnerConfigUpdated(name string, maxKib, maxPerHour int) string { + return fmt.Sprintf(` + resource "stackit_intake_runner" "example" { + project_id = "%s" + name = "%s" + description = "" + max_message_size_kib = %d + max_messages_per_hour = %d + labels = {} + region = "eu01" + } + `, + testutil.ProjectId, + name, + maxKib, + maxPerHour, + ) +} + +func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { + ctx := context.Background() + var client *intake.APIClient + var err error + if testutil.IntakeCustomEndpoint == "" { + client, err = intake.NewAPIClient( + sdkConfig.WithRegion("eu01"), + ) + } else { + client, err = intake.NewAPIClient(sdkConfig.WithEndpoint(testutil.IntakeCustomEndpoint)) + } + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_intake_runner" { + continue + } + // Try to find the runner + _, err := client.GetIntakeRunner(ctx, rs.Primary.Attributes["project_id"], rs.Primary.Attributes["region"], rs.Primary.Attributes["runner_id"]).Execute() + if err == nil { + return fmt.Errorf("intake runner with ID %s still exists", rs.Primary.ID) + } + var oapiErr *oapierror.GenericOpenAPIError + if !errors.As(err, &oapiErr) || oapiErr.StatusCode != http.StatusNotFound { + return fmt.Errorf("expected 404 not found, got error: %w", err) + } + } + + return nil +} diff --git a/stackit/internal/services/intake/runner/resource_test.go b/stackit/internal/services/intake/runner/resource_test.go new file mode 100644 index 000000000..921349c64 --- /dev/null +++ b/stackit/internal/services/intake/runner/resource_test.go @@ -0,0 +1,254 @@ +package runner + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" +) + +func TestMapFields(t *testing.T) { + runnerId := uuid.New().String() + tests := []struct { + description string + input *intake.IntakeRunnerResponse + model *Model + expected *Model + wantErr bool + }{ + { + "success", + &intake.IntakeRunnerResponse{ + Id: utils.Ptr(runnerId), + DisplayName: utils.Ptr("name"), + Description: utils.Ptr("description"), + Labels: &map[string]string{"key": "value"}, + MaxMessageSizeKiB: utils.Ptr(int64(1024)), + MaxMessagesPerHour: utils.Ptr(int64(100)), + }, + &Model{ + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + }, + &Model{ + Id: types.StringValue(fmt.Sprintf("pid,eu01,%s", runnerId)), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + RunnerId: types.StringValue(runnerId), + Name: types.StringValue("name"), + Description: types.StringValue("description"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + MaxMessageSizeKiB: types.Int64Value(1024), + MaxMessagesPerHour: types.Int64Value(100), + }, + false, + }, + { + "nil input", + nil, + &Model{}, + nil, + true, + }, + { + "nil model", + &intake.IntakeRunnerResponse{}, + nil, + nil, + true, + }, + { + "empty response", + &intake.IntakeRunnerResponse{ + Id: utils.Ptr(""), + Labels: &map[string]string{}, + }, + &Model{ + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + }, + &Model{ + Id: types.StringValue("pid,eu01,"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + RunnerId: types.StringNull(), + Name: types.StringNull(), + Description: types.StringValue(""), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}), + MaxMessageSizeKiB: types.Int64Null(), + MaxMessagesPerHour: types.Int64Null(), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + err := mapFields(tt.input, tt.model) + if (err != nil) != tt.wantErr { + t.Errorf("mapFields error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if diff := cmp.Diff(tt.expected, tt.model); diff != "" { + t.Errorf("mapFields mismatch (-want +got):\n%s", diff) + } + } + }) + } +} + +func TestToCreatePayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *intake.CreateIntakeRunnerPayload + wantErr bool + }{ + { + "success", + &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("description"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + MaxMessageSizeKiB: types.Int64Value(1024), + MaxMessagesPerHour: types.Int64Value(100), + }, + &intake.CreateIntakeRunnerPayload{ + DisplayName: utils.Ptr("name"), + Description: utils.Ptr("description"), + Labels: utils.Ptr(map[string]string{"key": "value"}), + MaxMessageSizeKiB: utils.Ptr(int64(1024)), + MaxMessagesPerHour: utils.Ptr(int64(100)), + }, + false, + }, + { + "nil model", + nil, + nil, + true, + }, + { + "empty model", + &Model{}, + &intake.CreateIntakeRunnerPayload{ + DisplayName: nil, + Description: nil, + Labels: nil, + MaxMessageSizeKiB: nil, + MaxMessagesPerHour: nil, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + payload, err := toCreatePayload(tt.model) + if (err != nil) != tt.wantErr { + t.Errorf("toCreatePayload error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if diff := cmp.Diff(tt.expected, payload); diff != "" { + t.Errorf("toCreatePayload mismatch (-want +got):\n%s", diff) + } + } + }) + } +} + +func TestToUpdatePayload(t *testing.T) { + tests := []struct { + description string + model *Model + state *Model + expected *intake.UpdateIntakeRunnerPayload + wantErr bool + }{ + { + "success", + &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("description"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + MaxMessageSizeKiB: types.Int64Value(1024), + MaxMessagesPerHour: types.Int64Value(100), + }, + &Model{}, + &intake.UpdateIntakeRunnerPayload{ + DisplayName: conversion.StringValueToPointer(types.StringValue("name")), + Description: conversion.StringValueToPointer(types.StringValue("description")), + Labels: utils.Ptr(map[string]string{"key": "value"}), + MaxMessageSizeKiB: conversion.Int64ValueToPointer(types.Int64Value(1024)), + MaxMessagesPerHour: conversion.Int64ValueToPointer(types.Int64Value(100)), + }, + false, + }, + { + "nil model", + nil, + &Model{}, + nil, + true, + }, + { + "nil state", + &Model{}, + nil, + nil, + true, + }, + { + "empty model", + &Model{}, + &Model{}, + &intake.UpdateIntakeRunnerPayload{ + Description: utils.Ptr(""), + Labels: &map[string]string{}, + }, + false, + }, + { + "unknown values", + &Model{ + Name: types.StringUnknown(), + Description: types.StringUnknown(), + Labels: types.MapUnknown(types.StringType), + MaxMessageSizeKiB: types.Int64Unknown(), + MaxMessagesPerHour: types.Int64Unknown(), + }, + &Model{}, + &intake.UpdateIntakeRunnerPayload{}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + var labels map[string]string + if tt.model != nil && !tt.model.Labels.IsNull() && !tt.model.Labels.IsUnknown() { + diags := tt.model.Labels.ElementsAs(context.Background(), &labels, false) + if diags.HasError() { + t.Fatalf("error preparing test %v", diags) + } + } + + payload, err := toUpdatePayload(tt.model, tt.state) + if (err != nil) != tt.wantErr { + t.Errorf("toUpdatePayload error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if diff := cmp.Diff(tt.expected, payload); diff != "" { + t.Errorf("toUpdatePayload mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/stackit/internal/services/intake/utils/utils.go b/stackit/internal/services/intake/utils/utils.go new file mode 100644 index 000000000..b6357b496 --- /dev/null +++ b/stackit/internal/services/intake/utils/utils.go @@ -0,0 +1,31 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *intake.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.IntakeCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.IntakeCustomEndpoint)) + } else { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion())) + } + apiClient, err := intake.NewAPIClient(apiClientConfigOptions...) + if err != nil { + core.LogAndAddError(ctx, diags, "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 nil + } + + return apiClient +} diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index 5be2e7281..553e54774 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -98,6 +98,7 @@ var ( ServiceAccountCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVICE_ACCOUNT_CUSTOM_ENDPOINT", providerName: "service_account_custom_endpoint"} TokenCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TOKEN_CUSTOM_ENDPOINT", providerName: "token_custom_endpoint"} SKECustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SKE_CUSTOM_ENDPOINT", providerName: "ske_custom_endpoint"} + IntakeCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_INTAKE_CUSTOM_ENDPOINT", providerName: "intake_custom_endpoint"} allCustomEndpoints = []customEndpointConfig{ ALBCustomEndpoint, diff --git a/stackit/provider.go b/stackit/provider.go index f08e2e6af..64727e089 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -61,6 +61,7 @@ import ( iaasServiceAccountAttach "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/serviceaccountattach" iaasVolume "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/volume" iaasVolumeAttach "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/volumeattach" + intakeRunner "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/intake/runner" kmsKey "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/key" kmsKeyRing "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/keyring" kmsWrappingKey "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/wrapping-key" @@ -170,6 +171,7 @@ type providerModel struct { EdgeCloudCustomEndpoint types.String `tfsdk:"edgecloud_custom_endpoint"` GitCustomEndpoint types.String `tfsdk:"git_custom_endpoint"` IaaSCustomEndpoint types.String `tfsdk:"iaas_custom_endpoint"` + IntakeCustomEndpoint types.String `tfsdk:"intake_custom_endpoint"` KmsCustomEndpoint types.String `tfsdk:"kms_custom_endpoint"` LoadBalancerCustomEndpoint types.String `tfsdk:"loadbalancer_custom_endpoint"` LogMeCustomEndpoint types.String `tfsdk:"logme_custom_endpoint"` @@ -359,6 +361,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["iaas_custom_endpoint"], }, + "intake_custom_endpoint": schema.StringAttribute{ + Optional: true, + Description: descriptions["intake_custom_endpoint"], + }, "kms_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["kms_custom_endpoint"], @@ -518,6 +524,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, setStringField(providerConfig.EdgeCloudCustomEndpoint, func(v string) { providerData.EdgeCloudCustomEndpoint = v }) setStringField(providerConfig.GitCustomEndpoint, func(v string) { providerData.GitCustomEndpoint = v }) setStringField(providerConfig.IaaSCustomEndpoint, func(v string) { providerData.IaaSCustomEndpoint = v }) + setStringField(providerConfig.IntakeCustomEndpoint, func(v string) { providerData.IntakeCustomEndpoint = v }) setStringField(providerConfig.KmsCustomEndpoint, func(v string) { providerData.KMSCustomEndpoint = v }) setStringField(providerConfig.LoadBalancerCustomEndpoint, func(v string) { providerData.LoadBalancerCustomEndpoint = v }) setStringField(providerConfig.LogMeCustomEndpoint, func(v string) { providerData.LogMeCustomEndpoint = v }) @@ -737,6 +744,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { iaasSecurityGroupRule.NewSecurityGroupRuleResource, iaasRoutingTable.NewRoutingTableResource, iaasRoutingTableRoute.NewRoutingTableRouteResource, + intakeRunner.NewRunnerResource, kmsKey.NewKeyResource, kmsKeyRing.NewKeyRingResource, kmsWrappingKey.NewWrappingKeyResource, From 2c59ef1a29dd80c9e0338e92655a4fafd5c3751c Mon Sep 17 00:00:00 2001 From: Devansh Thakur Date: Mon, 8 Dec 2025 09:07:58 +0100 Subject: [PATCH 02/41] added example of intake runner --- .../resources/stackit_intake_runner/resource.tf | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 examples/resources/stackit_intake_runner/resource.tf diff --git a/examples/resources/stackit_intake_runner/resource.tf b/examples/resources/stackit_intake_runner/resource.tf new file mode 100644 index 000000000..ceda583ff --- /dev/null +++ b/examples/resources/stackit_intake_runner/resource.tf @@ -0,0 +1,17 @@ +resource "stackit_intake_runner" "example" { + project_id = var.project_id + name = "example-runner-full" + description = "An example runner for STACKIT Intake" + max_message_size_kib = 2048 + max_messages_per_hour = 1500 + labels = { + "created_by" = "terraform-example" + "env" = "production" + } + region = var.region +} + +import { + to = stackit_intake_runner.example + id = "${var.project_id},${var.region},${var.runner_id}" +} \ No newline at end of file From b2fd78ef76fccf30e8263abfff626165bb784d4a Mon Sep 17 00:00:00 2001 From: Devansh Thakur Date: Tue, 16 Dec 2025 22:19:46 +0100 Subject: [PATCH 03/41] added datasource for intake runner --- .../services/intake/runner/data_source.go | 167 ++++++++++++++++++ .../services/intake/runner/resource.go | 2 +- stackit/provider.go | 1 + 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 stackit/internal/services/intake/runner/data_source.go diff --git a/stackit/internal/services/intake/runner/data_source.go b/stackit/internal/services/intake/runner/data_source.go new file mode 100644 index 000000000..84728b714 --- /dev/null +++ b/stackit/internal/services/intake/runner/data_source.go @@ -0,0 +1,167 @@ +package runner + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "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/oapierror" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + intakeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/intake/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + + "github.com/stackitcloud/stackit-sdk-go/services/intake" +) + +// Ensure the implementation satisfies the expected interfaces +var ( + _ datasource.DataSource = &runnerDataSource{} +) + +// NewRunnerDataSource is a helper function to simplify the provider implementation +func NewRunnerDataSource() datasource.DataSource { + return &runnerDataSource{} +} + +type runnerDataSource struct { + client *intake.APIClient +} + +func (r *runnerDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_intake_runner" +} + +// Configure adds the provider configured client to the data source +func (r *runnerDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := intakeUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + r.client = apiClient + tflog.Info(ctx, "Intake runner client configured for data source") +} + +// Schema defines the schema for the data source +func (r *runnerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + descriptions := map[string]string{ + "main": "Datasource for STACKIT Intake Runner.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`runner_id`\".", + "project_id": "STACKIT Project ID to which the runner is associated.", + "runner_id": "The runner ID.", + "name": "The name of the runner.", + "region": "The resource region.", + "description": "The description of the runner.", + "labels": "User-defined labels.", + "max_message_size_kib": "The maximum message size in KiB.", + "max_messages_per_hour": "The maximum number of messages per hour.", + } + + 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(), + validate.NoSeparator(), + }, + }, + "runner_id": schema.StringAttribute{ + Description: descriptions["runner_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Description: descriptions["region"], + Required: true, + }, + "name": schema.StringAttribute{ + Description: descriptions["name"], + Computed: true, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Computed: true, + }, + "labels": schema.MapAttribute{ + Description: descriptions["labels"], + ElementType: types.StringType, + Computed: true, + }, + "max_message_size_kib": schema.Int64Attribute{ + Description: descriptions["max_message_size_kib"], + Computed: true, + }, + "max_messages_per_hour": schema.Int64Attribute{ + Description: descriptions["max_messages_per_hour"], + Computed: true, + }, + }, + } +} + +// Read refreshes the Terraform state with the latest data. +func (r *runnerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + resp.Diagnostics.Append(req.Config.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() + runnerId := model.RunnerId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "runner_id", runnerId) + + runnerResp, err := r.client.GetIntakeRunner(ctx, projectId, region, runnerId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Runner with ID %s not found in project %s and region %s", runnerId, projectId, region)) + return + } + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(runnerResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Intake runner read") +} diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index d9de6667a..8c7a6b93a 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -115,7 +115,7 @@ func (r *runnerResource) ModifyPlan(ctx context.Context, req resource.ModifyPlan func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { descriptions := map[string]string{ "main": "Manages STACKIT Intake Runner.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`runner_id`\".", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`runner_id`\".", "project_id": "STACKIT Project ID to which the runner is associated.", "runner_id": "The runner ID.", "name": "The name of the runner.", diff --git a/stackit/provider.go b/stackit/provider.go index 64727e089..5f3096a33 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -652,6 +652,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource iaasRoutingTables.NewRoutingTablesDataSource, iaasRoutingTableRoutes.NewRoutingTableRoutesDataSource, iaasSecurityGroupRule.NewSecurityGroupRuleDataSource, + intakeRunner.NewRunnerDataSource, kmsKey.NewKeyDataSource, kmsKeyRing.NewKeyRingDataSource, kmsWrappingKey.NewWrappingKeyDataSource, From c98792cd6c936fd72d3b9831621675588c78d246 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Thu, 22 Jan 2026 11:33:04 +0100 Subject: [PATCH 04/41] Add docs --- docs/data-sources/intake_runner.md | 31 ++++++++++++++++++++++++++++++ docs/resources/intake_runner.md | 24 +++++++++++++++++++++-- 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 docs/data-sources/intake_runner.md diff --git a/docs/data-sources/intake_runner.md b/docs/data-sources/intake_runner.md new file mode 100644 index 000000000..d914e5d61 --- /dev/null +++ b/docs/data-sources/intake_runner.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_intake_runner Data Source - stackit" +subcategory: "" +description: |- + Datasource for STACKIT Intake Runner. +--- + +# stackit_intake_runner (Data Source) + +Datasource for STACKIT Intake Runner. + + + + +## Schema + +### Required + +- `project_id` (String) STACKIT Project ID to which the runner is associated. +- `region` (String) The resource region. +- `runner_id` (String) The runner ID. + +### Read-Only + +- `description` (String) The description of the runner. +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`runner_id`". +- `labels` (Map of String) User-defined labels. +- `max_message_size_kib` (Number) The maximum message size in KiB. +- `max_messages_per_hour` (Number) The maximum number of messages per hour. +- `name` (String) The name of the runner. diff --git a/docs/resources/intake_runner.md b/docs/resources/intake_runner.md index 65a5c3206..9a246d535 100644 --- a/docs/resources/intake_runner.md +++ b/docs/resources/intake_runner.md @@ -10,7 +10,27 @@ description: |- Manages STACKIT Intake Runner. - +## Example Usage + +```terraform +resource "stackit_intake_runner" "example" { + project_id = var.project_id + name = "example-runner-full" + description = "An example runner for STACKIT Intake" + max_message_size_kib = 2048 + max_messages_per_hour = 1500 + labels = { + "created_by" = "terraform-example" + "env" = "production" + } + region = var.region +} + +import { + to = stackit_intake_runner.example + id = "${var.project_id},${var.region},${var.runner_id}" +} +``` ## Schema @@ -30,5 +50,5 @@ Manages STACKIT Intake Runner. ### Read-Only -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`runner_id`". +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`runner_id`". - `runner_id` (String) The runner ID. From 876408daf4a3c8c21138b7d264b8277659d9f371 Mon Sep 17 00:00:00 2001 From: BipBopBipBop Date: Thu, 22 Jan 2026 11:56:50 +0100 Subject: [PATCH 05/41] Update stackit/internal/services/intake/runner/data_source.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ruben Hönle --- stackit/internal/services/intake/runner/data_source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackit/internal/services/intake/runner/data_source.go b/stackit/internal/services/intake/runner/data_source.go index 84728b714..200fe0c9c 100644 --- a/stackit/internal/services/intake/runner/data_source.go +++ b/stackit/internal/services/intake/runner/data_source.go @@ -45,7 +45,7 @@ func (r *runnerDataSource) Configure(ctx context.Context, req datasource.Configu return } - apiClient := intakeUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + r.client := intakeUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } From 47a32376f5ffba47f36b9a6e9e9e10e66e0313ee Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Thu, 22 Jan 2026 17:12:32 +0100 Subject: [PATCH 06/41] Remove region field --- docs/data-sources/intake_runner.md | 1 - docs/index.md | 2 +- .../services/intake/runner/data_source.go | 7 +------ .../services/intake/runner/resource_acc_test.go | 15 +++++++-------- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/docs/data-sources/intake_runner.md b/docs/data-sources/intake_runner.md index d914e5d61..60950b2f1 100644 --- a/docs/data-sources/intake_runner.md +++ b/docs/data-sources/intake_runner.md @@ -18,7 +18,6 @@ Datasource for STACKIT Intake Runner. ### Required - `project_id` (String) STACKIT Project ID to which the runner is associated. -- `region` (String) The resource region. - `runner_id` (String) The runner ID. ### Read-Only diff --git a/docs/index.md b/docs/index.md index 0b10f72ce..300fcbd63 100644 --- a/docs/index.md +++ b/docs/index.md @@ -175,7 +175,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de - `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: iam, routing-tables, network - `git_custom_endpoint` (String) Custom endpoint for the Git service - `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service -- `intake_custom_endpoint` (String) +- `intake_custom_endpoint` (String) Custom endpoint for the Intake service - `kms_custom_endpoint` (String) Custom endpoint for the KMS service - `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service - `logme_custom_endpoint` (String) Custom endpoint for the LogMe service diff --git a/stackit/internal/services/intake/runner/data_source.go b/stackit/internal/services/intake/runner/data_source.go index 200fe0c9c..ffa1aa1d1 100644 --- a/stackit/internal/services/intake/runner/data_source.go +++ b/stackit/internal/services/intake/runner/data_source.go @@ -45,7 +45,7 @@ func (r *runnerDataSource) Configure(ctx context.Context, req datasource.Configu return } - r.client := intakeUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + apiClient := intakeUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -61,7 +61,6 @@ func (r *runnerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, "project_id": "STACKIT Project ID to which the runner is associated.", "runner_id": "The runner ID.", "name": "The name of the runner.", - "region": "The resource region.", "description": "The description of the runner.", "labels": "User-defined labels.", "max_message_size_kib": "The maximum message size in KiB.", @@ -91,10 +90,6 @@ func (r *runnerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, validate.NoSeparator(), }, }, - "region": schema.StringAttribute{ - Description: descriptions["region"], - Required: true, - }, "name": schema.StringAttribute{ Description: descriptions["name"], Computed: true, diff --git a/stackit/internal/services/intake/runner/resource_acc_test.go b/stackit/internal/services/intake/runner/resource_acc_test.go index 7a65bc10a..948c879ad 100644 --- a/stackit/internal/services/intake/runner/resource_acc_test.go +++ b/stackit/internal/services/intake/runner/resource_acc_test.go @@ -29,7 +29,6 @@ func TestAccIntakeRunner(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ProjectId), resource.TestCheckResourceAttr(intakeRunnerResource, "name", "example-runner-minimal"), - resource.TestCheckResourceAttr(intakeRunnerResource, "region", "eu01"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), resource.TestCheckResourceAttr(intakeRunnerResource, "description", ""), resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "0"), @@ -74,7 +73,6 @@ func testAccIntakeRunnerConfigMinimal(name string) string { resource "stackit_intake_runner" "example" { project_id = "%s" name = "%s" - region = "eu01" max_message_size_kib = 1024 max_messages_per_hour = 1000 } @@ -96,7 +94,6 @@ func testAccIntakeRunnerConfigFull(name, description string, maxKib, maxPerHour "created_by" = "terraform-provider-stackit" "env" = "development" } - region = "eu01" } `, testutil.ProjectId, @@ -116,7 +113,6 @@ func testAccIntakeRunnerConfigUpdated(name string, maxKib, maxPerHour int) strin max_message_size_kib = %d max_messages_per_hour = %d labels = {} - region = "eu01" } `, testutil.ProjectId, @@ -131,9 +127,7 @@ func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { var client *intake.APIClient var err error if testutil.IntakeCustomEndpoint == "" { - client, err = intake.NewAPIClient( - sdkConfig.WithRegion("eu01"), - ) + client, err = intake.NewAPIClient() } else { client, err = intake.NewAPIClient(sdkConfig.WithEndpoint(testutil.IntakeCustomEndpoint)) } @@ -148,7 +142,12 @@ func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { // Try to find the runner _, err := client.GetIntakeRunner(ctx, rs.Primary.Attributes["project_id"], rs.Primary.Attributes["region"], rs.Primary.Attributes["runner_id"]).Execute() if err == nil { - return fmt.Errorf("intake runner with ID %s still exists", rs.Primary.ID) + err = client.DeleteIntakeRunner(ctx, rs.Primary.Attributes["project_id"], rs.Primary.Attributes["region"], rs.Primary.Attributes["runner_id"]).Execute() + if err != nil { + return fmt.Errorf("intake runner with ID %s still existed, got an error removing", rs.Primary.ID, err) + } + + return fmt.Errorf("intake runner with ID %s still existed", rs.Primary.ID) } var oapiErr *oapierror.GenericOpenAPIError if !errors.As(err, &oapiErr) || oapiErr.StatusCode != http.StatusNotFound { From f292331576510337e3d5874f57a27a36886e5951 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Thu, 22 Jan 2026 17:12:50 +0100 Subject: [PATCH 07/41] Add description to custom endpoint --- stackit/provider.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stackit/provider.go b/stackit/provider.go index 5f3096a33..fa25475e0 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -227,6 +227,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "edgecloud_custom_endpoint": "Custom endpoint for the Edge Cloud service", "git_custom_endpoint": "Custom endpoint for the Git service", "iaas_custom_endpoint": "Custom endpoint for the IaaS service", + "intake_custom_endpoint": "Custom endpoint for the Intake service", "kms_custom_endpoint": "Custom endpoint for the KMS service", "mongodbflex_custom_endpoint": "Custom endpoint for the MongoDB Flex service", "modelserving_custom_endpoint": "Custom endpoint for the AI Model Serving service", @@ -255,6 +256,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "enable_beta_resources": "Enable beta resources. Default is false.", "experiments": fmt.Sprintf("Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: %v", strings.Join(features.AvailableExperiments, ", ")), } + resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "credentials_path": schema.StringAttribute{ From 05ef09d565851e46d2b02676e83b712e898e8731 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Thu, 22 Jan 2026 17:47:36 +0100 Subject: [PATCH 08/41] Adjust fields for resource.go (still missing region) --- .../services/intake/runner/resource.go | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index 8c7a6b93a..4d2a208b1 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -489,18 +489,8 @@ func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, er } payload := &intake.UpdateIntakeRunnerPayload{} - - if !model.Name.IsUnknown() { - payload.DisplayName = conversion.StringValueToPointer(model.Name) - } - - if !model.MaxMessageSizeKiB.IsUnknown() { - payload.MaxMessageSizeKiB = conversion.Int64ValueToPointer(model.MaxMessageSizeKiB) - } - - if !model.MaxMessagesPerHour.IsUnknown() { - payload.MaxMessagesPerHour = conversion.Int64ValueToPointer(model.MaxMessagesPerHour) - } + payload.MaxMessageSizeKiB = conversion.Int64ValueToPointer(model.MaxMessageSizeKiB) + payload.MaxMessagesPerHour = conversion.Int64ValueToPointer(model.MaxMessagesPerHour) // Handle optional fields if !model.Description.IsUnknown() || model.Description.IsNull() { @@ -513,10 +503,7 @@ func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, er var labels map[string]string if !model.Labels.IsUnknown() { - if model.Labels.IsNull() { - labels = map[string]string{} - payload.Labels = &labels - } else { + if !model.Labels.IsNull() { diags := model.Labels.ElementsAs(context.Background(), &labels, false) if diags.HasError() { return nil, fmt.Errorf("failed to convert labels: %w", core.DiagsToError(diags)) From 941944657adf51361ace28633f04946b532ed9e4 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 13:33:43 +0100 Subject: [PATCH 09/41] Reintroduce region as optional field in data resource --- .../services/intake/runner/data_source.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/stackit/internal/services/intake/runner/data_source.go b/stackit/internal/services/intake/runner/data_source.go index ffa1aa1d1..f44dfaf69 100644 --- a/stackit/internal/services/intake/runner/data_source.go +++ b/stackit/internal/services/intake/runner/data_source.go @@ -31,7 +31,8 @@ func NewRunnerDataSource() datasource.DataSource { } type runnerDataSource struct { - client *intake.APIClient + client *intake.APIClient + providerData core.ProviderData } func (r *runnerDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -40,12 +41,13 @@ func (r *runnerDataSource) Metadata(_ context.Context, req datasource.MetadataRe // Configure adds the provider configured client to the data source func (r *runnerDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + var ok bool + r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) if !ok { return } - apiClient := intakeUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + apiClient := intakeUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -65,6 +67,7 @@ func (r *runnerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, "labels": "User-defined labels.", "max_message_size_kib": "The maximum message size in KiB.", "max_messages_per_hour": "The maximum number of messages per hour.", + "region": "The resource region. If not defined, the provider region is used.", } resp.Schema = schema.Schema{ @@ -111,6 +114,10 @@ func (r *runnerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, Description: descriptions["max_messages_per_hour"], Computed: true, }, + "region": schema.StringAttribute{ + Optional: true, + Description: descriptions["region"], + }, }, } } @@ -126,7 +133,7 @@ func (r *runnerDataSource) Read(ctx context.Context, req datasource.ReadRequest, ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() - region := model.Region.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) runnerId := model.RunnerId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) @@ -147,7 +154,7 @@ func (r *runnerDataSource) Read(ctx context.Context, req datasource.ReadRequest, ctx = core.LogResponse(ctx) - err = mapFields(runnerResp, &model) + err = mapFields(runnerResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Processing API payload: %v", err)) return From f1ac8b0f8180d8fae84781f5c51dda7c1fc64f5d Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 13:51:35 +0100 Subject: [PATCH 10/41] Adjust resource implementation and implement review comments --- .../services/intake/runner/resource.go | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index 4d2a208b1..40b02599b 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -7,7 +7,6 @@ import ( "net/http" "strings" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -18,7 +17,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" intakeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/intake/utils" @@ -42,12 +40,12 @@ type Model struct { Id types.String `tfsdk:"id"` // needed by TF ProjectId types.String `tfsdk:"project_id"` RunnerId types.String `tfsdk:"runner_id"` - Region types.String `tfsdk:"region"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` Labels types.Map `tfsdk:"labels"` MaxMessageSizeKiB types.Int64 `tfsdk:"max_message_size_kib"` MaxMessagesPerHour types.Int64 `tfsdk:"max_messages_per_hour"` + Region types.String `tfsdk:"region"` } // NewRunnerResource is a helper function to simplify the provider implementation. @@ -132,12 +130,16 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res "id": schema.StringAttribute{ Description: descriptions["id"], Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "project_id": schema.StringAttribute{ Description: descriptions["project_id"], Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), }, Validators: []validator.String{ validate.UUID(), @@ -154,6 +156,9 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res "name": schema.StringAttribute{ Description: descriptions["name"], Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "description": schema.StringAttribute{ Description: descriptions["description"], @@ -187,9 +192,6 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, - Validators: []validator.String{ - stringvalidator.OneOf("eu01"), // Currently Intake supports only EU01 region - }, }, }, } @@ -217,7 +219,7 @@ func (r *runnerResource) Create(ctx context.Context, req resource.CreateRequest, return } - // Create new bar + // Create new runner runnerResp, err := r.client.CreateIntakeRunner(ctx, projectId, region).CreateIntakeRunnerPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating runner", fmt.Sprintf("Calling API: %v", err)) @@ -232,7 +234,7 @@ func (r *runnerResource) Create(ctx context.Context, req resource.CreateRequest, return } - err = mapFields(runnerResp, &model) + err = mapFields(runnerResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating runner", fmt.Sprintf("Processing API payload: %v", err)) return @@ -277,7 +279,7 @@ func (r *runnerResource) Read(ctx context.Context, req resource.ReadRequest, res ctx = core.LogResponse(ctx) // Map response body to schema - err = mapFields(runnerResp, &model) + err = mapFields(runnerResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Processing API payload: %v", err)) return @@ -304,7 +306,7 @@ func (r *runnerResource) Update(ctx context.Context, req resource.UpdateRequest, projectId := model.ProjectId.ValueString() runnerId := model.RunnerId.ValueString() - region := r.providerData.GetRegionWithOverride(model.Region) + region := model.Region.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "runner_id", runnerId) ctx = tflog.SetField(ctx, "region", region) @@ -330,7 +332,7 @@ func (r *runnerResource) Update(ctx context.Context, req resource.UpdateRequest, } // Map response body to schema - err = mapFields(runnerResp, &model) + err = mapFields(runnerResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating runner", fmt.Sprintf("Processing API response: %v", err)) return @@ -361,7 +363,7 @@ func (r *runnerResource) Delete(ctx context.Context, req resource.DeleteRequest, ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "runner_id", runnerId) - // Delete existing bar + // Delete existing runner err := r.client.DeleteIntakeRunner(ctx, projectId, region, runnerId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError @@ -407,7 +409,7 @@ func (r *runnerResource) ImportState(ctx context.Context, req resource.ImportSta } // Maps runner fields to the provider internal model -func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model) error { +func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region string) error { if runnerResp == nil { return fmt.Errorf("response input is nil") } @@ -422,7 +424,7 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model) error { model.Id = utils.BuildInternalTerraformId( model.ProjectId.ValueString(), - model.Region.ValueString(), + region, runnerId, ) @@ -447,12 +449,13 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model) error { } else { model.Description = types.StringPointerValue(runnerResp.Description) } + model.Region = types.StringValue(region) model.MaxMessageSizeKiB = types.Int64PointerValue(runnerResp.MaxMessageSizeKiB) model.MaxMessagesPerHour = types.Int64PointerValue(runnerResp.MaxMessagesPerHour) return nil } -// Build CreateBarPayload from provider's model +// Build CreateIntakeRunnerPayload from provider's model func toCreatePayload(model *Model) (*intake.CreateIntakeRunnerPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") @@ -480,6 +483,7 @@ func toCreatePayload(model *Model) (*intake.CreateIntakeRunnerPayload, error) { }, nil } +// Build UpdateIntakeRunnerPayload from provider's model func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, error) { if model == nil { return nil, fmt.Errorf("model is nil") @@ -492,14 +496,9 @@ func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, er payload.MaxMessageSizeKiB = conversion.Int64ValueToPointer(model.MaxMessageSizeKiB) payload.MaxMessagesPerHour = conversion.Int64ValueToPointer(model.MaxMessagesPerHour) - // Handle optional fields - if !model.Description.IsUnknown() || model.Description.IsNull() { - if model.Description.IsNull() { - payload.Description = sdkUtils.Ptr("") - } else { - payload.Description = conversion.StringValueToPointer(model.Description) - } - } + // Optional fields + payload.DisplayName = conversion.StringValueToPointer(model.Name) + payload.Description = conversion.StringValueToPointer(model.Description) var labels map[string]string if !model.Labels.IsUnknown() { From 276a8831cbafdf4155b9656cf505b663d582e3a7 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 14:45:43 +0100 Subject: [PATCH 11/41] Reshape resource_acc_test --- .../services/intake/resource_acc_test.go | 199 ++++++++++++++++++ .../services/intake/runner/resource.go | 12 +- .../intake/runner/resource_acc_test.go | 159 -------------- .../services/intake/testdata/resource-max.tf | 15 ++ .../services/intake/testdata/resource-min.tf | 10 + 5 files changed, 229 insertions(+), 166 deletions(-) create mode 100644 stackit/internal/services/intake/resource_acc_test.go delete mode 100644 stackit/internal/services/intake/runner/resource_acc_test.go create mode 100644 stackit/internal/services/intake/testdata/resource-max.tf create mode 100644 stackit/internal/services/intake/testdata/resource-min.tf diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go new file mode 100644 index 000000000..4c8cb9fd9 --- /dev/null +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -0,0 +1,199 @@ +package intake_test + +import ( + "context" + _ "embed" + "errors" + "fmt" + "maps" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +//go:embed testdata/resource-min.tf +var resourceMin string + +//go:embed testdata/resource-max.tf +var resourceMax string + +const intakeRunnerResource = "stackit_intake_runner.example" + +const ( + intakeRunnerMinName = "intake-min-runner" + intakeRunnerMinNameUpdated = "intake-min-runner-upd" + intakeRunnerMaxName = "intake-max-runner" + intakeRunnerMaxNameUpdated = "intake-max-runner-upd" +) + +var testConfigVarsMin = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable(intakeRunnerMinName), +} + +var testConfigVarsMax = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable(intakeRunnerMaxName), +} + +func testConfigVarsMinUpdated() config.Variables { + tempConfig := make(config.Variables, len(testConfigVarsMin)) + maps.Copy(tempConfig, testConfigVarsMin) + tempConfig["name"] = config.StringVariable(intakeRunnerMinNameUpdated) + return tempConfig +} + +func testConfigVarsMaxUpdated() config.Variables { + tempConfig := make(config.Variables, len(testConfigVarsMax)) + maps.Copy(tempConfig, testConfigVarsMax) + tempConfig["name"] = config.StringVariable(intakeRunnerMaxNameUpdated) + return tempConfig +} + +func TestAccIntakeRunnerMin(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckIntakeRunnerDestroy, + Steps: []resource.TestStep{ + // Create the minimum runner from the HCL file + { + ConfigVariables: testConfigVarsMin, + Config: testutil.IntakeProviderConfig() + "\n" + resourceMin, + Check: resource.ComposeAggregateTestCheckFunc( + // Verify project_id, name and the existence of runner_id + resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMinName), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), + ), + }, + // Data source check: creates config that includes resource and data source + { + ConfigVariables: testConfigVarsMin, + Config: fmt.Sprintf(` + %s + data "stackit_intake_runner" "example" { + project_id = %s.project_id + runner_id = %s.runner_id + region = %s.region + }`, testutil.IntakeProviderConfig()+"\n"+resourceMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), + Check: resource.ComposeAggregateTestCheckFunc( + // Make sure it's correctly found resource by comparing runner_id attribute + resource.TestCheckResourceAttrPair(intakeRunnerResource, "runner_id", "data.stackit_intake_runner.example", "runner_id"), + ), + }, + // Simulate terraform import + { + ConfigVariables: testConfigVarsMin, + Config: testutil.IntakeProviderConfig() + "\n" + resourceMin, + ResourceName: intakeRunnerResource, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + // Construct ID string + r, ok := s.RootModule().Resources[intakeRunnerResource] + if !ok { + return "", fmt.Errorf("couldn't find resource %s", intakeRunnerResource) + } + return fmt.Sprintf("%s,%s,%s", r.Primary.Attributes["project_id"], r.Primary.Attributes["region"], r.Primary.Attributes["runner_id"]), nil + }, + }, + // Update check: verifies API updated resource name without crashing + { + ConfigVariables: testConfigVarsMinUpdated(), + Config: testutil.IntakeProviderConfig() + "\n" + resourceMin, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMinNameUpdated), + ), + }, + }, + }) +} + +func TestAccIntakeRunnerMax(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckIntakeRunnerDestroy, + Steps: []resource.TestStep{ + // Create the max intake runner from HCL files and verify comparison + { + ConfigVariables: testConfigVarsMax, + Config: testutil.IntakeProviderConfig() + "\n" + resourceMax, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMaxName), + resource.TestCheckResourceAttr(intakeRunnerResource, "description", "An example runner for Intake"), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), + ), + }, + // Update and verify changes are reflected + { + ConfigVariables: testConfigVarsMaxUpdated(), + Config: testutil.IntakeProviderConfig() + "\n" + resourceMax, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMaxNameUpdated), + ), + }, + }, + }) +} + +// testAccCheckIntakeRunnerDestroy act as independent auditor to verify destroy operation +func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { + // Create own raw API client + ctx := context.Background() + var client *intake.APIClient + var err error + + // todo: check this again + effectiveRegion := testutil.Region + if effectiveRegion == "" { + effectiveRegion = "eu01" + } + + if testutil.IntakeCustomEndpoint == "" { + client, err = intake.NewAPIClient(sdkConfig.WithRegion(effectiveRegion)) + } else { + client, err = intake.NewAPIClient( + sdkConfig.WithEndpoint(testutil.IntakeCustomEndpoint), + sdkConfig.WithRegion(effectiveRegion), + ) + } + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + // Loop through resources that should have been deleted + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_intake_runner" { + continue + } + + pID := rs.Primary.Attributes["project_id"] + reg := rs.Primary.Attributes["region"] + rID := rs.Primary.Attributes["runner_id"] + + // If it still exists, destroy operation was unsuccessful + _, err := client.GetIntakeRunner(ctx, pID, reg, rID).Execute() + if err == nil { + // Delete to prevent orphaned instances + errDel := client.DeleteIntakeRunner(ctx, pID, reg, rID).Execute() + if errDel != nil { + return fmt.Errorf("resource leaked and manual cleanup failed: %w", errDel) + } + + return fmt.Errorf("intake runner %s still exists in region %s", rID, reg) + } + + var oapiErr *oapierror.GenericOpenAPIError + if !errors.As(err, &oapiErr) || oapiErr.StatusCode != http.StatusNotFound { + return fmt.Errorf("unexpected error checking destruction: %w", err) + } + } + return nil +} diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index 40b02599b..ebea2b85a 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -501,14 +501,12 @@ func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, er payload.Description = conversion.StringValueToPointer(model.Description) var labels map[string]string - if !model.Labels.IsUnknown() { - if !model.Labels.IsNull() { - diags := model.Labels.ElementsAs(context.Background(), &labels, false) - if diags.HasError() { - return nil, fmt.Errorf("failed to convert labels: %w", core.DiagsToError(diags)) - } - payload.Labels = &labels + if !model.Labels.IsUnknown() && !model.Labels.IsNull() { + diags := model.Labels.ElementsAs(context.Background(), &labels, false) + if diags.HasError() { + return nil, fmt.Errorf("failed to convert labels: %w", core.DiagsToError(diags)) } + payload.Labels = &labels } return payload, nil diff --git a/stackit/internal/services/intake/runner/resource_acc_test.go b/stackit/internal/services/intake/runner/resource_acc_test.go deleted file mode 100644 index 948c879ad..000000000 --- a/stackit/internal/services/intake/runner/resource_acc_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package runner_test - -import ( - "context" - "errors" - "fmt" - "net/http" - "testing" - - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/terraform" - sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - "github.com/stackitcloud/stackit-sdk-go/services/intake" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" -) - -// intakeRunnerResource is the name of the test resource -const intakeRunnerResource = "stackit_intake_runner.example" - -func TestAccIntakeRunner(t *testing.T) { - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckIntakeRunnerDestroy, - Steps: []resource.TestStep{ - // create the runner - { - Config: testutil.IntakeProviderConfig() + testAccIntakeRunnerConfigMinimal("example-runner-minimal"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ProjectId), - resource.TestCheckResourceAttr(intakeRunnerResource, "name", "example-runner-minimal"), - resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), - resource.TestCheckResourceAttr(intakeRunnerResource, "description", ""), - resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "0"), - ), - }, - // update the runner - { - Config: testutil.IntakeProviderConfig() + testAccIntakeRunnerConfigFull("example-runner-full", "An example runner for Intake", 1024, 1100), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "name", "example-runner-full"), - resource.TestCheckResourceAttr(intakeRunnerResource, "description", "An example runner for Intake"), - resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), - resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1100"), - resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "2"), - resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), - resource.TestCheckResourceAttr(intakeRunnerResource, "labels.env", "development"), - ), - }, - // importing the runner - { - ResourceName: intakeRunnerResource, - ImportState: true, - ImportStateVerify: true, - }, - // update to remove optional attributes - { - Config: testutil.IntakeProviderConfig() + testAccIntakeRunnerConfigUpdated("example-runner-updated", 1024, 1100), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "name", "example-runner-updated"), - resource.TestCheckResourceAttr(intakeRunnerResource, "description", ""), - resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "0"), - resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), - resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1100"), - ), - }, - }, - }) -} - -func testAccIntakeRunnerConfigMinimal(name string) string { - return fmt.Sprintf(` - resource "stackit_intake_runner" "example" { - project_id = "%s" - name = "%s" - max_message_size_kib = 1024 - max_messages_per_hour = 1000 - } - `, - testutil.ProjectId, - name, - ) -} - -func testAccIntakeRunnerConfigFull(name, description string, maxKib, maxPerHour int) string { - return fmt.Sprintf(` - resource "stackit_intake_runner" "example" { - project_id = "%s" - name = "%s" - description = "%s" - max_message_size_kib = %d - max_messages_per_hour = %d - labels = { - "created_by" = "terraform-provider-stackit" - "env" = "development" - } - } - `, - testutil.ProjectId, - name, - description, - maxKib, - maxPerHour, - ) -} - -func testAccIntakeRunnerConfigUpdated(name string, maxKib, maxPerHour int) string { - return fmt.Sprintf(` - resource "stackit_intake_runner" "example" { - project_id = "%s" - name = "%s" - description = "" - max_message_size_kib = %d - max_messages_per_hour = %d - labels = {} - } - `, - testutil.ProjectId, - name, - maxKib, - maxPerHour, - ) -} - -func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { - ctx := context.Background() - var client *intake.APIClient - var err error - if testutil.IntakeCustomEndpoint == "" { - client, err = intake.NewAPIClient() - } else { - client, err = intake.NewAPIClient(sdkConfig.WithEndpoint(testutil.IntakeCustomEndpoint)) - } - if err != nil { - return fmt.Errorf("creating client: %w", err) - } - - for _, rs := range s.RootModule().Resources { - if rs.Type != "stackit_intake_runner" { - continue - } - // Try to find the runner - _, err := client.GetIntakeRunner(ctx, rs.Primary.Attributes["project_id"], rs.Primary.Attributes["region"], rs.Primary.Attributes["runner_id"]).Execute() - if err == nil { - err = client.DeleteIntakeRunner(ctx, rs.Primary.Attributes["project_id"], rs.Primary.Attributes["region"], rs.Primary.Attributes["runner_id"]).Execute() - if err != nil { - return fmt.Errorf("intake runner with ID %s still existed, got an error removing", rs.Primary.ID, err) - } - - return fmt.Errorf("intake runner with ID %s still existed", rs.Primary.ID) - } - var oapiErr *oapierror.GenericOpenAPIError - if !errors.As(err, &oapiErr) || oapiErr.StatusCode != http.StatusNotFound { - return fmt.Errorf("expected 404 not found, got error: %w", err) - } - } - - return nil -} diff --git a/stackit/internal/services/intake/testdata/resource-max.tf b/stackit/internal/services/intake/testdata/resource-max.tf new file mode 100644 index 000000000..5030bb196 --- /dev/null +++ b/stackit/internal/services/intake/testdata/resource-max.tf @@ -0,0 +1,15 @@ + +variable "project_id" {} +variable "name" {} + +resource "stackit_intake_runner" "example" { + project_id = var.project_id + name = var.name + description = "An example runner for Intake" + max_message_size_kib = 1024 + max_messages_per_hour = 1100 + labels = { + "created_by" = "terraform-provider-stackit" + "env" = "development" + } +} diff --git a/stackit/internal/services/intake/testdata/resource-min.tf b/stackit/internal/services/intake/testdata/resource-min.tf new file mode 100644 index 000000000..3760c61e8 --- /dev/null +++ b/stackit/internal/services/intake/testdata/resource-min.tf @@ -0,0 +1,10 @@ + +variable "project_id" {} +variable "name" {} + +resource "stackit_intake_runner" "example" { + project_id = var.project_id + name = var.name + max_message_size_kib = 1024 + max_messages_per_hour = 1000 +} From 528f92e070262e6e417bf0473cb63b923f056a8a Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 16:52:48 +0100 Subject: [PATCH 12/41] Remove replace directive during name change & complement test checks --- stackit/internal/services/intake/resource_acc_test.go | 10 ++++++++++ stackit/internal/services/intake/runner/resource.go | 3 --- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index 4c8cb9fd9..a171c7bf3 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -71,6 +71,11 @@ func TestAccIntakeRunnerMin(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ProjectId), resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMinName), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1000"), + // Verify empty fields + resource.TestCheckResourceAttr(intakeRunnerResource, "description", ""), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "0"), ), }, // Data source check: creates config that includes resource and data source @@ -129,6 +134,11 @@ func TestAccIntakeRunnerMax(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMaxName), resource.TestCheckResourceAttr(intakeRunnerResource, "description", "An example runner for Intake"), resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1100"), + // Verify map size + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "2"), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.env", "development"), ), }, // Update and verify changes are reflected diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index ebea2b85a..fd17024bd 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -156,9 +156,6 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res "name": schema.StringAttribute{ Description: descriptions["name"], Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, }, "description": schema.StringAttribute{ Description: descriptions["description"], From e4c8e5d2fb8f2871009d780dccb4a2d30b935d4f Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 17:08:46 +0100 Subject: [PATCH 13/41] docs --- docs/data-sources/intake_runner.md | 4 ++ docs/ephemeral-resources/access_token.md | 73 ------------------------ docs/resources/volume.md | 2 +- 3 files changed, 5 insertions(+), 74 deletions(-) delete mode 100644 docs/ephemeral-resources/access_token.md diff --git a/docs/data-sources/intake_runner.md b/docs/data-sources/intake_runner.md index 60950b2f1..bb995f4a7 100644 --- a/docs/data-sources/intake_runner.md +++ b/docs/data-sources/intake_runner.md @@ -20,6 +20,10 @@ Datasource for STACKIT Intake Runner. - `project_id` (String) STACKIT Project ID to which the runner is associated. - `runner_id` (String) The runner ID. +### Optional + +- `region` (String) The resource region. If not defined, the provider region is used. + ### Read-Only - `description` (String) The description of the runner. diff --git a/docs/ephemeral-resources/access_token.md b/docs/ephemeral-resources/access_token.md deleted file mode 100644 index b45fd715e..000000000 --- a/docs/ephemeral-resources/access_token.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "stackit_access_token Ephemeral Resource - stackit" -subcategory: "" -description: |- - Ephemeral resource that generates a short-lived STACKIT access token (JWT) using a service account key. A new token is generated each time the resource is evaluated, and it remains consistent for the duration of a Terraform operation. If a private key is not explicitly provided, the provider attempts to extract it from the service account key instead. Access tokens generated from service account keys expire after 60 minutes. - ~> Service account key credentials must be configured either in the STACKIT provider configuration or via environment variables (see example below). If any other authentication method is configured, this ephemeral resource will fail with an error. - ~> This ephemeral-resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources. ---- - -# stackit_access_token (Ephemeral Resource) - -Ephemeral resource that generates a short-lived STACKIT access token (JWT) using a service account key. A new token is generated each time the resource is evaluated, and it remains consistent for the duration of a Terraform operation. If a private key is not explicitly provided, the provider attempts to extract it from the service account key instead. Access tokens generated from service account keys expire after 60 minutes. - -~> Service account key credentials must be configured either in the STACKIT provider configuration or via environment variables (see example below). If any other authentication method is configured, this ephemeral resource will fail with an error. - -~> This ephemeral-resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources. - -## Example Usage - -```terraform -provider "stackit" { - default_region = "eu01" - service_account_key_path = "/path/to/sa_key.json" - enable_beta_resources = true -} - -ephemeral "stackit_access_token" "example" {} - -locals { - stackit_api_base_url = "https://iaas.api.stackit.cloud" - public_ip_path = "/v2/projects/${var.project_id}/regions/${var.region}/public-ips" - - public_ip_payload = { - labels = { - key = "value" - } - } -} - -# Docs: https://registry.terraform.io/providers/Mastercard/restapi/latest -provider "restapi" { - uri = local.stackit_api_base_url - write_returns_object = true - - headers = { - Authorization = "Bearer ${ephemeral.stackit_access_token.example.access_token}" - Content-Type = "application/json" - } - - create_method = "POST" - update_method = "PATCH" - destroy_method = "DELETE" -} - -resource "restapi_object" "public_ip_restapi" { - path = local.public_ip_path - data = jsonencode(local.public_ip_payload) - - id_attribute = "id" - read_method = "GET" - create_method = "POST" - update_method = "PATCH" - destroy_method = "DELETE" -} -``` - - -## Schema - -### Read-Only - -- `access_token` (String, Sensitive) JWT access token for STACKIT API authentication. diff --git a/docs/resources/volume.md b/docs/resources/volume.md index 125fed296..fb57dff6d 100644 --- a/docs/resources/volume.md +++ b/docs/resources/volume.md @@ -72,7 +72,7 @@ Required: Optional: - `key_payload_base64` (String, Sensitive) Optional predefined secret, which will be encrypted against the key-encryption-key within the STACKIT-KMS. If not defined, a random secret will be generated by the API and encrypted against the STACKIT-KMS. If a key-payload is provided here, it must be base64 encoded. -- `key_payload_base64_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Optional predefined secret, which will be encrypted against the key-encryption-key within the STACKIT-KMS. If not defined, a random secret will be generated by the API and encrypted against the STACKIT-KMS. If a key-payload is provided here, it must be base64 encoded. +- `key_payload_base64_wo` (String, Sensitive) Optional predefined secret, which will be encrypted against the key-encryption-key within the STACKIT-KMS. If not defined, a random secret will be generated by the API and encrypted against the STACKIT-KMS. If a key-payload is provided here, it must be base64 encoded. - `key_payload_base64_wo_version` (Number) Used together with `key_payload_base64_wo` to trigger an re-create. Increment this value when an update to `key_payload_base64_wo` is required. From c0ae83ca964a7af26b89e41c2dedbbaf304c2787 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 17:11:34 +0100 Subject: [PATCH 14/41] lint --- .../services/intake/testdata/resource-max.tf | 18 +++++++++--------- .../services/intake/testdata/resource-min.tf | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/stackit/internal/services/intake/testdata/resource-max.tf b/stackit/internal/services/intake/testdata/resource-max.tf index 5030bb196..ff8324311 100644 --- a/stackit/internal/services/intake/testdata/resource-max.tf +++ b/stackit/internal/services/intake/testdata/resource-max.tf @@ -3,13 +3,13 @@ variable "project_id" {} variable "name" {} resource "stackit_intake_runner" "example" { - project_id = var.project_id - name = var.name - description = "An example runner for Intake" - max_message_size_kib = 1024 - max_messages_per_hour = 1100 - labels = { - "created_by" = "terraform-provider-stackit" - "env" = "development" - } + project_id = var.project_id + name = var.name + description = "An example runner for Intake" + max_message_size_kib = 1024 + max_messages_per_hour = 1100 + labels = { + "created_by" = "terraform-provider-stackit" + "env" = "development" + } } diff --git a/stackit/internal/services/intake/testdata/resource-min.tf b/stackit/internal/services/intake/testdata/resource-min.tf index 3760c61e8..29673b437 100644 --- a/stackit/internal/services/intake/testdata/resource-min.tf +++ b/stackit/internal/services/intake/testdata/resource-min.tf @@ -3,8 +3,8 @@ variable "project_id" {} variable "name" {} resource "stackit_intake_runner" "example" { - project_id = var.project_id - name = var.name - max_message_size_kib = 1024 - max_messages_per_hour = 1000 + project_id = var.project_id + name = var.name + max_message_size_kib = 1024 + max_messages_per_hour = 1000 } From bc971188aa7dc64930c89147c98e2775b1d38002 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 17:12:18 +0100 Subject: [PATCH 15/41] Adjust resource_test --- .../services/intake/runner/resource_test.go | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/stackit/internal/services/intake/runner/resource_test.go b/stackit/internal/services/intake/runner/resource_test.go index 921349c64..9d721d462 100644 --- a/stackit/internal/services/intake/runner/resource_test.go +++ b/stackit/internal/services/intake/runner/resource_test.go @@ -1,7 +1,6 @@ package runner import ( - "context" "fmt" "testing" @@ -20,6 +19,7 @@ func TestMapFields(t *testing.T) { description string input *intake.IntakeRunnerResponse model *Model + region string expected *Model wantErr bool }{ @@ -35,8 +35,8 @@ func TestMapFields(t *testing.T) { }, &Model{ ProjectId: types.StringValue("pid"), - Region: types.StringValue("eu01"), }, + "eu01", &Model{ Id: types.StringValue(fmt.Sprintf("pid,eu01,%s", runnerId)), ProjectId: types.StringValue("pid"), @@ -54,6 +54,7 @@ func TestMapFields(t *testing.T) { "nil input", nil, &Model{}, + "eu01", nil, true, }, @@ -61,6 +62,7 @@ func TestMapFields(t *testing.T) { "nil model", &intake.IntakeRunnerResponse{}, nil, + "eu01", nil, true, }, @@ -72,8 +74,8 @@ func TestMapFields(t *testing.T) { }, &Model{ ProjectId: types.StringValue("pid"), - Region: types.StringValue("eu01"), }, + "eu01", &Model{ Id: types.StringValue("pid,eu01,"), ProjectId: types.StringValue("pid"), @@ -90,7 +92,7 @@ func TestMapFields(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - err := mapFields(tt.input, tt.model) + err := mapFields(tt.input, tt.model, tt.region) if (err != nil) != tt.wantErr { t.Errorf("mapFields error = %v, wantErr %v", err, tt.wantErr) return @@ -209,10 +211,7 @@ func TestToUpdatePayload(t *testing.T) { "empty model", &Model{}, &Model{}, - &intake.UpdateIntakeRunnerPayload{ - Description: utils.Ptr(""), - Labels: &map[string]string{}, - }, + &intake.UpdateIntakeRunnerPayload{}, false, }, { @@ -231,14 +230,6 @@ func TestToUpdatePayload(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - var labels map[string]string - if tt.model != nil && !tt.model.Labels.IsNull() && !tt.model.Labels.IsUnknown() { - diags := tt.model.Labels.ElementsAs(context.Background(), &labels, false) - if diags.HasError() { - t.Fatalf("error preparing test %v", diags) - } - } - payload, err := toUpdatePayload(tt.model, tt.state) if (err != nil) != tt.wantErr { t.Errorf("toUpdatePayload error = %v, wantErr %v", err, tt.wantErr) From 64819d13583439bab8756e8f765fcee04c20cc38 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 17:20:56 +0100 Subject: [PATCH 16/41] adjust last small tweaks --- stackit/internal/services/intake/runner/resource.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index fd17024bd..2222a5790 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -435,17 +435,13 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str model.Labels = labels } - if runnerResp.Id != nil && *runnerResp.Id == "" { + if runnerResp.Id != nil || *runnerResp.Id == "" { model.RunnerId = types.StringNull() } else { model.RunnerId = types.StringPointerValue(runnerResp.Id) } model.Name = types.StringPointerValue(runnerResp.DisplayName) - if runnerResp.Description == nil { - model.Description = types.StringValue("") - } else { - model.Description = types.StringPointerValue(runnerResp.Description) - } + model.Description = types.StringPointerValue(runnerResp.Description) model.Region = types.StringValue(region) model.MaxMessageSizeKiB = types.Int64PointerValue(runnerResp.MaxMessageSizeKiB) model.MaxMessagesPerHour = types.Int64PointerValue(runnerResp.MaxMessagesPerHour) @@ -498,7 +494,7 @@ func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, er payload.Description = conversion.StringValueToPointer(model.Description) var labels map[string]string - if !model.Labels.IsUnknown() && !model.Labels.IsNull() { + if !model.Labels.IsNull() && !model.Labels.IsUnknown() { diags := model.Labels.ElementsAs(context.Background(), &labels, false) if diags.HasError() { return nil, fmt.Errorf("failed to convert labels: %w", core.DiagsToError(diags)) From 5e0930788b33de09625b96679988d20051d88d4e Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 17:25:13 +0100 Subject: [PATCH 17/41] lint --- docs/resources/intake_runner.md | 12 ++++++------ .../resources/stackit_intake_runner/resource.tf | 12 ++++++------ .../services/intake/testdata/resource-max.tf | 16 ++++++++-------- .../services/intake/testdata/resource-min.tf | 10 +++++----- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/resources/intake_runner.md b/docs/resources/intake_runner.md index 9a246d535..215b5316f 100644 --- a/docs/resources/intake_runner.md +++ b/docs/resources/intake_runner.md @@ -14,16 +14,16 @@ Manages STACKIT Intake Runner. ```terraform resource "stackit_intake_runner" "example" { - project_id = var.project_id - name = "example-runner-full" - description = "An example runner for STACKIT Intake" - max_message_size_kib = 2048 - max_messages_per_hour = 1500 + project_id = var.project_id + name = "example-runner-full" + description = "An example runner for STACKIT Intake" + max_message_size_kib = 2048 + max_messages_per_hour = 1500 labels = { "created_by" = "terraform-example" "env" = "production" } - region = var.region + region = var.region } import { diff --git a/examples/resources/stackit_intake_runner/resource.tf b/examples/resources/stackit_intake_runner/resource.tf index ceda583ff..311a9f41f 100644 --- a/examples/resources/stackit_intake_runner/resource.tf +++ b/examples/resources/stackit_intake_runner/resource.tf @@ -1,14 +1,14 @@ resource "stackit_intake_runner" "example" { - project_id = var.project_id - name = "example-runner-full" - description = "An example runner for STACKIT Intake" - max_message_size_kib = 2048 - max_messages_per_hour = 1500 + project_id = var.project_id + name = "example-runner-full" + description = "An example runner for STACKIT Intake" + max_message_size_kib = 2048 + max_messages_per_hour = 1500 labels = { "created_by" = "terraform-example" "env" = "production" } - region = var.region + region = var.region } import { diff --git a/stackit/internal/services/intake/testdata/resource-max.tf b/stackit/internal/services/intake/testdata/resource-max.tf index ff8324311..5614426bb 100644 --- a/stackit/internal/services/intake/testdata/resource-max.tf +++ b/stackit/internal/services/intake/testdata/resource-max.tf @@ -3,13 +3,13 @@ variable "project_id" {} variable "name" {} resource "stackit_intake_runner" "example" { - project_id = var.project_id - name = var.name - description = "An example runner for Intake" - max_message_size_kib = 1024 - max_messages_per_hour = 1100 + project_id = var.project_id + name = var.name + description = "An example runner for Intake" + max_message_size_kib = 1024 + max_messages_per_hour = 1100 labels = { - "created_by" = "terraform-provider-stackit" - "env" = "development" + "created_by" = "terraform-provider-stackit" + "env" = "development" } -} +} \ No newline at end of file diff --git a/stackit/internal/services/intake/testdata/resource-min.tf b/stackit/internal/services/intake/testdata/resource-min.tf index 29673b437..e7c8d77fa 100644 --- a/stackit/internal/services/intake/testdata/resource-min.tf +++ b/stackit/internal/services/intake/testdata/resource-min.tf @@ -3,8 +3,8 @@ variable "project_id" {} variable "name" {} resource "stackit_intake_runner" "example" { - project_id = var.project_id - name = var.name - max_message_size_kib = 1024 - max_messages_per_hour = 1000 -} + project_id = var.project_id + name = var.name + max_message_size_kib = 1024 + max_messages_per_hour = 1000 +} \ No newline at end of file From 8a42eac55a3ea7628f45da29c5b8b05ff12e49b8 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 18:32:45 +0100 Subject: [PATCH 18/41] Add missing checks in tests --- .../services/intake/resource_acc_test.go | 43 +++++++++++++++---- .../services/intake/runner/resource.go | 11 ++--- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index a171c7bf3..92d12ebb3 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -67,15 +67,13 @@ func TestAccIntakeRunnerMin(t *testing.T) { ConfigVariables: testConfigVarsMin, Config: testutil.IntakeProviderConfig() + "\n" + resourceMin, Check: resource.ComposeAggregateTestCheckFunc( - // Verify project_id, name and the existence of runner_id resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ProjectId), resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMinName), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1000"), - // Verify empty fields - resource.TestCheckResourceAttr(intakeRunnerResource, "description", ""), - resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "0"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "region"), ), }, // Data source check: creates config that includes resource and data source @@ -90,7 +88,11 @@ func TestAccIntakeRunnerMin(t *testing.T) { }`, testutil.IntakeProviderConfig()+"\n"+resourceMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), Check: resource.ComposeAggregateTestCheckFunc( // Make sure it's correctly found resource by comparing runner_id attribute + resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "runner_id", "data.stackit_intake_runner.example", "runner_id"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "name", "data.stackit_intake_runner.example", "name"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "region", "data.stackit_intake_runner.example", "region"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "max_messages_per_hour", "data.stackit_intake_runner.example", "max_messages_per_hour"), ), }, // Simulate terraform import @@ -131,14 +133,35 @@ func TestAccIntakeRunnerMax(t *testing.T) { ConfigVariables: testConfigVarsMax, Config: testutil.IntakeProviderConfig() + "\n" + resourceMax, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMaxName), + resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testConfigVarsMax["name"])), resource.TestCheckResourceAttr(intakeRunnerResource, "description", "An example runner for Intake"), resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1100"), - // Verify map size resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "2"), - resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), resource.TestCheckResourceAttr(intakeRunnerResource, "labels.env", "development"), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "region"), + ), + }, + { + ConfigVariables: testConfigVarsMax, + Config: fmt.Sprintf(` + %s + data "stackit_intake_runner" "example" { + project_id = %s.project_id + runner_id = %s.runner_id + }`, testutil.IntakeProviderConfig()+"\n"+resourceMax, intakeRunnerResource, intakeRunnerResource), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "runner_id", "data.stackit_intake_runner.example", "runner_id"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "name", "data.stackit_intake_runner.example", "name"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "description", "data.stackit_intake_runner.example", "description"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "region", "data.stackit_intake_runner.example", "region"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "labels.env", "data.stackit_intake_runner.example", "labels.env"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "max_messages_per_hour", "data.stackit_intake_runner.example", "max_messages_per_hour"), ), }, // Update and verify changes are reflected @@ -146,7 +169,10 @@ func TestAccIntakeRunnerMax(t *testing.T) { ConfigVariables: testConfigVarsMaxUpdated(), Config: testutil.IntakeProviderConfig() + "\n" + resourceMax, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMaxNameUpdated), + resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["name"])), + // Ensure optional fields survived the update (didn't get wiped by a bad Update payload) + resource.TestCheckResourceAttr(intakeRunnerResource, "description", "An example runner for Intake"), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.env", "development"), ), }, }, @@ -160,7 +186,6 @@ func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { var client *intake.APIClient var err error - // todo: check this again effectiveRegion := testutil.Region if effectiveRegion == "" { effectiveRegion = "eu01" diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index 2222a5790..c73818bcf 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -425,6 +425,12 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str runnerId, ) + if runnerResp.Id == nil || *runnerResp.Id == "" { + model.RunnerId = types.StringNull() + } else { + model.RunnerId = types.StringPointerValue(runnerResp.Id) + } + if runnerResp.Labels == nil { model.Labels = types.MapValueMust(types.StringType, map[string]attr.Value{}) } else { @@ -435,11 +441,6 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str model.Labels = labels } - if runnerResp.Id != nil || *runnerResp.Id == "" { - model.RunnerId = types.StringNull() - } else { - model.RunnerId = types.StringPointerValue(runnerResp.Id) - } model.Name = types.StringPointerValue(runnerResp.DisplayName) model.Description = types.StringPointerValue(runnerResp.Description) model.Region = types.StringValue(region) From 1ff8d7b83aa4a5da038c62044067b6e2fa9abdc1 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 26 Jan 2026 18:46:04 +0100 Subject: [PATCH 19/41] Fix remaining test failing in resource_test.go --- stackit/internal/services/intake/runner/resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackit/internal/services/intake/runner/resource_test.go b/stackit/internal/services/intake/runner/resource_test.go index 9d721d462..b6d3594a2 100644 --- a/stackit/internal/services/intake/runner/resource_test.go +++ b/stackit/internal/services/intake/runner/resource_test.go @@ -82,7 +82,7 @@ func TestMapFields(t *testing.T) { Region: types.StringValue("eu01"), RunnerId: types.StringNull(), Name: types.StringNull(), - Description: types.StringValue(""), + Description: types.StringNull(), Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}), MaxMessageSizeKiB: types.Int64Null(), MaxMessagesPerHour: types.Int64Null(), From 7b3ea4ceea0e511401cfd9c4637c3cf34298eec3 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 27 Jan 2026 15:52:57 +0100 Subject: [PATCH 20/41] Adjust destroy function --- .../services/intake/resource_acc_test.go | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index 92d12ebb3..5adb68761 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -3,18 +3,19 @@ package intake_test import ( "context" _ "embed" - "errors" "fmt" "maps" - "net/http" + "strings" "testing" "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/intake" + "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" ) @@ -181,53 +182,57 @@ func TestAccIntakeRunnerMax(t *testing.T) { // testAccCheckIntakeRunnerDestroy act as independent auditor to verify destroy operation func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { - // Create own raw API client ctx := context.Background() var client *intake.APIClient var err error - effectiveRegion := testutil.Region - if effectiveRegion == "" { - effectiveRegion = "eu01" - } - if testutil.IntakeCustomEndpoint == "" { - client, err = intake.NewAPIClient(sdkConfig.WithRegion(effectiveRegion)) + client, err = intake.NewAPIClient() } else { client, err = intake.NewAPIClient( sdkConfig.WithEndpoint(testutil.IntakeCustomEndpoint), - sdkConfig.WithRegion(effectiveRegion), ) } if err != nil { return fmt.Errorf("creating client: %w", err) } - // Loop through resources that should have been deleted + instancesToDestroy := []string{} for _, rs := range s.RootModule().Resources { if rs.Type != "stackit_intake_runner" { continue } + // Intake internal ID: "[project_id],[region],[runner_id]" + runnerId := strings.Split(rs.Primary.ID, core.Separator)[2] + instancesToDestroy = append(instancesToDestroy, runnerId) + } - pID := rs.Primary.Attributes["project_id"] - reg := rs.Primary.Attributes["region"] - rID := rs.Primary.Attributes["runner_id"] - - // If it still exists, destroy operation was unsuccessful - _, err := client.GetIntakeRunner(ctx, pID, reg, rID).Execute() - if err == nil { - // Delete to prevent orphaned instances - errDel := client.DeleteIntakeRunner(ctx, pID, reg, rID).Execute() - if errDel != nil { - return fmt.Errorf("resource leaked and manual cleanup failed: %w", errDel) - } + // List all resources in the project/region to see what's left + instancesResp, err := client.ListIntakeRunners(ctx, testutil.ProjectId, testutil.Region).Execute() + if err != nil { + return fmt.Errorf("getting instancesResp: %w", err) + } - return fmt.Errorf("intake runner %s still exists in region %s", rID, reg) + // If the API returns a list of runners, check if our deleted ones are still there + items := *instancesResp.IntakeRunners + for i := range items { + if items[i].Id == nil { + continue } - var oapiErr *oapierror.GenericOpenAPIError - if !errors.As(err, &oapiErr) || oapiErr.StatusCode != http.StatusNotFound { - return fmt.Errorf("unexpected error checking destruction: %w", err) + // If a runner we thought we deleted is found in the list + if utils.Contains(instancesToDestroy, *items[i].Id) { + // Attempt a final delete and wait, just like Postgres + err := client.DeleteIntakeRunner(ctx, testutil.ProjectId, testutil.Region, *items[i].Id).Execute() + if err != nil { + return fmt.Errorf("deleting runner %s during CheckDestroy: %w", *items[i].Id, err) + } + + // Using the wait handler for destruction verification + _, err = wait.DeleteIntakeRunnerWaitHandler(ctx, client, testutil.ProjectId, testutil.Region, *items[i].Id).WaitWithContext(ctx) + if err != nil { + return fmt.Errorf("deleting runner %s during CheckDestroy: waiting for deletion %w", *items[i].Id, err) + } } } return nil From f7444af72dd47d926596e86689e31be3fba54f6e Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Thu, 29 Jan 2026 10:42:01 +0100 Subject: [PATCH 21/41] Reestablish tf-plugin-docs to newer version and re-generate docs properly --- docs/ephemeral-resources/access_token.md | 73 ++++++++++++++++++++++++ docs/resources/volume.md | 2 +- 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 docs/ephemeral-resources/access_token.md diff --git a/docs/ephemeral-resources/access_token.md b/docs/ephemeral-resources/access_token.md new file mode 100644 index 000000000..b45fd715e --- /dev/null +++ b/docs/ephemeral-resources/access_token.md @@ -0,0 +1,73 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_access_token Ephemeral Resource - stackit" +subcategory: "" +description: |- + Ephemeral resource that generates a short-lived STACKIT access token (JWT) using a service account key. A new token is generated each time the resource is evaluated, and it remains consistent for the duration of a Terraform operation. If a private key is not explicitly provided, the provider attempts to extract it from the service account key instead. Access tokens generated from service account keys expire after 60 minutes. + ~> Service account key credentials must be configured either in the STACKIT provider configuration or via environment variables (see example below). If any other authentication method is configured, this ephemeral resource will fail with an error. + ~> This ephemeral-resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources. +--- + +# stackit_access_token (Ephemeral Resource) + +Ephemeral resource that generates a short-lived STACKIT access token (JWT) using a service account key. A new token is generated each time the resource is evaluated, and it remains consistent for the duration of a Terraform operation. If a private key is not explicitly provided, the provider attempts to extract it from the service account key instead. Access tokens generated from service account keys expire after 60 minutes. + +~> Service account key credentials must be configured either in the STACKIT provider configuration or via environment variables (see example below). If any other authentication method is configured, this ephemeral resource will fail with an error. + +~> This ephemeral-resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources. + +## Example Usage + +```terraform +provider "stackit" { + default_region = "eu01" + service_account_key_path = "/path/to/sa_key.json" + enable_beta_resources = true +} + +ephemeral "stackit_access_token" "example" {} + +locals { + stackit_api_base_url = "https://iaas.api.stackit.cloud" + public_ip_path = "/v2/projects/${var.project_id}/regions/${var.region}/public-ips" + + public_ip_payload = { + labels = { + key = "value" + } + } +} + +# Docs: https://registry.terraform.io/providers/Mastercard/restapi/latest +provider "restapi" { + uri = local.stackit_api_base_url + write_returns_object = true + + headers = { + Authorization = "Bearer ${ephemeral.stackit_access_token.example.access_token}" + Content-Type = "application/json" + } + + create_method = "POST" + update_method = "PATCH" + destroy_method = "DELETE" +} + +resource "restapi_object" "public_ip_restapi" { + path = local.public_ip_path + data = jsonencode(local.public_ip_payload) + + id_attribute = "id" + read_method = "GET" + create_method = "POST" + update_method = "PATCH" + destroy_method = "DELETE" +} +``` + + +## Schema + +### Read-Only + +- `access_token` (String, Sensitive) JWT access token for STACKIT API authentication. diff --git a/docs/resources/volume.md b/docs/resources/volume.md index fb57dff6d..125fed296 100644 --- a/docs/resources/volume.md +++ b/docs/resources/volume.md @@ -72,7 +72,7 @@ Required: Optional: - `key_payload_base64` (String, Sensitive) Optional predefined secret, which will be encrypted against the key-encryption-key within the STACKIT-KMS. If not defined, a random secret will be generated by the API and encrypted against the STACKIT-KMS. If a key-payload is provided here, it must be base64 encoded. -- `key_payload_base64_wo` (String, Sensitive) Optional predefined secret, which will be encrypted against the key-encryption-key within the STACKIT-KMS. If not defined, a random secret will be generated by the API and encrypted against the STACKIT-KMS. If a key-payload is provided here, it must be base64 encoded. +- `key_payload_base64_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Optional predefined secret, which will be encrypted against the key-encryption-key within the STACKIT-KMS. If not defined, a random secret will be generated by the API and encrypted against the STACKIT-KMS. If a key-payload is provided here, it must be base64 encoded. - `key_payload_base64_wo_version` (Number) Used together with `key_payload_base64_wo` to trigger an re-create. Increment this value when an update to `key_payload_base64_wo` is required. From bdd95d36d592b9eda021ba936ffb5365eb3d3d15 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 17 Feb 2026 13:49:07 +0100 Subject: [PATCH 22/41] Address review --- .../services/intake/resource_acc_test.go | 131 +++++++++++------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index 5adb68761..fd964eea1 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -20,41 +20,39 @@ import ( ) //go:embed testdata/resource-min.tf -var resourceMin string +var resourceIntakeRunnerMin string //go:embed testdata/resource-max.tf -var resourceMax string +var resourceIntakeRunnerMax string const intakeRunnerResource = "stackit_intake_runner.example" -const ( - intakeRunnerMinName = "intake-min-runner" - intakeRunnerMinNameUpdated = "intake-min-runner-upd" - intakeRunnerMaxName = "intake-max-runner" - intakeRunnerMaxNameUpdated = "intake-max-runner-upd" -) - -var testConfigVarsMin = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "name": config.StringVariable(intakeRunnerMinName), +var testIntakeRunnerConfigVarsMin = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable("intake-min-runner"), + "max_message_size_kib": config.IntegerVariable(1024), + "max_messages_per_hour": config.IntegerVariable(1000), } -var testConfigVarsMax = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "name": config.StringVariable(intakeRunnerMaxName), +var testIntakeRunnerConfigVarsMax = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable("intake-max-runner"), + "description": config.StringVariable("An example runner for Intake"), + "max_message_size_kib": config.IntegerVariable(1024), + "max_messages_per_hour": config.IntegerVariable(1100), } -func testConfigVarsMinUpdated() config.Variables { - tempConfig := make(config.Variables, len(testConfigVarsMin)) - maps.Copy(tempConfig, testConfigVarsMin) - tempConfig["name"] = config.StringVariable(intakeRunnerMinNameUpdated) +func testIntakeRunnerConfigVarsMinUpdated() config.Variables { + tempConfig := make(config.Variables, len(testIntakeRunnerConfigVarsMin)) + maps.Copy(tempConfig, testIntakeRunnerConfigVarsMin) + tempConfig["name"] = config.StringVariable("intake-min-runner-upd") return tempConfig } -func testConfigVarsMaxUpdated() config.Variables { - tempConfig := make(config.Variables, len(testConfigVarsMax)) - maps.Copy(tempConfig, testConfigVarsMax) - tempConfig["name"] = config.StringVariable(intakeRunnerMaxNameUpdated) +func testIntakeRunnerConfigVarsMaxUpdated() config.Variables { + tempConfig := make(config.Variables, len(testIntakeRunnerConfigVarsMax)) + maps.Copy(tempConfig, testIntakeRunnerConfigVarsMax) + tempConfig["name"] = config.StringVariable("intake-max-runner-upd") return tempConfig } @@ -65,28 +63,28 @@ func TestAccIntakeRunnerMin(t *testing.T) { Steps: []resource.TestStep{ // Create the minimum runner from the HCL file { - ConfigVariables: testConfigVarsMin, - Config: testutil.IntakeProviderConfig() + "\n" + resourceMin, + ConfigVariables: testIntakeRunnerConfigVarsMin, + Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMin, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ProjectId), - resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMinName), + resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["name"])), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), - resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), - resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1000"), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_message_size_kib"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_messages_per_hour"])), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), - resource.TestCheckResourceAttrSet(intakeRunnerResource, "region"), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), ), }, // Data source check: creates config that includes resource and data source { - ConfigVariables: testConfigVarsMin, + ConfigVariables: testIntakeRunnerConfigVarsMin, Config: fmt.Sprintf(` %s data "stackit_intake_runner" "example" { project_id = %s.project_id runner_id = %s.runner_id region = %s.region - }`, testutil.IntakeProviderConfig()+"\n"+resourceMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), + }`, testutil.IntakeProviderConfig()+"\n"+resourceIntakeRunnerMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), Check: resource.ComposeAggregateTestCheckFunc( // Make sure it's correctly found resource by comparing runner_id attribute resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), @@ -98,8 +96,8 @@ func TestAccIntakeRunnerMin(t *testing.T) { }, // Simulate terraform import { - ConfigVariables: testConfigVarsMin, - Config: testutil.IntakeProviderConfig() + "\n" + resourceMin, + ConfigVariables: testIntakeRunnerConfigVarsMin, + Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMin, ResourceName: intakeRunnerResource, ImportState: true, ImportStateVerify: true, @@ -114,10 +112,16 @@ func TestAccIntakeRunnerMin(t *testing.T) { }, // Update check: verifies API updated resource name without crashing { - ConfigVariables: testConfigVarsMinUpdated(), - Config: testutil.IntakeProviderConfig() + "\n" + resourceMin, + ConfigVariables: testIntakeRunnerConfigVarsMinUpdated(), + Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMin, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "name", intakeRunnerMinNameUpdated), + resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMinUpdated()["name"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_message_size_kib"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_messages_per_hour"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), ), }, }, @@ -131,30 +135,30 @@ func TestAccIntakeRunnerMax(t *testing.T) { Steps: []resource.TestStep{ // Create the max intake runner from HCL files and verify comparison { - ConfigVariables: testConfigVarsMax, - Config: testutil.IntakeProviderConfig() + "\n" + resourceMax, + ConfigVariables: testIntakeRunnerConfigVarsMax, + Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMax, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), - resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testConfigVarsMax["name"])), - resource.TestCheckResourceAttr(intakeRunnerResource, "description", "An example runner for Intake"), - resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", "1024"), - resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", "1100"), + resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["project_id"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["name"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "description", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["description"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["max_message_size_kib"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["max_messages_per_hour"])), resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "2"), resource.TestCheckResourceAttr(intakeRunnerResource, "labels.env", "development"), resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), - resource.TestCheckResourceAttrSet(intakeRunnerResource, "region"), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), ), }, { - ConfigVariables: testConfigVarsMax, + ConfigVariables: testIntakeRunnerConfigVarsMax, Config: fmt.Sprintf(` %s data "stackit_intake_runner" "example" { project_id = %s.project_id runner_id = %s.runner_id - }`, testutil.IntakeProviderConfig()+"\n"+resourceMax, intakeRunnerResource, intakeRunnerResource), + }`, testutil.IntakeProviderConfig()+"\n"+resourceIntakeRunnerMax, intakeRunnerResource, intakeRunnerResource), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "runner_id", "data.stackit_intake_runner.example", "runner_id"), @@ -165,15 +169,38 @@ func TestAccIntakeRunnerMax(t *testing.T) { resource.TestCheckResourceAttrPair(intakeRunnerResource, "max_messages_per_hour", "data.stackit_intake_runner.example", "max_messages_per_hour"), ), }, + // Simulate terraform import + { + ConfigVariables: testIntakeRunnerConfigVarsMax, + Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMax, + ResourceName: intakeRunnerResource, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + // Construct ID string + r, ok := s.RootModule().Resources[intakeRunnerResource] + if !ok { + return "", fmt.Errorf("couldn't find resource %s", intakeRunnerResource) + } + return fmt.Sprintf("%s,%s,%s", r.Primary.Attributes["project_id"], r.Primary.Attributes["region"], r.Primary.Attributes["runner_id"]), nil + }, + }, // Update and verify changes are reflected { - ConfigVariables: testConfigVarsMaxUpdated(), - Config: testutil.IntakeProviderConfig() + "\n" + resourceMax, + ConfigVariables: testIntakeRunnerConfigVarsMaxUpdated(), + Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMax, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["name"])), - // Ensure optional fields survived the update (didn't get wiped by a bad Update payload) - resource.TestCheckResourceAttr(intakeRunnerResource, "description", "An example runner for Intake"), + resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["project_id"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMaxUpdated()["name"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "description", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["description"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["max_message_size_kib"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["max_messages_per_hour"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.%", "2"), resource.TestCheckResourceAttr(intakeRunnerResource, "labels.env", "development"), + resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), ), }, }, From ae2e875e3829531f328823885dc586e9fcdb2e94 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 17 Feb 2026 16:16:36 +0100 Subject: [PATCH 23/41] Small adjustment --- .../services/intake/resource_acc_test.go | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index fd964eea1..31c8fb2cc 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -8,11 +8,12 @@ import ( "strings" "testing" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/intake" "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -30,6 +31,7 @@ const intakeRunnerResource = "stackit_intake_runner.example" var testIntakeRunnerConfigVarsMin = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "name": config.StringVariable("intake-min-runner"), + "region": config.StringVariable(testutil.Region), "max_message_size_kib": config.IntegerVariable(1024), "max_messages_per_hour": config.IntegerVariable(1000), } @@ -37,6 +39,7 @@ var testIntakeRunnerConfigVarsMin = config.Variables{ var testIntakeRunnerConfigVarsMax = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "name": config.StringVariable("intake-max-runner"), + "region": config.StringVariable(testutil.Region), "description": config.StringVariable("An example runner for Intake"), "max_message_size_kib": config.IntegerVariable(1024), "max_messages_per_hour": config.IntegerVariable(1100), @@ -72,19 +75,19 @@ func TestAccIntakeRunnerMin(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_message_size_kib"])), resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_messages_per_hour"])), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), - resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["region"])), ), }, // Data source check: creates config that includes resource and data source { ConfigVariables: testIntakeRunnerConfigVarsMin, Config: fmt.Sprintf(` - %s - data "stackit_intake_runner" "example" { - project_id = %s.project_id - runner_id = %s.runner_id - region = %s.region - }`, testutil.IntakeProviderConfig()+"\n"+resourceIntakeRunnerMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), + %s + data "stackit_intake_runner" "example" { + project_id = %s.project_id + runner_id = %s.runner_id + region = %s.region + }`, testutil.IntakeProviderConfig()+"\n"+resourceIntakeRunnerMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), Check: resource.ComposeAggregateTestCheckFunc( // Make sure it's correctly found resource by comparing runner_id attribute resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), @@ -107,6 +110,7 @@ func TestAccIntakeRunnerMin(t *testing.T) { if !ok { return "", fmt.Errorf("couldn't find resource %s", intakeRunnerResource) } + // ID structure: project_id, region, runner_id return fmt.Sprintf("%s,%s,%s", r.Primary.Attributes["project_id"], r.Primary.Attributes["region"], r.Primary.Attributes["runner_id"]), nil }, }, @@ -115,11 +119,11 @@ func TestAccIntakeRunnerMin(t *testing.T) { ConfigVariables: testIntakeRunnerConfigVarsMinUpdated(), Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMin, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMinUpdated()["project_id"])), resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMinUpdated()["name"])), resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_message_size_kib"])), resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_messages_per_hour"])), - resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["region"])), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), ), @@ -148,17 +152,17 @@ func TestAccIntakeRunnerMax(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), - resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["region"])), ), }, { ConfigVariables: testIntakeRunnerConfigVarsMax, Config: fmt.Sprintf(` - %s - data "stackit_intake_runner" "example" { - project_id = %s.project_id - runner_id = %s.runner_id - }`, testutil.IntakeProviderConfig()+"\n"+resourceIntakeRunnerMax, intakeRunnerResource, intakeRunnerResource), + %s + data "stackit_intake_runner" "example" { + project_id = %s.project_id + runner_id = %s.runner_id + }`, testutil.IntakeProviderConfig()+"\n"+resourceIntakeRunnerMax, intakeRunnerResource, intakeRunnerResource), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "runner_id", "data.stackit_intake_runner.example", "runner_id"), @@ -182,6 +186,7 @@ func TestAccIntakeRunnerMax(t *testing.T) { if !ok { return "", fmt.Errorf("couldn't find resource %s", intakeRunnerResource) } + // ID structure: project_id, region, runner_id return fmt.Sprintf("%s,%s,%s", r.Primary.Attributes["project_id"], r.Primary.Attributes["region"], r.Primary.Attributes["runner_id"]), nil }, }, @@ -200,7 +205,7 @@ func TestAccIntakeRunnerMax(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), - resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["region"])), ), }, }, @@ -214,7 +219,9 @@ func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { var err error if testutil.IntakeCustomEndpoint == "" { - client, err = intake.NewAPIClient() + client, err = intake.NewAPIClient( + sdkConfig.WithRegion(testutil.Region), + ) } else { client, err = intake.NewAPIClient( sdkConfig.WithEndpoint(testutil.IntakeCustomEndpoint), From 9fcce2e2e2e8cc6f20284e3a1a5d5ea38fb201c6 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Fri, 20 Feb 2026 16:48:48 +0100 Subject: [PATCH 24/41] Small adjustment and complete docs --- docs/data-sources/intake_runner.md | 9 ++++++++- docs/resources/intake_runner.md | 18 ++++++------------ .../stackit_intake_runner/data-source.tf | 4 ++++ .../stackit_intake_runner/resource.tf | 18 ++++++------------ .../services/intake/testdata/resource-max.tf | 2 ++ .../services/intake/testdata/resource-min.tf | 2 ++ 6 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 examples/data-sources/stackit_intake_runner/data-source.tf diff --git a/docs/data-sources/intake_runner.md b/docs/data-sources/intake_runner.md index bb995f4a7..52806a24a 100644 --- a/docs/data-sources/intake_runner.md +++ b/docs/data-sources/intake_runner.md @@ -10,7 +10,14 @@ description: |- Datasource for STACKIT Intake Runner. - +## Example Usage + +```terraform +data "stackit_intake_runner" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + runner_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` ## Schema diff --git a/docs/resources/intake_runner.md b/docs/resources/intake_runner.md index 215b5316f..4dd64b6ab 100644 --- a/docs/resources/intake_runner.md +++ b/docs/resources/intake_runner.md @@ -14,21 +14,15 @@ Manages STACKIT Intake Runner. ```terraform resource "stackit_intake_runner" "example" { - project_id = var.project_id - name = "example-runner-full" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-runner" + region = "eu01" description = "An example runner for STACKIT Intake" - max_message_size_kib = 2048 - max_messages_per_hour = 1500 + max_message_size_kib = 1024 + max_messages_per_hour = 1000 labels = { - "created_by" = "terraform-example" - "env" = "production" + "env" = "development" } - region = var.region -} - -import { - to = stackit_intake_runner.example - id = "${var.project_id},${var.region},${var.runner_id}" } ``` diff --git a/examples/data-sources/stackit_intake_runner/data-source.tf b/examples/data-sources/stackit_intake_runner/data-source.tf new file mode 100644 index 000000000..0c6ea2288 --- /dev/null +++ b/examples/data-sources/stackit_intake_runner/data-source.tf @@ -0,0 +1,4 @@ +data "stackit_intake_runner" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + runner_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} diff --git a/examples/resources/stackit_intake_runner/resource.tf b/examples/resources/stackit_intake_runner/resource.tf index 311a9f41f..d85980bce 100644 --- a/examples/resources/stackit_intake_runner/resource.tf +++ b/examples/resources/stackit_intake_runner/resource.tf @@ -1,17 +1,11 @@ resource "stackit_intake_runner" "example" { - project_id = var.project_id - name = "example-runner-full" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-runner" + region = "eu01" description = "An example runner for STACKIT Intake" - max_message_size_kib = 2048 - max_messages_per_hour = 1500 + max_message_size_kib = 1024 + max_messages_per_hour = 1000 labels = { - "created_by" = "terraform-example" - "env" = "production" + "env" = "development" } - region = var.region } - -import { - to = stackit_intake_runner.example - id = "${var.project_id},${var.region},${var.runner_id}" -} \ No newline at end of file diff --git a/stackit/internal/services/intake/testdata/resource-max.tf b/stackit/internal/services/intake/testdata/resource-max.tf index 5614426bb..9e5ff9e9f 100644 --- a/stackit/internal/services/intake/testdata/resource-max.tf +++ b/stackit/internal/services/intake/testdata/resource-max.tf @@ -1,10 +1,12 @@ variable "project_id" {} variable "name" {} +variable "region" {} resource "stackit_intake_runner" "example" { project_id = var.project_id name = var.name + region = var.region description = "An example runner for Intake" max_message_size_kib = 1024 max_messages_per_hour = 1100 diff --git a/stackit/internal/services/intake/testdata/resource-min.tf b/stackit/internal/services/intake/testdata/resource-min.tf index e7c8d77fa..7c6f26fc0 100644 --- a/stackit/internal/services/intake/testdata/resource-min.tf +++ b/stackit/internal/services/intake/testdata/resource-min.tf @@ -1,10 +1,12 @@ variable "project_id" {} variable "name" {} +variable "region" {} resource "stackit_intake_runner" "example" { project_id = var.project_id name = var.name + region = var.region max_message_size_kib = 1024 max_messages_per_hour = 1000 } \ No newline at end of file From 44e192ef3afc4d3d4859dd59abffa56d72ce8e4d Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Mon, 23 Feb 2026 10:17:32 +0100 Subject: [PATCH 25/41] lint --- stackit/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackit/provider.go b/stackit/provider.go index fa25475e0..96792a4e7 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -227,7 +227,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "edgecloud_custom_endpoint": "Custom endpoint for the Edge Cloud service", "git_custom_endpoint": "Custom endpoint for the Git service", "iaas_custom_endpoint": "Custom endpoint for the IaaS service", - "intake_custom_endpoint": "Custom endpoint for the Intake service", + "intake_custom_endpoint": "Custom endpoint for the Intake service", "kms_custom_endpoint": "Custom endpoint for the KMS service", "mongodbflex_custom_endpoint": "Custom endpoint for the MongoDB Flex service", "modelserving_custom_endpoint": "Custom endpoint for the AI Model Serving service", From 336dcba0c5a395c113e48cbab921916914e597c9 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Thu, 19 Mar 2026 08:34:08 +0100 Subject: [PATCH 26/41] Use new SDK with region removed from hostname --- stackit/internal/services/intake/resource_acc_test.go | 4 +--- stackit/internal/services/intake/utils/utils.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index 31c8fb2cc..2706a452d 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -219,9 +219,7 @@ func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { var err error if testutil.IntakeCustomEndpoint == "" { - client, err = intake.NewAPIClient( - sdkConfig.WithRegion(testutil.Region), - ) + client, err = intake.NewAPIClient() } else { client, err = intake.NewAPIClient( sdkConfig.WithEndpoint(testutil.IntakeCustomEndpoint), diff --git a/stackit/internal/services/intake/utils/utils.go b/stackit/internal/services/intake/utils/utils.go index b6357b496..4752fe79f 100644 --- a/stackit/internal/services/intake/utils/utils.go +++ b/stackit/internal/services/intake/utils/utils.go @@ -19,7 +19,7 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags if providerData.IntakeCustomEndpoint != "" { apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.IntakeCustomEndpoint)) } else { - apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion())) + apiClientConfigOptions = append(apiClientConfigOptions) } apiClient, err := intake.NewAPIClient(apiClientConfigOptions...) if err != nil { From 9afa4d0784995b8f1b6ee1315480e2d5fd62a6a5 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 31 Mar 2026 12:36:03 +0200 Subject: [PATCH 27/41] Remove computed field from description and labels --- stackit/internal/services/intake/runner/resource.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index c73818bcf..eb86cce48 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -160,7 +160,6 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res "description": schema.StringAttribute{ Description: descriptions["description"], Optional: true, - Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -169,7 +168,6 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res Description: descriptions["labels"], ElementType: types.StringType, Optional: true, - Computed: true, PlanModifiers: []planmodifier.Map{ mapplanmodifier.UseStateForUnknown(), }, From e76cfc273a408f7e52daf9dea5e39f214de197c9 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 31 Mar 2026 12:37:00 +0200 Subject: [PATCH 28/41] Remove region from resource-min.tf test --- stackit/internal/services/intake/testdata/resource-min.tf | 2 -- 1 file changed, 2 deletions(-) diff --git a/stackit/internal/services/intake/testdata/resource-min.tf b/stackit/internal/services/intake/testdata/resource-min.tf index 7c6f26fc0..e7c8d77fa 100644 --- a/stackit/internal/services/intake/testdata/resource-min.tf +++ b/stackit/internal/services/intake/testdata/resource-min.tf @@ -1,12 +1,10 @@ variable "project_id" {} variable "name" {} -variable "region" {} resource "stackit_intake_runner" "example" { project_id = var.project_id name = var.name - region = var.region max_message_size_kib = 1024 max_messages_per_hour = 1000 } \ No newline at end of file From ccd08896a6587f6366f67435c6409afa3080ccde Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 31 Mar 2026 12:43:10 +0200 Subject: [PATCH 29/41] Move numerical payloads to variables --- stackit/internal/services/intake/testdata/resource-max.tf | 6 ++++-- stackit/internal/services/intake/testdata/resource-min.tf | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/stackit/internal/services/intake/testdata/resource-max.tf b/stackit/internal/services/intake/testdata/resource-max.tf index 9e5ff9e9f..273d7aa7a 100644 --- a/stackit/internal/services/intake/testdata/resource-max.tf +++ b/stackit/internal/services/intake/testdata/resource-max.tf @@ -2,14 +2,16 @@ variable "project_id" {} variable "name" {} variable "region" {} +variable "max_message_size_kib" {} +variable "max_messages_per_hour" {} resource "stackit_intake_runner" "example" { project_id = var.project_id name = var.name region = var.region description = "An example runner for Intake" - max_message_size_kib = 1024 - max_messages_per_hour = 1100 + max_message_size_kib = var.max_message_size_kib + max_messages_per_hour = var.max_messages_per_hour labels = { "created_by" = "terraform-provider-stackit" "env" = "development" diff --git a/stackit/internal/services/intake/testdata/resource-min.tf b/stackit/internal/services/intake/testdata/resource-min.tf index e7c8d77fa..5b1cfd21b 100644 --- a/stackit/internal/services/intake/testdata/resource-min.tf +++ b/stackit/internal/services/intake/testdata/resource-min.tf @@ -1,10 +1,12 @@ variable "project_id" {} variable "name" {} +variable "max_message_size_kib" {} +variable "max_messages_per_hour" {} resource "stackit_intake_runner" "example" { project_id = var.project_id name = var.name - max_message_size_kib = 1024 - max_messages_per_hour = 1000 + max_message_size_kib = var.max_message_size_kib + max_messages_per_hour = var.max_messages_per_hour } \ No newline at end of file From 097fea45772ad8a269153f11accdb09c92509d75 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 31 Mar 2026 12:44:34 +0200 Subject: [PATCH 30/41] Remove region from resource-min.tf test --- stackit/internal/services/intake/resource_acc_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index 2706a452d..cb2acc241 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -31,7 +31,6 @@ const intakeRunnerResource = "stackit_intake_runner.example" var testIntakeRunnerConfigVarsMin = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "name": config.StringVariable("intake-min-runner"), - "region": config.StringVariable(testutil.Region), "max_message_size_kib": config.IntegerVariable(1024), "max_messages_per_hour": config.IntegerVariable(1000), } From fe76b6a5f0758ccd8b8c6abc3a3041812332c3fa Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 31 Mar 2026 12:50:10 +0200 Subject: [PATCH 31/41] Replace unnecessary concatenation with sprintf --- stackit/internal/services/intake/resource_acc_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index cb2acc241..81d1c85a7 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -82,11 +82,12 @@ func TestAccIntakeRunnerMin(t *testing.T) { ConfigVariables: testIntakeRunnerConfigVarsMin, Config: fmt.Sprintf(` %s + %s data "stackit_intake_runner" "example" { project_id = %s.project_id runner_id = %s.runner_id region = %s.region - }`, testutil.IntakeProviderConfig()+"\n"+resourceIntakeRunnerMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), + }`, testutil.IntakeProviderConfig(), resourceIntakeRunnerMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), Check: resource.ComposeAggregateTestCheckFunc( // Make sure it's correctly found resource by comparing runner_id attribute resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), @@ -158,10 +159,11 @@ func TestAccIntakeRunnerMax(t *testing.T) { ConfigVariables: testIntakeRunnerConfigVarsMax, Config: fmt.Sprintf(` %s + %s data "stackit_intake_runner" "example" { project_id = %s.project_id runner_id = %s.runner_id - }`, testutil.IntakeProviderConfig()+"\n"+resourceIntakeRunnerMax, intakeRunnerResource, intakeRunnerResource), + }`, testutil.IntakeProviderConfig(), resourceIntakeRunnerMax, intakeRunnerResource, intakeRunnerResource), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "runner_id", "data.stackit_intake_runner.example", "runner_id"), From 4afc0b8590128edc543fbaadc082d72d563200c9 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 31 Mar 2026 16:51:04 +0200 Subject: [PATCH 32/41] Add state fields before starting wait handler & retrieve ctx from SetAndLogStateFields --- stackit/internal/services/intake/runner/resource.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index eb86cce48..9773a80df 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -220,7 +220,17 @@ func (r *runnerResource) Create(ctx context.Context, req resource.CreateRequest, core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating runner", fmt.Sprintf("Calling API: %v", err)) return } + ctx = core.LogResponse(ctx) + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]interface{}{ + "project_id": projectId, + "region": region, + "runner_id": *runnerResp.Id, + }) + + if resp.Diagnostics.HasError() { + return + } // Wait for creation of intake runner _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, r.client, projectId, region, runnerResp.GetId()).WaitWithContext(ctx) @@ -394,7 +404,7 @@ func (r *runnerResource) ImportState(ctx context.Context, req resource.ImportSta return } - utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ "project_id": idParts[0], "region": idParts[1], "runner_id": idParts[2], From 37212aa012c91d3b0b89dbf0db3543dbe82172d3 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 31 Mar 2026 16:56:44 +0200 Subject: [PATCH 33/41] Log response during update --- stackit/internal/services/intake/runner/resource.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index 9773a80df..3362a2a38 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -329,6 +329,8 @@ func (r *runnerResource) Update(ctx context.Context, req resource.UpdateRequest, return } + ctx = core.LogResponse(ctx) + // Wait for update _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, r.client, projectId, region, runnerId).WaitWithContext(ctx) if err != nil { From 3c8523b1438c1f78baec3b3830a2539c56cc58b7 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 31 Mar 2026 17:20:06 +0200 Subject: [PATCH 34/41] Reestablish 'Computed: true' for labels until we agree on a solution --- stackit/internal/services/intake/runner/resource.go | 1 + 1 file changed, 1 insertion(+) diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index 3362a2a38..a56eb0f2c 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -168,6 +168,7 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res Description: descriptions["labels"], ElementType: types.StringType, Optional: true, + Computed: true, PlanModifiers: []planmodifier.Map{ mapplanmodifier.UseStateForUnknown(), }, From eff162f5b8f76b0f3fa8a9bc08e77d22bb4c4a3c Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Wed, 1 Apr 2026 15:00:13 +0200 Subject: [PATCH 35/41] Use new SDK API hierarchy --- .../services/intake/resource_acc_test.go | 22 ++--- .../services/intake/runner/data_source.go | 8 +- .../services/intake/runner/resource.go | 86 +++++++++++-------- .../services/intake/runner/resource_test.go | 56 ++++++------ .../internal/services/intake/utils/utils.go | 4 +- 5 files changed, 91 insertions(+), 85 deletions(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index 81d1c85a7..fe2b83962 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -14,8 +14,8 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/services/intake" - "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" + "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" ) @@ -241,30 +241,26 @@ func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { } // List all resources in the project/region to see what's left - instancesResp, err := client.ListIntakeRunners(ctx, testutil.ProjectId, testutil.Region).Execute() + instancesResp, err := client.DefaultAPI.ListIntakeRunners(ctx, testutil.ProjectId, testutil.Region).Execute() if err != nil { return fmt.Errorf("getting instancesResp: %w", err) } // If the API returns a list of runners, check if our deleted ones are still there - items := *instancesResp.IntakeRunners + items := instancesResp.IntakeRunners for i := range items { - if items[i].Id == nil { - continue - } - // If a runner we thought we deleted is found in the list - if utils.Contains(instancesToDestroy, *items[i].Id) { + if utils.Contains(instancesToDestroy, items[i].Id) { // Attempt a final delete and wait, just like Postgres - err := client.DeleteIntakeRunner(ctx, testutil.ProjectId, testutil.Region, *items[i].Id).Execute() + err := client.DefaultAPI.DeleteIntakeRunner(ctx, testutil.ProjectId, testutil.Region, items[i].Id).Execute() if err != nil { - return fmt.Errorf("deleting runner %s during CheckDestroy: %w", *items[i].Id, err) + return fmt.Errorf("deleting runner %s during CheckDestroy: %w", items[i].Id, err) } // Using the wait handler for destruction verification - _, err = wait.DeleteIntakeRunnerWaitHandler(ctx, client, testutil.ProjectId, testutil.Region, *items[i].Id).WaitWithContext(ctx) + _, err = wait.DeleteIntakeRunnerWaitHandler(ctx, client.DefaultAPI, testutil.ProjectId, testutil.Region, items[i].Id).WaitWithContext(ctx) if err != nil { - return fmt.Errorf("deleting runner %s during CheckDestroy: waiting for deletion %w", *items[i].Id, err) + return fmt.Errorf("deleting runner %s during CheckDestroy: waiting for deletion %w", items[i].Id, err) } } } diff --git a/stackit/internal/services/intake/runner/data_source.go b/stackit/internal/services/intake/runner/data_source.go index f44dfaf69..0d075be7a 100644 --- a/stackit/internal/services/intake/runner/data_source.go +++ b/stackit/internal/services/intake/runner/data_source.go @@ -17,7 +17,7 @@ import ( intakeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/intake/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "github.com/stackitcloud/stackit-sdk-go/services/intake" + intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" ) // Ensure the implementation satisfies the expected interfaces @@ -106,11 +106,11 @@ func (r *runnerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, ElementType: types.StringType, Computed: true, }, - "max_message_size_kib": schema.Int64Attribute{ + "max_message_size_kib": schema.Int32Attribute{ Description: descriptions["max_message_size_kib"], Computed: true, }, - "max_messages_per_hour": schema.Int64Attribute{ + "max_messages_per_hour": schema.Int32Attribute{ Description: descriptions["max_messages_per_hour"], Computed: true, }, @@ -139,7 +139,7 @@ func (r *runnerDataSource) Read(ctx context.Context, req datasource.ReadRequest, ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "runner_id", runnerId) - runnerResp, err := r.client.GetIntakeRunner(ctx, projectId, region, runnerId).Execute() + runnerResp, err := r.client.DefaultAPI.GetIntakeRunner(ctx, projectId, region, runnerId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index a56eb0f2c..d2a8bb967 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -17,14 +17,15 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + coreUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" intakeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/intake/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "github.com/stackitcloud/stackit-sdk-go/services/intake" - "github.com/stackitcloud/stackit-sdk-go/services/intake/wait" + intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" + "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi/wait" ) // Ensure the implementation satisfies the expected interfaces. @@ -43,8 +44,8 @@ type Model struct { Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` Labels types.Map `tfsdk:"labels"` - MaxMessageSizeKiB types.Int64 `tfsdk:"max_message_size_kib"` - MaxMessagesPerHour types.Int64 `tfsdk:"max_messages_per_hour"` + MaxMessageSizeKiB types.Int32 `tfsdk:"max_message_size_kib"` + MaxMessagesPerHour types.Int32 `tfsdk:"max_messages_per_hour"` Region types.String `tfsdk:"region"` } @@ -173,11 +174,11 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res mapplanmodifier.UseStateForUnknown(), }, }, - "max_message_size_kib": schema.Int64Attribute{ + "max_message_size_kib": schema.Int32Attribute{ Description: descriptions["max_message_size_kib"], Required: true, }, - "max_messages_per_hour": schema.Int64Attribute{ + "max_messages_per_hour": schema.Int32Attribute{ Description: descriptions["max_messages_per_hour"], Required: true, }, @@ -216,7 +217,7 @@ func (r *runnerResource) Create(ctx context.Context, req resource.CreateRequest, } // Create new runner - runnerResp, err := r.client.CreateIntakeRunner(ctx, projectId, region).CreateIntakeRunnerPayload(*payload).Execute() + runnerResp, err := r.client.DefaultAPI.CreateIntakeRunner(ctx, projectId, region).CreateIntakeRunnerPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating runner", fmt.Sprintf("Calling API: %v", err)) return @@ -226,7 +227,7 @@ func (r *runnerResource) Create(ctx context.Context, req resource.CreateRequest, ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]interface{}{ "project_id": projectId, "region": region, - "runner_id": *runnerResp.Id, + "runner_id": runnerResp.Id, }) if resp.Diagnostics.HasError() { @@ -234,7 +235,7 @@ func (r *runnerResource) Create(ctx context.Context, req resource.CreateRequest, } // Wait for creation of intake runner - _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, r.client, projectId, region, runnerResp.GetId()).WaitWithContext(ctx) + _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, r.client.DefaultAPI, projectId, region, runnerResp.GetId()).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating runner", fmt.Sprintf("Intake runner creation waiting: %v", err)) return @@ -269,7 +270,7 @@ func (r *runnerResource) Read(ctx context.Context, req resource.ReadRequest, res ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "runner_id", runnerId) - runnerResp, err := r.client.GetIntakeRunner(ctx, projectId, region, runnerId).Execute() + runnerResp, err := r.client.DefaultAPI.GetIntakeRunner(ctx, projectId, region, runnerId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -324,7 +325,7 @@ func (r *runnerResource) Update(ctx context.Context, req resource.UpdateRequest, } // Update runner - runnerResp, err := r.client.UpdateIntakeRunner(ctx, projectId, region, runnerId).UpdateIntakeRunnerPayload(*payload).Execute() + runnerResp, err := r.client.DefaultAPI.UpdateIntakeRunner(ctx, projectId, region, runnerId).UpdateIntakeRunnerPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating runner", fmt.Sprintf("Calling API: %v", err)) return @@ -333,7 +334,7 @@ func (r *runnerResource) Update(ctx context.Context, req resource.UpdateRequest, ctx = core.LogResponse(ctx) // Wait for update - _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, r.client, projectId, region, runnerId).WaitWithContext(ctx) + _, err = wait.CreateOrUpdateIntakeRunnerWaitHandler(ctx, r.client.DefaultAPI, projectId, region, runnerId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating runner", fmt.Sprintf("Runner update waiting: %v", err)) return @@ -372,7 +373,7 @@ func (r *runnerResource) Delete(ctx context.Context, req resource.DeleteRequest, ctx = tflog.SetField(ctx, "runner_id", runnerId) // Delete existing runner - err := r.client.DeleteIntakeRunner(ctx, projectId, region, runnerId).Execute() + err := r.client.DefaultAPI.DeleteIntakeRunner(ctx, projectId, region, runnerId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { @@ -386,7 +387,7 @@ func (r *runnerResource) Delete(ctx context.Context, req resource.DeleteRequest, ctx = core.LogResponse(ctx) // Wait for the delete operation to complete - _, err = wait.DeleteIntakeRunnerWaitHandler(ctx, r.client, projectId, region, runnerId).WaitWithContext(ctx) + _, err = wait.DeleteIntakeRunnerWaitHandler(ctx, r.client.DefaultAPI, projectId, region, runnerId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting runner", fmt.Sprintf("Runner deletion waiting: %v", err)) return @@ -425,21 +426,16 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str return fmt.Errorf("model input is nil") } - var runnerId string - if runnerResp.Id != nil { - runnerId = *runnerResp.Id - } - model.Id = utils.BuildInternalTerraformId( model.ProjectId.ValueString(), region, - runnerId, + runnerResp.Id, ) - if runnerResp.Id == nil || *runnerResp.Id == "" { + if runnerResp.Id == "" { model.RunnerId = types.StringNull() } else { - model.RunnerId = types.StringPointerValue(runnerResp.Id) + model.RunnerId = types.StringValue(runnerResp.Id) } if runnerResp.Labels == nil { @@ -452,11 +448,27 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str model.Labels = labels } - model.Name = types.StringPointerValue(runnerResp.DisplayName) + if runnerResp.DisplayName == "" { + model.Name = types.StringNull() + } else { + model.Name = types.StringValue(runnerResp.DisplayName) + } + model.Description = types.StringPointerValue(runnerResp.Description) model.Region = types.StringValue(region) - model.MaxMessageSizeKiB = types.Int64PointerValue(runnerResp.MaxMessageSizeKiB) - model.MaxMessagesPerHour = types.Int64PointerValue(runnerResp.MaxMessagesPerHour) + + if runnerResp.MaxMessageSizeKiB == 0 { + model.MaxMessageSizeKiB = types.Int32Null() + } else { + model.MaxMessageSizeKiB = types.Int32Value(runnerResp.MaxMessageSizeKiB) + } + + if runnerResp.MaxMessagesPerHour == 0 { + model.MaxMessagesPerHour = types.Int32Null() + } else { + model.MaxMessagesPerHour = types.Int32Value(runnerResp.MaxMessagesPerHour) + } + return nil } @@ -474,17 +486,12 @@ func toCreatePayload(model *Model) (*intake.CreateIntakeRunnerPayload, error) { } } - var labelsPtr *map[string]string - if len(labels) > 0 { - labelsPtr = &labels - } - return &intake.CreateIntakeRunnerPayload{ Description: conversion.StringValueToPointer(model.Description), - DisplayName: conversion.StringValueToPointer(model.Name), - Labels: labelsPtr, - MaxMessageSizeKiB: conversion.Int64ValueToPointer(model.MaxMessageSizeKiB), - MaxMessagesPerHour: conversion.Int64ValueToPointer(model.MaxMessagesPerHour), + DisplayName: model.Name.ValueString(), + Labels: labels, + MaxMessageSizeKiB: int32(model.MaxMessageSizeKiB.ValueInt32()), + MaxMessagesPerHour: int32(model.MaxMessagesPerHour.ValueInt32()), }, nil } @@ -498,8 +505,13 @@ func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, er } payload := &intake.UpdateIntakeRunnerPayload{} - payload.MaxMessageSizeKiB = conversion.Int64ValueToPointer(model.MaxMessageSizeKiB) - payload.MaxMessagesPerHour = conversion.Int64ValueToPointer(model.MaxMessagesPerHour) + if !model.MaxMessageSizeKiB.IsNull() && !model.MaxMessageSizeKiB.IsUnknown() { + payload.MaxMessageSizeKiB = coreUtils.Ptr(model.MaxMessageSizeKiB.ValueInt32()) + } + + if !model.MaxMessagesPerHour.IsNull() && !model.MaxMessagesPerHour.IsUnknown() { + payload.MaxMessagesPerHour = coreUtils.Ptr(model.MaxMessagesPerHour.ValueInt32()) + } // Optional fields payload.DisplayName = conversion.StringValueToPointer(model.Name) @@ -511,7 +523,7 @@ func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, er if diags.HasError() { return nil, fmt.Errorf("failed to convert labels: %w", core.DiagsToError(diags)) } - payload.Labels = &labels + payload.Labels = labels } return payload, nil diff --git a/stackit/internal/services/intake/runner/resource_test.go b/stackit/internal/services/intake/runner/resource_test.go index b6d3594a2..8c79a70a3 100644 --- a/stackit/internal/services/intake/runner/resource_test.go +++ b/stackit/internal/services/intake/runner/resource_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/utils" - "github.com/stackitcloud/stackit-sdk-go/services/intake" + intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" ) @@ -26,12 +26,12 @@ func TestMapFields(t *testing.T) { { "success", &intake.IntakeRunnerResponse{ - Id: utils.Ptr(runnerId), - DisplayName: utils.Ptr("name"), + Id: runnerId, + DisplayName: "name", Description: utils.Ptr("description"), - Labels: &map[string]string{"key": "value"}, - MaxMessageSizeKiB: utils.Ptr(int64(1024)), - MaxMessagesPerHour: utils.Ptr(int64(100)), + Labels: map[string]string{"key": "value"}, + MaxMessageSizeKiB: int32(1024), + MaxMessagesPerHour: int32(100), }, &Model{ ProjectId: types.StringValue("pid"), @@ -45,8 +45,8 @@ func TestMapFields(t *testing.T) { Name: types.StringValue("name"), Description: types.StringValue("description"), Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), - MaxMessageSizeKiB: types.Int64Value(1024), - MaxMessagesPerHour: types.Int64Value(100), + MaxMessageSizeKiB: types.Int32Value(1024), + MaxMessagesPerHour: types.Int32Value(100), }, false, }, @@ -69,8 +69,8 @@ func TestMapFields(t *testing.T) { { "empty response", &intake.IntakeRunnerResponse{ - Id: utils.Ptr(""), - Labels: &map[string]string{}, + Id: "", + Labels: map[string]string{}, }, &Model{ ProjectId: types.StringValue("pid"), @@ -84,8 +84,8 @@ func TestMapFields(t *testing.T) { Name: types.StringNull(), Description: types.StringNull(), Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}), - MaxMessageSizeKiB: types.Int64Null(), - MaxMessagesPerHour: types.Int64Null(), + MaxMessageSizeKiB: types.Int32Null(), + MaxMessagesPerHour: types.Int32Null(), }, false, }, @@ -119,15 +119,15 @@ func TestToCreatePayload(t *testing.T) { Name: types.StringValue("name"), Description: types.StringValue("description"), Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), - MaxMessageSizeKiB: types.Int64Value(1024), - MaxMessagesPerHour: types.Int64Value(100), + MaxMessageSizeKiB: types.Int32Value(1024), + MaxMessagesPerHour: types.Int32Value(100), }, &intake.CreateIntakeRunnerPayload{ - DisplayName: utils.Ptr("name"), + DisplayName: "name", Description: utils.Ptr("description"), - Labels: utils.Ptr(map[string]string{"key": "value"}), - MaxMessageSizeKiB: utils.Ptr(int64(1024)), - MaxMessagesPerHour: utils.Ptr(int64(100)), + Labels: map[string]string{"key": "value"}, + MaxMessageSizeKiB: int32(1024), + MaxMessagesPerHour: int32(100), }, false, }, @@ -141,11 +141,11 @@ func TestToCreatePayload(t *testing.T) { "empty model", &Model{}, &intake.CreateIntakeRunnerPayload{ - DisplayName: nil, + DisplayName: "", Description: nil, Labels: nil, - MaxMessageSizeKiB: nil, - MaxMessagesPerHour: nil, + MaxMessageSizeKiB: 0, + MaxMessagesPerHour: 0, }, false, }, @@ -180,16 +180,16 @@ func TestToUpdatePayload(t *testing.T) { Name: types.StringValue("name"), Description: types.StringValue("description"), Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), - MaxMessageSizeKiB: types.Int64Value(1024), - MaxMessagesPerHour: types.Int64Value(100), + MaxMessageSizeKiB: types.Int32Value(1024), + MaxMessagesPerHour: types.Int32Value(100), }, &Model{}, &intake.UpdateIntakeRunnerPayload{ DisplayName: conversion.StringValueToPointer(types.StringValue("name")), Description: conversion.StringValueToPointer(types.StringValue("description")), - Labels: utils.Ptr(map[string]string{"key": "value"}), - MaxMessageSizeKiB: conversion.Int64ValueToPointer(types.Int64Value(1024)), - MaxMessagesPerHour: conversion.Int64ValueToPointer(types.Int64Value(100)), + Labels: map[string]string{"key": "value"}, + MaxMessageSizeKiB: utils.Ptr(int32(1024)), + MaxMessagesPerHour: utils.Ptr(int32(100)), }, false, }, @@ -220,8 +220,8 @@ func TestToUpdatePayload(t *testing.T) { Name: types.StringUnknown(), Description: types.StringUnknown(), Labels: types.MapUnknown(types.StringType), - MaxMessageSizeKiB: types.Int64Unknown(), - MaxMessagesPerHour: types.Int64Unknown(), + MaxMessageSizeKiB: types.Int32Unknown(), + MaxMessagesPerHour: types.Int32Unknown(), }, &Model{}, &intake.UpdateIntakeRunnerPayload{}, diff --git a/stackit/internal/services/intake/utils/utils.go b/stackit/internal/services/intake/utils/utils.go index 4752fe79f..15b4ff7ca 100644 --- a/stackit/internal/services/intake/utils/utils.go +++ b/stackit/internal/services/intake/utils/utils.go @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/services/intake" + intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) @@ -18,8 +18,6 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags } if providerData.IntakeCustomEndpoint != "" { apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.IntakeCustomEndpoint)) - } else { - apiClientConfigOptions = append(apiClientConfigOptions) } apiClient, err := intake.NewAPIClient(apiClientConfigOptions...) if err != nil { From a3a8fc3103a52c73a3668a9649e01b644cb86951 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Wed, 1 Apr 2026 16:35:36 +0200 Subject: [PATCH 36/41] Handle empty labels --- stackit/internal/services/intake/runner/resource.go | 6 ++---- stackit/internal/services/intake/runner/resource_test.go | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index d2a8bb967..c7480c438 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -7,7 +7,6 @@ import ( "net/http" "strings" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" @@ -169,7 +168,6 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res Description: descriptions["labels"], ElementType: types.StringType, Optional: true, - Computed: true, PlanModifiers: []planmodifier.Map{ mapplanmodifier.UseStateForUnknown(), }, @@ -438,8 +436,8 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str model.RunnerId = types.StringValue(runnerResp.Id) } - if runnerResp.Labels == nil { - model.Labels = types.MapValueMust(types.StringType, map[string]attr.Value{}) + if runnerResp.Labels == nil || len(runnerResp.Labels) == 0 { + model.Labels = types.MapNull(types.StringType) } else { labels, diags := types.MapValueFrom(context.Background(), types.StringType, runnerResp.Labels) if diags.HasError() { diff --git a/stackit/internal/services/intake/runner/resource_test.go b/stackit/internal/services/intake/runner/resource_test.go index 8c79a70a3..5c885ecc0 100644 --- a/stackit/internal/services/intake/runner/resource_test.go +++ b/stackit/internal/services/intake/runner/resource_test.go @@ -83,7 +83,7 @@ func TestMapFields(t *testing.T) { RunnerId: types.StringNull(), Name: types.StringNull(), Description: types.StringNull(), - Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}), + Labels: types.MapNull(types.StringType), MaxMessageSizeKiB: types.Int32Null(), MaxMessagesPerHour: types.Int32Null(), }, From bbed9ac8d94a76714f1a63642e2427f3b0eae3bb Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Tue, 21 Apr 2026 10:54:41 +0200 Subject: [PATCH 37/41] Address review comments --- stackit/internal/services/intake/resource_acc_test.go | 10 ++++++++-- .../internal/services/intake/testdata/resource-max.tf | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/resource_acc_test.go index fe2b83962..7f4e9f854 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/resource_acc_test.go @@ -71,10 +71,12 @@ func TestAccIntakeRunnerMin(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["project_id"])), resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["name"])), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), + resource.TestCheckNoResourceAttr(intakeRunnerResource, "description"), + resource.TestCheckNoResourceAttr(intakeRunnerResource, "labels"), resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_message_size_kib"])), resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_messages_per_hour"])), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), - resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["region"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), ), }, // Data source check: creates config that includes resource and data source @@ -94,6 +96,8 @@ func TestAccIntakeRunnerMin(t *testing.T) { resource.TestCheckResourceAttrPair(intakeRunnerResource, "runner_id", "data.stackit_intake_runner.example", "runner_id"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "name", "data.stackit_intake_runner.example", "name"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "region", "data.stackit_intake_runner.example", "region"), + resource.TestCheckNoResourceAttr(intakeRunnerResource, "description"), + resource.TestCheckNoResourceAttr(intakeRunnerResource, "labels"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "max_messages_per_hour", "data.stackit_intake_runner.example", "max_messages_per_hour"), ), }, @@ -123,7 +127,9 @@ func TestAccIntakeRunnerMin(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMinUpdated()["name"])), resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_message_size_kib"])), resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_messages_per_hour"])), - resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["region"])), + resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), + resource.TestCheckNoResourceAttr(intakeRunnerResource, "description"), + resource.TestCheckNoResourceAttr(intakeRunnerResource, "labels"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), ), diff --git a/stackit/internal/services/intake/testdata/resource-max.tf b/stackit/internal/services/intake/testdata/resource-max.tf index 273d7aa7a..ea41988fe 100644 --- a/stackit/internal/services/intake/testdata/resource-max.tf +++ b/stackit/internal/services/intake/testdata/resource-max.tf @@ -2,6 +2,7 @@ variable "project_id" {} variable "name" {} variable "region" {} +variable "description" {} variable "max_message_size_kib" {} variable "max_messages_per_hour" {} @@ -9,7 +10,7 @@ resource "stackit_intake_runner" "example" { project_id = var.project_id name = var.name region = var.region - description = "An example runner for Intake" + description = var.description max_message_size_kib = var.max_message_size_kib max_messages_per_hour = var.max_messages_per_hour labels = { From 7a7032e8ad9776b504949dc26cfe5623dbbc6bae Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Thu, 23 Apr 2026 07:25:20 +0200 Subject: [PATCH 38/41] Adjust to new config builder func and new client API --- ...esource_acc_test.go => intake_acc_test.go} | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) rename stackit/internal/services/intake/{resource_acc_test.go => intake_acc_test.go} (92%) diff --git a/stackit/internal/services/intake/resource_acc_test.go b/stackit/internal/services/intake/intake_acc_test.go similarity index 92% rename from stackit/internal/services/intake/resource_acc_test.go rename to stackit/internal/services/intake/intake_acc_test.go index 7f4e9f854..950e4f0fb 100644 --- a/stackit/internal/services/intake/resource_acc_test.go +++ b/stackit/internal/services/intake/intake_acc_test.go @@ -13,7 +13,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" - sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -66,7 +65,7 @@ func TestAccIntakeRunnerMin(t *testing.T) { // Create the minimum runner from the HCL file { ConfigVariables: testIntakeRunnerConfigVarsMin, - Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMin, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceIntakeRunnerMin, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["project_id"])), resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["name"])), @@ -89,7 +88,7 @@ func TestAccIntakeRunnerMin(t *testing.T) { project_id = %s.project_id runner_id = %s.runner_id region = %s.region - }`, testutil.IntakeProviderConfig(), resourceIntakeRunnerMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), + }`, testutil.NewConfigBuilder().BuildProviderConfig(), resourceIntakeRunnerMin, intakeRunnerResource, intakeRunnerResource, intakeRunnerResource), Check: resource.ComposeAggregateTestCheckFunc( // Make sure it's correctly found resource by comparing runner_id attribute resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), @@ -104,7 +103,7 @@ func TestAccIntakeRunnerMin(t *testing.T) { // Simulate terraform import { ConfigVariables: testIntakeRunnerConfigVarsMin, - Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMin, + Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceIntakeRunnerMin, ResourceName: intakeRunnerResource, ImportState: true, ImportStateVerify: true, @@ -121,7 +120,7 @@ func TestAccIntakeRunnerMin(t *testing.T) { // Update check: verifies API updated resource name without crashing { ConfigVariables: testIntakeRunnerConfigVarsMinUpdated(), - Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMin, + Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceIntakeRunnerMin, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMinUpdated()["project_id"])), resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMinUpdated()["name"])), @@ -146,7 +145,7 @@ func TestAccIntakeRunnerMax(t *testing.T) { // Create the max intake runner from HCL files and verify comparison { ConfigVariables: testIntakeRunnerConfigVarsMax, - Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMax, + Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceIntakeRunnerMax, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["project_id"])), resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["name"])), @@ -169,7 +168,7 @@ func TestAccIntakeRunnerMax(t *testing.T) { data "stackit_intake_runner" "example" { project_id = %s.project_id runner_id = %s.runner_id - }`, testutil.IntakeProviderConfig(), resourceIntakeRunnerMax, intakeRunnerResource, intakeRunnerResource), + }`, testutil.NewConfigBuilder().BuildProviderConfig(), resourceIntakeRunnerMax, intakeRunnerResource, intakeRunnerResource), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(intakeRunnerResource, "project_id", "data.stackit_intake_runner.example", "project_id"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "runner_id", "data.stackit_intake_runner.example", "runner_id"), @@ -183,7 +182,7 @@ func TestAccIntakeRunnerMax(t *testing.T) { // Simulate terraform import { ConfigVariables: testIntakeRunnerConfigVarsMax, - Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMax, + Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceIntakeRunnerMax, ResourceName: intakeRunnerResource, ImportState: true, ImportStateVerify: true, @@ -200,7 +199,7 @@ func TestAccIntakeRunnerMax(t *testing.T) { // Update and verify changes are reflected { ConfigVariables: testIntakeRunnerConfigVarsMaxUpdated(), - Config: testutil.IntakeProviderConfig() + "\n" + resourceIntakeRunnerMax, + Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceIntakeRunnerMax, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(intakeRunnerResource, "project_id", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["project_id"])), resource.TestCheckResourceAttr(intakeRunnerResource, "name", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMaxUpdated()["name"])), @@ -222,16 +221,11 @@ func TestAccIntakeRunnerMax(t *testing.T) { // testAccCheckIntakeRunnerDestroy act as independent auditor to verify destroy operation func testAccCheckIntakeRunnerDestroy(s *terraform.State) error { ctx := context.Background() - var client *intake.APIClient - var err error - - if testutil.IntakeCustomEndpoint == "" { - client, err = intake.NewAPIClient() - } else { - client, err = intake.NewAPIClient( - sdkConfig.WithEndpoint(testutil.IntakeCustomEndpoint), - ) + client, err := intake.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.GitCustomEndpoint, false)...) + if err != nil { + return fmt.Errorf("creating client: %w", err) } + if err != nil { return fmt.Errorf("creating client: %w", err) } From 4229efd315ad286b02af8c7e4cbc0c7b4b3f5fd1 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Thu, 23 Apr 2026 07:43:23 +0200 Subject: [PATCH 39/41] Rebase --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index e68fae315..f32cd3685 100644 --- a/go.mod +++ b/go.mod @@ -210,6 +210,7 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect + github.com/stackitcloud/stackit-sdk-go/services/intake v0.8.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.11.1 // indirect diff --git a/go.sum b/go.sum index ac675d7c3..175ba4254 100644 --- a/go.sum +++ b/go.sum @@ -686,6 +686,8 @@ github.com/stackitcloud/stackit-sdk-go/services/git v0.12.1 h1:H82bqo3QB8q78nKxj github.com/stackitcloud/stackit-sdk-go/services/git v0.12.1/go.mod h1:K7jZxMtjTgITmG0Z6UoWgnZo32IkF8cHjlxXNifRZZE= github.com/stackitcloud/stackit-sdk-go/services/iaas v1.10.1 h1:cnuhSWcd2MxG/LWkJgxTZCbtLCBLNSB31/iGfCj7rUw= github.com/stackitcloud/stackit-sdk-go/services/iaas v1.10.1/go.mod h1:Qa7486Y17g07N/9HbAVEVPcPZ+e1l3y7F9I7j5nKewY= +github.com/stackitcloud/stackit-sdk-go/services/intake v0.8.1 h1:Wkr/1OS/bn6cokqHtKJsYZjeHr+ludfMnoxKu9XVTWE= +github.com/stackitcloud/stackit-sdk-go/services/intake v0.8.1/go.mod h1:A7HS+7n4ZyIwFbHoJwBOGNTBd3udE+/LumEwPu2KvRg= github.com/stackitcloud/stackit-sdk-go/services/kms v1.7.1 h1:EvA2QwlGBS1slqwmDKWqrd7r+YjxRNFm3RwTSyMmJaY= github.com/stackitcloud/stackit-sdk-go/services/kms v1.7.1/go.mod h1:38j2u5IeVU5fec9A4tgZpM3OMaORPe6vrl0Sj/Z9aZE= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.12.0 h1:NV8gJ75nPvnJvxXVAp3zSoer04jq1LLxEhGZdH68jQU= From 98ab4ae21efab4b3ac55e1ec0b565271c8650df4 Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Fri, 24 Apr 2026 16:02:04 +0200 Subject: [PATCH 40/41] Lint --- stackit/internal/services/intake/intake_acc_test.go | 1 + stackit/internal/services/intake/runner/data_source.go | 1 + stackit/internal/services/intake/runner/resource.go | 3 ++- stackit/internal/services/intake/runner/resource_test.go | 1 + stackit/internal/services/intake/utils/utils.go | 1 + 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/stackit/internal/services/intake/intake_acc_test.go b/stackit/internal/services/intake/intake_acc_test.go index 950e4f0fb..91d1c7a98 100644 --- a/stackit/internal/services/intake/intake_acc_test.go +++ b/stackit/internal/services/intake/intake_acc_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi/wait" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" ) diff --git a/stackit/internal/services/intake/runner/data_source.go b/stackit/internal/services/intake/runner/data_source.go index 0d075be7a..f0f92ede9 100644 --- a/stackit/internal/services/intake/runner/data_source.go +++ b/stackit/internal/services/intake/runner/data_source.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" intakeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/intake/utils" diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index c7480c438..16a8cf556 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" coreUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" intakeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/intake/utils" @@ -436,7 +437,7 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str model.RunnerId = types.StringValue(runnerResp.Id) } - if runnerResp.Labels == nil || len(runnerResp.Labels) == 0 { + if len(runnerResp.Labels) == 0 { model.Labels = types.MapNull(types.StringType) } else { labels, diags := types.MapValueFrom(context.Background(), types.StringType, runnerResp.Labels) diff --git a/stackit/internal/services/intake/runner/resource_test.go b/stackit/internal/services/intake/runner/resource_test.go index 5c885ecc0..f7c9f2b9b 100644 --- a/stackit/internal/services/intake/runner/resource_test.go +++ b/stackit/internal/services/intake/runner/resource_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/utils" intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" ) diff --git a/stackit/internal/services/intake/utils/utils.go b/stackit/internal/services/intake/utils/utils.go index 15b4ff7ca..87d1fd648 100644 --- a/stackit/internal/services/intake/utils/utils.go +++ b/stackit/internal/services/intake/utils/utils.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/stackitcloud/stackit-sdk-go/core/config" intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) From 30243a06353e7cb03f381a55518e3c310c316faf Mon Sep 17 00:00:00 2001 From: Yago Carlos Fernandez Gou Date: Fri, 24 Apr 2026 15:53:40 +0200 Subject: [PATCH 41/41] Introduce create time and URI to Intake Runner # Conflicts: # stackit/internal/services/intake/runner/data_source.go --- docs/data-sources/intake_runner.md | 2 ++ docs/resources/intake_runner.md | 2 ++ .../services/intake/intake_acc_test.go | 15 +++++++++++-- .../services/intake/runner/data_source.go | 13 ++++++++++-- .../services/intake/runner/resource.go | 21 +++++++++++++++++++ .../services/intake/runner/resource_test.go | 9 ++++++++ 6 files changed, 58 insertions(+), 4 deletions(-) diff --git a/docs/data-sources/intake_runner.md b/docs/data-sources/intake_runner.md index 52806a24a..73968c0c9 100644 --- a/docs/data-sources/intake_runner.md +++ b/docs/data-sources/intake_runner.md @@ -33,9 +33,11 @@ data "stackit_intake_runner" "example" { ### Read-Only +- `create_time` (String) The creation time of the runner. - `description` (String) The description of the runner. - `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`runner_id`". - `labels` (Map of String) User-defined labels. - `max_message_size_kib` (Number) The maximum message size in KiB. - `max_messages_per_hour` (Number) The maximum number of messages per hour. - `name` (String) The name of the runner. +- `uri` (String) The URI of the runner. diff --git a/docs/resources/intake_runner.md b/docs/resources/intake_runner.md index 4dd64b6ab..4ec4a797d 100644 --- a/docs/resources/intake_runner.md +++ b/docs/resources/intake_runner.md @@ -44,5 +44,7 @@ resource "stackit_intake_runner" "example" { ### Read-Only +- `create_time` (String) The creation time of the runner. - `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`runner_id`". - `runner_id` (String) The runner ID. +- `uri` (String) The URI of the runner. diff --git a/stackit/internal/services/intake/intake_acc_test.go b/stackit/internal/services/intake/intake_acc_test.go index 91d1c7a98..0d6d83f4d 100644 --- a/stackit/internal/services/intake/intake_acc_test.go +++ b/stackit/internal/services/intake/intake_acc_test.go @@ -8,11 +8,10 @@ import ( "strings" "testing" - "github.com/stackitcloud/stackit-sdk-go/core/utils" - "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/stackitcloud/stackit-sdk-go/core/utils" intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi/wait" @@ -76,6 +75,8 @@ func TestAccIntakeRunnerMin(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "max_message_size_kib", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_message_size_kib"])), resource.TestCheckResourceAttr(intakeRunnerResource, "max_messages_per_hour", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMin["max_messages_per_hour"])), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "uri"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "create_time"), resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.Region), ), }, @@ -98,6 +99,8 @@ func TestAccIntakeRunnerMin(t *testing.T) { resource.TestCheckResourceAttrPair(intakeRunnerResource, "region", "data.stackit_intake_runner.example", "region"), resource.TestCheckNoResourceAttr(intakeRunnerResource, "description"), resource.TestCheckNoResourceAttr(intakeRunnerResource, "labels"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "uri", "data.stackit_intake_runner.example", "uri"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "create_time", "data.stackit_intake_runner.example", "create_time"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "max_messages_per_hour", "data.stackit_intake_runner.example", "max_messages_per_hour"), ), }, @@ -131,6 +134,8 @@ func TestAccIntakeRunnerMin(t *testing.T) { resource.TestCheckNoResourceAttr(intakeRunnerResource, "description"), resource.TestCheckNoResourceAttr(intakeRunnerResource, "labels"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "uri"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "create_time"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), ), }, @@ -158,6 +163,8 @@ func TestAccIntakeRunnerMax(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "uri"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "create_time"), resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["region"])), ), }, @@ -176,6 +183,8 @@ func TestAccIntakeRunnerMax(t *testing.T) { resource.TestCheckResourceAttrPair(intakeRunnerResource, "name", "data.stackit_intake_runner.example", "name"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "description", "data.stackit_intake_runner.example", "description"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "region", "data.stackit_intake_runner.example", "region"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "uri", "data.stackit_intake_runner.example", "uri"), + resource.TestCheckResourceAttrPair(intakeRunnerResource, "create_time", "data.stackit_intake_runner.example", "create_time"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "labels.env", "data.stackit_intake_runner.example", "labels.env"), resource.TestCheckResourceAttrPair(intakeRunnerResource, "max_messages_per_hour", "data.stackit_intake_runner.example", "max_messages_per_hour"), ), @@ -212,6 +221,8 @@ func TestAccIntakeRunnerMax(t *testing.T) { resource.TestCheckResourceAttr(intakeRunnerResource, "labels.created_by", "terraform-provider-stackit"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "runner_id"), resource.TestCheckResourceAttrSet(intakeRunnerResource, "id"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "uri"), + resource.TestCheckResourceAttrSet(intakeRunnerResource, "create_time"), resource.TestCheckResourceAttr(intakeRunnerResource, "region", testutil.ConvertConfigVariable(testIntakeRunnerConfigVarsMax["region"])), ), }, diff --git a/stackit/internal/services/intake/runner/data_source.go b/stackit/internal/services/intake/runner/data_source.go index f0f92ede9..343f0802d 100644 --- a/stackit/internal/services/intake/runner/data_source.go +++ b/stackit/internal/services/intake/runner/data_source.go @@ -12,13 +12,12 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" intakeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/intake/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - - intake "github.com/stackitcloud/stackit-sdk-go/services/intake/v1betaapi" ) // Ensure the implementation satisfies the expected interfaces @@ -68,6 +67,8 @@ func (r *runnerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, "labels": "User-defined labels.", "max_message_size_kib": "The maximum message size in KiB.", "max_messages_per_hour": "The maximum number of messages per hour.", + "uri": "The URI of the runner.", + "create_time": "The creation time of the runner.", "region": "The resource region. If not defined, the provider region is used.", } @@ -115,6 +116,14 @@ func (r *runnerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, Description: descriptions["max_messages_per_hour"], Computed: true, }, + "uri": schema.StringAttribute{ + Description: descriptions["uri"], + Computed: true, + }, + "create_time": schema.StringAttribute{ + Description: descriptions["create_time"], + Computed: true, + }, "region": schema.StringAttribute{ Optional: true, Description: descriptions["region"], diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index 16a8cf556..758671204 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -47,6 +47,8 @@ type Model struct { MaxMessageSizeKiB types.Int32 `tfsdk:"max_message_size_kib"` MaxMessagesPerHour types.Int32 `tfsdk:"max_messages_per_hour"` Region types.String `tfsdk:"region"` + Uri types.String `tfsdk:"uri"` + CreateTime types.String `tfsdk:"create_time"` } // NewRunnerResource is a helper function to simplify the provider implementation. @@ -123,6 +125,8 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res "labels": "User-defined labels.", "max_message_size_kib": "The maximum message size in KiB.", "max_messages_per_hour": "The maximum number of messages per hour.", + "uri": "The URI of the runner.", + "create_time": "The creation time of the runner.", } resp.Schema = schema.Schema{ @@ -181,6 +185,20 @@ func (r *runnerResource) Schema(_ context.Context, _ resource.SchemaRequest, res Description: descriptions["max_messages_per_hour"], Required: true, }, + "uri": schema.StringAttribute{ + Description: descriptions["uri"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "create_time": schema.StringAttribute{ + Description: descriptions["create_time"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, "region": schema.StringAttribute{ Optional: true, Computed: true, @@ -468,6 +486,9 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str model.MaxMessagesPerHour = types.Int32Value(runnerResp.MaxMessagesPerHour) } + model.Uri = types.StringValue(runnerResp.Uri) + model.CreateTime = types.StringValue(runnerResp.CreateTime.String()) + return nil } diff --git a/stackit/internal/services/intake/runner/resource_test.go b/stackit/internal/services/intake/runner/resource_test.go index f7c9f2b9b..3054e2d55 100644 --- a/stackit/internal/services/intake/runner/resource_test.go +++ b/stackit/internal/services/intake/runner/resource_test.go @@ -3,6 +3,7 @@ package runner import ( "fmt" "testing" + "time" "github.com/google/go-cmp/cmp" "github.com/google/uuid" @@ -16,6 +17,8 @@ import ( func TestMapFields(t *testing.T) { runnerId := uuid.New().String() + now := time.Now() + tests := []struct { description string input *intake.IntakeRunnerResponse @@ -33,6 +36,8 @@ func TestMapFields(t *testing.T) { Labels: map[string]string{"key": "value"}, MaxMessageSizeKiB: int32(1024), MaxMessagesPerHour: int32(100), + Uri: "c512e9ea-b086-4945-b8e1-8f9a4b592b06.intake.eu01.onstackit.cloud:9094", + CreateTime: now, }, &Model{ ProjectId: types.StringValue("pid"), @@ -48,6 +53,8 @@ func TestMapFields(t *testing.T) { Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), MaxMessageSizeKiB: types.Int32Value(1024), MaxMessagesPerHour: types.Int32Value(100), + Uri: types.StringValue("c512e9ea-b086-4945-b8e1-8f9a4b592b06.intake.eu01.onstackit.cloud:9094"), + CreateTime: types.StringValue(now.String()), }, false, }, @@ -87,6 +94,8 @@ func TestMapFields(t *testing.T) { Labels: types.MapNull(types.StringType), MaxMessageSizeKiB: types.Int32Null(), MaxMessagesPerHour: types.Int32Null(), + Uri: types.StringValue(""), + CreateTime: types.StringValue(time.Time{}.String()), }, false, },