From 40eba814a53fbcb0876ca7cdccf5b1f4139ae359 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Tue, 5 May 2026 15:41:06 +0200 Subject: [PATCH 01/30] Add create instance functionality --- go.mod | 1 + go.sum | 2 + stackit/internal/core/core.go | 1 + .../modelexperiments/instance/resource.go | 374 ++++++++++++++++++ .../services/modelexperiments/utils/util.go | 59 +++ stackit/internal/utils/utils.go | 7 +- stackit/provider.go | 9 + 7 files changed, 450 insertions(+), 3 deletions(-) create mode 100644 stackit/internal/services/modelexperiments/instance/resource.go create mode 100644 stackit/internal/services/modelexperiments/utils/util.go diff --git a/go.mod b/go.mod index 2339c9453..c4af3ab29 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,7 @@ require ( 4d63.com/gochecknoglobals v0.2.2 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect + dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments v0.41.0 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect dev.gaijin.team/go/golib v0.6.0 // indirect github.com/4meepo/tagalign v1.4.3 // indirect diff --git a/go.sum b/go.sum index 99763d1c5..5693b3589 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments v0.41.0 h1:vu4HvuP03eT9UAl/3+44wFqm4Em8kVczMfPtF1Oh59c= +dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments v0.41.0/go.mod h1:gh67VQXccqlE/xsWWKZSt+brkbB8RHZymZ8dYo5FunU= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go index d71a0cfed..b2bd160ab 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -57,6 +57,7 @@ type ProviderData struct { MariaDBCustomEndpoint string MongoDBFlexCustomEndpoint string ModelServingCustomEndpoint string + ModelExperimentsCustomEndpoint string ObjectStorageCustomEndpoint string ObservabilityCustomEndpoint string OpenSearchCustomEndpoint string diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go new file mode 100644 index 000000000..2568a572b --- /dev/null +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -0,0 +1,374 @@ +package instance + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/core/wait" + serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" + serviceEnablementWait "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api/wait" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + modelexperimentsutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ resource.Resource = &instanceResource{} + _ resource.ResourceWithConfigure = &instanceResource{} + //_ resource.ResourceWithModifyPlan = &tokenResource{} +) + +//go:embed description.md +var markdownDescription string + +type Model struct { + Id types.String `tfsdk:"id"` // needed by TF + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + DeletedExperimentRetention types.String `tfsdk:"deletedExperimentRetention"` + Labels types.Map `tfsdk:"labels"` + State types.String `tfsdk:"state"` + BucketName types.String `tfsdk:"bucket_name"` + ErrorMessage types.String `tfsdk:"error_message"` + InstanceId types.String `tfsdk:"instance_id"` + Url types.String `tfsdk:"url"` +} + +func NewInstanceResource() resource.Resource { + return &instanceResource{} +} + +type instanceResource struct { + client *modelexperiments.APIClient + providerData core.ProviderData + serviceEnablementClient *serviceenablement.APIClient +} + +func (i *instanceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_order" +} + +// Configure adds the provider configured client to the resource. +func (i *instanceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + var ok bool + i.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := modelexperimentsutils.ConfigureClient(ctx, &i.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + serviceEnablementClient := modelexperimentsutils.ConfigureServiceEnablementClient(ctx, &i.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + i.client = apiClient + i.serviceEnablementClient = serviceEnablementClient + tflog.Info(ctx, "Model-Serving auth token client configured") +} + +func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: markdownDescription, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Terraform's internal data source. ID. It is structured as \"`project_id`,`region`,`instance_id`\".", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "STACKIT project ID to which the AI model experiments instance is associated.", + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, + Description: "Region to which the AI model experiments instance is associated. If not defined, the provider region is used", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "instance_id": schema.StringAttribute{ + Description: "The AI model experiments instance ID.", + Computed: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "labels": schema.MapAttribute{ + Description: "A map of arbitrary key/value pairs for the AI model experiments instance", + Optional: true, + Required: false, + Computed: true, + ElementType: types.StringType, + }, + "description": schema.StringAttribute{ + Description: "The description of the AI model experiments instance.", + Required: false, + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(0, 160), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the AI model experiments instance.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 64), + }, + }, + "state": schema.StringAttribute{ + Description: "State of the AI model experiments instance.", + Computed: true, + }, + "url": schema.StringAttribute{ + Description: "URL of the AI model experiments instance.", + Computed: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(0, 1000), + }, + }, + "deletedExperimentRetention": schema.StringAttribute{ + Description: "The deleted experiment retention of the AI model experiments instance.", + Optional: true, + Required: false, + Computed: true, + }, + "bucket_name": schema.StringAttribute{ + Description: "The object storage bucket name of the AI model experiments instance.", + Computed: true, + }, + "error_message": schema.StringAttribute{ + Description: "Error messages of the AI model experiments instance.", + Optional: true, + Required: false, + Computed: true, + }, + }, + } +} + +// Create creates the resource and sets the initial Terraform state. +func (i *instanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := i.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + err := i.serviceEnablementClient.DefaultAPI.EnableServiceRegional(ctx, region, projectId, utils.ModelExperimentsServiceId). + Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error enabling AI model experiments", + fmt.Sprintf("Service not available in region %s \n%v", region, err), + ) + return + } + } + core.LogAndAddError( + ctx, + &resp.Diagnostics, + "Error enabling AI model experiments", + fmt.Sprintf("Error enabling AI model experiments: %v", err), + ) + return + } + + _, err = serviceEnablementWait.EnableServiceWaitHandler(ctx, i.serviceEnablementClient.DefaultAPI, region, projectId, utils.ModelServingServiceId). + WaitWithContext(ctx) + if err != nil { + core.LogAndAddError( + ctx, + &resp.Diagnostics, + "Error enabling AI model experiments", + fmt.Sprintf("Error enabling AI model serving: %v", err), + ) + return + } + + payload, err := toCreatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model serving auth token", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + createInstanceResp, err := i.client.DefaultAPI.CreateInstance(ctx, projectId, region).CreateInstancePayload(*payload).Execute() + if err != nil { + core.LogAndAddError( + ctx, + &resp.Diagnostics, + "Error creating AI model experiments instance", + fmt.Sprintf("Calling API: %v", err), + ) + return + } + ctx = core.LogResponse(ctx) + + if createInstanceResp.Instance.Id == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", "Got empty instance id") + return + } + + instanceId := createInstanceResp.Instance.Id + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": projectId, + "region": region, + "instance_id": instanceId, + }) + if resp.Diagnostics.HasError() { + return + } + + mapValue, diags := types.MapValueFrom(ctx, types.StringType, createInstanceResp.Instance.Labels) + if diags.HasError() { + return + } + + //If model experiments instance is impaired, write state avoid dangling resources and return + waitResp, err := CreateModelExperimentsWaitHandler(ctx, i.client, region, projectId, instanceId).WaitWithContext(ctx) + if err != nil { + mapCreateResponse(createInstanceResp, waitResp, &model, region, mapValue) + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Waiting for instance to be active: %v", err)) + return + } + + // Map response body to schema + err = mapCreateResponse(createInstanceResp, waitResp, &model, region, mapValue) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set state to fully populated data + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Info(ctx, "Model Experiments instance created") +} + +// Read refreshes the Terraform state with the latest data. +func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +} + +// Update updates the resource and sets the updated Terraform state on success. +func (i *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +} + +// Delete deletes the resource and removes the Terraform state on success. +func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +func mapCreateResponse(instanceCreateResp *modelexperiments.CreateInstanceResponse, waitResp *modelexperiments.GetInstanceResponse, model *Model, region string, labels basetypes.MapValue) error { + if instanceCreateResp == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + instance := instanceCreateResp.Instance + + if instance.Id == "" { + return fmt.Errorf("instance id not present") + } + + if waitResp == nil { + return fmt.Errorf("response input is nil") + } + + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceCreateResp.Instance.Id) + model.InstanceId = types.StringValue(instance.Id) + model.Name = types.StringValue(instance.Name) + model.State = types.StringValue(waitResp.Instance.State) + model.Description = types.StringPointerValue(instance.Description) + model.DeletedExperimentRetention = types.StringPointerValue(instance.DeletedExperimentRetention) + model.BucketName = types.StringPointerValue(instance.BucketName) + model.ErrorMessage = types.StringPointerValue(instance.ErrorMessage) + model.Labels = labels + + return nil +} + +func toCreatePayload(model *Model) (*modelexperiments.CreateInstancePayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + + modelLabels := model.Labels.Elements() + labels, err := conversion.ToOptStringMap(modelLabels) + if err != nil { + return nil, fmt.Errorf("converting to Go map: %w", err) + } + + return &modelexperiments.CreateInstancePayload{ + Name: model.Name.ValueString(), + Description: conversion.StringValueToPointer(model.Description), + DeletedExperimentRetention: conversion.StringValueToPointer(model.DeletedExperimentRetention), + Labels: labels, + }, nil +} + +func CreateModelExperimentsWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { + handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { + getInstanceResp, err := a.DefaultAPI.GetInstance(ctx, region, projectId, instanceId).Execute() + if err != nil { + return false, nil, err + } + if getInstanceResp.Instance.State == modelexperimentsutils.INSTANCESTATE_ACTIVE { + return true, getInstanceResp, nil + } + if getInstanceResp.Instance.State == modelexperimentsutils.INSTANCESTATE_IMPAIRED { + return true, getInstanceResp, fmt.Errorf("AI model experiments instance is impaired") + } + + return false, nil, nil + }) + + handler.SetTimeout(10 * time.Minute) + + return handler +} diff --git a/stackit/internal/services/modelexperiments/utils/util.go b/stackit/internal/services/modelexperiments/utils/util.go new file mode 100644 index 000000000..455470b92 --- /dev/null +++ b/stackit/internal/services/modelexperiments/utils/util.go @@ -0,0 +1,59 @@ +package utils + +import ( + "context" + "fmt" + + modelexperiment "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/stackitcloud/stackit-sdk-go/core/config" + serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +const ( + INSTANCESTATE_CREATING = "creating" + INSTANCESTATE_ACTIVE = "active" + INSTANCESTATE_DELETING = "deleting" + INSTANCESTATE_PENDING = "pending" + INSTANCESTATE_UPDATING = "updating" + INSTANCESTATE_IMPAIRED = "impaired" + INSTANCESTATE_RECONCILING = "reconciling" +) + +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *modelexperiment.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.ModelExperimentsCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.ModelExperimentsCustomEndpoint)) + } + apiClient, err := modelexperiment.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 +} + +func ConfigureServiceEnablementClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *serviceenablement.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.ServiceEnablementCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint)) + } else { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion())) + } + apiClient, err := serviceenablement.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/utils/utils.go b/stackit/internal/utils/utils.go index b81600a64..40cae9994 100644 --- a/stackit/internal/utils/utils.go +++ b/stackit/internal/utils/utils.go @@ -24,9 +24,10 @@ import ( ) const ( - SKEServiceId = "cloud.stackit.ske" - ModelServingServiceId = "cloud.stackit.model-serving" - EdgecloudServiceId = "cloud.stackit.edge-cloud" + SKEServiceId = "cloud.stackit.ske" + ModelServingServiceId = "cloud.stackit.model-serving" + EdgecloudServiceId = "cloud.stackit.edge-cloud" + ModelExperimentsServiceId = "cloud.stackit.model-experiments" ) var ( diff --git a/stackit/provider.go b/stackit/provider.go index 7c653f474..bf8c7ea85 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -76,6 +76,7 @@ import ( logsInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logs/instance" mariaDBCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/credential" mariaDBInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/instance" + modelExperimentsInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" modelServingToken "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelserving/token" mongoDBFlexInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/instance" mongoDBFlexUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/user" @@ -191,6 +192,7 @@ type providerModel struct { LogsCustomEndpoint types.String `tfsdk:"logs_custom_endpoint"` MariaDBCustomEndpoint types.String `tfsdk:"mariadb_custom_endpoint"` ModelServingCustomEndpoint types.String `tfsdk:"modelserving_custom_endpoint"` + ModelExperimentsCustomEndpoint types.String `tfsdk:"modelexperiments_custom_endpoint"` MongoDBFlexCustomEndpoint types.String `tfsdk:"mongodbflex_custom_endpoint"` ObjectStorageCustomEndpoint types.String `tfsdk:"objectstorage_custom_endpoint"` ObservabilityCustomEndpoint types.String `tfsdk:"observability_custom_endpoint"` @@ -248,6 +250,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "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", + "modelexperiments_custom_endpoint": "Custom endpoint for the AI Model Experiments service", "loadbalancer_custom_endpoint": "Custom endpoint for the Load Balancer service", "logme_custom_endpoint": "Custom endpoint for the LogMe service", "logs_custom_endpoint": "Custom endpoint for the Logs service", @@ -407,6 +410,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["modelserving_custom_endpoint"], }, + "modelexperiments_custom_endpoint": schema.StringAttribute{ + Optional: true, + Description: descriptions["modelexperiments_custom_endpoint"], + }, "authorization_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["authorization_custom_endpoint"], @@ -570,6 +577,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, setStringField(providerConfig.LogsCustomEndpoint, func(v string) { providerData.LogsCustomEndpoint = v }) setStringField(providerConfig.MariaDBCustomEndpoint, func(v string) { providerData.MariaDBCustomEndpoint = v }) setStringField(providerConfig.ModelServingCustomEndpoint, func(v string) { providerData.ModelServingCustomEndpoint = v }) + setStringField(providerConfig.ModelExperimentsCustomEndpoint, func(v string) { providerData.ModelExperimentsCustomEndpoint = v }) setStringField(providerConfig.MongoDBFlexCustomEndpoint, func(v string) { providerData.MongoDBFlexCustomEndpoint = v }) setStringField(providerConfig.ObjectStorageCustomEndpoint, func(v string) { providerData.ObjectStorageCustomEndpoint = v }) setStringField(providerConfig.ObservabilityCustomEndpoint, func(v string) { providerData.ObservabilityCustomEndpoint = v }) @@ -815,6 +823,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { mariaDBInstance.NewInstanceResource, mariaDBCredential.NewCredentialResource, modelServingToken.NewTokenResource, + modelExperimentsInstance.NewInstanceResource, mongoDBFlexInstance.NewInstanceResource, mongoDBFlexUser.NewUserResource, objectStorageBucket.NewBucketResource, From 401924af497851f28c587b6519a835331f7a943e Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Wed, 6 May 2026 11:34:30 +0200 Subject: [PATCH 02/30] Add Read update delete func for mlflow instance --- .../modelexperiments/instance/resource.go | 260 +++++++++++++++++- 1 file changed, 248 insertions(+), 12 deletions(-) diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 2568a572b..abf88f67f 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -84,7 +84,7 @@ func (i *instanceResource) Configure(ctx context.Context, req resource.Configure } i.client = apiClient i.serviceEnablementClient = serviceEnablementClient - tflog.Info(ctx, "Model-Serving auth token client configured") + tflog.Info(ctx, "Model experiments client configured") } func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -174,7 +174,7 @@ func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r } // Create creates the resource and sets the initial Terraform state. -func (i *instanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +func (i *instanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform // Retrieve values from plan var model Model diags := req.Plan.Get(ctx, &model) @@ -226,7 +226,7 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques payload, err := toCreatePayload(&model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model serving auth token", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Creating API payload: %v", err)) return } @@ -257,13 +257,16 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - mapValue, diags := types.MapValueFrom(ctx, types.StringType, createInstanceResp.Instance.Labels) - if diags.HasError() { - return + var mapValue basetypes.MapValue + if createInstanceResp.Instance.Labels != nil { + mapValue, diags = types.MapValueFrom(ctx, types.StringType, createInstanceResp.Instance.Labels) + if diags.HasError() { + return + } } //If model experiments instance is impaired, write state avoid dangling resources and return - waitResp, err := CreateModelExperimentsWaitHandler(ctx, i.client, region, projectId, instanceId).WaitWithContext(ctx) + waitResp, err := CreateMExpInstanceWaitHandler(ctx, i.client, region, projectId, instanceId).WaitWithContext(ctx) if err != nil { mapCreateResponse(createInstanceResp, waitResp, &model, region, mapValue) diags = resp.State.Set(ctx, model) @@ -287,19 +290,188 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - tflog.Info(ctx, "Model Experiments instance created") + tflog.Info(ctx, "Model experiments instance created") } // Read refreshes the Terraform state with the latest data. -func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + instanceId := model.InstanceId.ValueString() + if instanceId == "" { + // Resource not yet created; ID is unknown. + resp.State.RemoveResource(ctx) + return + } + region := i.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) + + getInstanceResp, err := i.client.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + // Remove the resource from the state so Terraform will recreate it + resp.State.RemoveResource(ctx) + return + } + } + + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading AI model experiments instance", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapInstance(ctx, getInstanceResp.Instance, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Info(ctx, "Model experiments instance read") + } // Update updates the resource and sets the updated Terraform state on success. -func (i *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (i *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform + // Retrieve values from plan + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Get current state + var state Model + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := state.ProjectId.ValueString() + instanceId := state.InstanceId.ValueString() + + region := i.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) + + payload, err := toUpdatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + updateInstanceResp, err := i.client.DefaultAPI.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + // Remove the resource from the state so Terraform will recreate it + resp.State.RemoveResource(ctx) + return + } + } + + core.LogAndAddError( + ctx, + &resp.Diagnostics, + "Error updating AI model experiments instance", + fmt.Sprintf( + "Calling API: %v, instanceId: %s, region: %s, projectId: %s", + err, + instanceId, + region, + projectId, + ), + ) + return + } + + ctx = core.LogResponse(ctx) + + err = mapInstance(ctx, updateInstanceResp.Instance, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Info(ctx, "Model experiments instance updated") } // Delete deletes the resource and removes the Terraform state on success. -func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform + // Retrieve values from plan + 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() + instanceId := model.InstanceId.ValueString() + + region := i.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) + + _, err := i.client.DefaultAPI.DeleteInstance(ctx, projectId, region, instanceId).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 deleting AI model experiments instance", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + _, err = DeleteMExpInstanceWaitHandler(ctx, i.client, region, projectId, instanceId). + WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Waiting for instance to be deleted: %v", err)) + return + } + + tflog.Info(ctx, "Model experiments instance deleted") } func mapCreateResponse(instanceCreateResp *modelexperiments.CreateInstanceResponse, waitResp *modelexperiments.GetInstanceResponse, model *Model, region string, labels basetypes.MapValue) error { @@ -333,6 +505,29 @@ func mapCreateResponse(instanceCreateResp *modelexperiments.CreateInstanceRespon return nil } +func mapInstance(ctx context.Context, instance modelexperiments.Instance, model *Model) error { + if model == nil { + return fmt.Errorf("model input is nil") + } + + mapValue, diags := types.MapValueFrom(ctx, types.StringType, instance.Labels) + if diags.HasError() { + return fmt.Errorf("failure in mapping labels") + } + + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.Region.ValueString(), model.InstanceId.ValueString()) + model.InstanceId = types.StringValue(instance.Id) + model.Name = types.StringValue(instance.Name) + model.State = types.StringValue(instance.State) + model.Description = types.StringPointerValue(instance.Description) + model.DeletedExperimentRetention = types.StringPointerValue(instance.DeletedExperimentRetention) + model.BucketName = types.StringPointerValue(instance.BucketName) + model.ErrorMessage = types.StringPointerValue(instance.ErrorMessage) + model.Labels = mapValue + + return nil +} + func toCreatePayload(model *Model) (*modelexperiments.CreateInstancePayload, error) { if model == nil { return nil, fmt.Errorf("nil model") @@ -352,7 +547,24 @@ func toCreatePayload(model *Model) (*modelexperiments.CreateInstancePayload, err }, nil } -func CreateModelExperimentsWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { +func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstancePayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + modelLabels := model.Labels.Elements() + labels, err := conversion.ToOptStringMap(modelLabels) + if err != nil { + return nil, fmt.Errorf("converting to Go map: %w", err) + } + return &modelexperiments.PartialUpdateInstancePayload{ + Name: model.Name.ValueStringPointer(), + Description: model.Description.ValueStringPointer(), + DeletedExperimentRetention: model.DeletedExperimentRetention.ValueStringPointer(), + Labels: labels, + }, nil +} + +func CreateMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { getInstanceResp, err := a.DefaultAPI.GetInstance(ctx, region, projectId, instanceId).Execute() if err != nil { @@ -372,3 +584,27 @@ func CreateModelExperimentsWaitHandler(ctx context.Context, a *modelexperiments. return handler } + +func DeleteMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { + handler := wait.New( + func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { + _, err = a.DefaultAPI.GetInstance(ctx, region, projectId, instanceId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + return true, nil, nil + } + } + + return false, nil, err + } + + return false, nil, nil + }, + ) + + handler.SetTimeout(10 * time.Minute) + + return handler +} From 6ae2549b8e69be189e4848f2d53180cf9e999e3f Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Wed, 6 May 2026 17:02:22 +0200 Subject: [PATCH 03/30] Add mlflow instance token resource --- .../modelexperiments/instance/resource.go | 110 ++- .../modelexperiments/token/resource.go | 642 ++++++++++++++++++ .../services/modelexperiments/utils/util.go | 5 + stackit/provider.go | 2 + 4 files changed, 730 insertions(+), 29 deletions(-) create mode 100644 stackit/internal/services/modelexperiments/token/resource.go diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index abf88f67f..cded52a95 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -15,7 +15,6 @@ import ( "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-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/wait" @@ -29,12 +28,11 @@ import ( ) var ( - _ resource.Resource = &instanceResource{} - _ resource.ResourceWithConfigure = &instanceResource{} - //_ resource.ResourceWithModifyPlan = &tokenResource{} + _ resource.Resource = &instanceResource{} + _ resource.ResourceWithConfigure = &instanceResource{} + _ resource.ResourceWithModifyPlan = &instanceResource{} ) -//go:embed description.md var markdownDescription string type Model struct { @@ -43,7 +41,7 @@ type Model struct { Region types.String `tfsdk:"region"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` - DeletedExperimentRetention types.String `tfsdk:"deletedExperimentRetention"` + DeletedExperimentRetention types.String `tfsdk:"deleted_experiment_retention"` Labels types.Map `tfsdk:"labels"` State types.String `tfsdk:"state"` BucketName types.String `tfsdk:"bucket_name"` @@ -52,18 +50,21 @@ type Model struct { Url types.String `tfsdk:"url"` } +// NewInstanceResource is a helper function to simplify the provider implementation. func NewInstanceResource() resource.Resource { return &instanceResource{} } +// instanceResource is the resource implementation. type instanceResource struct { client *modelexperiments.APIClient providerData core.ProviderData serviceEnablementClient *serviceenablement.APIClient } +// Metadata returns the resource type name. func (i *instanceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_order" + resp.TypeName = req.ProviderTypeName + "_modelexperiments_instance" } // Configure adds the provider configured client to the resource. @@ -87,6 +88,44 @@ func (i *instanceResource) Configure(ctx context.Context, req resource.Configure tflog.Info(ctx, "Model experiments client configured") } +// ModifyPlan implements resource.ResourceWithModifyPlan. +// Use the modifier to set the effective region in the current plan. +func (i *instanceResource) 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, + i.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 (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: markdownDescription, @@ -153,7 +192,7 @@ func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r stringvalidator.LengthBetween(0, 1000), }, }, - "deletedExperimentRetention": schema.StringAttribute{ + "deleted_experiment_retention": schema.StringAttribute{ Description: "The deleted experiment retention of the AI model experiments instance.", Optional: true, Required: false, @@ -257,27 +296,23 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - var mapValue basetypes.MapValue - if createInstanceResp.Instance.Labels != nil { - mapValue, diags = types.MapValueFrom(ctx, types.StringType, createInstanceResp.Instance.Labels) - if diags.HasError() { - return - } - } - //If model experiments instance is impaired, write state avoid dangling resources and return waitResp, err := CreateMExpInstanceWaitHandler(ctx, i.client, region, projectId, instanceId).WaitWithContext(ctx) if err != nil { - mapCreateResponse(createInstanceResp, waitResp, &model, region, mapValue) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Waiting for instance to be active: %v", err)) + + err = mapCreateResponse(ctx, createInstanceResp, waitResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) + } diags = resp.State.Set(ctx, model) resp.Diagnostics.Append(diags...) - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Waiting for instance to be active: %v", err)) return } // Map response body to schema - err = mapCreateResponse(createInstanceResp, waitResp, &model, region, mapValue) + err = mapCreateResponse(ctx, createInstanceResp, waitResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -456,10 +491,14 @@ func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteReques resp.State.RemoveResource(ctx) return } + if oapiErr.StatusCode != http.StatusConflict { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Calling API: %v", err)) + return + } + } else { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Calling API: %v", err)) + return } - - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Calling API: %v", err)) - return } ctx = core.LogResponse(ctx) @@ -474,7 +513,8 @@ func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteReques tflog.Info(ctx, "Model experiments instance deleted") } -func mapCreateResponse(instanceCreateResp *modelexperiments.CreateInstanceResponse, waitResp *modelexperiments.GetInstanceResponse, model *Model, region string, labels basetypes.MapValue) error { +// mapCreateResponse maps the instace creation response and GET instance response to the model +func mapCreateResponse(ctx context.Context, instanceCreateResp *modelexperiments.CreateInstanceResponse, waitResp *modelexperiments.GetInstanceResponse, model *Model, region string) error { if instanceCreateResp == nil { return fmt.Errorf("response input is nil") } @@ -488,28 +528,39 @@ func mapCreateResponse(instanceCreateResp *modelexperiments.CreateInstanceRespon return fmt.Errorf("instance id not present") } - if waitResp == nil { - return fmt.Errorf("response input is nil") + mapValue, diags := types.MapValueFrom(ctx, types.StringType, instance.Labels) + if diags.HasError() { + return fmt.Errorf("failure in mapping labels") } + if waitResp == nil { + model.State = types.StringValue("unknown") + } else { + model.State = types.StringValue(waitResp.Instance.State) + } model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceCreateResp.Instance.Id) model.InstanceId = types.StringValue(instance.Id) model.Name = types.StringValue(instance.Name) - model.State = types.StringValue(waitResp.Instance.State) model.Description = types.StringPointerValue(instance.Description) model.DeletedExperimentRetention = types.StringPointerValue(instance.DeletedExperimentRetention) model.BucketName = types.StringPointerValue(instance.BucketName) model.ErrorMessage = types.StringPointerValue(instance.ErrorMessage) - model.Labels = labels + model.Labels = mapValue + model.Url = types.StringPointerValue(&instance.Url) return nil } +// mapInstance maps instances to the resource model func mapInstance(ctx context.Context, instance modelexperiments.Instance, model *Model) error { if model == nil { return fmt.Errorf("model input is nil") } + if instance.Id == "" { + return fmt.Errorf("instance id not present") + } + mapValue, diags := types.MapValueFrom(ctx, types.StringType, instance.Labels) if diags.HasError() { return fmt.Errorf("failure in mapping labels") @@ -524,6 +575,7 @@ func mapInstance(ctx context.Context, instance modelexperiments.Instance, model model.BucketName = types.StringPointerValue(instance.BucketName) model.ErrorMessage = types.StringPointerValue(instance.ErrorMessage) model.Labels = mapValue + model.Url = types.StringPointerValue(&instance.Url) return nil } @@ -566,7 +618,7 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstancePaylo func CreateMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - getInstanceResp, err := a.DefaultAPI.GetInstance(ctx, region, projectId, instanceId).Execute() + getInstanceResp, err := a.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute() if err != nil { return false, nil, err } @@ -588,7 +640,7 @@ func CreateMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIC func DeleteMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New( func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - _, err = a.DefaultAPI.GetInstance(ctx, region, projectId, instanceId).Execute() + _, err = a.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go new file mode 100644 index 000000000..b47ae9bf3 --- /dev/null +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -0,0 +1,642 @@ +package instance + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/core/wait" + serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + modelexperimentsutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ resource.Resource = &tokenResource{} + _ resource.ResourceWithConfigure = &tokenResource{} + _ resource.ResourceWithModifyPlan = &tokenResource{} +) + +var markdownDescription string + +type Model struct { + Id types.String `tfsdk:"id"` // needed by TF + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + InstanceId types.String `tfsdk:"instance_id"` + TokenId types.String `tfsdk:"token_id"` + Labels types.Map `tfsdk:"labels"` + State types.String `tfsdk:"state"` + ValidUntil types.String `tfsdk:"valid_until"` + TTLDuration types.String `tfsdk:"ttl_duration"` + Token types.String `tfsdk:"token"` +} + +// NewInstanceTokenResource is a helper function to simplify the provider implementation. +func NewInstanceTokenResource() resource.Resource { + return &tokenResource{} +} + +// tokenResource is the resource implementation. +type tokenResource struct { + client *modelexperiments.APIClient + providerData core.ProviderData + serviceEnablementClient *serviceenablement.APIClient +} + +// Metadata returns the resource type name. +func (i *tokenResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_modelexperiments_token" +} + +// Configure adds the provider configured client to the resource. +func (i *tokenResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + var ok bool + i.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := modelexperimentsutils.ConfigureClient(ctx, &i.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + serviceEnablementClient := modelexperimentsutils.ConfigureServiceEnablementClient(ctx, &i.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + i.client = apiClient + i.serviceEnablementClient = serviceEnablementClient + tflog.Info(ctx, "Model experiments client configured") +} + +// ModifyPlan implements resource.ResourceWithModifyPlan. +// Use the modifier to set the effective region in the current plan. +func (i *tokenResource) 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, + i.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 (i *tokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: markdownDescription, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Terraform's internal data source. ID. It is structured as \"`project_id`,`region`,`token_id`\".", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "STACKIT project ID to which the AI model experiments instance token is associated.", + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, + Description: "Region to which the AI model experiments instance token is associated. If not defined, the provider region is used", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the AI model experiments instance token.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 64), + }, + }, + "instance_id": schema.StringAttribute{ + Description: "The AI model experiments instance ID.", + Computed: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "token_id": schema.StringAttribute{ + Description: "The AI model experiments instance token ID.", + Computed: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "labels": schema.MapAttribute{ + Description: "A map of arbitrary key/value pairs for the AI model experiments instance token.", + Optional: true, + Required: false, + Computed: true, + ElementType: types.StringType, + }, + "description": schema.StringAttribute{ + Description: "The description of the AI model experiments instance token.", + Required: false, + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(0, 160), + }, + }, + "state": schema.StringAttribute{ + Description: "State of the AI model experiments instance token.", + Computed: true, + }, + "token": schema.StringAttribute{ + Description: "Content of the AI model experiments instance token.", + Computed: true, + Sensitive: true, + }, + "valid_until": schema.StringAttribute{ + Description: "The time until the AI model experiments instance token is valid.", + Computed: true, + }, + "ttl_duration": schema.StringAttribute{ + Description: "The TTL duration of the AI model experiments instance token. E.g. 5h30m40s,5h,5h30m,30m,30s", + Required: false, + Optional: true, + Validators: []validator.String{ + validate.ValidDurationString(), + }, + }, + }, + } +} + +// Create creates the resource and sets the initial Terraform state. +func (i *tokenResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform + // Retrieve values from plan + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + instanceId := model.InstanceId.ValueString() + region := i.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) + + payload, err := toCreatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance token", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + createInstanceTokenResp, err := i.client.DefaultAPI.CreateInstanceToken(ctx, projectId, region, instanceId).CreateInstanceTokenPayload(*payload).Execute() + if err != nil { + core.LogAndAddError( + ctx, + &resp.Diagnostics, + "Error creating AI model experiments instance token", + fmt.Sprintf("Calling API: %v", err), + ) + return + } + ctx = core.LogResponse(ctx) + + if createInstanceTokenResp.Token.Id == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance token", "Got empty token id") + return + } + + tokenId := createInstanceTokenResp.Token.Id + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": projectId, + "region": region, + "instance_id": instanceId, + "token_id": tokenId, + }) + if resp.Diagnostics.HasError() { + return + } + + waitResp, err := CreateMExpTokenWaitHandler(ctx, i.client, region, projectId, instanceId, tokenId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance token", fmt.Sprintf("Waiting for instance to be active: %v", err)) + + err = mapCreateResponse(ctx, createInstanceTokenResp, waitResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance token", fmt.Sprintf("Processing API payload: %v", err)) + } + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + + return + } + + // Map response body to schema + err = mapCreateResponse(ctx, createInstanceTokenResp, waitResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance token", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set state to fully populated data + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Info(ctx, "Model experiments instance token created") +} + +// Read refreshes the Terraform state with the latest data. +func (i *tokenResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + instanceId := model.InstanceId.ValueString() + tokenId := model.TokenId.ValueString() + if tokenId == "" { + // Resource not yet created; ID is unknown. + resp.State.RemoveResource(ctx) + return + } + + region := i.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "token_id", tokenId) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) + + getInstanceTokenResp, err := i.client.DefaultAPI.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + // Remove the resource from the state so Terraform will recreate it + resp.State.RemoveResource(ctx) + return + } + } + + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading AI model experiments instance token", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapToken(ctx, getInstanceTokenResp.Token, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Info(ctx, "Model experiments instance token read") + +} + +// Update updates the resource and sets the updated Terraform state on success. +func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform + // Retrieve values from plan + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Get current state + var state Model + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := state.ProjectId.ValueString() + instanceId := state.InstanceId.ValueString() + tokenId := state.TokenId.ValueString() + region := i.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "token_id", tokenId) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) + + payload, err := toUpdatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + updateInstanceTokenResp, err := i.client.DefaultAPI.PartialUpdateInstanceToken(ctx, projectId, region, tokenId, instanceId).PartialUpdateInstanceTokenPayload(*payload).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + // Remove the resource from the state so Terraform will recreate it + resp.State.RemoveResource(ctx) + return + } + } + + core.LogAndAddError( + ctx, + &resp.Diagnostics, + "Error updating AI model experiments instance token", + fmt.Sprintf( + "Calling API: %v, tokenId: %s, instanceId: %s, region: %s, projectId: %s", + err, + tokenId, + instanceId, + region, + projectId, + ), + ) + return + } + + ctx = core.LogResponse(ctx) + + if updateInstanceTokenResp != nil && updateInstanceTokenResp.Token.State == modelexperiments.TOKENSTATE_INACTIVE { + resp.State.RemoveResource(ctx) + core.LogAndAddWarning(ctx, &resp.Diagnostics, "Error updating AI model experiments instance token", "AI model experiments token has expired") + return + } + + err = mapToken(ctx, updateInstanceTokenResp.Token, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance token", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Info(ctx, "Model experiments instance token updated") +} + +// Delete deletes the resource and removes the Terraform state on success. +func (i *tokenResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform + // Retrieve values from plan + 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() + instanceId := model.InstanceId.ValueString() + tokenId := model.TokenId.ValueString() + + region := i.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "token_id", tokenId) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "region", region) + + _, err := i.client.DefaultAPI.DeleteInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + if oapiErr.StatusCode != http.StatusConflict { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Calling API: %v", err)) + return + } + } else { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Calling API: %v", err)) + return + } + } + + ctx = core.LogResponse(ctx) + + _, err = DeleteMExpTokenWaitHandler(ctx, i.client, region, projectId, instanceId, tokenId). + WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Waiting for instance to be deleted: %v", err)) + return + } + + tflog.Info(ctx, "Model experiments instance token deleted") +} + +// mapCreateResponse maps the instace creation response and GET instance response to the model +func mapCreateResponse(ctx context.Context, instanceTokenResp *modelexperiments.CreateTokenResponse, waitResp *modelexperiments.GetTokenResponse, model *Model, region string) error { + if instanceTokenResp == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + token := instanceTokenResp.Token + + if token.Id == "" { + return fmt.Errorf("token id not present") + } + + if waitResp == nil { + model.State = types.StringValue("unknown") + } else { + model.State = types.StringValue(string(waitResp.Token.State)) + } + + mapValue, diags := types.MapValueFrom(ctx, types.StringType, token.Labels) + if diags.HasError() { + return fmt.Errorf("failure in mapping labels") + } + + validUntil := types.StringNull() + if !token.ValidUntil.IsZero() { + validUntil = types.StringValue(token.ValidUntil.Format(time.RFC3339)) + } + + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, token.Id) + model.TokenId = types.StringValue(token.Id) + model.Name = types.StringValue(token.Name) + model.Description = types.StringPointerValue(token.Description) + model.ValidUntil = validUntil + model.Token = types.StringValue(token.Content) + model.Labels = mapValue + + return nil +} + +// mapToken maps instances to the resource model +func mapToken(ctx context.Context, token modelexperiments.TokenMetadata, model *Model) error { + if model == nil { + return fmt.Errorf("model input is nil") + } + + if token.Id == "" { + return fmt.Errorf("token id not present") + } + + mapValue, diags := types.MapValueFrom(ctx, types.StringType, token.Labels) + if diags.HasError() { + return fmt.Errorf("failure in mapping labels") + } + + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.Region.ValueString(), model.TokenId.ValueString()) + model.TokenId = types.StringValue(token.Id) + model.Name = types.StringValue(token.Name) + model.State = types.StringValue(string(token.State)) + model.Description = types.StringPointerValue(token.Description) + model.ValidUntil = types.StringValue(token.ValidUntil.Format(time.RFC3339)) + model.Labels = mapValue + + return nil +} + +func toCreatePayload(model *Model) (*modelexperiments.CreateInstanceTokenPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + + modelLabels := model.Labels.Elements() + labels, err := conversion.ToOptStringMap(modelLabels) + if err != nil { + return nil, fmt.Errorf("converting to Go map: %w", err) + } + + return &modelexperiments.CreateInstanceTokenPayload{ + Name: model.Name.ValueString(), + Description: conversion.StringValueToPointer(model.Description), + TtlDuration: conversion.StringValueToPointer(model.TTLDuration), + Labels: labels, + }, nil +} + +func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstanceTokenPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + modelLabels := model.Labels.Elements() + labels, err := conversion.ToOptStringMap(modelLabels) + if err != nil { + return nil, fmt.Errorf("converting to Go map: %w", err) + } + return &modelexperiments.PartialUpdateInstanceTokenPayload{ + Name: model.Name.ValueStringPointer(), + Description: model.Description.ValueStringPointer(), + Labels: labels, + }, nil +} + +func CreateMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetTokenResponse] { + handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetTokenResponse, err error) { + getTokenResp, err := a.DefaultAPI.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + if err != nil { + return false, nil, err + } + if getTokenResp.Token.State == modelexperimentsutils.TOKENSTATE_ACTIVE { + return true, getTokenResp, nil + } + + return false, nil, nil + }) + + handler.SetTimeout(10 * time.Minute) + + return handler +} + +func DeleteMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { + handler := wait.New( + func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { + _, err = a.DefaultAPI.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + return true, nil, nil + } + } + + return false, nil, err + } + + return false, nil, nil + }, + ) + + handler.SetTimeout(10 * time.Minute) + + return handler +} diff --git a/stackit/internal/services/modelexperiments/utils/util.go b/stackit/internal/services/modelexperiments/utils/util.go index 455470b92..05af4dca3 100644 --- a/stackit/internal/services/modelexperiments/utils/util.go +++ b/stackit/internal/services/modelexperiments/utils/util.go @@ -20,6 +20,11 @@ const ( INSTANCESTATE_UPDATING = "updating" INSTANCESTATE_IMPAIRED = "impaired" INSTANCESTATE_RECONCILING = "reconciling" + + TOKENSTATE_ACTIVE = "active" + TOKENSTATE_CREATING = "creating" + TOKENSTATE_DELETING = "deleting" + TOKENSTATE_INACTIVE = "inactive" ) func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *modelexperiment.APIClient { diff --git a/stackit/provider.go b/stackit/provider.go index bf8c7ea85..c49d44d07 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -77,6 +77,7 @@ import ( mariaDBCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/credential" mariaDBInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/instance" modelExperimentsInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" + modelExperimentsToken "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" modelServingToken "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelserving/token" mongoDBFlexInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/instance" mongoDBFlexUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/user" @@ -824,6 +825,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { mariaDBCredential.NewCredentialResource, modelServingToken.NewTokenResource, modelExperimentsInstance.NewInstanceResource, + modelExperimentsToken.NewInstanceTokenResource, mongoDBFlexInstance.NewInstanceResource, mongoDBFlexUser.NewUserResource, objectStorageBucket.NewBucketResource, From 9832906cf755feea90a2b4f3225e315826b23520 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Wed, 6 May 2026 17:14:53 +0200 Subject: [PATCH 04/30] Removed unnecessary boxing --- .../services/modelexperiments/instance/resource.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index cded52a95..fafbd1f0f 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -486,19 +486,14 @@ func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteReques _, err := i.client.DefaultAPI.DeleteInstance(ctx, projectId, region, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { + if !errors.As(err, &oapiErr) { if oapiErr.StatusCode == http.StatusNotFound { resp.State.RemoveResource(ctx) return } - if oapiErr.StatusCode != http.StatusConflict { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Calling API: %v", err)) - return - } - } else { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Calling API: %v", err)) - return } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Calling API: %v", err)) + return } ctx = core.LogResponse(ctx) From e2e22cbbec4f29af7f0607c708103591ca79712b Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Thu, 7 May 2026 13:59:37 +0200 Subject: [PATCH 05/30] fix bugs in mlflow api --- go.mod | 2 +- .../modelexperiments/instance/resource.go | 16 +++++++++------- .../services/modelexperiments/token/resource.go | 3 ++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index c4af3ab29..33c24638a 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( 4d63.com/gochecknoglobals v0.2.2 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect - dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments v0.41.0 // indirect + dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments v0.41.0 dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect dev.gaijin.team/go/golib v0.6.0 // indirect github.com/4meepo/tagalign v1.4.3 // indirect diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index fafbd1f0f..774bb99ad 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -160,7 +160,7 @@ func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r }, }, "labels": schema.MapAttribute{ - Description: "A map of arbitrary key/value pairs for the AI model experiments instance", + Description: "A map of arbitrary key/value pairs that can be attached to the AI model experiments instance", Optional: true, Required: false, Computed: true, @@ -603,12 +603,14 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstancePaylo if err != nil { return nil, fmt.Errorf("converting to Go map: %w", err) } - return &modelexperiments.PartialUpdateInstancePayload{ - Name: model.Name.ValueStringPointer(), - Description: model.Description.ValueStringPointer(), - DeletedExperimentRetention: model.DeletedExperimentRetention.ValueStringPointer(), - Labels: labels, - }, nil + payload := &modelexperiments.PartialUpdateInstancePayload{} + payload.Name = model.Name.ValueStringPointer() + payload.Description = model.Description.ValueStringPointer() + payload.Labels = labels + if !model.DeletedExperimentRetention.IsUnknown() { + payload.DeletedExperimentRetention = model.DeletedExperimentRetention.ValueStringPointer() + } + return payload, nil } func CreateMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index b47ae9bf3..cae53ad04 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -159,7 +159,7 @@ func (i *tokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp }, "instance_id": schema.StringAttribute{ Description: "The AI model experiments instance ID.", - Computed: true, + Required: true, Validators: []validator.String{ validate.UUID(), validate.NoSeparator(), @@ -428,6 +428,7 @@ func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, return } + model.Token = state.Token err = mapToken(ctx, updateInstanceTokenResp.Token, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance token", fmt.Sprintf("Processing API payload: %v", err)) From 2e519b2409a07214c211282be1104500efd417fc Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Fri, 8 May 2026 14:27:30 +0200 Subject: [PATCH 06/30] Improve create update payload mapping --- .../services/modelexperiments/instance/resource.go | 14 ++++++-------- .../services/modelexperiments/token/resource.go | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 774bb99ad..0a8a43688 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -603,14 +603,12 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstancePaylo if err != nil { return nil, fmt.Errorf("converting to Go map: %w", err) } - payload := &modelexperiments.PartialUpdateInstancePayload{} - payload.Name = model.Name.ValueStringPointer() - payload.Description = model.Description.ValueStringPointer() - payload.Labels = labels - if !model.DeletedExperimentRetention.IsUnknown() { - payload.DeletedExperimentRetention = model.DeletedExperimentRetention.ValueStringPointer() - } - return payload, nil + return &modelexperiments.PartialUpdateInstancePayload{ + Name: conversion.StringValueToPointer(model.Name), + Description: conversion.StringValueToPointer(model.Description), + Labels: labels, + DeletedExperimentRetention: conversion.StringValueToPointer(model.DeletedExperimentRetention), + }, nil } func CreateMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index cae53ad04..e37c4a044 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -1,4 +1,4 @@ -package instance +package token import ( "context" From 7436228aa6ab3ec3701ba02f210d8a9fa1a249a2 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Fri, 8 May 2026 14:29:53 +0200 Subject: [PATCH 07/30] Improve conversion in token update payload method --- stackit/internal/services/modelexperiments/token/resource.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index e37c4a044..efe6beed3 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -594,8 +594,8 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstanceToken return nil, fmt.Errorf("converting to Go map: %w", err) } return &modelexperiments.PartialUpdateInstanceTokenPayload{ - Name: model.Name.ValueStringPointer(), - Description: model.Description.ValueStringPointer(), + Name: conversion.StringValueToPointer(model.Name), + Description: conversion.StringValueToPointer(model.Description), Labels: labels, }, nil } From d326b202341c2f9ed173234ceebbe35f235d87aa Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Fri, 8 May 2026 14:30:45 +0200 Subject: [PATCH 08/30] Add unit tests --- .../instance/resource_test.go | 373 +++++++++++++++ .../modelexperiments/token/resource_test.go | 427 ++++++++++++++++++ 2 files changed, 800 insertions(+) create mode 100644 stackit/internal/services/modelexperiments/instance/resource_test.go create mode 100644 stackit/internal/services/modelexperiments/token/resource_test.go diff --git a/stackit/internal/services/modelexperiments/instance/resource_test.go b/stackit/internal/services/modelexperiments/instance/resource_test.go new file mode 100644 index 000000000..24ad1a5f4 --- /dev/null +++ b/stackit/internal/services/modelexperiments/instance/resource_test.go @@ -0,0 +1,373 @@ +package instance + +import ( + "context" + "testing" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestMapInstanceFields(t *testing.T) { + t.Parallel() + + tests := []struct { + description string + state *Model + input modelexperiments.Instance + expected Model + isValid bool + }{ + { + description: "should error when state is nil", + state: nil, + input: modelexperiments.Instance{ + Id: "id", + }, + expected: Model{}, + isValid: false, + }, + { + description: "should error when instance id is not present", + state: &Model{}, + input: modelexperiments.Instance{}, + expected: Model{}, + isValid: false, + }, + { + description: "should map fields correctly", + state: &Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue("id"), + Region: types.StringValue("eu01"), + }, + input: modelexperiments.Instance{ + Id: "id", + BucketName: new("bucketName"), + Description: new("description"), + DeletedExperimentRetention: new("30d"), + ErrorMessage: nil, + Labels: &map[string]string{"key": "value"}, + State: "active", + Url: "url", + Name: "name", + }, + expected: Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + InstanceId: types.StringValue("id"), + Name: types.StringValue("name"), + Description: types.StringValue("description"), + State: types.StringValue("active"), + BucketName: types.StringValue("bucketName"), + DeletedExperimentRetention: types.StringValue("30d"), + Url: types.StringValue("url"), + ErrorMessage: types.StringNull(), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + err := mapInstance(ctx, tt.input, tt.state) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + + if tt.isValid { + diff := cmp.Diff(tt.state, &tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + +func TestMapCreateResponseFields(t *testing.T) { + t.Parallel() + + tests := []struct { + description string + state *Model + inputCreateResponse *modelexperiments.CreateInstanceResponse + inputGetResponse *modelexperiments.GetInstanceResponse + expected Model + isValid bool + }{ + { + description: "should error when instance create response is nil", + state: &Model{}, + inputCreateResponse: nil, + inputGetResponse: &modelexperiments.GetInstanceResponse{}, + expected: Model{}, + isValid: false, + }, + { + description: "should error when state is nil", + state: nil, + inputCreateResponse: &modelexperiments.CreateInstanceResponse{}, + inputGetResponse: &modelexperiments.GetInstanceResponse{}, + expected: Model{}, + isValid: false, + }, + { + description: "should error when instance id is not present", + state: &Model{}, + inputCreateResponse: &modelexperiments.CreateInstanceResponse{ + Instance: modelexperiments.Instance{}, + }, + inputGetResponse: &modelexperiments.GetInstanceResponse{}, + expected: Model{}, + isValid: false, + }, + { + description: "should map fields correctly even if Get Response is nil", + state: &Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue("id"), + Region: types.StringValue("eu01"), + }, + inputCreateResponse: &modelexperiments.CreateInstanceResponse{ + Instance: modelexperiments.Instance{ + Id: "id", + BucketName: new("bucketName"), + Description: new("description"), + DeletedExperimentRetention: new("30d"), + ErrorMessage: nil, + Labels: &map[string]string{"key": "value"}, + State: "pending", + Url: "url", + Name: "name", + }}, + inputGetResponse: nil, + expected: Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + InstanceId: types.StringValue("id"), + Name: types.StringValue("name"), + Description: types.StringValue("description"), + State: types.StringValue("unknown"), + BucketName: types.StringValue("bucketName"), + DeletedExperimentRetention: types.StringValue("30d"), + Url: types.StringValue("url"), + ErrorMessage: types.StringNull(), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + }, + isValid: true, + }, + { + description: "should map fields correctly", + state: &Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue("id"), + Region: types.StringValue("eu01"), + }, + inputCreateResponse: &modelexperiments.CreateInstanceResponse{ + Instance: modelexperiments.Instance{ + Id: "id", + BucketName: new("bucketName"), + Description: new("description"), + DeletedExperimentRetention: new("30d"), + ErrorMessage: nil, + Labels: &map[string]string{"key": "value"}, + State: "pending", + Url: "url", + Name: "name", + }}, + inputGetResponse: &modelexperiments.GetInstanceResponse{ + Instance: modelexperiments.Instance{ + State: "active", + }, + }, + expected: Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + InstanceId: types.StringValue("id"), + Name: types.StringValue("name"), + Description: types.StringValue("description"), + State: types.StringValue("active"), + BucketName: types.StringValue("bucketName"), + DeletedExperimentRetention: types.StringValue("30d"), + Url: types.StringValue("url"), + ErrorMessage: types.StringNull(), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + err := mapCreateResponse(ctx, tt.inputCreateResponse, tt.inputGetResponse, tt.state, "eu01") + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + + if tt.isValid { + diff := cmp.Diff(tt.state, &tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + +func TestToCreatePayload(t *testing.T) { + t.Parallel() + + tests := []struct { + description string + input *Model + expected *modelexperiments.CreateInstancePayload + isValid bool + }{ + { + description: "should error on nil input", + input: nil, + expected: nil, + isValid: false, + }, + { + description: "should error when map is not correct", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapValueMust(types.Int64Type, map[string]attr.Value{"key": types.Int64Value(33)}), + DeletedExperimentRetention: types.StringValue("50d"), + }, + expected: nil, + isValid: false, + }, + { + description: "should convert correctly", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + DeletedExperimentRetention: types.StringNull(), + }, + expected: &modelexperiments.CreateInstancePayload{ + Name: "name", + Description: new("desc"), + Labels: &map[string]string{"key": "value"}, + DeletedExperimentRetention: nil, + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + t.Parallel() + + output, err := toCreatePayload(tt.input) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + + if tt.isValid { + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + +func TestToUpdatePayload(t *testing.T) { + t.Parallel() + + tests := []struct { + description string + input *Model + expected *modelexperiments.PartialUpdateInstancePayload + isValid bool + }{ + { + description: "should error on nil input", + input: nil, + expected: nil, + isValid: false, + }, + { + description: "should error when map is not correct", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapValueMust(types.Int64Type, map[string]attr.Value{"key": types.Int64Value(33)}), + DeletedExperimentRetention: types.StringValue("50d"), + }, + expected: nil, + isValid: false, + }, + { + description: "should convert correctly", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + DeletedExperimentRetention: types.StringValue("50d"), + }, + expected: &modelexperiments.PartialUpdateInstancePayload{ + Name: new("name"), + Description: new("desc"), + Labels: &map[string]string{"key": "value"}, + DeletedExperimentRetention: new("50d"), + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + t.Parallel() + + output, err := toUpdatePayload(tt.input) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + + if tt.isValid { + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} diff --git a/stackit/internal/services/modelexperiments/token/resource_test.go b/stackit/internal/services/modelexperiments/token/resource_test.go new file mode 100644 index 000000000..e14223ef3 --- /dev/null +++ b/stackit/internal/services/modelexperiments/token/resource_test.go @@ -0,0 +1,427 @@ +package token + +import ( + "context" + "testing" + "time" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestMapTokenFields(t *testing.T) { + t.Parallel() + + tests := []struct { + description string + state *Model + input modelexperiments.TokenMetadata + expected Model + isValid bool + }{ + { + description: "should error when state is nil", + state: nil, + input: modelexperiments.TokenMetadata{ + Id: "id", + }, + expected: Model{}, + isValid: false, + }, + { + description: "should error when token id is not present", + state: &Model{}, + input: modelexperiments.TokenMetadata{}, + expected: Model{}, + isValid: false, + }, + { + description: "should map fields correctly", + state: &Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue("id"), + Region: types.StringValue("eu01"), + TokenId: types.StringValue("id"), + }, + input: modelexperiments.TokenMetadata{ + Id: "id", + Description: new("description"), + Labels: &map[string]string{"key": "value"}, + State: "active", + Name: "name", + ValidUntil: time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC), + }, + expected: Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + InstanceId: types.StringValue("id"), + Name: types.StringValue("name"), + Description: types.StringValue("description"), + State: types.StringValue("active"), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + TokenId: types.StringValue("id"), + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + err := mapToken(ctx, tt.input, tt.state) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + + if tt.isValid { + diff := cmp.Diff(tt.state, &tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + +func TestMapCreateResponseFields(t *testing.T) { + t.Parallel() + + tests := []struct { + description string + state *Model + inputCreateResponse *modelexperiments.CreateTokenResponse + inputGetResponse *modelexperiments.GetTokenResponse + expected Model + isValid bool + }{ + { + description: "should error when token create response is nil", + state: &Model{}, + inputCreateResponse: nil, + inputGetResponse: &modelexperiments.GetTokenResponse{}, + expected: Model{}, + isValid: false, + }, + { + description: "should error when state is nil", + state: nil, + inputCreateResponse: &modelexperiments.CreateTokenResponse{}, + inputGetResponse: &modelexperiments.GetTokenResponse{}, + expected: Model{}, + isValid: false, + }, + { + description: "should error when token id is not present", + state: &Model{}, + inputCreateResponse: &modelexperiments.CreateTokenResponse{ + Token: modelexperiments.Token{}, + }, + inputGetResponse: &modelexperiments.GetTokenResponse{}, + expected: Model{}, + isValid: false, + }, + { + description: "should map fields correctly even if Get Response is nil", + state: &Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue("id"), + Region: types.StringValue("eu01"), + }, + inputCreateResponse: &modelexperiments.CreateTokenResponse{ + Token: modelexperiments.Token{ + Id: "id", + Content: "token", + Description: new("description"), + Labels: &map[string]string{"key": "value"}, + State: "active", + Name: "name", + ValidUntil: time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC), + }}, + inputGetResponse: nil, + expected: Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + InstanceId: types.StringValue("id"), + Name: types.StringValue("name"), + Description: types.StringValue("description"), + State: types.StringValue("unknown"), + Token: types.StringValue("token"), + TokenId: types.StringValue("id"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + }, + isValid: true, + }, + { + description: "should map fields correctly", + state: &Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue("id"), + Region: types.StringValue("eu01"), + }, + inputCreateResponse: &modelexperiments.CreateTokenResponse{ + Token: modelexperiments.Token{ + Id: "id", + Content: "token", + Description: new("description"), + Labels: &map[string]string{"key": "value"}, + State: "active", + Name: "name", + ValidUntil: time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC), + }}, + inputGetResponse: &modelexperiments.GetTokenResponse{ + Token: modelexperiments.TokenMetadata{ + State: "active", + }, + }, + expected: Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + InstanceId: types.StringValue("id"), + Name: types.StringValue("name"), + Description: types.StringValue("description"), + State: types.StringValue("active"), + Token: types.StringValue("token"), + TokenId: types.StringValue("id"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + }, + isValid: true, + }, + { + description: "should map fields correctly with label nil", + state: &Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue("id"), + Region: types.StringValue("eu01"), + }, + inputCreateResponse: &modelexperiments.CreateTokenResponse{ + Token: modelexperiments.Token{ + Id: "id", + Content: "token", + Description: new("description"), + Labels: nil, + State: "active", + Name: "name", + ValidUntil: time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC), + }}, + inputGetResponse: &modelexperiments.GetTokenResponse{ + Token: modelexperiments.TokenMetadata{ + State: "active", + }, + }, + expected: Model{ + Id: types.StringValue("pid,eu01,id"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + InstanceId: types.StringValue("id"), + Name: types.StringValue("name"), + Description: types.StringValue("description"), + State: types.StringValue("active"), + Token: types.StringValue("token"), + TokenId: types.StringValue("id"), + Labels: types.MapNull(types.StringType), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + err := mapCreateResponse(ctx, tt.inputCreateResponse, tt.inputGetResponse, tt.state, "eu01") + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + + if tt.isValid { + diff := cmp.Diff(tt.state, &tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + +func TestToCreatePayload(t *testing.T) { + t.Parallel() + + tests := []struct { + description string + input *Model + expected *modelexperiments.CreateInstanceTokenPayload + isValid bool + }{ + { + description: "should error on nil input", + input: nil, + expected: nil, + isValid: false, + }, + { + description: "should error when map is not correct", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapValueMust(types.Int64Type, map[string]attr.Value{"key": types.Int64Value(33)}), + TTLDuration: types.StringValue("30d"), + }, + expected: nil, + isValid: false, + }, + { + description: "should convert correctly", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + TTLDuration: types.StringValue("30d"), + }, + expected: &modelexperiments.CreateInstanceTokenPayload{ + Name: "name", + Description: new("desc"), + Labels: &map[string]string{"key": "value"}, + TtlDuration: new("30d"), + }, + isValid: true, + }, + { + description: "should convert correctly without labels", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapNull(types.StringType), + TTLDuration: types.StringValue("30d"), + }, + expected: &modelexperiments.CreateInstanceTokenPayload{ + Name: "name", + Description: new("desc"), + TtlDuration: new("30d"), + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + t.Parallel() + + output, err := toCreatePayload(tt.input) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + + if tt.isValid { + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + +func TestToUpdatePayload(t *testing.T) { + t.Parallel() + + tests := []struct { + description string + input *Model + expected *modelexperiments.PartialUpdateInstanceTokenPayload + isValid bool + }{ + { + description: "should error on nil input", + input: nil, + expected: nil, + isValid: false, + }, + { + description: "should error when map is not correct", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapValueMust(types.Int64Type, map[string]attr.Value{"key": types.Int64Value(33)}), + }, + expected: nil, + isValid: false, + }, + { + description: "should convert correctly", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), + }, + expected: &modelexperiments.PartialUpdateInstanceTokenPayload{ + Name: new("name"), + Description: new("desc"), + Labels: &map[string]string{"key": "value"}, + }, + isValid: true, + }, + { + description: "should convert correctly without labels", + input: &Model{ + Name: types.StringValue("name"), + Description: types.StringValue("desc"), + Labels: types.MapNull(types.StringType), + }, + expected: &modelexperiments.PartialUpdateInstanceTokenPayload{ + Name: new("name"), + Description: new("desc"), + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + t.Parallel() + + output, err := toUpdatePayload(tt.input) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + + if tt.isValid { + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} From 79607826518f0f4b868dd7edbc75721c66ae22f0 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Fri, 8 May 2026 16:39:35 +0200 Subject: [PATCH 09/30] Add acc test --- .../modelexperiments_acc_test.go | 172 ++++++++++++++++++ .../modelexperiments/utils/util_test.go | 93 ++++++++++ stackit/internal/testutil/testutil.go | 74 ++++---- 3 files changed, 303 insertions(+), 36 deletions(-) create mode 100644 stackit/internal/services/modelexperiments/modelexperiments_acc_test.go create mode 100644 stackit/internal/services/modelexperiments/utils/util_test.go diff --git a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go new file mode 100644 index 000000000..d4ebac985 --- /dev/null +++ b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go @@ -0,0 +1,172 @@ +package modelexperiments_test + +import ( + "context" + "errors" + "fmt" + "net/http" + "slices" + "strings" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/core/wait" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +var instanceResource = map[string]string{ + "project_id": testutil.ProjectId, + "name": "instance01", + "description": "my description", + "description_updated": "my description updated", + "region": testutil.Region, +} + +func inputInstanceConfig(name, description string) string { + return fmt.Sprintf(` + %s + + resource "stackit_modelexperiments_instance" "example" { + project_id = "%s" + name = "%s" + region = "%s" + description = "%s" + } + `, + testutil.NewConfigBuilder().BuildProviderConfig(), + instanceResource["project_id"], + name, + instanceResource["region"], + description, + ) +} + +func TestAccModelExperimentsInstanceResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckModelServingTokenDestroy, + Steps: []resource.TestStep{ + // Creation + { + Config: inputInstanceConfig( + instanceResource["name"], + instanceResource["description"], + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "project_id", instanceResource["project_id"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "region", instanceResource["region"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "name", instanceResource["name"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "description", instanceResource["description"]), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "state"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "bucket_name"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "deleted_experiment_retention"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "url"), + ), + }, + // Update + { + Config: inputInstanceConfig( + instanceResource["name"], + instanceResource["description_updated"], + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "project_id", instanceResource["project_id"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "region", instanceResource["region"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "name", instanceResource["name"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "description", instanceResource["description_updated"]), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "state"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "bucket_name"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "deleted_experiment_retention"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "url"), + ), + }, + // Deletion is done by the framework implicitly + }, + }) +} + +func testAccCheckModelServingTokenDestroy(s *terraform.State) error { + ctx := context.Background() + client, err := modelexperiments.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.ModelExperimentsCustomEndpoint, false)...) + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + instancesToDestroy := []string{} + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_modelexperiments_instance" { + continue + } + + // Token terraform ID: "[project_id],[region],[token_id]" + idParts := strings.Split(rs.Primary.ID, core.Separator) + if len(idParts) != 3 { + return fmt.Errorf("invalid ID: %s", rs.Primary.ID) + } + if idParts[2] != "" { + instancesToDestroy = append(instancesToDestroy, idParts[2]) + } + } + + if len(instancesToDestroy) == 0 { + return nil + } + + instancesResp, err := client.DefaultAPI.ListInstances(ctx, testutil.ProjectId, testutil.Region).Execute() + if err != nil { + return fmt.Errorf("getting instanceResp: %w", err) + } + + if len(instancesResp.Instances) == 0 { + fmt.Print("No instances found for project \n") + return nil + } + + items := instancesResp.Instances + for i := range items { + if slices.Contains(instancesToDestroy, items[i].Name) { + _, err := client.DefaultAPI.DeleteInstance(ctx, testutil.ProjectId, testutil.Region, items[i].Id).Execute() + if err != nil { + return fmt.Errorf("destroying instance %s during CheckDestroy: %w", items[i].Name, err) + } + _, err = deleteModelExperimentsWaitHandler(ctx, client.DefaultAPI, testutil.Region, testutil.ProjectId, items[i].Id).WaitWithContext(ctx) + if err != nil { + return fmt.Errorf("destroying token %s during CheckDestroy: waiting for deletion %w", items[i].Name, err) + } + } + } + return nil +} + +func deleteModelExperimentsWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { + handler := wait.New( + func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { + _, err = a.GetInstance(ctx, projectId, region, instanceId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + return true, nil, nil + } + } + + return false, nil, err + } + + return false, nil, nil + }, + ) + + handler.SetTimeout(10 * time.Minute) + + return handler +} diff --git a/stackit/internal/services/modelexperiments/utils/util_test.go b/stackit/internal/services/modelexperiments/utils/util_test.go new file mode 100644 index 000000000..a1e37f1f8 --- /dev/null +++ b/stackit/internal/services/modelexperiments/utils/util_test.go @@ -0,0 +1,93 @@ +package utils + +import ( + "context" + "os" + "reflect" + "testing" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/hashicorp/terraform-plugin-framework/diag" + sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" + "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +const ( + testVersion = "1.2.3" + testCustomEndpoint = "https://modelexperiments-custom-endpoint.api.stackit.cloud" +) + +func TestConfigureClient(t *testing.T) { + /* mock authentication by setting service account token env variable */ + os.Clearenv() + err := os.Setenv(sdkClients.ServiceAccountToken, "mock-val") + if err != nil { + t.Errorf("error setting env variable: %v", err) + } + + type args struct { + providerData *core.ProviderData + } + tests := []struct { + name string + args args + wantErr bool + expected *modelexperiments.APIClient + }{ + { + name: "default endpoint", + args: args{ + providerData: &core.ProviderData{ + Version: testVersion, + }, + }, + expected: func() *modelexperiments.APIClient { + apiClient, err := modelexperiments.NewAPIClient( + utils.UserAgentConfigOption(testVersion), + ) + if err != nil { + t.Errorf("error configuring client: %v", err) + } + return apiClient + }(), + wantErr: false, + }, + { + name: "custom endpoint", + args: args{ + providerData: &core.ProviderData{ + Version: testVersion, + ModelExperimentsCustomEndpoint: testCustomEndpoint, + }, + }, + expected: func() *modelexperiments.APIClient { + apiClient, err := modelexperiments.NewAPIClient( + utils.UserAgentConfigOption(testVersion), + config.WithEndpoint(testCustomEndpoint), + ) + if err != nil { + t.Errorf("error configuring client: %v", err) + } + return apiClient + }(), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + diags := diag.Diagnostics{} + + actual := ConfigureClient(ctx, tt.args.providerData, &diags) + if diags.HasError() != tt.wantErr { + t.Errorf("ConfigureClient() error = %v, want %v", diags.HasError(), tt.wantErr) + } + + if !reflect.DeepEqual(actual, tt.expected) { + t.Errorf("ConfigureClient() = %v, want %v", actual, tt.expected) + } + }) + } +} diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index e119666ad..fd7322f52 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -67,42 +67,43 @@ var ( // TestImageLocalFilePath is the local path to an image file used for image acceptance tests TestImageLocalFilePath = getenv("TF_ACC_TEST_IMAGE_LOCAL_FILE_PATH", "default") - ALBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CUSTOM_ENDPOINT", providerName: "alb_custom_endpoint"} - ALBCertCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CERT_CUSTOM_ENDPOINT", providerName: "alb_certificates_custom_endpoint"} - CdnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_CDN_CUSTOM_ENDPOINT", providerName: "cdn_custom_endpoint"} - DnsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DNS_CUSTOM_ENDPOINT", providerName: "dns_custom_endpoint"} - DremioCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DREMIO_CUSTOM_ENDPOINT", providerName: "dremio_custom_endpoint"} - EdgeCloudCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_EDGECLOUD_CUSTOM_ENDPOINT", providerName: "edgecloud_custom_endpoint"} - GitCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_GIT_CUSTOM_ENDPOINT", providerName: "git_custom_endpoint"} - IaaSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_IAAS_CUSTOM_ENDPOINT", providerName: "iaas_custom_endpoint"} - KMSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_KMS_CUSTOM_ENDPOINT", providerName: "kms_custom_endpoint"} - LoadBalancerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOADBALANCER_CUSTOM_ENDPOINT", providerName: "loadbalancer_custom_endpoint"} - LogMeCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOGME_CUSTOM_ENDPOINT", providerName: "logme_custom_endpoint"} - LogsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOGS_CUSTOM_ENDPOINT", providerName: "logs_custom_endpoint"} - MariaDBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MARIADB_CUSTOM_ENDPOINT", providerName: "mariadb_custom_endpoint"} - ModelServingCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MODELSERVING_CUSTOM_ENDPOINT", providerName: "modelserving_custom_endpoint"} - AuthorizationCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_AUTHORIZATION_CUSTOM_ENDPOINT", providerName: "authorization_custom_endpoint"} - MongoDBFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MONGODBFLEX_CUSTOM_ENDPOINT", providerName: "mongodbflex_custom_endpoint"} - OpenSearchCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OPENSEARCH_CUSTOM_ENDPOINT", providerName: "opensearch_custom_endpoint"} - ObservabilityCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OBSERVABILITY_CUSTOM_ENDPOINT", providerName: "observability_custom_endpoint"} - ObjectStorageCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OBJECTSTORAGE_CUSTOM_ENDPOINT", providerName: "objectstorage_custom_endpoint"} - PostgresFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_POSTGRESFLEX_CUSTOM_ENDPOINT", providerName: "postgresflex_custom_endpoint"} - RabbitMQCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_RABBITMQ_CUSTOM_ENDPOINT", providerName: "rabbitmq_custom_endpoint"} - RedisCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_REDIS_CUSTOM_ENDPOINT", providerName: "redis_custom_endpoint"} - ResourceManagerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_RESOURCEMANAGER_CUSTOM_ENDPOINT", providerName: "resourcemanager_custom_endpoint"} - ScfCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SCF_CUSTOM_ENDPOINT", providerName: "scf_custom_endpoint"} - SecretsManagerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SECRETSMANAGER_CUSTOM_ENDPOINT", providerName: "secretsmanager_custom_endpoint"} - SQLServerFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SQLSERVERFLEX_CUSTOM_ENDPOINT", providerName: "sqlserverflex_custom_endpoint"} - ServerBackupCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVER_BACKUP_CUSTOM_ENDPOINT", providerName: "server_backup_custom_endpoint"} - ServerUpdateCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVER_UPDATE_CUSTOM_ENDPOINT", providerName: "server_update_custom_endpoint"} - SFSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SFS_CUSTOM_ENDPOINT", providerName: "sfs_custom_endpoint"} - 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"} - VpnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_VPN_CUSTOM_ENDPOINT", providerName: "vpn_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"} - TelemetryRouterCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYROUTER_CUSTOM_ENDPOINT", providerName: "telemetryrouter_custom_endpoint"} - TelemetryLinkCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYLINK_CUSTOM_ENDPOINT", providerName: "telemetrylink_custom_endpoint"} + ALBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CUSTOM_ENDPOINT", providerName: "alb_custom_endpoint"} + ALBCertCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CERT_CUSTOM_ENDPOINT", providerName: "alb_certificates_custom_endpoint"} + CdnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_CDN_CUSTOM_ENDPOINT", providerName: "cdn_custom_endpoint"} + DnsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DNS_CUSTOM_ENDPOINT", providerName: "dns_custom_endpoint"} + DremioCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DREMIO_CUSTOM_ENDPOINT", providerName: "dremio_custom_endpoint"} + EdgeCloudCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_EDGECLOUD_CUSTOM_ENDPOINT", providerName: "edgecloud_custom_endpoint"} + GitCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_GIT_CUSTOM_ENDPOINT", providerName: "git_custom_endpoint"} + IaaSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_IAAS_CUSTOM_ENDPOINT", providerName: "iaas_custom_endpoint"} + KMSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_KMS_CUSTOM_ENDPOINT", providerName: "kms_custom_endpoint"} + LoadBalancerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOADBALANCER_CUSTOM_ENDPOINT", providerName: "loadbalancer_custom_endpoint"} + LogMeCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOGME_CUSTOM_ENDPOINT", providerName: "logme_custom_endpoint"} + LogsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOGS_CUSTOM_ENDPOINT", providerName: "logs_custom_endpoint"} + MariaDBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MARIADB_CUSTOM_ENDPOINT", providerName: "mariadb_custom_endpoint"} + ModelServingCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MODELSERVING_CUSTOM_ENDPOINT", providerName: "modelserving_custom_endpoint"} + ModelExperimentsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MODELEXPERIMENTS_CUSTOM_ENDPOINT", providerName: "modelexperiments_custom_endpoint"} + AuthorizationCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_AUTHORIZATION_CUSTOM_ENDPOINT", providerName: "authorization_custom_endpoint"} + MongoDBFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MONGODBFLEX_CUSTOM_ENDPOINT", providerName: "mongodbflex_custom_endpoint"} + OpenSearchCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OPENSEARCH_CUSTOM_ENDPOINT", providerName: "opensearch_custom_endpoint"} + ObservabilityCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OBSERVABILITY_CUSTOM_ENDPOINT", providerName: "observability_custom_endpoint"} + ObjectStorageCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OBJECTSTORAGE_CUSTOM_ENDPOINT", providerName: "objectstorage_custom_endpoint"} + PostgresFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_POSTGRESFLEX_CUSTOM_ENDPOINT", providerName: "postgresflex_custom_endpoint"} + RabbitMQCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_RABBITMQ_CUSTOM_ENDPOINT", providerName: "rabbitmq_custom_endpoint"} + RedisCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_REDIS_CUSTOM_ENDPOINT", providerName: "redis_custom_endpoint"} + ResourceManagerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_RESOURCEMANAGER_CUSTOM_ENDPOINT", providerName: "resourcemanager_custom_endpoint"} + ScfCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SCF_CUSTOM_ENDPOINT", providerName: "scf_custom_endpoint"} + SecretsManagerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SECRETSMANAGER_CUSTOM_ENDPOINT", providerName: "secretsmanager_custom_endpoint"} + SQLServerFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SQLSERVERFLEX_CUSTOM_ENDPOINT", providerName: "sqlserverflex_custom_endpoint"} + ServerBackupCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVER_BACKUP_CUSTOM_ENDPOINT", providerName: "server_backup_custom_endpoint"} + ServerUpdateCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVER_UPDATE_CUSTOM_ENDPOINT", providerName: "server_update_custom_endpoint"} + SFSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SFS_CUSTOM_ENDPOINT", providerName: "sfs_custom_endpoint"} + 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"} + VpnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_VPN_CUSTOM_ENDPOINT", providerName: "vpn_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"} + TelemetryRouterCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYROUTER_CUSTOM_ENDPOINT", providerName: "telemetryrouter_custom_endpoint"} + TelemetryLinkCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYLINK_CUSTOM_ENDPOINT", providerName: "telemetrylink_custom_endpoint"} allCustomEndpoints = []customEndpointConfig{ ALBCustomEndpoint, @@ -118,6 +119,7 @@ var ( LogsCustomEndpoint, MariaDBCustomEndpoint, ModelServingCustomEndpoint, + ModelExperimentsCustomEndpoint, AuthorizationCustomEndpoint, MongoDBFlexCustomEndpoint, OpenSearchCustomEndpoint, From 5e684e3e45a51b320ef81330ce7925a7d414a4c2 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Fri, 8 May 2026 16:39:47 +0200 Subject: [PATCH 10/30] Fix bug with bucket_name mapping --- .../internal/services/modelexperiments/instance/resource.go | 2 +- .../services/modelexperiments/instance/resource_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 0a8a43688..d57b43986 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -532,13 +532,13 @@ func mapCreateResponse(ctx context.Context, instanceCreateResp *modelexperiments model.State = types.StringValue("unknown") } else { model.State = types.StringValue(waitResp.Instance.State) + model.BucketName = types.StringValue(*waitResp.Instance.BucketName) } model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceCreateResp.Instance.Id) model.InstanceId = types.StringValue(instance.Id) model.Name = types.StringValue(instance.Name) model.Description = types.StringPointerValue(instance.Description) model.DeletedExperimentRetention = types.StringPointerValue(instance.DeletedExperimentRetention) - model.BucketName = types.StringPointerValue(instance.BucketName) model.ErrorMessage = types.StringPointerValue(instance.ErrorMessage) model.Labels = mapValue model.Url = types.StringPointerValue(&instance.Url) diff --git a/stackit/internal/services/modelexperiments/instance/resource_test.go b/stackit/internal/services/modelexperiments/instance/resource_test.go index 24ad1a5f4..ff2a4f2a5 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_test.go @@ -182,7 +182,6 @@ func TestMapCreateResponseFields(t *testing.T) { inputCreateResponse: &modelexperiments.CreateInstanceResponse{ Instance: modelexperiments.Instance{ Id: "id", - BucketName: new("bucketName"), Description: new("description"), DeletedExperimentRetention: new("30d"), ErrorMessage: nil, @@ -193,7 +192,8 @@ func TestMapCreateResponseFields(t *testing.T) { }}, inputGetResponse: &modelexperiments.GetInstanceResponse{ Instance: modelexperiments.Instance{ - State: "active", + State: "active", + BucketName: new("bucketName"), }, }, expected: Model{ From 10a323577de8399d544c414d0f79e9c956e66c60 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Mon, 11 May 2026 11:33:02 +0200 Subject: [PATCH 11/30] Add instance token to acc test --- .../modelexperiments_acc_test.go | 53 +++++++++++-- stackit/internal/testutil/testutil.go | 76 ++++++++++--------- 2 files changed, 84 insertions(+), 45 deletions(-) diff --git a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go index d4ebac985..3c0dee01d 100644 --- a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go +++ b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go @@ -22,14 +22,17 @@ import ( ) var instanceResource = map[string]string{ - "project_id": testutil.ProjectId, - "name": "instance01", - "description": "my description", - "description_updated": "my description updated", - "region": testutil.Region, + "project_id": testutil.ProjectId, + "name": "instance01", + "description": "my description", + "description_updated": "my description updated", + "region": testutil.Region, + "tokenName": "token01", + "tokenDescription": "my token description", + "tokenDescriptionUpdated": "my token description updated", } -func inputInstanceConfig(name, description string) string { +func inputInstanceConfig(instanceName, instanceDescription, tokenName, tokenDescription string) string { return fmt.Sprintf(` %s @@ -39,12 +42,24 @@ func inputInstanceConfig(name, description string) string { region = "%s" description = "%s" } + + resource "stackit_modelexperiments_token" "token" { + project_id = "%s" + name = "%s" + region = "%s" + instance_id = stackit_modelexperiments_instance.example.instance_id + description = "%s" + } `, testutil.NewConfigBuilder().BuildProviderConfig(), instanceResource["project_id"], - name, + instanceName, + instanceResource["region"], + instanceDescription, + instanceResource["project_id"], + tokenName, instanceResource["region"], - description, + tokenDescription, ) } @@ -58,6 +73,8 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { Config: inputInstanceConfig( instanceResource["name"], instanceResource["description"], + instanceResource["tokenName"], + instanceResource["tokenDescription"], ), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "project_id", instanceResource["project_id"]), @@ -69,6 +86,15 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "bucket_name"), resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "deleted_experiment_retention"), resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "url"), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "project_id", instanceResource["project_id"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "region", instanceResource["region"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "name", instanceResource["tokenName"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "description", instanceResource["tokenDescription"]), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "token_id"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "state"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "token"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "valid_until"), ), }, // Update @@ -76,6 +102,8 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { Config: inputInstanceConfig( instanceResource["name"], instanceResource["description_updated"], + instanceResource["tokenName"], + instanceResource["tokenDescriptionUpdated"], ), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "project_id", instanceResource["project_id"]), @@ -87,6 +115,15 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "bucket_name"), resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "deleted_experiment_retention"), resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "url"), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "project_id", instanceResource["project_id"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "region", instanceResource["region"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "name", instanceResource["tokenName"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "description", instanceResource["tokenDescriptionUpdated"]), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "token_id"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "state"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "token"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "valid_until"), ), }, // Deletion is done by the framework implicitly diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index fd7322f52..1eac4b2a3 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -67,43 +67,44 @@ var ( // TestImageLocalFilePath is the local path to an image file used for image acceptance tests TestImageLocalFilePath = getenv("TF_ACC_TEST_IMAGE_LOCAL_FILE_PATH", "default") - ALBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CUSTOM_ENDPOINT", providerName: "alb_custom_endpoint"} - ALBCertCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CERT_CUSTOM_ENDPOINT", providerName: "alb_certificates_custom_endpoint"} - CdnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_CDN_CUSTOM_ENDPOINT", providerName: "cdn_custom_endpoint"} - DnsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DNS_CUSTOM_ENDPOINT", providerName: "dns_custom_endpoint"} - DremioCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DREMIO_CUSTOM_ENDPOINT", providerName: "dremio_custom_endpoint"} - EdgeCloudCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_EDGECLOUD_CUSTOM_ENDPOINT", providerName: "edgecloud_custom_endpoint"} - GitCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_GIT_CUSTOM_ENDPOINT", providerName: "git_custom_endpoint"} - IaaSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_IAAS_CUSTOM_ENDPOINT", providerName: "iaas_custom_endpoint"} - KMSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_KMS_CUSTOM_ENDPOINT", providerName: "kms_custom_endpoint"} - LoadBalancerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOADBALANCER_CUSTOM_ENDPOINT", providerName: "loadbalancer_custom_endpoint"} - LogMeCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOGME_CUSTOM_ENDPOINT", providerName: "logme_custom_endpoint"} - LogsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOGS_CUSTOM_ENDPOINT", providerName: "logs_custom_endpoint"} - MariaDBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MARIADB_CUSTOM_ENDPOINT", providerName: "mariadb_custom_endpoint"} - ModelServingCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MODELSERVING_CUSTOM_ENDPOINT", providerName: "modelserving_custom_endpoint"} - ModelExperimentsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MODELEXPERIMENTS_CUSTOM_ENDPOINT", providerName: "modelexperiments_custom_endpoint"} - AuthorizationCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_AUTHORIZATION_CUSTOM_ENDPOINT", providerName: "authorization_custom_endpoint"} - MongoDBFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MONGODBFLEX_CUSTOM_ENDPOINT", providerName: "mongodbflex_custom_endpoint"} - OpenSearchCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OPENSEARCH_CUSTOM_ENDPOINT", providerName: "opensearch_custom_endpoint"} - ObservabilityCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OBSERVABILITY_CUSTOM_ENDPOINT", providerName: "observability_custom_endpoint"} - ObjectStorageCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OBJECTSTORAGE_CUSTOM_ENDPOINT", providerName: "objectstorage_custom_endpoint"} - PostgresFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_POSTGRESFLEX_CUSTOM_ENDPOINT", providerName: "postgresflex_custom_endpoint"} - RabbitMQCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_RABBITMQ_CUSTOM_ENDPOINT", providerName: "rabbitmq_custom_endpoint"} - RedisCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_REDIS_CUSTOM_ENDPOINT", providerName: "redis_custom_endpoint"} - ResourceManagerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_RESOURCEMANAGER_CUSTOM_ENDPOINT", providerName: "resourcemanager_custom_endpoint"} - ScfCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SCF_CUSTOM_ENDPOINT", providerName: "scf_custom_endpoint"} - SecretsManagerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SECRETSMANAGER_CUSTOM_ENDPOINT", providerName: "secretsmanager_custom_endpoint"} - SQLServerFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SQLSERVERFLEX_CUSTOM_ENDPOINT", providerName: "sqlserverflex_custom_endpoint"} - ServerBackupCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVER_BACKUP_CUSTOM_ENDPOINT", providerName: "server_backup_custom_endpoint"} - ServerUpdateCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVER_UPDATE_CUSTOM_ENDPOINT", providerName: "server_update_custom_endpoint"} - SFSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SFS_CUSTOM_ENDPOINT", providerName: "sfs_custom_endpoint"} - 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"} - VpnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_VPN_CUSTOM_ENDPOINT", providerName: "vpn_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"} - TelemetryRouterCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYROUTER_CUSTOM_ENDPOINT", providerName: "telemetryrouter_custom_endpoint"} - TelemetryLinkCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYLINK_CUSTOM_ENDPOINT", providerName: "telemetrylink_custom_endpoint"} + ALBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CUSTOM_ENDPOINT", providerName: "alb_custom_endpoint"} + ALBCertCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CERT_CUSTOM_ENDPOINT", providerName: "alb_certificates_custom_endpoint"} + CdnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_CDN_CUSTOM_ENDPOINT", providerName: "cdn_custom_endpoint"} + DnsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DNS_CUSTOM_ENDPOINT", providerName: "dns_custom_endpoint"} + DremioCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DREMIO_CUSTOM_ENDPOINT", providerName: "dremio_custom_endpoint"} + EdgeCloudCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_EDGECLOUD_CUSTOM_ENDPOINT", providerName: "edgecloud_custom_endpoint"} + GitCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_GIT_CUSTOM_ENDPOINT", providerName: "git_custom_endpoint"} + IaaSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_IAAS_CUSTOM_ENDPOINT", providerName: "iaas_custom_endpoint"} + KMSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_KMS_CUSTOM_ENDPOINT", providerName: "kms_custom_endpoint"} + LoadBalancerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOADBALANCER_CUSTOM_ENDPOINT", providerName: "loadbalancer_custom_endpoint"} + LogMeCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOGME_CUSTOM_ENDPOINT", providerName: "logme_custom_endpoint"} + LogsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_LOGS_CUSTOM_ENDPOINT", providerName: "logs_custom_endpoint"} + MariaDBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MARIADB_CUSTOM_ENDPOINT", providerName: "mariadb_custom_endpoint"} + ModelServingCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MODELSERVING_CUSTOM_ENDPOINT", providerName: "modelserving_custom_endpoint"} + ModelExperimentsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MODELEXPERIMENTS_CUSTOM_ENDPOINT", providerName: "modelexperiments_custom_endpoint"} + AuthorizationCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_AUTHORIZATION_CUSTOM_ENDPOINT", providerName: "authorization_custom_endpoint"} + MongoDBFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_MONGODBFLEX_CUSTOM_ENDPOINT", providerName: "mongodbflex_custom_endpoint"} + OpenSearchCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OPENSEARCH_CUSTOM_ENDPOINT", providerName: "opensearch_custom_endpoint"} + ObservabilityCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OBSERVABILITY_CUSTOM_ENDPOINT", providerName: "observability_custom_endpoint"} + ObjectStorageCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_OBJECTSTORAGE_CUSTOM_ENDPOINT", providerName: "objectstorage_custom_endpoint"} + PostgresFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_POSTGRESFLEX_CUSTOM_ENDPOINT", providerName: "postgresflex_custom_endpoint"} + RabbitMQCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_RABBITMQ_CUSTOM_ENDPOINT", providerName: "rabbitmq_custom_endpoint"} + RedisCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_REDIS_CUSTOM_ENDPOINT", providerName: "redis_custom_endpoint"} + ResourceManagerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_RESOURCEMANAGER_CUSTOM_ENDPOINT", providerName: "resourcemanager_custom_endpoint"} + ScfCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SCF_CUSTOM_ENDPOINT", providerName: "scf_custom_endpoint"} + SecretsManagerCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SECRETSMANAGER_CUSTOM_ENDPOINT", providerName: "secretsmanager_custom_endpoint"} + SQLServerFlexCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SQLSERVERFLEX_CUSTOM_ENDPOINT", providerName: "sqlserverflex_custom_endpoint"} + ServerBackupCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVER_BACKUP_CUSTOM_ENDPOINT", providerName: "server_backup_custom_endpoint"} + ServerUpdateCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVER_UPDATE_CUSTOM_ENDPOINT", providerName: "server_update_custom_endpoint"} + SFSCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SFS_CUSTOM_ENDPOINT", providerName: "sfs_custom_endpoint"} + ServiceAccountCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVICE_ACCOUNT_CUSTOM_ENDPOINT", providerName: "service_account_custom_endpoint"} + ServiceEnablementCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVICE_ENABLEMENT_CUSTOM_ENDPOINT", providerName: "service_enablement_custom_endpoint"} + TokenCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TOKEN_CUSTOM_ENDPOINT", providerName: "token_custom_endpoint"} + VpnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_VPN_CUSTOM_ENDPOINT", providerName: "vpn_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"} + TelemetryRouterCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYROUTER_CUSTOM_ENDPOINT", providerName: "telemetryrouter_custom_endpoint"} + TelemetryLinkCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYLINK_CUSTOM_ENDPOINT", providerName: "telemetrylink_custom_endpoint"} allCustomEndpoints = []customEndpointConfig{ ALBCustomEndpoint, @@ -136,6 +137,7 @@ var ( ServerUpdateCustomEndpoint, SFSCustomEndpoint, ServiceAccountCustomEndpoint, + ServiceEnablementCustomEndpoint, TokenCustomEndpoint, VpnCustomEndpoint, SKECustomEndpoint, From 5e7c6da98298308e9656502d4ecce2210e306bde Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Mon, 11 May 2026 11:52:16 +0200 Subject: [PATCH 12/30] Fix lining --- .../modelexperiments/instance/resource.go | 10 +++--- .../instance/resource_test.go | 2 +- .../modelexperiments/token/resource.go | 31 +++++++++---------- .../modelexperiments/token/resource_test.go | 2 +- .../services/modelexperiments/utils/util.go | 1 + .../modelexperiments/utils/util_test.go | 1 + 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index d57b43986..8418b813a 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -20,6 +20,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/wait" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" serviceEnablementWait "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api/wait" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" modelexperimentsutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils" @@ -296,7 +297,7 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - //If model experiments instance is impaired, write state avoid dangling resources and return + // If model experiments instance is impaired, write state avoid dangling resources and return waitResp, err := CreateMExpInstanceWaitHandler(ctx, i.client, region, projectId, instanceId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Waiting for instance to be active: %v", err)) @@ -368,7 +369,7 @@ func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r } ctx = core.LogResponse(ctx) - err = mapInstance(ctx, getInstanceResp.Instance, &model) + err = mapInstance(ctx, &getInstanceResp.Instance, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -381,7 +382,6 @@ func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r } tflog.Info(ctx, "Model experiments instance read") - } // Update updates the resource and sets the updated Terraform state on success. @@ -447,7 +447,7 @@ func (i *instanceResource) Update(ctx context.Context, req resource.UpdateReques ctx = core.LogResponse(ctx) - err = mapInstance(ctx, updateInstanceResp.Instance, &model) + err = mapInstance(ctx, &updateInstanceResp.Instance, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -547,7 +547,7 @@ func mapCreateResponse(ctx context.Context, instanceCreateResp *modelexperiments } // mapInstance maps instances to the resource model -func mapInstance(ctx context.Context, instance modelexperiments.Instance, model *Model) error { +func mapInstance(ctx context.Context, instance *modelexperiments.Instance, model *Model) error { if model == nil { return fmt.Errorf("model input is nil") } diff --git a/stackit/internal/services/modelexperiments/instance/resource_test.go b/stackit/internal/services/modelexperiments/instance/resource_test.go index ff2a4f2a5..485f8d34d 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_test.go @@ -78,7 +78,7 @@ func TestMapInstanceFields(t *testing.T) { t.Parallel() ctx := context.Background() - err := mapInstance(ctx, tt.input, tt.state) + err := mapInstance(ctx, &tt.input, tt.state) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index efe6beed3..a52b7d94f 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -19,6 +19,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/wait" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" modelexperimentsutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils" @@ -341,7 +342,7 @@ func (i *tokenResource) Read(ctx context.Context, req resource.ReadRequest, resp } ctx = core.LogResponse(ctx) - err = mapToken(ctx, getInstanceTokenResp.Token, &model) + err = mapToken(ctx, &getInstanceTokenResp.Token, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -354,7 +355,6 @@ func (i *tokenResource) Read(ctx context.Context, req resource.ReadRequest, resp } tflog.Info(ctx, "Model experiments instance token read") - } // Update updates the resource and sets the updated Terraform state on success. @@ -429,7 +429,7 @@ func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, } model.Token = state.Token - err = mapToken(ctx, updateInstanceTokenResp.Token, &model) + err = mapToken(ctx, &updateInstanceTokenResp.Token, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance token", fmt.Sprintf("Processing API payload: %v", err)) return @@ -470,16 +470,15 @@ func (i *tokenResource) Delete(ctx context.Context, req resource.DeleteRequest, _, err := i.client.DefaultAPI.DeleteInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { - if oapiErr.StatusCode == http.StatusNotFound { - resp.State.RemoveResource(ctx) - return - } - if oapiErr.StatusCode != http.StatusConflict { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Calling API: %v", err)) - return - } - } else { + if !errors.As(err, &oapiErr) { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Calling API: %v", err)) + return + } + if oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + if oapiErr.StatusCode != http.StatusConflict { core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Calling API: %v", err)) return } @@ -540,7 +539,7 @@ func mapCreateResponse(ctx context.Context, instanceTokenResp *modelexperiments. } // mapToken maps instances to the resource model -func mapToken(ctx context.Context, token modelexperiments.TokenMetadata, model *Model) error { +func mapToken(ctx context.Context, token *modelexperiments.TokenMetadata, model *Model) error { if model == nil { return fmt.Errorf("model input is nil") } @@ -600,7 +599,7 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstanceToken }, nil } -func CreateMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetTokenResponse] { +func CreateMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetTokenResponse] { handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetTokenResponse, err error) { getTokenResp, err := a.DefaultAPI.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() if err != nil { @@ -618,7 +617,7 @@ func CreateMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClie return handler } -func DeleteMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { +func DeleteMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New( func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { _, err = a.DefaultAPI.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() diff --git a/stackit/internal/services/modelexperiments/token/resource_test.go b/stackit/internal/services/modelexperiments/token/resource_test.go index e14223ef3..a725b3f9d 100644 --- a/stackit/internal/services/modelexperiments/token/resource_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_test.go @@ -75,7 +75,7 @@ func TestMapTokenFields(t *testing.T) { t.Parallel() ctx := context.Background() - err := mapToken(ctx, tt.input, tt.state) + err := mapToken(ctx, &tt.input, tt.state) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } diff --git a/stackit/internal/services/modelexperiments/utils/util.go b/stackit/internal/services/modelexperiments/utils/util.go index 05af4dca3..5ccf32215 100644 --- a/stackit/internal/services/modelexperiments/utils/util.go +++ b/stackit/internal/services/modelexperiments/utils/util.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/stackitcloud/stackit-sdk-go/core/config" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) diff --git a/stackit/internal/services/modelexperiments/utils/util_test.go b/stackit/internal/services/modelexperiments/utils/util_test.go index a1e37f1f8..8f9c8b71c 100644 --- a/stackit/internal/services/modelexperiments/utils/util_test.go +++ b/stackit/internal/services/modelexperiments/utils/util_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) From f52c1eeb544bb9b1ac730e96efbd4fa2b544a941 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Mon, 11 May 2026 11:59:36 +0200 Subject: [PATCH 13/30] Fix test --- .../services/modelexperiments/instance/resource_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/stackit/internal/services/modelexperiments/instance/resource_test.go b/stackit/internal/services/modelexperiments/instance/resource_test.go index 485f8d34d..6fc46178d 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_test.go @@ -145,7 +145,6 @@ func TestMapCreateResponseFields(t *testing.T) { inputCreateResponse: &modelexperiments.CreateInstanceResponse{ Instance: modelexperiments.Instance{ Id: "id", - BucketName: new("bucketName"), Description: new("description"), DeletedExperimentRetention: new("30d"), ErrorMessage: nil, @@ -163,7 +162,6 @@ func TestMapCreateResponseFields(t *testing.T) { Name: types.StringValue("name"), Description: types.StringValue("description"), State: types.StringValue("unknown"), - BucketName: types.StringValue("bucketName"), DeletedExperimentRetention: types.StringValue("30d"), Url: types.StringValue("url"), ErrorMessage: types.StringNull(), From 36d9512c4639e08b3e08774ac3469322ba5eaefe Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Mon, 11 May 2026 13:14:46 +0200 Subject: [PATCH 14/30] Add documentation --- docs/index.md | 1 + docs/resources/modelexperiments_instance.md | 58 +++++++++++++++ docs/resources/modelexperiments_token.md | 74 +++++++++++++++++++ .../modelexperiments/instance/description.md | 13 ++++ .../modelexperiments/instance/resource.go | 2 + .../modelexperiments/token/description.md | 21 ++++++ .../modelexperiments/token/resource.go | 2 + 7 files changed, 171 insertions(+) create mode 100644 docs/resources/modelexperiments_instance.md create mode 100644 docs/resources/modelexperiments_token.md create mode 100644 stackit/internal/services/modelexperiments/instance/description.md create mode 100644 stackit/internal/services/modelexperiments/token/description.md diff --git a/docs/index.md b/docs/index.md index 368beb47e..a28a56621 100644 --- a/docs/index.md +++ b/docs/index.md @@ -182,6 +182,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de - `logme_custom_endpoint` (String) Custom endpoint for the LogMe service - `logs_custom_endpoint` (String) Custom endpoint for the Logs service - `mariadb_custom_endpoint` (String) Custom endpoint for the MariaDB service +- `modelexperiments_custom_endpoint` (String) Custom endpoint for the AI Model Experiments service - `modelserving_custom_endpoint` (String) Custom endpoint for the AI Model Serving service - `mongodbflex_custom_endpoint` (String) Custom endpoint for the MongoDB Flex service - `objectstorage_custom_endpoint` (String) Custom endpoint for the Object Storage service diff --git a/docs/resources/modelexperiments_instance.md b/docs/resources/modelexperiments_instance.md new file mode 100644 index 000000000..9efca6760 --- /dev/null +++ b/docs/resources/modelexperiments_instance.md @@ -0,0 +1,58 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_modelexperiments_instance Resource - stackit" +subcategory: "" +description: |- + AI Model Experiment Instance Resource schema. + Example Usage + + + resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" + description = "Example description" + } +--- + +# stackit_modelexperiments_instance (Resource) + +AI Model Experiment Instance Resource schema. + +## Example Usage + +```terraform + +resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" + description = "Example description" +} +``` + + + + +## Schema + +### Required + +- `name` (String) Name of the AI model experiments instance. +- `project_id` (String) STACKIT project ID to which the AI model experiments instance is associated. + +### Optional + +- `deleted_experiment_retention` (String) The deleted experiment retention of the AI model experiments instance. +- `description` (String) The description of the AI model experiments instance. +- `error_message` (String) Error messages of the AI model experiments instance. +- `labels` (Map of String) A map of arbitrary key/value pairs that can be attached to the AI model experiments instance +- `region` (String) Region to which the AI model experiments instance is associated. If not defined, the provider region is used + +### Read-Only + +- `bucket_name` (String) The object storage bucket name of the AI model experiments instance. +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`instance_id`". +- `instance_id` (String) The AI model experiments instance ID. +- `state` (String) State of the AI model experiments instance. +- `url` (String) URL of the AI model experiments instance. diff --git a/docs/resources/modelexperiments_token.md b/docs/resources/modelexperiments_token.md new file mode 100644 index 000000000..878564b91 --- /dev/null +++ b/docs/resources/modelexperiments_token.md @@ -0,0 +1,74 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_modelexperiments_token Resource - stackit" +subcategory: "" +description: |- + AI Model Experiment Instance Token Resource schema. + Example Usage + + + resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" + description = "Example description" + } + + resource "stackit_modelexperiments_token" "token" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example token" + region = "eu01" + instance_id = stackit_modelexperiments_instance.example.instance_id + description = "Example description" + } +--- + +# stackit_modelexperiments_token (Resource) + +AI Model Experiment Instance Token Resource schema. + +## Example Usage + +```terraform + +resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" + description = "Example description" +} + +resource "stackit_modelexperiments_token" "token" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example token" + region = "eu01" + instance_id = stackit_modelexperiments_instance.example.instance_id + description = "Example description" +} +``` + + + + +## Schema + +### Required + +- `instance_id` (String) The AI model experiments instance ID. +- `name` (String) Name of the AI model experiments instance token. +- `project_id` (String) STACKIT project ID to which the AI model experiments instance token is associated. + +### Optional + +- `description` (String) The description of the AI model experiments instance token. +- `labels` (Map of String) A map of arbitrary key/value pairs for the AI model experiments instance token. +- `region` (String) Region to which the AI model experiments instance token is associated. If not defined, the provider region is used +- `ttl_duration` (String) The TTL duration of the AI model experiments instance token. E.g. 5h30m40s,5h,5h30m,30m,30s + +### Read-Only + +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`token_id`". +- `state` (String) State of the AI model experiments instance token. +- `token` (String, Sensitive) Content of the AI model experiments instance token. +- `token_id` (String) The AI model experiments instance token ID. +- `valid_until` (String) The time until the AI model experiments instance token is valid. diff --git a/stackit/internal/services/modelexperiments/instance/description.md b/stackit/internal/services/modelexperiments/instance/description.md new file mode 100644 index 000000000..0b29ea11a --- /dev/null +++ b/stackit/internal/services/modelexperiments/instance/description.md @@ -0,0 +1,13 @@ +AI Model Experiment Instance Resource schema. + +## Example Usage + +```terraform + +resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" + description = "Example description" +} +``` \ No newline at end of file diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 8418b813a..5fc554d38 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -2,6 +2,7 @@ package instance import ( "context" + _ "embed" "errors" "fmt" "net/http" @@ -34,6 +35,7 @@ var ( _ resource.ResourceWithModifyPlan = &instanceResource{} ) +//go:embed description.md var markdownDescription string type Model struct { diff --git a/stackit/internal/services/modelexperiments/token/description.md b/stackit/internal/services/modelexperiments/token/description.md new file mode 100644 index 000000000..ceb145d7b --- /dev/null +++ b/stackit/internal/services/modelexperiments/token/description.md @@ -0,0 +1,21 @@ +AI Model Experiment Instance Token Resource schema. + +## Example Usage + +```terraform + +resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" + description = "Example description" +} + +resource "stackit_modelexperiments_token" "token" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example token" + region = "eu01" + instance_id = stackit_modelexperiments_instance.example.instance_id + description = "Example description" +} +``` \ No newline at end of file diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index a52b7d94f..2f1e3be6d 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -2,6 +2,7 @@ package token import ( "context" + _ "embed" "errors" "fmt" "net/http" @@ -33,6 +34,7 @@ var ( _ resource.ResourceWithModifyPlan = &tokenResource{} ) +//go:embed description.md var markdownDescription string type Model struct { From 83c91303ce987725ad213551acd342fda62b242e Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Mon, 11 May 2026 16:34:53 +0200 Subject: [PATCH 15/30] Change sdk clients to DefaultAPI --- .../modelexperiments/instance/resource.go | 28 ++++++++++--------- .../modelexperiments/token/resource.go | 20 ++++++------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 5fc554d38..a7b8ae0f4 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -21,10 +21,10 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/wait" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" serviceEnablementWait "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api/wait" + modelexperimentsutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" - modelexperimentsutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" ) @@ -54,15 +54,17 @@ type Model struct { } // NewInstanceResource is a helper function to simplify the provider implementation. +// +//go:generate mockgen -destination=./mock/instance.go -package=mock_instance dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api DefaultAPI func NewInstanceResource() resource.Resource { return &instanceResource{} } // instanceResource is the resource implementation. type instanceResource struct { - client *modelexperiments.APIClient + client modelexperiments.DefaultAPI providerData core.ProviderData - serviceEnablementClient *serviceenablement.APIClient + serviceEnablementClient serviceenablement.DefaultAPI } // Metadata returns the resource type name. @@ -233,7 +235,7 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) - err := i.serviceEnablementClient.DefaultAPI.EnableServiceRegional(ctx, region, projectId, utils.ModelExperimentsServiceId). + err := i.serviceEnablementClient.EnableServiceRegional(ctx, region, projectId, utils.ModelExperimentsServiceId). Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError @@ -254,7 +256,7 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - _, err = serviceEnablementWait.EnableServiceWaitHandler(ctx, i.serviceEnablementClient.DefaultAPI, region, projectId, utils.ModelServingServiceId). + _, err = serviceEnablementWait.EnableServiceWaitHandler(ctx, i.serviceEnablementClient, region, projectId, utils.ModelServingServiceId). WaitWithContext(ctx) if err != nil { core.LogAndAddError( @@ -272,7 +274,7 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - createInstanceResp, err := i.client.DefaultAPI.CreateInstance(ctx, projectId, region).CreateInstancePayload(*payload).Execute() + createInstanceResp, err := i.client.CreateInstance(ctx, projectId, region).CreateInstancePayload(*payload).Execute() if err != nil { core.LogAndAddError( ctx, @@ -355,7 +357,7 @@ func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - getInstanceResp, err := i.client.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute() + getInstanceResp, err := i.client.GetInstance(ctx, projectId, region, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -421,7 +423,7 @@ func (i *instanceResource) Update(ctx context.Context, req resource.UpdateReques return } - updateInstanceResp, err := i.client.DefaultAPI.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload).Execute() + updateInstanceResp, err := i.client.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -485,7 +487,7 @@ func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteReques ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - _, err := i.client.DefaultAPI.DeleteInstance(ctx, projectId, region, instanceId).Execute() + _, err := i.client.DeleteInstance(ctx, projectId, region, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if !errors.As(err, &oapiErr) { @@ -613,9 +615,9 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstancePaylo }, nil } -func CreateMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { +func CreateMExpInstanceWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - getInstanceResp, err := a.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute() + getInstanceResp, err := a.GetInstance(ctx, projectId, region, instanceId).Execute() if err != nil { return false, nil, err } @@ -634,10 +636,10 @@ func CreateMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIC return handler } -func DeleteMExpInstanceWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { +func DeleteMExpInstanceWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New( func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - _, err = a.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute() + _, err = a.GetInstance(ctx, projectId, region, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index 2f1e3be6d..e29022f6d 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -59,9 +59,9 @@ func NewInstanceTokenResource() resource.Resource { // tokenResource is the resource implementation. type tokenResource struct { - client *modelexperiments.APIClient + client modelexperiments.DefaultAPI providerData core.ProviderData - serviceEnablementClient *serviceenablement.APIClient + serviceEnablementClient serviceenablement.DefaultAPI } // Metadata returns the resource type name. @@ -242,7 +242,7 @@ func (i *tokenResource) Create(ctx context.Context, req resource.CreateRequest, return } - createInstanceTokenResp, err := i.client.DefaultAPI.CreateInstanceToken(ctx, projectId, region, instanceId).CreateInstanceTokenPayload(*payload).Execute() + createInstanceTokenResp, err := i.client.CreateInstanceToken(ctx, projectId, region, instanceId).CreateInstanceTokenPayload(*payload).Execute() if err != nil { core.LogAndAddError( ctx, @@ -328,7 +328,7 @@ func (i *tokenResource) Read(ctx context.Context, req resource.ReadRequest, resp ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - getInstanceTokenResp, err := i.client.DefaultAPI.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + getInstanceTokenResp, err := i.client.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -395,7 +395,7 @@ func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, return } - updateInstanceTokenResp, err := i.client.DefaultAPI.PartialUpdateInstanceToken(ctx, projectId, region, tokenId, instanceId).PartialUpdateInstanceTokenPayload(*payload).Execute() + updateInstanceTokenResp, err := i.client.PartialUpdateInstanceToken(ctx, projectId, region, tokenId, instanceId).PartialUpdateInstanceTokenPayload(*payload).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -469,7 +469,7 @@ func (i *tokenResource) Delete(ctx context.Context, req resource.DeleteRequest, ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - _, err := i.client.DefaultAPI.DeleteInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + _, err := i.client.DeleteInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if !errors.As(err, &oapiErr) { @@ -601,9 +601,9 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstanceToken }, nil } -func CreateMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetTokenResponse] { +func CreateMExpTokenWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetTokenResponse] { handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetTokenResponse, err error) { - getTokenResp, err := a.DefaultAPI.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + getTokenResp, err := a.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() if err != nil { return false, nil, err } @@ -619,10 +619,10 @@ func CreateMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClie return handler } -func DeleteMExpTokenWaitHandler(ctx context.Context, a *modelexperiments.APIClient, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { +func DeleteMExpTokenWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New( func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - _, err = a.DefaultAPI.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + _, err = a.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { From e90ba127721bf42d310499f98f8896abb31aab28 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Mon, 11 May 2026 16:35:19 +0200 Subject: [PATCH 16/30] Add some mocks and test_utils --- go.mod | 2 + go.sum | 2 + .../instance/mock/instance.go | 332 ++++++++++++++++++ .../instance/resource_create_test.go | 7 + .../utils/mock/serviceenablement.go | 156 ++++++++ .../modelexperiments/utils/test_utils.go | 21 ++ .../services/modelexperiments/utils/util.go | 9 +- 7 files changed, 525 insertions(+), 4 deletions(-) create mode 100644 stackit/internal/services/modelexperiments/instance/mock/instance.go create mode 100644 stackit/internal/services/modelexperiments/instance/resource_create_test.go create mode 100644 stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go create mode 100644 stackit/internal/services/modelexperiments/utils/test_utils.go diff --git a/go.mod b/go.mod index 33c24638a..7861ae496 100644 --- a/go.mod +++ b/go.mod @@ -52,6 +52,8 @@ require ( golang.org/x/mod v0.37.0 ) +require go.uber.org/mock v0.6.0 // indirect + require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect diff --git a/go.sum b/go.sum index 5693b3589..c73d31674 100644 --- a/go.sum +++ b/go.sum @@ -843,6 +843,8 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6 go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= diff --git a/stackit/internal/services/modelexperiments/instance/mock/instance.go b/stackit/internal/services/modelexperiments/instance/mock/instance.go new file mode 100644 index 000000000..08735830a --- /dev/null +++ b/stackit/internal/services/modelexperiments/instance/mock/instance.go @@ -0,0 +1,332 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api (interfaces: DefaultAPI) +// +// Generated by this command: +// +// mockgen -destination=./mock/instance.go -package=mock_instance dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api DefaultAPI +// + +// Package mock_instance is a generated GoMock package. +package mock_instance + +import ( + context "context" + reflect "reflect" + + v1api "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + gomock "go.uber.org/mock/gomock" +) + +// MockDefaultAPI is a mock of DefaultAPI interface. +type MockDefaultAPI struct { + ctrl *gomock.Controller + recorder *MockDefaultAPIMockRecorder + isgomock struct{} +} + +// MockDefaultAPIMockRecorder is the mock recorder for MockDefaultAPI. +type MockDefaultAPIMockRecorder struct { + mock *MockDefaultAPI +} + +// NewMockDefaultAPI creates a new mock instance. +func NewMockDefaultAPI(ctrl *gomock.Controller) *MockDefaultAPI { + mock := &MockDefaultAPI{ctrl: ctrl} + mock.recorder = &MockDefaultAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDefaultAPI) EXPECT() *MockDefaultAPIMockRecorder { + return m.recorder +} + +// CreateInstance mocks base method. +func (m *MockDefaultAPI) CreateInstance(ctx context.Context, projectId, regionId string) v1api.ApiCreateInstanceRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateInstance", ctx, projectId, regionId) + ret0, _ := ret[0].(v1api.ApiCreateInstanceRequest) + return ret0 +} + +// CreateInstance indicates an expected call of CreateInstance. +func (mr *MockDefaultAPIMockRecorder) CreateInstance(ctx, projectId, regionId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateInstance", reflect.TypeOf((*MockDefaultAPI)(nil).CreateInstance), ctx, projectId, regionId) +} + +// CreateInstanceExecute mocks base method. +func (m *MockDefaultAPI) CreateInstanceExecute(r v1api.ApiCreateInstanceRequest) (*v1api.CreateInstanceResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateInstanceExecute", r) + ret0, _ := ret[0].(*v1api.CreateInstanceResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateInstanceExecute indicates an expected call of CreateInstanceExecute. +func (mr *MockDefaultAPIMockRecorder) CreateInstanceExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateInstanceExecute", reflect.TypeOf((*MockDefaultAPI)(nil).CreateInstanceExecute), r) +} + +// CreateInstanceToken mocks base method. +func (m *MockDefaultAPI) CreateInstanceToken(ctx context.Context, projectId, regionId, instanceId string) v1api.ApiCreateInstanceTokenRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateInstanceToken", ctx, projectId, regionId, instanceId) + ret0, _ := ret[0].(v1api.ApiCreateInstanceTokenRequest) + return ret0 +} + +// CreateInstanceToken indicates an expected call of CreateInstanceToken. +func (mr *MockDefaultAPIMockRecorder) CreateInstanceToken(ctx, projectId, regionId, instanceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).CreateInstanceToken), ctx, projectId, regionId, instanceId) +} + +// CreateInstanceTokenExecute mocks base method. +func (m *MockDefaultAPI) CreateInstanceTokenExecute(r v1api.ApiCreateInstanceTokenRequest) (*v1api.CreateTokenResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateInstanceTokenExecute", r) + ret0, _ := ret[0].(*v1api.CreateTokenResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateInstanceTokenExecute indicates an expected call of CreateInstanceTokenExecute. +func (mr *MockDefaultAPIMockRecorder) CreateInstanceTokenExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateInstanceTokenExecute", reflect.TypeOf((*MockDefaultAPI)(nil).CreateInstanceTokenExecute), r) +} + +// DeleteInstance mocks base method. +func (m *MockDefaultAPI) DeleteInstance(ctx context.Context, projectId, regionId, instanceId string) v1api.ApiDeleteInstanceRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteInstance", ctx, projectId, regionId, instanceId) + ret0, _ := ret[0].(v1api.ApiDeleteInstanceRequest) + return ret0 +} + +// DeleteInstance indicates an expected call of DeleteInstance. +func (mr *MockDefaultAPIMockRecorder) DeleteInstance(ctx, projectId, regionId, instanceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInstance", reflect.TypeOf((*MockDefaultAPI)(nil).DeleteInstance), ctx, projectId, regionId, instanceId) +} + +// DeleteInstanceExecute mocks base method. +func (m *MockDefaultAPI) DeleteInstanceExecute(r v1api.ApiDeleteInstanceRequest) (*v1api.DeleteInstanceResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteInstanceExecute", r) + ret0, _ := ret[0].(*v1api.DeleteInstanceResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteInstanceExecute indicates an expected call of DeleteInstanceExecute. +func (mr *MockDefaultAPIMockRecorder) DeleteInstanceExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInstanceExecute", reflect.TypeOf((*MockDefaultAPI)(nil).DeleteInstanceExecute), r) +} + +// DeleteInstanceToken mocks base method. +func (m *MockDefaultAPI) DeleteInstanceToken(ctx context.Context, projectId, regionId, tId, instanceId string) v1api.ApiDeleteInstanceTokenRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteInstanceToken", ctx, projectId, regionId, tId, instanceId) + ret0, _ := ret[0].(v1api.ApiDeleteInstanceTokenRequest) + return ret0 +} + +// DeleteInstanceToken indicates an expected call of DeleteInstanceToken. +func (mr *MockDefaultAPIMockRecorder) DeleteInstanceToken(ctx, projectId, regionId, tId, instanceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).DeleteInstanceToken), ctx, projectId, regionId, tId, instanceId) +} + +// DeleteInstanceTokenExecute mocks base method. +func (m *MockDefaultAPI) DeleteInstanceTokenExecute(r v1api.ApiDeleteInstanceTokenRequest) (*v1api.DeleteTokenResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteInstanceTokenExecute", r) + ret0, _ := ret[0].(*v1api.DeleteTokenResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteInstanceTokenExecute indicates an expected call of DeleteInstanceTokenExecute. +func (mr *MockDefaultAPIMockRecorder) DeleteInstanceTokenExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInstanceTokenExecute", reflect.TypeOf((*MockDefaultAPI)(nil).DeleteInstanceTokenExecute), r) +} + +// GetInstance mocks base method. +func (m *MockDefaultAPI) GetInstance(ctx context.Context, projectId, regionId, instanceId string) v1api.ApiGetInstanceRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstance", ctx, projectId, regionId, instanceId) + ret0, _ := ret[0].(v1api.ApiGetInstanceRequest) + return ret0 +} + +// GetInstance indicates an expected call of GetInstance. +func (mr *MockDefaultAPIMockRecorder) GetInstance(ctx, projectId, regionId, instanceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstance", reflect.TypeOf((*MockDefaultAPI)(nil).GetInstance), ctx, projectId, regionId, instanceId) +} + +// GetInstanceExecute mocks base method. +func (m *MockDefaultAPI) GetInstanceExecute(r v1api.ApiGetInstanceRequest) (*v1api.GetInstanceResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstanceExecute", r) + ret0, _ := ret[0].(*v1api.GetInstanceResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstanceExecute indicates an expected call of GetInstanceExecute. +func (mr *MockDefaultAPIMockRecorder) GetInstanceExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceExecute", reflect.TypeOf((*MockDefaultAPI)(nil).GetInstanceExecute), r) +} + +// GetInstanceToken mocks base method. +func (m *MockDefaultAPI) GetInstanceToken(ctx context.Context, projectId, regionId, tId, instanceId string) v1api.ApiGetInstanceTokenRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstanceToken", ctx, projectId, regionId, tId, instanceId) + ret0, _ := ret[0].(v1api.ApiGetInstanceTokenRequest) + return ret0 +} + +// GetInstanceToken indicates an expected call of GetInstanceToken. +func (mr *MockDefaultAPIMockRecorder) GetInstanceToken(ctx, projectId, regionId, tId, instanceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).GetInstanceToken), ctx, projectId, regionId, tId, instanceId) +} + +// GetInstanceTokenExecute mocks base method. +func (m *MockDefaultAPI) GetInstanceTokenExecute(r v1api.ApiGetInstanceTokenRequest) (*v1api.GetTokenResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInstanceTokenExecute", r) + ret0, _ := ret[0].(*v1api.GetTokenResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInstanceTokenExecute indicates an expected call of GetInstanceTokenExecute. +func (mr *MockDefaultAPIMockRecorder) GetInstanceTokenExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceTokenExecute", reflect.TypeOf((*MockDefaultAPI)(nil).GetInstanceTokenExecute), r) +} + +// ListInstanceTokens mocks base method. +func (m *MockDefaultAPI) ListInstanceTokens(ctx context.Context, projectId, regionId, instanceId string) v1api.ApiListInstanceTokensRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListInstanceTokens", ctx, projectId, regionId, instanceId) + ret0, _ := ret[0].(v1api.ApiListInstanceTokensRequest) + return ret0 +} + +// ListInstanceTokens indicates an expected call of ListInstanceTokens. +func (mr *MockDefaultAPIMockRecorder) ListInstanceTokens(ctx, projectId, regionId, instanceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListInstanceTokens", reflect.TypeOf((*MockDefaultAPI)(nil).ListInstanceTokens), ctx, projectId, regionId, instanceId) +} + +// ListInstanceTokensExecute mocks base method. +func (m *MockDefaultAPI) ListInstanceTokensExecute(r v1api.ApiListInstanceTokensRequest) (*v1api.ListTokenResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListInstanceTokensExecute", r) + ret0, _ := ret[0].(*v1api.ListTokenResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListInstanceTokensExecute indicates an expected call of ListInstanceTokensExecute. +func (mr *MockDefaultAPIMockRecorder) ListInstanceTokensExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListInstanceTokensExecute", reflect.TypeOf((*MockDefaultAPI)(nil).ListInstanceTokensExecute), r) +} + +// ListInstances mocks base method. +func (m *MockDefaultAPI) ListInstances(ctx context.Context, projectId, regionId string) v1api.ApiListInstancesRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListInstances", ctx, projectId, regionId) + ret0, _ := ret[0].(v1api.ApiListInstancesRequest) + return ret0 +} + +// ListInstances indicates an expected call of ListInstances. +func (mr *MockDefaultAPIMockRecorder) ListInstances(ctx, projectId, regionId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListInstances", reflect.TypeOf((*MockDefaultAPI)(nil).ListInstances), ctx, projectId, regionId) +} + +// ListInstancesExecute mocks base method. +func (m *MockDefaultAPI) ListInstancesExecute(r v1api.ApiListInstancesRequest) (*v1api.ListInstanceResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListInstancesExecute", r) + ret0, _ := ret[0].(*v1api.ListInstanceResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListInstancesExecute indicates an expected call of ListInstancesExecute. +func (mr *MockDefaultAPIMockRecorder) ListInstancesExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListInstancesExecute", reflect.TypeOf((*MockDefaultAPI)(nil).ListInstancesExecute), r) +} + +// PartialUpdateInstance mocks base method. +func (m *MockDefaultAPI) PartialUpdateInstance(ctx context.Context, projectId, regionId, instanceId string) v1api.ApiPartialUpdateInstanceRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PartialUpdateInstance", ctx, projectId, regionId, instanceId) + ret0, _ := ret[0].(v1api.ApiPartialUpdateInstanceRequest) + return ret0 +} + +// PartialUpdateInstance indicates an expected call of PartialUpdateInstance. +func (mr *MockDefaultAPIMockRecorder) PartialUpdateInstance(ctx, projectId, regionId, instanceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PartialUpdateInstance", reflect.TypeOf((*MockDefaultAPI)(nil).PartialUpdateInstance), ctx, projectId, regionId, instanceId) +} + +// PartialUpdateInstanceExecute mocks base method. +func (m *MockDefaultAPI) PartialUpdateInstanceExecute(r v1api.ApiPartialUpdateInstanceRequest) (*v1api.PartialUpdateInstanceResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PartialUpdateInstanceExecute", r) + ret0, _ := ret[0].(*v1api.PartialUpdateInstanceResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PartialUpdateInstanceExecute indicates an expected call of PartialUpdateInstanceExecute. +func (mr *MockDefaultAPIMockRecorder) PartialUpdateInstanceExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PartialUpdateInstanceExecute", reflect.TypeOf((*MockDefaultAPI)(nil).PartialUpdateInstanceExecute), r) +} + +// PartialUpdateInstanceToken mocks base method. +func (m *MockDefaultAPI) PartialUpdateInstanceToken(ctx context.Context, projectId, regionId, tId, instanceId string) v1api.ApiPartialUpdateInstanceTokenRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PartialUpdateInstanceToken", ctx, projectId, regionId, tId, instanceId) + ret0, _ := ret[0].(v1api.ApiPartialUpdateInstanceTokenRequest) + return ret0 +} + +// PartialUpdateInstanceToken indicates an expected call of PartialUpdateInstanceToken. +func (mr *MockDefaultAPIMockRecorder) PartialUpdateInstanceToken(ctx, projectId, regionId, tId, instanceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PartialUpdateInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).PartialUpdateInstanceToken), ctx, projectId, regionId, tId, instanceId) +} + +// PartialUpdateInstanceTokenExecute mocks base method. +func (m *MockDefaultAPI) PartialUpdateInstanceTokenExecute(r v1api.ApiPartialUpdateInstanceTokenRequest) (*v1api.PartialUpdateTokenResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PartialUpdateInstanceTokenExecute", r) + ret0, _ := ret[0].(*v1api.PartialUpdateTokenResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PartialUpdateInstanceTokenExecute indicates an expected call of PartialUpdateInstanceTokenExecute. +func (mr *MockDefaultAPIMockRecorder) PartialUpdateInstanceTokenExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PartialUpdateInstanceTokenExecute", reflect.TypeOf((*MockDefaultAPI)(nil).PartialUpdateInstanceTokenExecute), r) +} diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go new file mode 100644 index 000000000..05e71d286 --- /dev/null +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -0,0 +1,7 @@ +package instance + +import "testing" + +func TestCreate_Success(t *testing.T) { + +} diff --git a/stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go b/stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go new file mode 100644 index 000000000..6d0fce345 --- /dev/null +++ b/stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go @@ -0,0 +1,156 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api (interfaces: DefaultAPI) +// +// Generated by this command: +// +// mockgen -destination=./mock/serviceenablement.go -package=mock_serviceenablement github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api DefaultAPI +// + +// Package mock_serviceenablement is a generated GoMock package. +package mock_serviceenablement + +import ( + context "context" + reflect "reflect" + + v2api "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" + gomock "go.uber.org/mock/gomock" +) + +// MockDefaultAPI is a mock of DefaultAPI interface. +type MockDefaultAPI struct { + ctrl *gomock.Controller + recorder *MockDefaultAPIMockRecorder + isgomock struct{} +} + +// MockDefaultAPIMockRecorder is the mock recorder for MockDefaultAPI. +type MockDefaultAPIMockRecorder struct { + mock *MockDefaultAPI +} + +// NewMockDefaultAPI creates a new mock instance. +func NewMockDefaultAPI(ctrl *gomock.Controller) *MockDefaultAPI { + mock := &MockDefaultAPI{ctrl: ctrl} + mock.recorder = &MockDefaultAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDefaultAPI) EXPECT() *MockDefaultAPIMockRecorder { + return m.recorder +} + +// DisableServiceRegional mocks base method. +func (m *MockDefaultAPI) DisableServiceRegional(ctx context.Context, region, projectId, serviceId string) v2api.ApiDisableServiceRegionalRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DisableServiceRegional", ctx, region, projectId, serviceId) + ret0, _ := ret[0].(v2api.ApiDisableServiceRegionalRequest) + return ret0 +} + +// DisableServiceRegional indicates an expected call of DisableServiceRegional. +func (mr *MockDefaultAPIMockRecorder) DisableServiceRegional(ctx, region, projectId, serviceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableServiceRegional", reflect.TypeOf((*MockDefaultAPI)(nil).DisableServiceRegional), ctx, region, projectId, serviceId) +} + +// DisableServiceRegionalExecute mocks base method. +func (m *MockDefaultAPI) DisableServiceRegionalExecute(r v2api.ApiDisableServiceRegionalRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DisableServiceRegionalExecute", r) + ret0, _ := ret[0].(error) + return ret0 +} + +// DisableServiceRegionalExecute indicates an expected call of DisableServiceRegionalExecute. +func (mr *MockDefaultAPIMockRecorder) DisableServiceRegionalExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableServiceRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).DisableServiceRegionalExecute), r) +} + +// EnableServiceRegional mocks base method. +func (m *MockDefaultAPI) EnableServiceRegional(ctx context.Context, region, projectId, serviceId string) v2api.ApiEnableServiceRegionalRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableServiceRegional", ctx, region, projectId, serviceId) + ret0, _ := ret[0].(v2api.ApiEnableServiceRegionalRequest) + return ret0 +} + +// EnableServiceRegional indicates an expected call of EnableServiceRegional. +func (mr *MockDefaultAPIMockRecorder) EnableServiceRegional(ctx, region, projectId, serviceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableServiceRegional", reflect.TypeOf((*MockDefaultAPI)(nil).EnableServiceRegional), ctx, region, projectId, serviceId) +} + +// EnableServiceRegionalExecute mocks base method. +func (m *MockDefaultAPI) EnableServiceRegionalExecute(r v2api.ApiEnableServiceRegionalRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableServiceRegionalExecute", r) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnableServiceRegionalExecute indicates an expected call of EnableServiceRegionalExecute. +func (mr *MockDefaultAPIMockRecorder) EnableServiceRegionalExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableServiceRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).EnableServiceRegionalExecute), r) +} + +// GetServiceStatusRegional mocks base method. +func (m *MockDefaultAPI) GetServiceStatusRegional(ctx context.Context, region, projectId, serviceId string) v2api.ApiGetServiceStatusRegionalRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServiceStatusRegional", ctx, region, projectId, serviceId) + ret0, _ := ret[0].(v2api.ApiGetServiceStatusRegionalRequest) + return ret0 +} + +// GetServiceStatusRegional indicates an expected call of GetServiceStatusRegional. +func (mr *MockDefaultAPIMockRecorder) GetServiceStatusRegional(ctx, region, projectId, serviceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceStatusRegional", reflect.TypeOf((*MockDefaultAPI)(nil).GetServiceStatusRegional), ctx, region, projectId, serviceId) +} + +// GetServiceStatusRegionalExecute mocks base method. +func (m *MockDefaultAPI) GetServiceStatusRegionalExecute(r v2api.ApiGetServiceStatusRegionalRequest) (*v2api.ServiceStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServiceStatusRegionalExecute", r) + ret0, _ := ret[0].(*v2api.ServiceStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServiceStatusRegionalExecute indicates an expected call of GetServiceStatusRegionalExecute. +func (mr *MockDefaultAPIMockRecorder) GetServiceStatusRegionalExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceStatusRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).GetServiceStatusRegionalExecute), r) +} + +// ListServiceStatusRegional mocks base method. +func (m *MockDefaultAPI) ListServiceStatusRegional(ctx context.Context, region, projectId string) v2api.ApiListServiceStatusRegionalRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListServiceStatusRegional", ctx, region, projectId) + ret0, _ := ret[0].(v2api.ApiListServiceStatusRegionalRequest) + return ret0 +} + +// ListServiceStatusRegional indicates an expected call of ListServiceStatusRegional. +func (mr *MockDefaultAPIMockRecorder) ListServiceStatusRegional(ctx, region, projectId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServiceStatusRegional", reflect.TypeOf((*MockDefaultAPI)(nil).ListServiceStatusRegional), ctx, region, projectId) +} + +// ListServiceStatusRegionalExecute mocks base method. +func (m *MockDefaultAPI) ListServiceStatusRegionalExecute(r v2api.ApiListServiceStatusRegionalRequest) (*v2api.ListServiceStatusRegional200Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListServiceStatusRegionalExecute", r) + ret0, _ := ret[0].(*v2api.ListServiceStatusRegional200Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListServiceStatusRegionalExecute indicates an expected call of ListServiceStatusRegionalExecute. +func (mr *MockDefaultAPIMockRecorder) ListServiceStatusRegionalExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServiceStatusRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).ListServiceStatusRegionalExecute), r) +} diff --git a/stackit/internal/services/modelexperiments/utils/test_utils.go b/stackit/internal/services/modelexperiments/utils/test_utils.go new file mode 100644 index 000000000..7df3f59dc --- /dev/null +++ b/stackit/internal/services/modelexperiments/utils/test_utils.go @@ -0,0 +1,21 @@ +package utils + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/resource" + mock_instance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance/mock" + mock_serviceenablement "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils/mock" + "go.uber.org/mock/gomock" +) + +type TestContext struct { + T *testing.T + MockCtrl *gomock.Controller + MockInstanceCLient *mock_instance.MockDefaultAPI + MockServiceEnablementClient *mock_serviceenablement.MockDefaultAPI + Resource *resource.Resource + Ctx context.Context + CancelFunc context.CancelFunc +} diff --git a/stackit/internal/services/modelexperiments/utils/util.go b/stackit/internal/services/modelexperiments/utils/util.go index 5ccf32215..3431a9a96 100644 --- a/stackit/internal/services/modelexperiments/utils/util.go +++ b/stackit/internal/services/modelexperiments/utils/util.go @@ -28,7 +28,7 @@ const ( TOKENSTATE_INACTIVE = "inactive" ) -func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *modelexperiment.APIClient { +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) modelexperiment.DefaultAPI { apiClientConfigOptions := []config.ConfigurationOption{ config.WithCustomAuth(providerData.RoundTripper), utils.UserAgentConfigOption(providerData.Version), @@ -42,10 +42,11 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags return nil } - return apiClient + return apiClient.DefaultAPI } -func ConfigureServiceEnablementClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *serviceenablement.APIClient { +//go:generate mockgen -destination=./mock/serviceenablement.go -package=mock_serviceenablement github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api DefaultAPI +func ConfigureServiceEnablementClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) serviceenablement.DefaultAPI { apiClientConfigOptions := []config.ConfigurationOption{ config.WithCustomAuth(providerData.RoundTripper), utils.UserAgentConfigOption(providerData.Version), @@ -61,5 +62,5 @@ func ConfigureServiceEnablementClient(ctx context.Context, providerData *core.Pr return nil } - return apiClient + return apiClient.DefaultAPI } From 0fb28eec6b6a9f6556861043db034a7755c6bd7f Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Tue, 12 May 2026 17:37:34 +0200 Subject: [PATCH 17/30] Add unit test --- go.mod | 12 +- go.sum | 26 +- .../modelexperiments/instance/resource.go | 30 +- .../instance/resource_create_test.go | 359 +++++++++++++++++- .../instance/resource_suite_test.go | 15 + .../modelexperiments/testutils/test_utils.go | 40 ++ .../modelexperiments/token/resource.go | 10 +- .../modelexperiments/utils/test_utils.go | 21 - stackit/provider.go | 4 +- 9 files changed, 472 insertions(+), 45 deletions(-) create mode 100644 stackit/internal/services/modelexperiments/instance/resource_suite_test.go create mode 100644 stackit/internal/services/modelexperiments/testutils/test_utils.go delete mode 100644 stackit/internal/services/modelexperiments/utils/test_utils.go diff --git a/go.mod b/go.mod index 7861ae496..85ca51139 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/hashicorp/terraform-plugin-go v0.31.0 github.com/hashicorp/terraform-plugin-log v0.10.0 github.com/hashicorp/terraform-plugin-testing v1.16.0 + github.com/onsi/gomega v1.40.0 github.com/stackitcloud/stackit-sdk-go/core v0.26.0 github.com/stackitcloud/stackit-sdk-go/services/alb v0.15.0 github.com/stackitcloud/stackit-sdk-go/services/cdn v1.18.0 @@ -52,7 +53,16 @@ require ( golang.org/x/mod v0.37.0 ) -require go.uber.org/mock v0.6.0 // indirect +require ( + github.com/onsi/ginkgo/v2 v2.28.1 + go.uber.org/mock v0.6.0 +) + +require ( + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect +) require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect diff --git a/go.sum b/go.sum index c73d31674..a98f724d9 100644 --- a/go.sum +++ b/go.sum @@ -205,6 +205,12 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -258,6 +264,8 @@ github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUW github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM= github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= @@ -444,6 +452,8 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -508,6 +518,8 @@ github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6 github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= @@ -524,6 +536,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -561,8 +575,8 @@ github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= +github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -766,6 +780,14 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg= github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index a7b8ae0f4..0b75f6a5c 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -56,10 +56,18 @@ type Model struct { // NewInstanceResource is a helper function to simplify the provider implementation. // //go:generate mockgen -destination=./mock/instance.go -package=mock_instance dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api DefaultAPI -func NewInstanceResource() resource.Resource { +func NewInstanceResourceEmpty() resource.Resource { return &instanceResource{} } +func NewInstanceResource(client modelexperiments.DefaultAPI, serviceClient serviceenablement.DefaultAPI, providerData core.ProviderData) resource.Resource { + return &instanceResource{ + client: client, + providerData: providerData, + serviceEnablementClient: serviceClient, + } +} + // instanceResource is the resource implementation. type instanceResource struct { client modelexperiments.DefaultAPI @@ -235,8 +243,9 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) - err := i.serviceEnablementClient.EnableServiceRegional(ctx, region, projectId, utils.ModelExperimentsServiceId). - Execute() + serviceEnablementReq := i.serviceEnablementClient.EnableServiceRegional(ctx, region, projectId, utils.ModelExperimentsServiceId) + err := i.serviceEnablementClient.EnableServiceRegionalExecute(serviceEnablementReq) + if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -274,7 +283,8 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - createInstanceResp, err := i.client.CreateInstance(ctx, projectId, region).CreateInstancePayload(*payload).Execute() + createInstanceRequest := i.client.CreateInstance(ctx, projectId, region).CreateInstancePayload(*payload) + createInstanceResp, err := i.client.CreateInstanceExecute(createInstanceRequest) if err != nil { core.LogAndAddError( ctx, @@ -305,15 +315,6 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques waitResp, err := CreateMExpInstanceWaitHandler(ctx, i.client, region, projectId, instanceId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Waiting for instance to be active: %v", err)) - - err = mapCreateResponse(ctx, createInstanceResp, waitResp, &model, region) - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) - } - diags = resp.State.Set(ctx, model) - resp.Diagnostics.Append(diags...) - - return } // Map response body to schema @@ -617,7 +618,8 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstancePaylo func CreateMExpInstanceWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - getInstanceResp, err := a.GetInstance(ctx, projectId, region, instanceId).Execute() + getInstanceRequest := a.GetInstance(ctx, projectId, region, instanceId) + getInstanceResp, err := a.GetInstanceExecute(getInstanceRequest) if err != nil { return false, nil, err } diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index 05e71d286..143456db3 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -1,7 +1,358 @@ -package instance +package instance_test -import "testing" +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" -func TestCreate_Success(t *testing.T) { + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" -} + "github.com/stackitcloud/stackit-sdk-go/core/config" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" + mock_instance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance/mock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "go.uber.org/mock/gomock" +) + +var _ = Describe("STACKIT ModelExperiments Resource", func() { + const ( + modelServingServiceId = "cloud.stackit.model-serving" + ) + + var ( + ctx context.Context + ctrl *gomock.Controller + mockInstanceCLient *mock_instance.MockDefaultAPI + + projectId uuid.UUID + instanceName string + description string + region string + instanceId uuid.UUID + ) + + BeforeEach(func() { + ctx = context.Background() + ctrl = gomock.NewController(GinkgoT()) + mockInstanceCLient = mock_instance.NewMockDefaultAPI(ctrl) + projectId = uuid.New() + instanceName = "test" + description = "description" + region = "eu01" + instanceId = uuid.New() + }) + + Describe("Instance resource", func() { + When("everything works", func() { + It("should create instance state correct", func() { + + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { + if r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) + } + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusAccepted) + } + } + }, + ), + ) + defer server.Close() + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + Version: "1.0.0", + ServiceEnablementCustomEndpoint: server.URL, + } + + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithoutAuthentication(), + config.WithHTTPClient(server.Client()), + utils.UserAgentConfigOption(providerData.Version), + config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + } + + apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) + if err != nil { + fmt.Println(err) + } + + instanceRes := instance.NewInstanceResource(mockInstanceCLient, apiClient.DefaultAPI, providerData) + + url := "url" + + createResp := &modelexperiments.CreateInstanceResponse{ + Instance: modelexperiments.Instance{ + DeletedExperimentRetention: new("1m"), + Description: &description, + Name: instanceName, + Region: new("eu01"), + Url: url, + Id: instanceId.String(), + State: "pending", + }, + } + mockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) + mockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(createResp, nil) + + getResp := &modelexperiments.GetInstanceResponse{ + Instance: modelexperiments.Instance{ + DeletedExperimentRetention: new("1m"), + Description: &description, + Name: instanceName, + Region: new("eu01"), + Url: url, + Id: instanceId.String(), + State: "active", + BucketName: new("bucket"), + }, + } + mockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + mockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(getResp, nil) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(ctx, resource.SchemaRequest{}, &schemaResp) + + model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) + req := testutils.CreateInstanceRequest(ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + instanceRes.Create(ctx, req, resp) + + Expect(resp.Diagnostics.HasError()).To(BeFalse()) + + var stateAfterCreate instance.Model + diags := resp.State.Get(ctx, &stateAfterCreate) + Expect(diags.HasError()).To(BeFalse()) + + Expect(stateAfterCreate.InstanceId.ValueString()).To(Equal(instanceId.String())) + Expect(stateAfterCreate.State.ValueString()).To(Equal("active")) + Expect(stateAfterCreate.Url.ValueString()).To(Equal(url)) + Expect(stateAfterCreate.ProjectId.ValueString()).To(Equal(projectId.String())) + Expect(stateAfterCreate.Region.ValueString()).To(Equal(region)) + Expect(stateAfterCreate.Name.ValueString()).To(Equal(instanceName)) + Expect(stateAfterCreate.Id).To(Equal(utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()))) + }) + }) + + When("service enablement not working", func() { + It("should not create state and throw error", func() { + + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { + if r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + } + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusNotFound) + } + } + }, + ), + ) + defer server.Close() + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + Version: "1.0.0", + ServiceEnablementCustomEndpoint: server.URL, + } + + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithoutAuthentication(), + config.WithHTTPClient(server.Client()), + utils.UserAgentConfigOption(providerData.Version), + config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + } + + apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) + if err != nil { + fmt.Println(err) + } + + instanceRes := instance.NewInstanceResource(mockInstanceCLient, apiClient.DefaultAPI, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(ctx, resource.SchemaRequest{}, &schemaResp) + + model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) + req := testutils.CreateInstanceRequest(ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + instanceRes.Create(ctx, req, resp) + + Expect(resp.Diagnostics.HasError()).To(BeTrue()) + + var stateAfterCreate *instance.Model + diags := resp.State.Get(ctx, &stateAfterCreate) + Expect(diags.HasError()).To(BeFalse()) + Expect(stateAfterCreate).To(BeNil()) + }) + }) + + When("instance not found after creation", func() { + It("should create instance state correct", func() { + + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { + if r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) + } + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusAccepted) + } + } + }, + ), + ) + defer server.Close() + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + Version: "1.0.0", + ServiceEnablementCustomEndpoint: server.URL, + } + + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithoutAuthentication(), + config.WithHTTPClient(server.Client()), + utils.UserAgentConfigOption(providerData.Version), + config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + } + + apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) + if err != nil { + fmt.Println(err) + } + + instanceRes := instance.NewInstanceResource(mockInstanceCLient, apiClient.DefaultAPI, providerData) + + url := "url" + + createResp := &modelexperiments.CreateInstanceResponse{ + Instance: modelexperiments.Instance{ + DeletedExperimentRetention: new("1m"), + Description: &description, + Name: instanceName, + Region: new("eu01"), + Url: url, + Id: instanceId.String(), + State: "pending", + }, + } + mockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) + mockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(createResp, nil) + + mockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + mockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(ctx, resource.SchemaRequest{}, &schemaResp) + + model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) + req := testutils.CreateInstanceRequest(ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + instanceRes.Create(ctx, req, resp) + + Expect(resp.Diagnostics.HasError()).To(BeTrue()) + + var stateAfterCreate instance.Model + diags := resp.State.Get(ctx, &stateAfterCreate) + Expect(diags.HasError()).To(BeFalse()) + + Expect(stateAfterCreate.InstanceId.ValueString()).To(Equal(instanceId.String())) + Expect(stateAfterCreate.State.ValueString()).To(Equal("unknown")) + Expect(stateAfterCreate.Url.ValueString()).To(Equal(url)) + Expect(stateAfterCreate.ProjectId.ValueString()).To(Equal(projectId.String())) + Expect(stateAfterCreate.Region.ValueString()).To(Equal(region)) + Expect(stateAfterCreate.Name.ValueString()).To(Equal(instanceName)) + Expect(stateAfterCreate.Id).To(Equal(utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()))) + Expect(stateAfterCreate.BucketName.ValueString()).To(Equal("")) + }) + }) + + When("instance creation not working", func() { + It("should not create state", func() { + + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { + if r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) + } + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusAccepted) + } + } + }, + ), + ) + defer server.Close() + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + Version: "1.0.0", + ServiceEnablementCustomEndpoint: server.URL, + } + + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithoutAuthentication(), + config.WithHTTPClient(server.Client()), + utils.UserAgentConfigOption(providerData.Version), + config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + } + + apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) + if err != nil { + fmt.Println(err) + } + + instanceRes := instance.NewInstanceResource(mockInstanceCLient, apiClient.DefaultAPI, providerData) + + mockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) + mockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(ctx, resource.SchemaRequest{}, &schemaResp) + + model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) + req := testutils.CreateInstanceRequest(ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + instanceRes.Create(ctx, req, resp) + + Expect(resp.Diagnostics.HasError()).To(BeTrue()) + + var stateAfterCreate *instance.Model + diags := resp.State.Get(ctx, &stateAfterCreate) + Expect(diags.HasError()).To(BeFalse()) + Expect(stateAfterCreate).To(BeNil()) + }) + }) + }) +}) diff --git a/stackit/internal/services/modelexperiments/instance/resource_suite_test.go b/stackit/internal/services/modelexperiments/instance/resource_suite_test.go new file mode 100644 index 000000000..ef7751cfb --- /dev/null +++ b/stackit/internal/services/modelexperiments/instance/resource_suite_test.go @@ -0,0 +1,15 @@ +package instance_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestObjectStorage(t *testing.T) { + t.Parallel() + + RegisterFailHandler(Fail) + RunSpecs(t, "STACKIT Adapter - Object Storage Suite") +} diff --git a/stackit/internal/services/modelexperiments/testutils/test_utils.go b/stackit/internal/services/modelexperiments/testutils/test_utils.go new file mode 100644 index 000000000..521bab1b8 --- /dev/null +++ b/stackit/internal/services/modelexperiments/testutils/test_utils.go @@ -0,0 +1,40 @@ +package testutils + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" +) + +func CreateInstanceTestModel(projectId, region, name, description string) instance.Model { + return instance.Model{ + ProjectId: types.StringValue(projectId), + Region: types.StringValue(region), + Name: types.StringValue(name), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + } +} + +func CreateInstanceRequest(ctx context.Context, schema resource.SchemaResponse, model instance.Model) resource.CreateRequest { + req := resource.CreateRequest{} + req.Plan = tfsdk.Plan{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.Plan.Set(ctx, model) + return req +} + +func CreateResponse(schema resource.SchemaResponse) *resource.CreateResponse { + resp := &resource.CreateResponse{} + resp.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + return resp +} diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index e29022f6d..93caa0c87 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -53,10 +53,18 @@ type Model struct { } // NewInstanceTokenResource is a helper function to simplify the provider implementation. -func NewInstanceTokenResource() resource.Resource { +func NewInstanceTokenResourceEmpty() resource.Resource { return &tokenResource{} } +func NewInstanceTokenResource(client modelexperiments.DefaultAPI, serviceEnablementClient serviceenablement.DefaultAPI, providerData core.ProviderData) resource.Resource { + return &tokenResource{ + client: client, + providerData: providerData, + serviceEnablementClient: serviceEnablementClient, + } +} + // tokenResource is the resource implementation. type tokenResource struct { client modelexperiments.DefaultAPI diff --git a/stackit/internal/services/modelexperiments/utils/test_utils.go b/stackit/internal/services/modelexperiments/utils/test_utils.go deleted file mode 100644 index 7df3f59dc..000000000 --- a/stackit/internal/services/modelexperiments/utils/test_utils.go +++ /dev/null @@ -1,21 +0,0 @@ -package utils - -import ( - "context" - "testing" - - "github.com/hashicorp/terraform-plugin-framework/resource" - mock_instance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance/mock" - mock_serviceenablement "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils/mock" - "go.uber.org/mock/gomock" -) - -type TestContext struct { - T *testing.T - MockCtrl *gomock.Controller - MockInstanceCLient *mock_instance.MockDefaultAPI - MockServiceEnablementClient *mock_serviceenablement.MockDefaultAPI - Resource *resource.Resource - Ctx context.Context - CancelFunc context.CancelFunc -} diff --git a/stackit/provider.go b/stackit/provider.go index c49d44d07..047bff2d6 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -824,8 +824,8 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { mariaDBInstance.NewInstanceResource, mariaDBCredential.NewCredentialResource, modelServingToken.NewTokenResource, - modelExperimentsInstance.NewInstanceResource, - modelExperimentsToken.NewInstanceTokenResource, + modelExperimentsInstance.NewInstanceResourceEmpty, + modelExperimentsToken.NewInstanceTokenResourceEmpty, mongoDBFlexInstance.NewInstanceResource, mongoDBFlexUser.NewUserResource, objectStorageBucket.NewBucketResource, From fa1c6ac8b1a8c51ea5f68fff61785013aec3a5c5 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Tue, 12 May 2026 21:04:30 +0200 Subject: [PATCH 18/30] Remove ginkgo testing framework --- go.mod | 14 +- go.sum | 22 - .../modelexperiments/instance/resource.go | 6 +- .../instance/resource_create_test.go | 632 +++++++++--------- .../instance/resource_read_test.go | 196 ++++++ .../instance/resource_suite_test.go | 15 - .../modelexperiments/testutils/test_utils.go | 100 +++ 7 files changed, 612 insertions(+), 373 deletions(-) create mode 100644 stackit/internal/services/modelexperiments/instance/resource_read_test.go delete mode 100644 stackit/internal/services/modelexperiments/instance/resource_suite_test.go diff --git a/go.mod b/go.mod index 85ca51139..8b93adfb1 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/hashicorp/terraform-plugin-go v0.31.0 github.com/hashicorp/terraform-plugin-log v0.10.0 github.com/hashicorp/terraform-plugin-testing v1.16.0 - github.com/onsi/gomega v1.40.0 github.com/stackitcloud/stackit-sdk-go/core v0.26.0 github.com/stackitcloud/stackit-sdk-go/services/alb v0.15.0 github.com/stackitcloud/stackit-sdk-go/services/cdn v1.18.0 @@ -53,16 +52,7 @@ require ( golang.org/x/mod v0.37.0 ) -require ( - github.com/onsi/ginkgo/v2 v2.28.1 - go.uber.org/mock v0.6.0 -) - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect -) +require go.uber.org/mock v0.6.0 require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect @@ -230,7 +220,7 @@ require ( github.com/ssgreg/nlreturn/v2 v2.2.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 + github.com/stretchr/testify v1.11.1 github.com/subosito/gotenv v1.4.1 // indirect github.com/tetafro/godot v1.5.4 // indirect github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect diff --git a/go.sum b/go.sum index a98f724d9..7f281eb2f 100644 --- a/go.sum +++ b/go.sum @@ -205,12 +205,6 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= -github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= -github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= -github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= -github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= -github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= -github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -264,8 +258,6 @@ github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUW github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM= github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= @@ -452,8 +444,6 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= -github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= -github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -518,8 +508,6 @@ github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6 github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= -github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= -github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= @@ -536,8 +524,6 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= -github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -780,14 +766,6 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg= github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 0b75f6a5c..33bd05a90 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -358,7 +358,8 @@ func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - getInstanceResp, err := i.client.GetInstance(ctx, projectId, region, instanceId).Execute() + getInstanceReq := i.client.GetInstance(ctx, projectId, region, instanceId) + getInstanceResp, err := i.client.GetInstanceExecute(getInstanceReq) if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -424,7 +425,8 @@ func (i *instanceResource) Update(ctx context.Context, req resource.UpdateReques return } - updateInstanceResp, err := i.client.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload).Execute() + updateInstanceReq := i.client.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload) + updateInstanceResp, err := i.client.PartialUpdateInstanceExecute(updateInstanceReq) if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index 143456db3..01bf1c6b1 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -1,15 +1,13 @@ package instance_test import ( - "context" "fmt" "net/http" "net/http/httptest" + "testing" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" "github.com/stackitcloud/stackit-sdk-go/core/config" @@ -17,342 +15,332 @@ import ( serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" - mock_instance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance/mock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) -var _ = Describe("STACKIT ModelExperiments Resource", func() { - const ( - modelServingServiceId = "cloud.stackit.model-serving" - ) - - var ( - ctx context.Context - ctrl *gomock.Controller - mockInstanceCLient *mock_instance.MockDefaultAPI - - projectId uuid.UUID - instanceName string - description string - region string - instanceId uuid.UUID - ) - - BeforeEach(func() { - ctx = context.Background() - ctrl = gomock.NewController(GinkgoT()) - mockInstanceCLient = mock_instance.NewMockDefaultAPI(ctrl) - projectId = uuid.New() - instanceName = "test" - description = "description" - region = "eu01" - instanceId = uuid.New() - }) - - Describe("Instance resource", func() { - When("everything works", func() { - It("should create instance state correct", func() { - - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) - } - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusAccepted) - } - } - }, - ), - ) - defer server.Close() - - providerData := core.ProviderData{ - DefaultRegion: "eu01", - Version: "1.0.0", - ServiceEnablementCustomEndpoint: server.URL, - } - - apiClientConfigOptions := []config.ConfigurationOption{ - config.WithoutAuthentication(), - config.WithHTTPClient(server.Client()), - utils.UserAgentConfigOption(providerData.Version), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), - } - - apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) - if err != nil { - fmt.Println(err) - } - - instanceRes := instance.NewInstanceResource(mockInstanceCLient, apiClient.DefaultAPI, providerData) - - url := "url" - - createResp := &modelexperiments.CreateInstanceResponse{ - Instance: modelexperiments.Instance{ - DeletedExperimentRetention: new("1m"), - Description: &description, - Name: instanceName, - Region: new("eu01"), - Url: url, - Id: instanceId.String(), - State: "pending", - }, - } - mockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) - mockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(createResp, nil) - - getResp := &modelexperiments.GetInstanceResponse{ - Instance: modelexperiments.Instance{ - DeletedExperimentRetention: new("1m"), - Description: &description, - Name: instanceName, - Region: new("eu01"), - Url: url, - Id: instanceId.String(), - State: "active", - BucketName: new("bucket"), - }, - } - mockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) - mockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(getResp, nil) - - schemaResp := resource.SchemaResponse{} - instanceRes.Schema(ctx, resource.SchemaRequest{}, &schemaResp) - - model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) - req := testutils.CreateInstanceRequest(ctx, schemaResp, model) - resp := testutils.CreateResponse(schemaResp) - - instanceRes.Create(ctx, req, resp) - - Expect(resp.Diagnostics.HasError()).To(BeFalse()) - - var stateAfterCreate instance.Model - diags := resp.State.Get(ctx, &stateAfterCreate) - Expect(diags.HasError()).To(BeFalse()) - - Expect(stateAfterCreate.InstanceId.ValueString()).To(Equal(instanceId.String())) - Expect(stateAfterCreate.State.ValueString()).To(Equal("active")) - Expect(stateAfterCreate.Url.ValueString()).To(Equal(url)) - Expect(stateAfterCreate.ProjectId.ValueString()).To(Equal(projectId.String())) - Expect(stateAfterCreate.Region.ValueString()).To(Equal(region)) - Expect(stateAfterCreate.Name.ValueString()).To(Equal(instanceName)) - Expect(stateAfterCreate.Id).To(Equal(utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()))) - }) - }) - - When("service enablement not working", func() { - It("should not create state and throw error", func() { - - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusNotFound) - } - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusNotFound) - } - } - }, - ), - ) - defer server.Close() - - providerData := core.ProviderData{ - DefaultRegion: "eu01", - Version: "1.0.0", - ServiceEnablementCustomEndpoint: server.URL, - } - - apiClientConfigOptions := []config.ConfigurationOption{ - config.WithoutAuthentication(), - config.WithHTTPClient(server.Client()), - utils.UserAgentConfigOption(providerData.Version), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), - } +const ( + modelServingServiceId = "cloud.stackit.model-serving" +) - apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) - if err != nil { - fmt.Println(err) +func TestCreate_Success(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + description := "description" + region := "eu01" + instanceId := uuid.New() + + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { + if r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) + } + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusAccepted) + } } - - instanceRes := instance.NewInstanceResource(mockInstanceCLient, apiClient.DefaultAPI, providerData) - - schemaResp := resource.SchemaResponse{} - instanceRes.Schema(ctx, resource.SchemaRequest{}, &schemaResp) - - model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) - req := testutils.CreateInstanceRequest(ctx, schemaResp, model) - resp := testutils.CreateResponse(schemaResp) - - instanceRes.Create(ctx, req, resp) - - Expect(resp.Diagnostics.HasError()).To(BeTrue()) - - var stateAfterCreate *instance.Model - diags := resp.State.Get(ctx, &stateAfterCreate) - Expect(diags.HasError()).To(BeFalse()) - Expect(stateAfterCreate).To(BeNil()) - }) - }) - - When("instance not found after creation", func() { - It("should create instance state correct", func() { - - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) - } - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusAccepted) - } - } - }, - ), - ) - defer server.Close() - - providerData := core.ProviderData{ - DefaultRegion: "eu01", - Version: "1.0.0", - ServiceEnablementCustomEndpoint: server.URL, + }, + ), + ) + defer server.Close() + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + Version: "1.0.0", + ServiceEnablementCustomEndpoint: server.URL, + } + + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithoutAuthentication(), + config.WithHTTPClient(server.Client()), + utils.UserAgentConfigOption(providerData.Version), + config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + } + + apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) + if err != nil { + fmt.Println(err) + } + + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, apiClient.DefaultAPI, providerData) + + url := "url" + + createResp := &modelexperiments.CreateInstanceResponse{ + Instance: modelexperiments.Instance{ + DeletedExperimentRetention: new("1m"), + Description: &description, + Name: instanceName, + Region: new("eu01"), + Url: url, + Id: instanceId.String(), + State: "pending", + }, + } + tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(createResp, nil) + + getResp := &modelexperiments.GetInstanceResponse{ + Instance: modelexperiments.Instance{ + DeletedExperimentRetention: new("1m"), + Description: &description, + Name: instanceName, + Region: new("eu01"), + Url: url, + Id: instanceId.String(), + State: "active", + BucketName: new("bucket"), + }, + } + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(getResp, nil) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) + req := testutils.CreateInstanceRequest(tc.Ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + instanceRes.Create(tc.Ctx, req, resp) + + require.False(t, resp.Diagnostics.HasError(), "Create should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + var stateAfterCreate instance.Model + diags := resp.State.Get(tc.Ctx, &stateAfterCreate) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + + require.Equal(t, instanceId.String(), stateAfterCreate.InstanceId.ValueString()) + require.Equal(t, projectId.String(), stateAfterCreate.ProjectId.ValueString()) + require.Equal(t, instanceName, stateAfterCreate.Name.ValueString()) + require.Equal(t, url, stateAfterCreate.Url.ValueString()) + require.Equal(t, "active", stateAfterCreate.State.ValueString()) + require.Equal(t, region, stateAfterCreate.Region.ValueString()) + require.Equal(t, "bucket", stateAfterCreate.BucketName.ValueString()) +} + +func TestCreate_ServiceEnablementFailure(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + description := "description" + region := "eu01" + + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { + if r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + } + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusNotFound) + } } - - apiClientConfigOptions := []config.ConfigurationOption{ - config.WithoutAuthentication(), - config.WithHTTPClient(server.Client()), - utils.UserAgentConfigOption(providerData.Version), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + }, + ), + ) + defer server.Close() + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + Version: "1.0.0", + ServiceEnablementCustomEndpoint: server.URL, + } + + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithoutAuthentication(), + config.WithHTTPClient(server.Client()), + utils.UserAgentConfigOption(providerData.Version), + config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + } + + apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) + if err != nil { + fmt.Println(err) + } + + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, apiClient.DefaultAPI, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) + req := testutils.CreateInstanceRequest(tc.Ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + instanceRes.Create(tc.Ctx, req, resp) + + require.True(t, resp.Diagnostics.HasError(), "Create should not succeed, but got no errors") + + var stateAfterCreate *instance.Model + diags := resp.State.Get(tc.Ctx, &stateAfterCreate) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + require.Nil(t, stateAfterCreate, "State not nil") +} + +func TestCreate_GetInstanceFailure(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + description := "description" + region := "eu01" + instanceId := uuid.New() + + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { + if r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) + } + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusAccepted) + } } - - apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) - if err != nil { - fmt.Println(err) + }, + ), + ) + defer server.Close() + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + Version: "1.0.0", + ServiceEnablementCustomEndpoint: server.URL, + } + + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithoutAuthentication(), + config.WithHTTPClient(server.Client()), + utils.UserAgentConfigOption(providerData.Version), + config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + } + + apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) + if err != nil { + fmt.Println(err) + } + + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, apiClient.DefaultAPI, providerData) + + url := "url" + + createResp := &modelexperiments.CreateInstanceResponse{ + Instance: modelexperiments.Instance{ + DeletedExperimentRetention: new("1m"), + Description: &description, + Name: instanceName, + Region: new("eu01"), + Url: url, + Id: instanceId.String(), + State: "pending", + }, + } + tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(createResp, nil) + + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) + req := testutils.CreateInstanceRequest(tc.Ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + instanceRes.Create(tc.Ctx, req, resp) + + require.True(t, resp.Diagnostics.HasError(), "Create should succeed with errors") + + var stateAfterCreate instance.Model + diags := resp.State.Get(tc.Ctx, &stateAfterCreate) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + + require.Equal(t, instanceId.String(), stateAfterCreate.InstanceId.ValueString()) + require.Equal(t, projectId.String(), stateAfterCreate.ProjectId.ValueString()) + require.Equal(t, instanceName, stateAfterCreate.Name.ValueString()) + require.Equal(t, url, stateAfterCreate.Url.ValueString()) + require.Equal(t, "unknown", stateAfterCreate.State.ValueString()) + require.Equal(t, region, stateAfterCreate.Region.ValueString()) + require.Equal(t, "", stateAfterCreate.BucketName.ValueString()) +} + +func TestCreate_InstanceCreateFailure(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + description := "description" + region := "eu01" + + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { + if r.Method == http.MethodGet { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) + } + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusAccepted) + } } + }, + ), + ) + defer server.Close() - instanceRes := instance.NewInstanceResource(mockInstanceCLient, apiClient.DefaultAPI, providerData) - - url := "url" - - createResp := &modelexperiments.CreateInstanceResponse{ - Instance: modelexperiments.Instance{ - DeletedExperimentRetention: new("1m"), - Description: &description, - Name: instanceName, - Region: new("eu01"), - Url: url, - Id: instanceId.String(), - State: "pending", - }, - } - mockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) - mockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(createResp, nil) - - mockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) - mockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) - - schemaResp := resource.SchemaResponse{} - instanceRes.Schema(ctx, resource.SchemaRequest{}, &schemaResp) - - model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) - req := testutils.CreateInstanceRequest(ctx, schemaResp, model) - resp := testutils.CreateResponse(schemaResp) - - instanceRes.Create(ctx, req, resp) - - Expect(resp.Diagnostics.HasError()).To(BeTrue()) - - var stateAfterCreate instance.Model - diags := resp.State.Get(ctx, &stateAfterCreate) - Expect(diags.HasError()).To(BeFalse()) - - Expect(stateAfterCreate.InstanceId.ValueString()).To(Equal(instanceId.String())) - Expect(stateAfterCreate.State.ValueString()).To(Equal("unknown")) - Expect(stateAfterCreate.Url.ValueString()).To(Equal(url)) - Expect(stateAfterCreate.ProjectId.ValueString()).To(Equal(projectId.String())) - Expect(stateAfterCreate.Region.ValueString()).To(Equal(region)) - Expect(stateAfterCreate.Name.ValueString()).To(Equal(instanceName)) - Expect(stateAfterCreate.Id).To(Equal(utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()))) - Expect(stateAfterCreate.BucketName.ValueString()).To(Equal("")) - }) - }) - - When("instance creation not working", func() { - It("should not create state", func() { - - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) - } - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusAccepted) - } - } - }, - ), - ) - defer server.Close() - - providerData := core.ProviderData{ - DefaultRegion: "eu01", - Version: "1.0.0", - ServiceEnablementCustomEndpoint: server.URL, - } + providerData := core.ProviderData{ + DefaultRegion: "eu01", + Version: "1.0.0", + ServiceEnablementCustomEndpoint: server.URL, + } - apiClientConfigOptions := []config.ConfigurationOption{ - config.WithoutAuthentication(), - config.WithHTTPClient(server.Client()), - utils.UserAgentConfigOption(providerData.Version), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), - } + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithoutAuthentication(), + config.WithHTTPClient(server.Client()), + utils.UserAgentConfigOption(providerData.Version), + config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + } - apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) - if err != nil { - fmt.Println(err) - } + apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) + if err != nil { + fmt.Println(err) + } - instanceRes := instance.NewInstanceResource(mockInstanceCLient, apiClient.DefaultAPI, providerData) + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, apiClient.DefaultAPI, providerData) - mockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) - mockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) + tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) - schemaResp := resource.SchemaResponse{} - instanceRes.Schema(ctx, resource.SchemaRequest{}, &schemaResp) + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) - model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) - req := testutils.CreateInstanceRequest(ctx, schemaResp, model) - resp := testutils.CreateResponse(schemaResp) + model := testutils.CreateInstanceTestModel(projectId.String(), region, instanceName, description) + req := testutils.CreateInstanceRequest(tc.Ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) - instanceRes.Create(ctx, req, resp) + instanceRes.Create(tc.Ctx, req, resp) - Expect(resp.Diagnostics.HasError()).To(BeTrue()) + require.True(t, resp.Diagnostics.HasError(), "Create should not succeed, but got no errors") - var stateAfterCreate *instance.Model - diags := resp.State.Get(ctx, &stateAfterCreate) - Expect(diags.HasError()).To(BeFalse()) - Expect(stateAfterCreate).To(BeNil()) - }) - }) - }) -}) + var stateAfterCreate *instance.Model + diags := resp.State.Get(tc.Ctx, &stateAfterCreate) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + require.Nil(t, stateAfterCreate, "State not nil") +} diff --git a/stackit/internal/services/modelexperiments/instance/resource_read_test.go b/stackit/internal/services/modelexperiments/instance/resource_read_test.go new file mode 100644 index 000000000..036e537d9 --- /dev/null +++ b/stackit/internal/services/modelexperiments/instance/resource_read_test.go @@ -0,0 +1,196 @@ +package instance_test + +import ( + "testing" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestRead_Success(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + description := "description" + region := "eu01" + instanceId := uuid.New() + url := "url" + instanceNameUpdated := "updatedName" + + getResp := &modelexperiments.GetInstanceResponse{ + Instance: modelexperiments.Instance{ + DeletedExperimentRetention: new("1m"), + Description: &description, + Name: instanceNameUpdated, + Region: new("eu01"), + Url: url, + Id: instanceId.String(), + State: "active", + BucketName: new("bucket"), + }, + } + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(getResp, nil) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := instance.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(instanceName), + Labels: types.MapNull(types.StringType), + } + + req := testutils.ReadRequest(tc.Ctx, schemaResp, state) + resp := testutils.ReadResponse(schemaResp) + + instanceRes.Read(tc.Ctx, req, resp) + + require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + var refreshedState instance.Model + diags := resp.State.Get(tc.Ctx, &refreshedState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + + require.Equal(t, instanceId.String(), refreshedState.InstanceId.ValueString()) + require.Equal(t, projectId.String(), refreshedState.ProjectId.ValueString()) + require.Equal(t, instanceNameUpdated, refreshedState.Name.ValueString()) + require.Equal(t, url, refreshedState.Url.ValueString()) + require.Equal(t, "active", refreshedState.State.ValueString()) + require.Equal(t, region, refreshedState.Region.ValueString()) + require.Equal(t, "bucket", refreshedState.BucketName.ValueString()) +} + +func TestRead_InstanceIdEmptyFailure(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + region := "eu01" + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := instance.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(""), + Region: types.StringValue(region), + Name: types.StringValue(instanceName), + } + + req := testutils.ReadRequest(tc.Ctx, schemaResp, state) + resp := testutils.ReadResponse(schemaResp) + + instanceRes.Read(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Get should not succeed, but got no errors") + + var refreshedState *instance.Model + diags := resp.State.Get(tc.Ctx, &refreshedState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + require.Nil(t, refreshedState, "State not nil") +} + +func TestRead_InstanceNotFound(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceId := uuid.New() + instanceName := "test" + region := "eu01" + + oapiErr := oapierror.GenericOpenAPIError{ + StatusCode: 404, + } + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := instance.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(instanceName), + Labels: types.MapNull(types.StringType), + } + + req := testutils.ReadRequest(tc.Ctx, schemaResp, state) + resp := testutils.ReadResponse(schemaResp) + + instanceRes.Read(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Get should not succeed, but got no errors") + + var refreshedState *instance.Model + diags := resp.State.Get(tc.Ctx, &refreshedState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + require.Nil(t, refreshedState, "State not nil") +} + +func TestRead_GetRequestFailed(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + region := "eu01" + instanceId := uuid.New() + + oapiErr := oapierror.GenericOpenAPIError{ + StatusCode: 400, + } + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := instance.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(instanceName), + Labels: types.MapNull(types.StringType), + } + + req := testutils.ReadRequest(tc.Ctx, schemaResp, state) + resp := testutils.ReadResponse(schemaResp) + + instanceRes.Read(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Get should not succeed") + + var refreshedState *instance.Model + diags := resp.State.Get(tc.Ctx, &refreshedState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + require.Nil(t, refreshedState) +} diff --git a/stackit/internal/services/modelexperiments/instance/resource_suite_test.go b/stackit/internal/services/modelexperiments/instance/resource_suite_test.go deleted file mode 100644 index ef7751cfb..000000000 --- a/stackit/internal/services/modelexperiments/instance/resource_suite_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package instance_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestObjectStorage(t *testing.T) { - t.Parallel() - - RegisterFailHandler(Fail) - RunSpecs(t, "STACKIT Adapter - Object Storage Suite") -} diff --git a/stackit/internal/services/modelexperiments/testutils/test_utils.go b/stackit/internal/services/modelexperiments/testutils/test_utils.go index 521bab1b8..becf24370 100644 --- a/stackit/internal/services/modelexperiments/testutils/test_utils.go +++ b/stackit/internal/services/modelexperiments/testutils/test_utils.go @@ -2,14 +2,35 @@ package testutils import ( "context" + "testing" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" + mock_instance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance/mock" + "go.uber.org/mock/gomock" ) +type TestContext struct { + T *testing.T + MockCtrl *gomock.Controller + MockInstanceCLient *mock_instance.MockDefaultAPI + Ctx context.Context +} + +func NewTestContext(t *testing.T) *TestContext { + ctrl := gomock.NewController(t) + mockClient := mock_instance.NewMockDefaultAPI(ctrl) + return &TestContext{ + T: t, + MockCtrl: ctrl, + MockInstanceCLient: mockClient, + Ctx: context.Background(), + } +} + func CreateInstanceTestModel(projectId, region, name, description string) instance.Model { return instance.Model{ ProjectId: types.StringValue(projectId), @@ -38,3 +59,82 @@ func CreateResponse(schema resource.SchemaResponse) *resource.CreateResponse { } return resp } + +func UpdateRequest(ctx context.Context, schema resource.SchemaResponse, currentState, plannedState instance.Model) resource.UpdateRequest { + req := resource.UpdateRequest{} + req.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.Plan = tfsdk.Plan{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.State.Set(ctx, currentState) + req.Plan.Set(ctx, plannedState) + return req +} + +// UpdateResponse creates a test Update response +// Optionally initialize with current state to simulate Terraform framework behavior +func UpdateResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.UpdateResponse { + resp := &resource.UpdateResponse{} + resp.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + // Initialize with current state to simulate framework behavior + // When Update errors without calling State.Set(), this state is preserved + if currentState != nil { + resp.State.Set(ctx, *currentState) + } + return resp +} + +// DeleteRequest creates a test Delete request +func DeleteRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.DeleteRequest { + req := resource.DeleteRequest{} + req.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.State.Set(ctx, state) + return req +} + +// DeleteResponse creates a test Delete response +// Optionally initialize with current state to simulate Terraform framework behavior +func DeleteResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.DeleteResponse { + resp := &resource.DeleteResponse{} + resp.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + // Initialize with current state to simulate framework behavior + // When Delete errors without calling State.RemoveResource(), this state is preserved + if currentState != nil { + resp.State.Set(ctx, *currentState) + } + return resp +} + +// ReadRequest creates a test Read request +func ReadRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.ReadRequest { + req := resource.ReadRequest{} + req.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.State.Set(ctx, state) + return req +} + +// ReadResponse creates a test Read response +func ReadResponse(schema resource.SchemaResponse) *resource.ReadResponse { + resp := &resource.ReadResponse{} + resp.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + return resp +} From b3575fd52b5f2a9fdbf74bf71a706b246153941f Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Tue, 12 May 2026 21:23:12 +0200 Subject: [PATCH 19/30] Add update unit test --- .../instance/resource_update_test.go | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 stackit/internal/services/modelexperiments/instance/resource_update_test.go diff --git a/stackit/internal/services/modelexperiments/instance/resource_update_test.go b/stackit/internal/services/modelexperiments/instance/resource_update_test.go new file mode 100644 index 000000000..e725d9790 --- /dev/null +++ b/stackit/internal/services/modelexperiments/instance/resource_update_test.go @@ -0,0 +1,91 @@ +package instance_test + +import ( + "fmt" + "testing" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestUpdate_Success(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + instanceNameUpdated := "update name" + description := "description" + descriptionUpdated := "description updated" + region := "eu01" + instanceId := uuid.New() + url := "url" + + updateResp := modelexperiments.PartialUpdateInstanceResponse{ + Instance: modelexperiments.Instance{ + DeletedExperimentRetention: new("1m"), + Description: &descriptionUpdated, + Name: instanceNameUpdated, + Region: new("eu01"), + Url: url, + Id: instanceId.String(), + State: "active", + }, + } + + tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceExecute(gomock.Any()).Return(updateResp, nil) + + providerData := core.ProviderData{ + DefaultRegion: region, + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + currentState := instance.Model{ + Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceName), + Description: types.StringValue(description), + } + + plannedState := instance.Model{ + Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), + ProjectId: types.StringValue(projectId.String()), + Region: types.StringValue(region), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceNameUpdated), + Description: types.StringValue(descriptionUpdated), + } + + req := testutils.UpdateRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateResponse(tc.Ctx, schemaResp, nil) + + // Execute Update + instanceRes.Update(tc.Ctx, req, resp) + + require.False(t, resp.Diagnostics.HasError(), "Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + // Extract final state + var finalState instance.Model + diags := resp.State.Get(tc.Ctx, &finalState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + + // Verify all fields match the updated values from GetInstance + require.Equal(t, instanceId, finalState.InstanceId.ValueString()) + require.Equal(t, projectId, finalState.ProjectId.ValueString()) + require.Equal(t, instanceNameUpdated, finalState.Name.ValueString()) + require.Equal(t, descriptionUpdated, finalState.Description.ValueString()) + require.Equal(t, "active", finalState.State.ValueString()) + require.Equal(t, url, finalState.Url.ValueString()) + require.Equal(t, region, finalState.Region.ValueString()) +} From 1649042e74cd2cc1d597788bbdd2acb0c38d40b8 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Wed, 13 May 2026 11:13:22 +0200 Subject: [PATCH 20/30] Add instance unit tests --- .../modelexperiments/instance/resource.go | 53 ++--- .../instance/resource_create_test.go | 4 + .../instance/resource_delete_test.go | 188 ++++++++++++++++++ .../instance/resource_read_test.go | 18 +- .../instance/resource_update_test.go | 135 ++++++++++++- .../modelexperiments/testutils/test_utils.go | 7 +- .../utils/mock/serviceenablement.go | 156 --------------- .../services/modelexperiments/utils/util.go | 1 - 8 files changed, 360 insertions(+), 202 deletions(-) create mode 100644 stackit/internal/services/modelexperiments/instance/resource_delete_test.go delete mode 100644 stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 33bd05a90..726c229c2 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -3,7 +3,6 @@ package instance import ( "context" _ "embed" - "errors" "fmt" "net/http" "time" @@ -247,8 +246,8 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques err := i.serviceEnablementClient.EnableServiceRegionalExecute(serviceEnablementReq) if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) + if ok { if oapiErr.StatusCode == http.StatusNotFound { core.LogAndAddError(ctx, &resp.Diagnostics, "Error enabling AI model experiments", fmt.Sprintf("Service not available in region %s \n%v", region, err), @@ -256,6 +255,7 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } } + core.LogAndAddError( ctx, &resp.Diagnostics, @@ -361,10 +361,9 @@ func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r getInstanceReq := i.client.GetInstance(ctx, projectId, region, instanceId) getInstanceResp, err := i.client.GetInstanceExecute(getInstanceReq) if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) + if ok { if oapiErr.StatusCode == http.StatusNotFound { - // Remove the resource from the state so Terraform will recreate it resp.State.RemoveResource(ctx) return } @@ -428,27 +427,14 @@ func (i *instanceResource) Update(ctx context.Context, req resource.UpdateReques updateInstanceReq := i.client.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload) updateInstanceResp, err := i.client.PartialUpdateInstanceExecute(updateInstanceReq) if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) + if ok { if oapiErr.StatusCode == http.StatusNotFound { - // Remove the resource from the state so Terraform will recreate it resp.State.RemoveResource(ctx) return } } - - core.LogAndAddError( - ctx, - &resp.Diagnostics, - "Error updating AI model experiments instance", - fmt.Sprintf( - "Calling API: %v, instanceId: %s, region: %s, projectId: %s", - err, - instanceId, - region, - projectId, - ), - ) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Calling API: %v", err)) return } @@ -490,10 +476,11 @@ func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteReques ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - _, err := i.client.DeleteInstance(ctx, projectId, region, instanceId).Execute() + deleteInstanceReq := i.client.DeleteInstance(ctx, projectId, region, instanceId) + _, err := i.client.DeleteInstanceExecute(deleteInstanceReq) if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if !errors.As(err, &oapiErr) { + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) + if ok { if oapiErr.StatusCode == http.StatusNotFound { resp.State.RemoveResource(ctx) return @@ -643,16 +630,18 @@ func CreateMExpInstanceWaitHandler(ctx context.Context, a modelexperiments.Defau func DeleteMExpInstanceWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New( func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - _, err = a.GetInstance(ctx, projectId, region, instanceId).Execute() + getInstanceReq := a.GetInstance(ctx, projectId, region, instanceId) + _, err = a.GetInstanceExecute(getInstanceReq) if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { - if oapiErr.StatusCode == http.StatusNotFound { - return true, nil, nil - } + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) + if !ok { + return false, nil, err + } + if oapiErr.StatusCode != http.StatusNotFound { + return false, nil, err } - return false, nil, err + return true, nil, nil } return false, nil, nil diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index 01bf1c6b1..98394acbd 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -118,6 +118,7 @@ func TestCreate_Success(t *testing.T) { diags := resp.State.Get(tc.Ctx, &stateAfterCreate) require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + // state should be created correctly require.Equal(t, instanceId.String(), stateAfterCreate.InstanceId.ValueString()) require.Equal(t, projectId.String(), stateAfterCreate.ProjectId.ValueString()) require.Equal(t, instanceName, stateAfterCreate.Name.ValueString()) @@ -183,6 +184,7 @@ func TestCreate_ServiceEnablementFailure(t *testing.T) { require.True(t, resp.Diagnostics.HasError(), "Create should not succeed, but got no errors") + // state should not be created var stateAfterCreate *instance.Model diags := resp.State.Get(tc.Ctx, &stateAfterCreate) require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) @@ -270,6 +272,7 @@ func TestCreate_GetInstanceFailure(t *testing.T) { diags := resp.State.Get(tc.Ctx, &stateAfterCreate) require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + // state should be created even if get request failed require.Equal(t, instanceId.String(), stateAfterCreate.InstanceId.ValueString()) require.Equal(t, projectId.String(), stateAfterCreate.ProjectId.ValueString()) require.Equal(t, instanceName, stateAfterCreate.Name.ValueString()) @@ -339,6 +342,7 @@ func TestCreate_InstanceCreateFailure(t *testing.T) { require.True(t, resp.Diagnostics.HasError(), "Create should not succeed, but got no errors") + // no state should be created var stateAfterCreate *instance.Model diags := resp.State.Get(tc.Ctx, &stateAfterCreate) require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) diff --git a/stackit/internal/services/modelexperiments/instance/resource_delete_test.go b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go new file mode 100644 index 000000000..628d14738 --- /dev/null +++ b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go @@ -0,0 +1,188 @@ +package instance_test + +import ( + "net/http" + "testing" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestDelete_Success(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + region := "eu01" + instanceId := uuid.New() + + tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceExecute(gomock.Any()).Return(nil, nil) + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusNotFound, + } + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := instance.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(instanceName), + Labels: types.MapNull(types.StringType), + } + + req := testutils.DeleteRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteResponse(tc.Ctx, schemaResp, &state) + + instanceRes.Delete(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) +} + +func TestDelete_DeleteCallFailed(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + region := "eu01" + instanceId := uuid.New() + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusInternalServerError, + } + tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := instance.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(instanceName), + Labels: types.MapNull(types.StringType), + } + + req := testutils.DeleteRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteResponse(tc.Ctx, schemaResp, &state) + + instanceRes.Delete(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed, but got no errors") + + var finalState instance.Model + diags := resp.State.Get(tc.Ctx, &finalState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + + require.Equal(t, instanceId.String(), finalState.InstanceId.ValueString(), "state should not have been deleted") +} + +func TestDelete_InstanceAlreadyDeleted(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + region := "eu01" + instanceId := uuid.New() + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusNotFound, + } + tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := instance.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(instanceName), + Labels: types.MapNull(types.StringType), + } + + req := testutils.DeleteRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteResponse(tc.Ctx, schemaResp, &state) + + instanceRes.Delete(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + var finalState *instance.Model + diags := resp.State.Get(tc.Ctx, &finalState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + require.Nil(t, finalState, "state should have been deleted") +} + +func TestDelete_GetInstanceFailed(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + region := "eu01" + instanceId := uuid.New() + + tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceExecute(gomock.Any()).Return(nil, nil) + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusInternalServerError, + } + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := instance.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(instanceName), + Labels: types.MapNull(types.StringType), + } + + req := testutils.DeleteRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteResponse(tc.Ctx, schemaResp, &state) + + instanceRes.Delete(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed, but got no errors") + + var finalState instance.Model + diags := resp.State.Get(tc.Ctx, &finalState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + + require.Equal(t, instanceId.String(), state.InstanceId.ValueString(), "state should not have been deleted") +} diff --git a/stackit/internal/services/modelexperiments/instance/resource_read_test.go b/stackit/internal/services/modelexperiments/instance/resource_read_test.go index 036e537d9..28cf06121 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_read_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_read_test.go @@ -58,7 +58,7 @@ func TestRead_Success(t *testing.T) { } req := testutils.ReadRequest(tc.Ctx, schemaResp, state) - resp := testutils.ReadResponse(schemaResp) + resp := testutils.ReadResponse(tc.Ctx, schemaResp, nil) instanceRes.Read(tc.Ctx, req, resp) @@ -68,6 +68,7 @@ func TestRead_Success(t *testing.T) { diags := resp.State.Get(tc.Ctx, &refreshedState) require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + // state should be written according to GetInstance Response require.Equal(t, instanceId.String(), refreshedState.InstanceId.ValueString()) require.Equal(t, projectId.String(), refreshedState.ProjectId.ValueString()) require.Equal(t, instanceNameUpdated, refreshedState.Name.ValueString()) @@ -100,11 +101,12 @@ func TestRead_InstanceIdEmptyFailure(t *testing.T) { } req := testutils.ReadRequest(tc.Ctx, schemaResp, state) - resp := testutils.ReadResponse(schemaResp) + resp := testutils.ReadResponse(tc.Ctx, schemaResp, &state) instanceRes.Read(tc.Ctx, req, resp) require.True(t, resp.Diagnostics.HasError(), "Get should not succeed, but got no errors") + // state should be removed var refreshedState *instance.Model diags := resp.State.Get(tc.Ctx, &refreshedState) require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) @@ -119,7 +121,7 @@ func TestRead_InstanceNotFound(t *testing.T) { instanceName := "test" region := "eu01" - oapiErr := oapierror.GenericOpenAPIError{ + oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: 404, } tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) @@ -142,11 +144,12 @@ func TestRead_InstanceNotFound(t *testing.T) { } req := testutils.ReadRequest(tc.Ctx, schemaResp, state) - resp := testutils.ReadResponse(schemaResp) + resp := testutils.ReadResponse(tc.Ctx, schemaResp, &state) instanceRes.Read(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Get should not succeed, but got no errors") + require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + // state should be removed var refreshedState *instance.Model diags := resp.State.Get(tc.Ctx, &refreshedState) require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) @@ -161,7 +164,7 @@ func TestRead_GetRequestFailed(t *testing.T) { region := "eu01" instanceId := uuid.New() - oapiErr := oapierror.GenericOpenAPIError{ + oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: 400, } tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) @@ -184,11 +187,12 @@ func TestRead_GetRequestFailed(t *testing.T) { } req := testutils.ReadRequest(tc.Ctx, schemaResp, state) - resp := testutils.ReadResponse(schemaResp) + resp := testutils.ReadResponse(tc.Ctx, schemaResp, nil) instanceRes.Read(tc.Ctx, req, resp) require.True(t, resp.Diagnostics.HasError(), "Get should not succeed") + //state should not be set var refreshedState *instance.Model diags := resp.State.Get(tc.Ctx, &refreshedState) require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) diff --git a/stackit/internal/services/modelexperiments/instance/resource_update_test.go b/stackit/internal/services/modelexperiments/instance/resource_update_test.go index e725d9790..32d157eb3 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_update_test.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" @@ -27,7 +28,7 @@ func TestUpdate_Success(t *testing.T) { instanceId := uuid.New() url := "url" - updateResp := modelexperiments.PartialUpdateInstanceResponse{ + updateResp := &modelexperiments.PartialUpdateInstanceResponse{ Instance: modelexperiments.Instance{ DeletedExperimentRetention: new("1m"), Description: &descriptionUpdated, @@ -55,7 +56,9 @@ func TestUpdate_Success(t *testing.T) { ProjectId: types.StringValue(projectId.String()), InstanceId: types.StringValue(instanceId.String()), Name: types.StringValue(instanceName), + Region: types.StringValue(region), Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), } plannedState := instance.Model{ @@ -65,10 +68,11 @@ func TestUpdate_Success(t *testing.T) { InstanceId: types.StringValue(instanceId.String()), Name: types.StringValue(instanceNameUpdated), Description: types.StringValue(descriptionUpdated), + Labels: types.MapNull(types.StringType), } req := testutils.UpdateRequest(tc.Ctx, schemaResp, currentState, plannedState) - resp := testutils.UpdateResponse(tc.Ctx, schemaResp, nil) + resp := testutils.UpdateResponse(tc.Ctx, schemaResp, ¤tState) // Execute Update instanceRes.Update(tc.Ctx, req, resp) @@ -80,12 +84,133 @@ func TestUpdate_Success(t *testing.T) { diags := resp.State.Get(tc.Ctx, &finalState) require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - // Verify all fields match the updated values from GetInstance - require.Equal(t, instanceId, finalState.InstanceId.ValueString()) - require.Equal(t, projectId, finalState.ProjectId.ValueString()) + // Verify all fields match the updated values from GetInstance, state should be updated + require.Equal(t, instanceId.String(), finalState.InstanceId.ValueString()) + require.Equal(t, projectId.String(), finalState.ProjectId.ValueString()) require.Equal(t, instanceNameUpdated, finalState.Name.ValueString()) require.Equal(t, descriptionUpdated, finalState.Description.ValueString()) require.Equal(t, "active", finalState.State.ValueString()) require.Equal(t, url, finalState.Url.ValueString()) require.Equal(t, region, finalState.Region.ValueString()) } + +func TestUpdate_InstanceNotFound(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + instanceNameUpdated := "update name" + description := "description" + descriptionUpdated := "description updated" + region := "eu01" + instanceId := uuid.New() + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: 404, + } + tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: region, + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + currentState := instance.Model{ + Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceName), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + } + + plannedState := instance.Model{ + Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), + ProjectId: types.StringValue(projectId.String()), + Region: types.StringValue(region), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceNameUpdated), + Description: types.StringValue(descriptionUpdated), + Labels: types.MapNull(types.StringType), + } + + req := testutils.UpdateRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateResponse(tc.Ctx, schemaResp, ¤tState) + + // Execute Update + instanceRes.Update(tc.Ctx, req, resp) + + require.False(t, resp.Diagnostics.HasError(), "Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + // Extract final state, state should be deleted + var finalState *instance.Model + diags := resp.State.Get(tc.Ctx, &finalState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + require.Nil(t, finalState, "State should not be written") +} + +func TestUpdate_InstanceUpdateError(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + instanceName := "test" + instanceNameUpdated := "update name" + description := "description" + descriptionUpdated := "description updated" + region := "eu01" + instanceId := uuid.New() + + tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) + + providerData := core.ProviderData{ + DefaultRegion: region, + } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, nil, providerData) + + schemaResp := resource.SchemaResponse{} + instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + currentState := instance.Model{ + Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(instanceName), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + } + + plannedState := instance.Model{ + Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), + ProjectId: types.StringValue(projectId.String()), + Region: types.StringValue(region), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceNameUpdated), + Description: types.StringValue(descriptionUpdated), + Labels: types.MapNull(types.StringType), + } + + req := testutils.UpdateRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateResponse(tc.Ctx, schemaResp, ¤tState) + + // Execute Update + instanceRes.Update(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Update should not succeed, but got no errors") + + // Extract final state, instance should not be updated + var finalState instance.Model + diags := resp.State.Get(tc.Ctx, &finalState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + + require.Equal(t, description, finalState.Description.ValueString(), "value should not have changed") + require.Equal(t, instanceName, finalState.Name.ValueString(), "value should not have changed") + require.Equal(t, instanceId.String(), finalState.InstanceId.ValueString(), "value should not have changed") + require.Equal(t, region, finalState.Region.ValueString(), "value should not have changed") + require.Equal(t, projectId.String(), finalState.ProjectId.ValueString(), "value should not have changed") + require.Equal(t, fmt.Sprintf("%s,%s", projectId, instanceId), finalState.Id.ValueString(), "value should not have changed") +} diff --git a/stackit/internal/services/modelexperiments/testutils/test_utils.go b/stackit/internal/services/modelexperiments/testutils/test_utils.go index becf24370..326a7dee4 100644 --- a/stackit/internal/services/modelexperiments/testutils/test_utils.go +++ b/stackit/internal/services/modelexperiments/testutils/test_utils.go @@ -130,11 +130,16 @@ func ReadRequest(ctx context.Context, schema resource.SchemaResponse, state inst } // ReadResponse creates a test Read response -func ReadResponse(schema resource.SchemaResponse) *resource.ReadResponse { +func ReadResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.ReadResponse { resp := &resource.ReadResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), } + // Initialize with current state to simulate framework behavior + // When Delete errors without calling State.RemoveResource(), this state is preserved + if currentState != nil { + resp.State.Set(ctx, *currentState) + } return resp } diff --git a/stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go b/stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go deleted file mode 100644 index 6d0fce345..000000000 --- a/stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go +++ /dev/null @@ -1,156 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api (interfaces: DefaultAPI) -// -// Generated by this command: -// -// mockgen -destination=./mock/serviceenablement.go -package=mock_serviceenablement github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api DefaultAPI -// - -// Package mock_serviceenablement is a generated GoMock package. -package mock_serviceenablement - -import ( - context "context" - reflect "reflect" - - v2api "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" - gomock "go.uber.org/mock/gomock" -) - -// MockDefaultAPI is a mock of DefaultAPI interface. -type MockDefaultAPI struct { - ctrl *gomock.Controller - recorder *MockDefaultAPIMockRecorder - isgomock struct{} -} - -// MockDefaultAPIMockRecorder is the mock recorder for MockDefaultAPI. -type MockDefaultAPIMockRecorder struct { - mock *MockDefaultAPI -} - -// NewMockDefaultAPI creates a new mock instance. -func NewMockDefaultAPI(ctrl *gomock.Controller) *MockDefaultAPI { - mock := &MockDefaultAPI{ctrl: ctrl} - mock.recorder = &MockDefaultAPIMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockDefaultAPI) EXPECT() *MockDefaultAPIMockRecorder { - return m.recorder -} - -// DisableServiceRegional mocks base method. -func (m *MockDefaultAPI) DisableServiceRegional(ctx context.Context, region, projectId, serviceId string) v2api.ApiDisableServiceRegionalRequest { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DisableServiceRegional", ctx, region, projectId, serviceId) - ret0, _ := ret[0].(v2api.ApiDisableServiceRegionalRequest) - return ret0 -} - -// DisableServiceRegional indicates an expected call of DisableServiceRegional. -func (mr *MockDefaultAPIMockRecorder) DisableServiceRegional(ctx, region, projectId, serviceId any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableServiceRegional", reflect.TypeOf((*MockDefaultAPI)(nil).DisableServiceRegional), ctx, region, projectId, serviceId) -} - -// DisableServiceRegionalExecute mocks base method. -func (m *MockDefaultAPI) DisableServiceRegionalExecute(r v2api.ApiDisableServiceRegionalRequest) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DisableServiceRegionalExecute", r) - ret0, _ := ret[0].(error) - return ret0 -} - -// DisableServiceRegionalExecute indicates an expected call of DisableServiceRegionalExecute. -func (mr *MockDefaultAPIMockRecorder) DisableServiceRegionalExecute(r any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableServiceRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).DisableServiceRegionalExecute), r) -} - -// EnableServiceRegional mocks base method. -func (m *MockDefaultAPI) EnableServiceRegional(ctx context.Context, region, projectId, serviceId string) v2api.ApiEnableServiceRegionalRequest { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EnableServiceRegional", ctx, region, projectId, serviceId) - ret0, _ := ret[0].(v2api.ApiEnableServiceRegionalRequest) - return ret0 -} - -// EnableServiceRegional indicates an expected call of EnableServiceRegional. -func (mr *MockDefaultAPIMockRecorder) EnableServiceRegional(ctx, region, projectId, serviceId any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableServiceRegional", reflect.TypeOf((*MockDefaultAPI)(nil).EnableServiceRegional), ctx, region, projectId, serviceId) -} - -// EnableServiceRegionalExecute mocks base method. -func (m *MockDefaultAPI) EnableServiceRegionalExecute(r v2api.ApiEnableServiceRegionalRequest) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EnableServiceRegionalExecute", r) - ret0, _ := ret[0].(error) - return ret0 -} - -// EnableServiceRegionalExecute indicates an expected call of EnableServiceRegionalExecute. -func (mr *MockDefaultAPIMockRecorder) EnableServiceRegionalExecute(r any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableServiceRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).EnableServiceRegionalExecute), r) -} - -// GetServiceStatusRegional mocks base method. -func (m *MockDefaultAPI) GetServiceStatusRegional(ctx context.Context, region, projectId, serviceId string) v2api.ApiGetServiceStatusRegionalRequest { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetServiceStatusRegional", ctx, region, projectId, serviceId) - ret0, _ := ret[0].(v2api.ApiGetServiceStatusRegionalRequest) - return ret0 -} - -// GetServiceStatusRegional indicates an expected call of GetServiceStatusRegional. -func (mr *MockDefaultAPIMockRecorder) GetServiceStatusRegional(ctx, region, projectId, serviceId any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceStatusRegional", reflect.TypeOf((*MockDefaultAPI)(nil).GetServiceStatusRegional), ctx, region, projectId, serviceId) -} - -// GetServiceStatusRegionalExecute mocks base method. -func (m *MockDefaultAPI) GetServiceStatusRegionalExecute(r v2api.ApiGetServiceStatusRegionalRequest) (*v2api.ServiceStatus, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetServiceStatusRegionalExecute", r) - ret0, _ := ret[0].(*v2api.ServiceStatus) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetServiceStatusRegionalExecute indicates an expected call of GetServiceStatusRegionalExecute. -func (mr *MockDefaultAPIMockRecorder) GetServiceStatusRegionalExecute(r any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceStatusRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).GetServiceStatusRegionalExecute), r) -} - -// ListServiceStatusRegional mocks base method. -func (m *MockDefaultAPI) ListServiceStatusRegional(ctx context.Context, region, projectId string) v2api.ApiListServiceStatusRegionalRequest { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListServiceStatusRegional", ctx, region, projectId) - ret0, _ := ret[0].(v2api.ApiListServiceStatusRegionalRequest) - return ret0 -} - -// ListServiceStatusRegional indicates an expected call of ListServiceStatusRegional. -func (mr *MockDefaultAPIMockRecorder) ListServiceStatusRegional(ctx, region, projectId any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServiceStatusRegional", reflect.TypeOf((*MockDefaultAPI)(nil).ListServiceStatusRegional), ctx, region, projectId) -} - -// ListServiceStatusRegionalExecute mocks base method. -func (m *MockDefaultAPI) ListServiceStatusRegionalExecute(r v2api.ApiListServiceStatusRegionalRequest) (*v2api.ListServiceStatusRegional200Response, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListServiceStatusRegionalExecute", r) - ret0, _ := ret[0].(*v2api.ListServiceStatusRegional200Response) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListServiceStatusRegionalExecute indicates an expected call of ListServiceStatusRegionalExecute. -func (mr *MockDefaultAPIMockRecorder) ListServiceStatusRegionalExecute(r any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServiceStatusRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).ListServiceStatusRegionalExecute), r) -} diff --git a/stackit/internal/services/modelexperiments/utils/util.go b/stackit/internal/services/modelexperiments/utils/util.go index 3431a9a96..01ebec05a 100644 --- a/stackit/internal/services/modelexperiments/utils/util.go +++ b/stackit/internal/services/modelexperiments/utils/util.go @@ -45,7 +45,6 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags return apiClient.DefaultAPI } -//go:generate mockgen -destination=./mock/serviceenablement.go -package=mock_serviceenablement github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api DefaultAPI func ConfigureServiceEnablementClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) serviceenablement.DefaultAPI { apiClientConfigOptions := []config.ConfigurationOption{ config.WithCustomAuth(providerData.RoundTripper), From b2ad985439b70e3c2918dc0a5bdb156fd439c655 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Wed, 13 May 2026 16:00:43 +0200 Subject: [PATCH 21/30] Add unit tests --- .../instance/resource_delete_test.go | 18 +- .../instance/resource_read_test.go | 16 +- .../instance/resource_update_test.go | 12 +- .../modelexperiments/testutils/test_utils.go | 110 +++++- .../modelexperiments/token/resource.go | 82 ++--- .../token/resource_create_test.go | 261 ++++++++++++++ .../token/resource_delete_test.go | 199 +++++++++++ .../token/resource_read_test.go | 320 ++++++++++++++++++ .../token/resource_update_test.go | 319 +++++++++++++++++ 9 files changed, 1257 insertions(+), 80 deletions(-) create mode 100644 stackit/internal/services/modelexperiments/token/resource_create_test.go create mode 100644 stackit/internal/services/modelexperiments/token/resource_delete_test.go create mode 100644 stackit/internal/services/modelexperiments/token/resource_read_test.go create mode 100644 stackit/internal/services/modelexperiments/token/resource_update_test.go diff --git a/stackit/internal/services/modelexperiments/instance/resource_delete_test.go b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go index 628d14738..9477efbe6 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_delete_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go @@ -49,14 +49,14 @@ func TestDelete_Success(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.DeleteRequest(tc.Ctx, schemaResp, state) - resp := testutils.DeleteResponse(tc.Ctx, schemaResp, &state) + req := testutils.DeleteInstanceRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Delete(tc.Ctx, req, resp) require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) } -func TestDelete_DeleteCallFailed(t *testing.T) { +func TestDelete_DeleteInstanceFailed(t *testing.T) { tc := testutils.NewTestContext(t) projectId := uuid.New() @@ -86,8 +86,8 @@ func TestDelete_DeleteCallFailed(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.DeleteRequest(tc.Ctx, schemaResp, state) - resp := testutils.DeleteResponse(tc.Ctx, schemaResp, &state) + req := testutils.DeleteInstanceRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Delete(tc.Ctx, req, resp) require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed, but got no errors") @@ -129,8 +129,8 @@ func TestDelete_InstanceAlreadyDeleted(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.DeleteRequest(tc.Ctx, schemaResp, state) - resp := testutils.DeleteResponse(tc.Ctx, schemaResp, &state) + req := testutils.DeleteInstanceRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Delete(tc.Ctx, req, resp) require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) @@ -174,8 +174,8 @@ func TestDelete_GetInstanceFailed(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.DeleteRequest(tc.Ctx, schemaResp, state) - resp := testutils.DeleteResponse(tc.Ctx, schemaResp, &state) + req := testutils.DeleteInstanceRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Delete(tc.Ctx, req, resp) require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed, but got no errors") diff --git a/stackit/internal/services/modelexperiments/instance/resource_read_test.go b/stackit/internal/services/modelexperiments/instance/resource_read_test.go index 28cf06121..b9b75af87 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_read_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_read_test.go @@ -57,8 +57,8 @@ func TestRead_Success(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.ReadRequest(tc.Ctx, schemaResp, state) - resp := testutils.ReadResponse(tc.Ctx, schemaResp, nil) + req := testutils.ReadInstanceRequest(tc.Ctx, schemaResp, state) + resp := testutils.ReadInstanceResponse(tc.Ctx, schemaResp, nil) instanceRes.Read(tc.Ctx, req, resp) @@ -100,8 +100,8 @@ func TestRead_InstanceIdEmptyFailure(t *testing.T) { Name: types.StringValue(instanceName), } - req := testutils.ReadRequest(tc.Ctx, schemaResp, state) - resp := testutils.ReadResponse(tc.Ctx, schemaResp, &state) + req := testutils.ReadInstanceRequest(tc.Ctx, schemaResp, state) + resp := testutils.ReadInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Read(tc.Ctx, req, resp) require.True(t, resp.Diagnostics.HasError(), "Get should not succeed, but got no errors") @@ -143,8 +143,8 @@ func TestRead_InstanceNotFound(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.ReadRequest(tc.Ctx, schemaResp, state) - resp := testutils.ReadResponse(tc.Ctx, schemaResp, &state) + req := testutils.ReadInstanceRequest(tc.Ctx, schemaResp, state) + resp := testutils.ReadInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Read(tc.Ctx, req, resp) require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) @@ -186,8 +186,8 @@ func TestRead_GetRequestFailed(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.ReadRequest(tc.Ctx, schemaResp, state) - resp := testutils.ReadResponse(tc.Ctx, schemaResp, nil) + req := testutils.ReadInstanceRequest(tc.Ctx, schemaResp, state) + resp := testutils.ReadInstanceResponse(tc.Ctx, schemaResp, nil) instanceRes.Read(tc.Ctx, req, resp) require.True(t, resp.Diagnostics.HasError(), "Get should not succeed") diff --git a/stackit/internal/services/modelexperiments/instance/resource_update_test.go b/stackit/internal/services/modelexperiments/instance/resource_update_test.go index 32d157eb3..33c63db37 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_update_test.go @@ -71,8 +71,8 @@ func TestUpdate_Success(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.UpdateRequest(tc.Ctx, schemaResp, currentState, plannedState) - resp := testutils.UpdateResponse(tc.Ctx, schemaResp, ¤tState) + req := testutils.UpdateInstanceRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateInstanceResponse(tc.Ctx, schemaResp, ¤tState) // Execute Update instanceRes.Update(tc.Ctx, req, resp) @@ -138,8 +138,8 @@ func TestUpdate_InstanceNotFound(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.UpdateRequest(tc.Ctx, schemaResp, currentState, plannedState) - resp := testutils.UpdateResponse(tc.Ctx, schemaResp, ¤tState) + req := testutils.UpdateInstanceRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateInstanceResponse(tc.Ctx, schemaResp, ¤tState) // Execute Update instanceRes.Update(tc.Ctx, req, resp) @@ -195,8 +195,8 @@ func TestUpdate_InstanceUpdateError(t *testing.T) { Labels: types.MapNull(types.StringType), } - req := testutils.UpdateRequest(tc.Ctx, schemaResp, currentState, plannedState) - resp := testutils.UpdateResponse(tc.Ctx, schemaResp, ¤tState) + req := testutils.UpdateInstanceRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateInstanceResponse(tc.Ctx, schemaResp, ¤tState) // Execute Update instanceRes.Update(tc.Ctx, req, resp) diff --git a/stackit/internal/services/modelexperiments/testutils/test_utils.go b/stackit/internal/services/modelexperiments/testutils/test_utils.go index 326a7dee4..d64971aaf 100644 --- a/stackit/internal/services/modelexperiments/testutils/test_utils.go +++ b/stackit/internal/services/modelexperiments/testutils/test_utils.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" mock_instance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance/mock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" "go.uber.org/mock/gomock" ) @@ -51,6 +52,16 @@ func CreateInstanceRequest(ctx context.Context, schema resource.SchemaResponse, return req } +func CreateInstanceTokenRequest(ctx context.Context, schema resource.SchemaResponse, model token.Model) resource.CreateRequest { + req := resource.CreateRequest{} + req.Plan = tfsdk.Plan{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.Plan.Set(ctx, model) + return req +} + func CreateResponse(schema resource.SchemaResponse) *resource.CreateResponse { resp := &resource.CreateResponse{} resp.State = tfsdk.State{ @@ -60,7 +71,22 @@ func CreateResponse(schema resource.SchemaResponse) *resource.CreateResponse { return resp } -func UpdateRequest(ctx context.Context, schema resource.SchemaResponse, currentState, plannedState instance.Model) resource.UpdateRequest { +func UpdateInstanceRequest(ctx context.Context, schema resource.SchemaResponse, currentState, plannedState instance.Model) resource.UpdateRequest { + req := resource.UpdateRequest{} + req.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.Plan = tfsdk.Plan{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.State.Set(ctx, currentState) + req.Plan.Set(ctx, plannedState) + return req +} + +func UpdateTokenRequest(ctx context.Context, schema resource.SchemaResponse, currentState, plannedState token.Model) resource.UpdateRequest { req := resource.UpdateRequest{} req.State = tfsdk.State{ Schema: schema.Schema, @@ -75,9 +101,23 @@ func UpdateRequest(ctx context.Context, schema resource.SchemaResponse, currentS return req } -// UpdateResponse creates a test Update response +// UpdateInstanceResponse creates a test Update response // Optionally initialize with current state to simulate Terraform framework behavior -func UpdateResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.UpdateResponse { +func UpdateInstanceResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.UpdateResponse { + resp := &resource.UpdateResponse{} + resp.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + // Initialize with current state to simulate framework behavior + // When Update errors without calling State.Set(), this state is preserved + if currentState != nil { + resp.State.Set(ctx, *currentState) + } + return resp +} + +func UpdateTokenResponse(ctx context.Context, schema resource.SchemaResponse, currentState *token.Model) *resource.UpdateResponse { resp := &resource.UpdateResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, @@ -91,8 +131,18 @@ func UpdateResponse(ctx context.Context, schema resource.SchemaResponse, current return resp } -// DeleteRequest creates a test Delete request -func DeleteRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.DeleteRequest { +// DeleteInstanceRequest creates a test Delete request +func DeleteInstanceRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.DeleteRequest { + req := resource.DeleteRequest{} + req.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.State.Set(ctx, state) + return req +} + +func DeleteTokenRequest(ctx context.Context, schema resource.SchemaResponse, state token.Model) resource.DeleteRequest { req := resource.DeleteRequest{} req.State = tfsdk.State{ Schema: schema.Schema, @@ -102,9 +152,23 @@ func DeleteRequest(ctx context.Context, schema resource.SchemaResponse, state in return req } -// DeleteResponse creates a test Delete response +// DeleteInstanceResponse creates a test Delete response // Optionally initialize with current state to simulate Terraform framework behavior -func DeleteResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.DeleteResponse { +func DeleteInstanceResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.DeleteResponse { + resp := &resource.DeleteResponse{} + resp.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + // Initialize with current state to simulate framework behavior + // When Delete errors without calling State.RemoveResource(), this state is preserved + if currentState != nil { + resp.State.Set(ctx, *currentState) + } + return resp +} + +func DeleteTokenResponse(ctx context.Context, schema resource.SchemaResponse, currentState *token.Model) *resource.DeleteResponse { resp := &resource.DeleteResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, @@ -118,8 +182,18 @@ func DeleteResponse(ctx context.Context, schema resource.SchemaResponse, current return resp } -// ReadRequest creates a test Read request -func ReadRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.ReadRequest { +// ReadInstanceRequest creates a test Read request +func ReadInstanceRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.ReadRequest { + req := resource.ReadRequest{} + req.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + req.State.Set(ctx, state) + return req +} + +func ReadTokenRequest(ctx context.Context, schema resource.SchemaResponse, state token.Model) resource.ReadRequest { req := resource.ReadRequest{} req.State = tfsdk.State{ Schema: schema.Schema, @@ -129,8 +203,22 @@ func ReadRequest(ctx context.Context, schema resource.SchemaResponse, state inst return req } -// ReadResponse creates a test Read response -func ReadResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.ReadResponse { +// ReadInstanceResponse creates a test Read response +func ReadInstanceResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.ReadResponse { + resp := &resource.ReadResponse{} + resp.State = tfsdk.State{ + Schema: schema.Schema, + Raw: tftypes.NewValue(tftypes.DynamicPseudoType, nil), + } + // Initialize with current state to simulate framework behavior + // When Delete errors without calling State.RemoveResource(), this state is preserved + if currentState != nil { + resp.State.Set(ctx, *currentState) + } + return resp +} + +func ReadTokenResponse(ctx context.Context, schema resource.SchemaResponse, currentState *token.Model) *resource.ReadResponse { resp := &resource.ReadResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index 93caa0c87..e0a84f1d9 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -19,7 +19,6 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/wait" - serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -57,19 +56,17 @@ func NewInstanceTokenResourceEmpty() resource.Resource { return &tokenResource{} } -func NewInstanceTokenResource(client modelexperiments.DefaultAPI, serviceEnablementClient serviceenablement.DefaultAPI, providerData core.ProviderData) resource.Resource { +func NewInstanceTokenResource(client modelexperiments.DefaultAPI, providerData core.ProviderData) resource.Resource { return &tokenResource{ - client: client, - providerData: providerData, - serviceEnablementClient: serviceEnablementClient, + client: client, + providerData: providerData, } } // tokenResource is the resource implementation. type tokenResource struct { - client modelexperiments.DefaultAPI - providerData core.ProviderData - serviceEnablementClient serviceenablement.DefaultAPI + client modelexperiments.DefaultAPI + providerData core.ProviderData } // Metadata returns the resource type name. @@ -89,12 +86,7 @@ func (i *tokenResource) Configure(ctx context.Context, req resource.ConfigureReq if resp.Diagnostics.HasError() { return } - serviceEnablementClient := modelexperimentsutils.ConfigureServiceEnablementClient(ctx, &i.providerData, &resp.Diagnostics) - if resp.Diagnostics.HasError() { - return - } i.client = apiClient - i.serviceEnablementClient = serviceEnablementClient tflog.Info(ctx, "Model experiments client configured") } @@ -250,7 +242,8 @@ func (i *tokenResource) Create(ctx context.Context, req resource.CreateRequest, return } - createInstanceTokenResp, err := i.client.CreateInstanceToken(ctx, projectId, region, instanceId).CreateInstanceTokenPayload(*payload).Execute() + createTokenReq := i.client.CreateInstanceToken(ctx, projectId, region, instanceId).CreateInstanceTokenPayload(*payload) + createInstanceTokenResp, err := i.client.CreateInstanceTokenExecute(createTokenReq) if err != nil { core.LogAndAddError( ctx, @@ -281,15 +274,6 @@ func (i *tokenResource) Create(ctx context.Context, req resource.CreateRequest, waitResp, err := CreateMExpTokenWaitHandler(ctx, i.client, region, projectId, instanceId, tokenId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance token", fmt.Sprintf("Waiting for instance to be active: %v", err)) - - err = mapCreateResponse(ctx, createInstanceTokenResp, waitResp, &model, region) - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance token", fmt.Sprintf("Processing API payload: %v", err)) - } - diags = resp.State.Set(ctx, model) - resp.Diagnostics.Append(diags...) - - return } // Map response body to schema @@ -336,7 +320,8 @@ func (i *tokenResource) Read(ctx context.Context, req resource.ReadRequest, resp ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - getInstanceTokenResp, err := i.client.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + getTokenReq := i.client.GetInstanceToken(ctx, projectId, region, tokenId, instanceId) + getInstanceTokenResp, err := i.client.GetInstanceTokenExecute(getTokenReq) if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -352,6 +337,12 @@ func (i *tokenResource) Read(ctx context.Context, req resource.ReadRequest, resp } ctx = core.LogResponse(ctx) + if getInstanceTokenResp != nil && getInstanceTokenResp.Token.State == modelexperiments.TOKENSTATE_INACTIVE { + resp.State.RemoveResource(ctx) + core.LogAndAddWarning(ctx, &resp.Diagnostics, "Error updating AI model experiments instance token", "AI model experiments token has expired") + return + } + err = mapToken(ctx, &getInstanceTokenResp.Token, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) @@ -388,7 +379,7 @@ func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, ctx = core.InitProviderContext(ctx) projectId := state.ProjectId.ValueString() - instanceId := state.InstanceId.ValueString() + instanceId := model.InstanceId.ValueString() tokenId := state.TokenId.ValueString() region := i.providerData.GetRegionWithOverride(model.Region) @@ -403,7 +394,8 @@ func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, return } - updateInstanceTokenResp, err := i.client.PartialUpdateInstanceToken(ctx, projectId, region, tokenId, instanceId).PartialUpdateInstanceTokenPayload(*payload).Execute() + updateTokenReq := i.client.PartialUpdateInstanceToken(ctx, projectId, region, tokenId, instanceId).PartialUpdateInstanceTokenPayload(*payload) + updateInstanceTokenResp, err := i.client.PartialUpdateInstanceTokenExecute(updateTokenReq) if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -433,8 +425,8 @@ func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, ctx = core.LogResponse(ctx) if updateInstanceTokenResp != nil && updateInstanceTokenResp.Token.State == modelexperiments.TOKENSTATE_INACTIVE { - resp.State.RemoveResource(ctx) core.LogAndAddWarning(ctx, &resp.Diagnostics, "Error updating AI model experiments instance token", "AI model experiments token has expired") + resp.State.RemoveResource(ctx) return } @@ -477,21 +469,18 @@ func (i *tokenResource) Delete(ctx context.Context, req resource.DeleteRequest, ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - _, err := i.client.DeleteInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + deleteTokenReq := i.client.DeleteInstanceToken(ctx, projectId, region, tokenId, instanceId) + _, err := i.client.DeleteInstanceTokenExecute(deleteTokenReq) if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if !errors.As(err, &oapiErr) { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Calling API: %v", err)) - return - } - if oapiErr.StatusCode == http.StatusNotFound { - resp.State.RemoveResource(ctx) - return - } - if oapiErr.StatusCode != http.StatusConflict { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Calling API: %v", err)) - return + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) + if ok { + if oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Calling API: %v", err)) + return } ctx = core.LogResponse(ctx) @@ -563,7 +552,7 @@ func mapToken(ctx context.Context, token *modelexperiments.TokenMetadata, model return fmt.Errorf("failure in mapping labels") } - model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.Region.ValueString(), model.TokenId.ValueString()) + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.Region.ValueString(), token.Id) model.TokenId = types.StringValue(token.Id) model.Name = types.StringValue(token.Name) model.State = types.StringValue(string(token.State)) @@ -611,7 +600,8 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstanceToken func CreateMExpTokenWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetTokenResponse] { handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetTokenResponse, err error) { - getTokenResp, err := a.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + getTokenReq := a.GetInstanceToken(ctx, projectId, region, tokenId, instanceId) + getTokenResp, err := a.GetInstanceTokenExecute(getTokenReq) if err != nil { return false, nil, err } @@ -630,15 +620,15 @@ func CreateMExpTokenWaitHandler(ctx context.Context, a modelexperiments.DefaultA func DeleteMExpTokenWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { handler := wait.New( func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - _, err = a.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() + getTokenReq := a.GetInstanceToken(ctx, projectId, region, tokenId, instanceId) + _, err = a.GetInstanceTokenExecute(getTokenReq) if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) + if ok { if oapiErr.StatusCode == http.StatusNotFound { return true, nil, nil } } - return false, nil, err } diff --git a/stackit/internal/services/modelexperiments/token/resource_create_test.go b/stackit/internal/services/modelexperiments/token/resource_create_test.go new file mode 100644 index 000000000..8b2f4d791 --- /dev/null +++ b/stackit/internal/services/modelexperiments/token/resource_create_test.go @@ -0,0 +1,261 @@ +package token_test + +import ( + "testing" + "time" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestCreate_Success(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + description := "token description" + instanceId := uuid.New() + tokenId := uuid.New() + validUntil := time.Now().Add(10 * time.Minute) + + createTokenResp := &modelexperiments.CreateTokenResponse{ + Token: modelexperiments.Token{ + Content: "token", + Description: &description, + Id: tokenId.String(), + Name: name, + Region: region, + State: "creating", + ValidUntil: validUntil, + }, + } + tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceTokenExecute(gomock.Any()).Return(createTokenResp, nil) + + getTokenResp := &modelexperiments.GetTokenResponse{ + Token: modelexperiments.TokenMetadata{ + Description: &description, + Id: tokenId.String(), + Name: name, + Region: region, + State: "active", + ValidUntil: validUntil, + }, + } + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(getTokenResp, nil) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.CreateInstanceTokenRequest(tc.Ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + tokenRes.Create(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Create should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + var createdState token.Model + diags := resp.State.Get(tc.Ctx, &createdState) + require.False(t, diags.HasError(), "Failed to get state") + + require.Equal(t, tokenId.String(), createdState.TokenId.ValueString(), "Should be equal") + require.Equal(t, projectId.String(), createdState.ProjectId.ValueString(), "Should be equal") + require.Equal(t, instanceId.String(), createdState.InstanceId.ValueString(), "Should be equal") + require.Equal(t, name, createdState.Name.ValueString(), "Should be equal") + require.Equal(t, "active", createdState.State.ValueString(), "Should be equal") + require.Equal(t, description, createdState.Description.ValueString(), "Should be equal") + require.Equal(t, "token", createdState.Token.ValueString(), "Should be equal") + require.Equal(t, utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString(), createdState.Id.ValueString(), "Should be equal") +} + +func TestCreate_TokenIdEmpty(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + description := "token description" + instanceId := uuid.New() + validUntil := time.Now() + + createTokenResp := &modelexperiments.CreateTokenResponse{ + Token: modelexperiments.Token{ + Content: "token", + Description: &description, + Id: "", + Name: name, + Region: region, + State: "creating", + ValidUntil: validUntil, + }, + } + tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceTokenExecute(gomock.Any()).Return(createTokenResp, nil) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.CreateInstanceTokenRequest(tc.Ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + tokenRes.Create(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Create should not succeed but got no errors") + + // state should not be created + var createdState *token.Model + diags := resp.State.Get(tc.Ctx, &createdState) + require.False(t, diags.HasError(), "Failed to get state") + require.Nil(t, createdState) +} + +func TestCreate_CreateTokenFailure(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + description := "token description" + instanceId := uuid.New() + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: 400, + } + tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.CreateInstanceTokenRequest(tc.Ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + tokenRes.Create(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Create should not succeed but got no errors") + + // state should not be created + var createdState *token.Model + diags := resp.State.Get(tc.Ctx, &createdState) + require.False(t, diags.HasError(), "Failed to get state") + require.Nil(t, createdState) +} + +func TestCreate_GetTokenFailure(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + description := "token description" + instanceId := uuid.New() + tokenId := uuid.New() + validUntil := time.Now().Add(10 * time.Minute) + + createTokenResp := &modelexperiments.CreateTokenResponse{ + Token: modelexperiments.Token{ + Content: "token", + Description: &description, + Id: tokenId.String(), + Name: name, + Region: region, + State: "creating", + ValidUntil: validUntil, + }, + } + tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceTokenExecute(gomock.Any()).Return(createTokenResp, nil) + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: 404, + } + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.CreateInstanceTokenRequest(tc.Ctx, schemaResp, model) + resp := testutils.CreateResponse(schemaResp) + + tokenRes.Create(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Create should not succeed but got no errors") + + // state should be created + var createdState token.Model + diags := resp.State.Get(tc.Ctx, &createdState) + require.False(t, diags.HasError(), "Failed to get state") + + require.Equal(t, tokenId.String(), createdState.TokenId.ValueString(), "Should be equal") + require.Equal(t, projectId.String(), createdState.ProjectId.ValueString(), "Should be equal") + require.Equal(t, instanceId.String(), createdState.InstanceId.ValueString(), "Should be equal") + require.Equal(t, name, createdState.Name.ValueString(), "Should be equal") + require.Equal(t, "unknown", createdState.State.ValueString(), "Should be equal") + require.Equal(t, description, createdState.Description.ValueString(), "Should be equal") + require.Equal(t, "token", createdState.Token.ValueString(), "Should be equal") + require.Equal(t, utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString(), createdState.Id.ValueString(), "Should be equal") +} diff --git a/stackit/internal/services/modelexperiments/token/resource_delete_test.go b/stackit/internal/services/modelexperiments/token/resource_delete_test.go new file mode 100644 index 000000000..178ff1f0a --- /dev/null +++ b/stackit/internal/services/modelexperiments/token/resource_delete_test.go @@ -0,0 +1,199 @@ +package token_test + +import ( + "net/http" + "testing" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestDelete_Success(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + instanceId := uuid.New() + tokenId := uuid.New() + + tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceTokenExecute(gomock.Any()).Return(nil, nil) + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusNotFound, + } + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := token.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(name), + TokenId: types.StringValue(tokenId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.DeleteTokenRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteTokenResponse(tc.Ctx, schemaResp, nil) + + tokenRes.Delete(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) +} + +func TestDelete_DeleteTokenFailed(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + instanceId := uuid.New() + tokenId := uuid.New() + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusInternalServerError, + } + tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := token.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(name), + TokenId: types.StringValue(tokenId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.DeleteTokenRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteTokenResponse(tc.Ctx, schemaResp, &state) + + tokenRes.Delete(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed") + + //state should not be removed + var deletedState token.Model + diags := resp.State.Get(tc.Ctx, &deletedState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + + require.Equal(t, instanceId.String(), deletedState.InstanceId.ValueString()) +} + +func TestDelete_TokenNotFound(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + instanceId := uuid.New() + tokenId := uuid.New() + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusNotFound, + } + tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := token.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(name), + TokenId: types.StringValue(tokenId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.DeleteTokenRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteTokenResponse(tc.Ctx, schemaResp, &state) + + tokenRes.Delete(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + //state should be removed + var deletedState *token.Model + diags := resp.State.Get(tc.Ctx, &deletedState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + require.Nil(t, deletedState) +} + +func TestDelete_GetTokenFailed(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + instanceId := uuid.New() + tokenId := uuid.New() + + tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceTokenExecute(gomock.Any()).Return(nil, nil) + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusInternalServerError, + } + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + state := token.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Region: types.StringValue(region), + Name: types.StringValue(name), + TokenId: types.StringValue(tokenId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.DeleteTokenRequest(tc.Ctx, schemaResp, state) + resp := testutils.DeleteTokenResponse(tc.Ctx, schemaResp, &state) + + tokenRes.Delete(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed") + + //state should not be removed + var deletedState token.Model + diags := resp.State.Get(tc.Ctx, &deletedState) + require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + + require.Equal(t, instanceId.String(), deletedState.InstanceId.ValueString()) +} diff --git a/stackit/internal/services/modelexperiments/token/resource_read_test.go b/stackit/internal/services/modelexperiments/token/resource_read_test.go new file mode 100644 index 000000000..3b4926850 --- /dev/null +++ b/stackit/internal/services/modelexperiments/token/resource_read_test.go @@ -0,0 +1,320 @@ +package token_test + +import ( + "net/http" + "testing" + "time" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestRead_Success(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + description := "token description" + instanceId := uuid.New() + tokenId := uuid.New() + validUntil := time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC) + tokenContent := "token" + state := "active" + id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + + getTokenResp := &modelexperiments.GetTokenResponse{ + Token: modelexperiments.TokenMetadata{ + Description: &description, + Id: tokenId.String(), + Name: name, + Region: region, + State: "active", + ValidUntil: validUntil, + }, + } + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(getTokenResp, nil) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + Token: types.StringValue(tokenContent), + TokenId: types.StringValue(tokenId.String()), + Id: id, + State: types.StringValue(state), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + } + + req := testutils.ReadTokenRequest(tc.Ctx, schemaResp, model) + resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, nil) + + tokenRes.Read(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + // state should be set + var refreshedState token.Model + diags := resp.State.Get(tc.Ctx, &refreshedState) + require.False(t, diags.HasError(), "Failed to get state") + + require.Equal(t, tokenId.String(), refreshedState.TokenId.ValueString(), "Should be equal") + require.Equal(t, projectId.String(), refreshedState.ProjectId.ValueString(), "Should be equal") + require.Equal(t, instanceId.String(), refreshedState.InstanceId.ValueString(), "Should be equal") + require.Equal(t, name, refreshedState.Name.ValueString(), "Should be equal") + require.Equal(t, "active", refreshedState.State.ValueString(), "Should be equal") + require.Equal(t, description, refreshedState.Description.ValueString(), "Should be equal") + require.Equal(t, tokenContent, refreshedState.Token.ValueString(), "Should be equal") + require.Equal(t, id.ValueString(), refreshedState.Id.ValueString(), "Should be equal") + require.Equal(t, region, refreshedState.Region.ValueString(), "Should be equal") + require.Equal(t, "2099-01-01T00:00:00Z", refreshedState.ValidUntil.ValueString(), "Should be equal") +} + +func TestRead_TokenIdEmptyFailure(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + description := "token description" + instanceId := uuid.New() + tokenId := uuid.New() + tokenContent := "token" + state := "active" + id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + Token: types.StringValue(tokenContent), + TokenId: types.StringValue(""), + Id: id, + State: types.StringValue(state), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + } + + req := testutils.ReadTokenRequest(tc.Ctx, schemaResp, model) + resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, &model) + + tokenRes.Read(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + // state should be removed + var refreshedState *token.Model + diags := resp.State.Get(tc.Ctx, &refreshedState) + require.False(t, diags.HasError(), "Failed to get state") + require.Nil(t, refreshedState) +} + +func TestRead_TokenNotFound(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + description := "token description" + instanceId := uuid.New() + tokenId := uuid.New() + tokenContent := "token" + state := "active" + id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusNotFound, + } + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + Token: types.StringValue(tokenContent), + TokenId: types.StringValue(tokenId.String()), + Id: id, + State: types.StringValue(state), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + } + + req := testutils.ReadTokenRequest(tc.Ctx, schemaResp, model) + resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, &model) + + tokenRes.Read(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + // state should be removed + var refreshedState *token.Model + diags := resp.State.Get(tc.Ctx, &refreshedState) + require.False(t, diags.HasError(), "Failed to get state") + require.Nil(t, refreshedState) + +} + +func TestRead_GetTokenRequestFailed(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + description := "token description" + instanceId := uuid.New() + tokenId := uuid.New() + tokenContent := "token" + state := "active" + id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusInternalServerError, + } + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + Token: types.StringValue(tokenContent), + TokenId: types.StringValue(tokenId.String()), + Id: id, + State: types.StringValue(state), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + } + + req := testutils.ReadTokenRequest(tc.Ctx, schemaResp, model) + resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, &model) + + tokenRes.Read(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Get should not succeed") + + // state should not be edited + var refreshedState token.Model + diags := resp.State.Get(tc.Ctx, &refreshedState) + require.False(t, diags.HasError(), "Failed to get state") + + require.Equal(t, tokenId.String(), refreshedState.TokenId.ValueString(), "Should be equal") + require.Equal(t, projectId.String(), refreshedState.ProjectId.ValueString(), "Should be equal") + require.Equal(t, instanceId.String(), refreshedState.InstanceId.ValueString(), "Should be equal") + require.Equal(t, name, refreshedState.Name.ValueString(), "Should be equal") + require.Equal(t, "active", refreshedState.State.ValueString(), "Should be equal") + require.Equal(t, description, refreshedState.Description.ValueString(), "Should be equal") + require.Equal(t, tokenContent, refreshedState.Token.ValueString(), "Should be equal") + require.Equal(t, id.ValueString(), refreshedState.Id.ValueString(), "Should be equal") + require.Equal(t, region, refreshedState.Region.ValueString(), "Should be equal") + require.Equal(t, "2099-01-01T00:00:00Z", refreshedState.ValidUntil.ValueString(), "Should be equal") +} + +func TestRead_TokenInvalidError(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + region := "eu01" + description := "token description" + instanceId := uuid.New() + tokenId := uuid.New() + tokenContent := "token" + state := "active" + id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + validUntil := time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC) + + getTokenResp := &modelexperiments.GetTokenResponse{ + Token: modelexperiments.TokenMetadata{ + Description: &description, + Id: tokenId.String(), + Name: name, + Region: region, + State: "inactive", + ValidUntil: validUntil, + }, + } + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(getTokenResp, nil) + + providerData := core.ProviderData{ + DefaultRegion: "eu01", + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + model := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + Token: types.StringValue(tokenContent), + TokenId: types.StringValue(tokenId.String()), + Id: id, + State: types.StringValue(state), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + } + + req := testutils.ReadTokenRequest(tc.Ctx, schemaResp, model) + resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, &model) + + tokenRes.Read(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + // state should be removed + var refreshedState *token.Model + diags := resp.State.Get(tc.Ctx, &refreshedState) + require.False(t, diags.HasError(), "Failed to get state") + require.Nil(t, refreshedState) +} diff --git a/stackit/internal/services/modelexperiments/token/resource_update_test.go b/stackit/internal/services/modelexperiments/token/resource_update_test.go new file mode 100644 index 000000000..d1d44a06b --- /dev/null +++ b/stackit/internal/services/modelexperiments/token/resource_update_test.go @@ -0,0 +1,319 @@ +package token_test + +import ( + "net/http" + "testing" + "time" + + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestUpdate_Success(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + nameUpdated := "token update" + region := "eu01" + description := "token description" + descriptionUpdated := "description" + instanceId := uuid.New() + tokenId := uuid.New() + validUntil := time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC) + tokenContent := "token" + state := "active" + id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + + updateTokenResp := &modelexperiments.PartialUpdateTokenResponse{ + Token: modelexperiments.TokenMetadata{ + Description: &descriptionUpdated, + Id: tokenId.String(), + Name: nameUpdated, + Region: region, + State: "active", + ValidUntil: validUntil, + }, + } + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceTokenExecute(gomock.Any()).Return(updateTokenResp, nil) + + providerData := core.ProviderData{ + DefaultRegion: region, + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + currentState := token.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + Token: types.StringValue(tokenContent), + TokenId: types.StringValue(tokenId.String()), + Id: id, + State: types.StringValue(state), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + } + + plannedState := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(nameUpdated), + Region: types.StringValue(region), + Description: types.StringValue(descriptionUpdated), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.UpdateTokenRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateTokenResponse(tc.Ctx, schemaResp, ¤tState) + + // Execute Update + tokenRes.Update(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + // state should be updated + var updatedState token.Model + diags := resp.State.Get(tc.Ctx, &updatedState) + require.False(t, diags.HasError(), "Failed to get state") + + require.Equal(t, tokenId.String(), updatedState.TokenId.ValueString(), "Should be equal") + require.Equal(t, projectId.String(), updatedState.ProjectId.ValueString(), "Should be equal") + require.Equal(t, instanceId.String(), updatedState.InstanceId.ValueString(), "Should be equal") + require.Equal(t, nameUpdated, updatedState.Name.ValueString(), "Should be equal") + require.Equal(t, "active", updatedState.State.ValueString(), "Should be equal") + require.Equal(t, descriptionUpdated, updatedState.Description.ValueString(), "Should be equal") + require.Equal(t, tokenContent, updatedState.Token.ValueString(), "Should be equal") + require.Equal(t, id.ValueString(), updatedState.Id.ValueString(), "Should be equal") + require.Equal(t, region, updatedState.Region.ValueString(), "Should be equal") + require.Equal(t, "2099-01-01T00:00:00Z", updatedState.ValidUntil.ValueString(), "Should be equal") +} + +func TestUpdate_TokenNotFound(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + nameUpdated := "token update" + region := "eu01" + description := "token description" + descriptionUpdated := "description" + instanceId := uuid.New() + tokenId := uuid.New() + tokenContent := "token" + state := "active" + id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusNotFound, + } + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: region, + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + currentState := token.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + Token: types.StringValue(tokenContent), + TokenId: types.StringValue(tokenId.String()), + Id: id, + State: types.StringValue(state), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + } + + plannedState := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(nameUpdated), + Region: types.StringValue(region), + Description: types.StringValue(descriptionUpdated), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.UpdateTokenRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateTokenResponse(tc.Ctx, schemaResp, ¤tState) + + // Execute Update + tokenRes.Update(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + + // state should be removed + var updatedState *token.Model + diags := resp.State.Get(tc.Ctx, &updatedState) + require.False(t, diags.HasError(), "Failed to get state") + require.Nil(t, updatedState) +} + +func TestUpdate_TokenUpdateError(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + nameUpdated := "token update" + region := "eu01" + description := "token description" + descriptionUpdated := "description" + instanceId := uuid.New() + tokenId := uuid.New() + tokenContent := "token" + state := "active" + id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusInternalServerError, + } + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) + + providerData := core.ProviderData{ + DefaultRegion: region, + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + currentState := token.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + Token: types.StringValue(tokenContent), + TokenId: types.StringValue(tokenId.String()), + Id: id, + State: types.StringValue(state), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + } + + plannedState := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(nameUpdated), + Region: types.StringValue(region), + Description: types.StringValue(descriptionUpdated), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.UpdateTokenRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateTokenResponse(tc.Ctx, schemaResp, ¤tState) + + // Execute Update + tokenRes.Update(tc.Ctx, req, resp) + require.True(t, resp.Diagnostics.HasError(), "Update should not succeed") + + // state should not be changed + var updatedState token.Model + diags := resp.State.Get(tc.Ctx, &updatedState) + require.False(t, diags.HasError(), "Failed to get state") + + require.Equal(t, tokenId.String(), updatedState.TokenId.ValueString(), "Should be equal") + require.Equal(t, projectId.String(), updatedState.ProjectId.ValueString(), "Should be equal") + require.Equal(t, instanceId.String(), updatedState.InstanceId.ValueString(), "Should be equal") + require.Equal(t, name, updatedState.Name.ValueString(), "Should be equal") + require.Equal(t, "active", updatedState.State.ValueString(), "Should be equal") + require.Equal(t, description, updatedState.Description.ValueString(), "Should be equal") + require.Equal(t, tokenContent, updatedState.Token.ValueString(), "Should be equal") + require.Equal(t, id.ValueString(), updatedState.Id.ValueString(), "Should be equal") + require.Equal(t, region, updatedState.Region.ValueString(), "Should be equal") + require.Equal(t, "2099-01-01T00:00:00Z", updatedState.ValidUntil.ValueString(), "Should be equal") +} + +func TestUpdate_TokenInvalidStateError(t *testing.T) { + tc := testutils.NewTestContext(t) + + projectId := uuid.New() + name := "token" + nameUpdated := "token update" + region := "eu01" + description := "token description" + descriptionUpdated := "description" + instanceId := uuid.New() + tokenId := uuid.New() + tokenContent := "token" + state := "active" + id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + validUntil := time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC) + + updateTokenResp := &modelexperiments.PartialUpdateTokenResponse{ + Token: modelexperiments.TokenMetadata{ + Description: &descriptionUpdated, + Id: tokenId.String(), + Name: nameUpdated, + Region: region, + State: "inactive", + ValidUntil: validUntil, + }, + } + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceTokenExecute(gomock.Any()).Return(updateTokenResp, nil) + + providerData := core.ProviderData{ + DefaultRegion: region, + } + tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) + + schemaResp := resource.SchemaResponse{} + tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) + + currentState := token.Model{ + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(name), + Region: types.StringValue(region), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + Token: types.StringValue(tokenContent), + TokenId: types.StringValue(tokenId.String()), + Id: id, + State: types.StringValue(state), + ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + } + + plannedState := token.Model{ + ProjectId: types.StringValue(projectId.String()), + Name: types.StringValue(nameUpdated), + Region: types.StringValue(region), + Description: types.StringValue(descriptionUpdated), + InstanceId: types.StringValue(instanceId.String()), + Labels: types.MapNull(types.StringType), + } + + req := testutils.UpdateTokenRequest(tc.Ctx, schemaResp, currentState, plannedState) + resp := testutils.UpdateTokenResponse(tc.Ctx, schemaResp, ¤tState) + + // Execute Update + tokenRes.Update(tc.Ctx, req, resp) + require.False(t, resp.Diagnostics.HasError(), "Update should succeed") + + // state should not be removed + var updatedState *token.Model + diags := resp.State.Get(tc.Ctx, &updatedState) + require.False(t, diags.HasError(), "Failed to get state") + require.Nil(t, updatedState) +} From d44dab065171b2baa2d4c82bb7dcc214f6768bf3 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Wed, 13 May 2026 17:06:34 +0200 Subject: [PATCH 22/30] Fix linting & remove testify framework --- go.mod | 3 +- go.sum | 4 +- .../modelexperiments/instance/resource.go | 25 +-- .../instance/resource_create_test.go | 110 +++++++++---- .../instance/resource_delete_test.go | 44 ++++-- .../instance/resource_read_test.go | 78 ++++++--- .../instance/resource_update_test.go | 86 +++++++--- .../modelexperiments/testutils/test_utils.go | 33 ++-- .../modelexperiments/token/resource.go | 10 +- .../token/resource_create_test.go | 112 +++++++++---- .../token/resource_delete_test.go | 50 ++++-- .../token/resource_read_test.go | 149 ++++++++---------- .../token/resource_update_test.go | 80 ++++++---- 13 files changed, 497 insertions(+), 287 deletions(-) diff --git a/go.mod b/go.mod index 8b93adfb1..d02ba81da 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,8 @@ require ( require go.uber.org/mock v0.6.0 +require github.com/stretchr/testify v1.11.1 // indirect + require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect @@ -220,7 +222,6 @@ require ( github.com/ssgreg/nlreturn/v2 v2.2.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 github.com/subosito/gotenv v1.4.1 // indirect github.com/tetafro/godot v1.5.4 // indirect github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect diff --git a/go.sum b/go.sum index 7f281eb2f..c73d31674 100644 --- a/go.sum +++ b/go.sum @@ -561,8 +561,8 @@ github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= -github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 726c229c2..5d2d94a0a 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -3,6 +3,7 @@ package instance import ( "context" _ "embed" + "errors" "fmt" "net/http" "time" @@ -20,10 +21,10 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/wait" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" serviceEnablementWait "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api/wait" - modelexperimentsutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + modelexperimentsutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" ) @@ -59,7 +60,7 @@ func NewInstanceResourceEmpty() resource.Resource { return &instanceResource{} } -func NewInstanceResource(client modelexperiments.DefaultAPI, serviceClient serviceenablement.DefaultAPI, providerData core.ProviderData) resource.Resource { +func NewInstanceResource(client modelexperiments.DefaultAPI, serviceClient serviceenablement.DefaultAPI, providerData core.ProviderData) resource.Resource { //nolint:gocritic return &instanceResource{ client: client, providerData: providerData, @@ -246,8 +247,8 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques err := i.serviceEnablementClient.EnableServiceRegionalExecute(serviceEnablementReq) if err != nil { - oapiErr, ok := err.(*oapierror.GenericOpenAPIError) - if ok { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { if oapiErr.StatusCode == http.StatusNotFound { core.LogAndAddError(ctx, &resp.Diagnostics, "Error enabling AI model experiments", fmt.Sprintf("Service not available in region %s \n%v", region, err), @@ -361,8 +362,8 @@ func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r getInstanceReq := i.client.GetInstance(ctx, projectId, region, instanceId) getInstanceResp, err := i.client.GetInstanceExecute(getInstanceReq) if err != nil { - oapiErr, ok := err.(*oapierror.GenericOpenAPIError) - if ok { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { if oapiErr.StatusCode == http.StatusNotFound { resp.State.RemoveResource(ctx) return @@ -427,8 +428,8 @@ func (i *instanceResource) Update(ctx context.Context, req resource.UpdateReques updateInstanceReq := i.client.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload) updateInstanceResp, err := i.client.PartialUpdateInstanceExecute(updateInstanceReq) if err != nil { - oapiErr, ok := err.(*oapierror.GenericOpenAPIError) - if ok { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { if oapiErr.StatusCode == http.StatusNotFound { resp.State.RemoveResource(ctx) return @@ -479,8 +480,8 @@ func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteReques deleteInstanceReq := i.client.DeleteInstance(ctx, projectId, region, instanceId) _, err := i.client.DeleteInstanceExecute(deleteInstanceReq) if err != nil { - oapiErr, ok := err.(*oapierror.GenericOpenAPIError) - if ok { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { if oapiErr.StatusCode == http.StatusNotFound { resp.State.RemoveResource(ctx) return @@ -633,8 +634,8 @@ func DeleteMExpInstanceWaitHandler(ctx context.Context, a modelexperiments.Defau getInstanceReq := a.GetInstance(ctx, projectId, region, instanceId) _, err = a.GetInstanceExecute(getInstanceReq) if err != nil { - oapiErr, ok := err.(*oapierror.GenericOpenAPIError) - if !ok { + var oapiErr *oapierror.GenericOpenAPIError + if !errors.As(err, &oapiErr) { return false, nil, err } if oapiErr.StatusCode != http.StatusNotFound { diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index 98394acbd..e6483b56d 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -6,19 +6,17 @@ import ( "net/http/httptest" "testing" + modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/stackitcloud/stackit-sdk-go/core/config" - - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" + "go.uber.org/mock/gomock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) const ( @@ -41,7 +39,7 @@ func TestCreate_Success(t *testing.T) { if r.Method == http.MethodGet { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) + _, _ = w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) } if r.Method == http.MethodPost { w.WriteHeader(http.StatusAccepted) @@ -112,20 +110,38 @@ func TestCreate_Success(t *testing.T) { instanceRes.Create(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Create should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Create should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } var stateAfterCreate instance.Model diags := resp.State.Get(tc.Ctx, &stateAfterCreate) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } // state should be created correctly - require.Equal(t, instanceId.String(), stateAfterCreate.InstanceId.ValueString()) - require.Equal(t, projectId.String(), stateAfterCreate.ProjectId.ValueString()) - require.Equal(t, instanceName, stateAfterCreate.Name.ValueString()) - require.Equal(t, url, stateAfterCreate.Url.ValueString()) - require.Equal(t, "active", stateAfterCreate.State.ValueString()) - require.Equal(t, region, stateAfterCreate.Region.ValueString()) - require.Equal(t, "bucket", stateAfterCreate.BucketName.ValueString()) + if instanceId.String() != stateAfterCreate.InstanceId.ValueString() { + t.Fatalf("expected %v, got %v", instanceId.String(), stateAfterCreate.InstanceId.ValueString()) + } + if projectId.String() != stateAfterCreate.ProjectId.ValueString() { + t.Fatalf("expected %v, got %v", projectId.String(), stateAfterCreate.ProjectId.ValueString()) + } + if instanceName != stateAfterCreate.Name.ValueString() { + t.Fatalf("expected %v, got %v", instanceName, stateAfterCreate.Name.ValueString()) + } + if url != stateAfterCreate.Url.ValueString() { + t.Fatalf("expected %v, got %v", url, stateAfterCreate.Url.ValueString()) + } + if stateAfterCreate.State.ValueString() != "active" { + t.Fatalf("expected %v, got %v", "active", stateAfterCreate.State.ValueString()) + } + if region != stateAfterCreate.Region.ValueString() { + t.Fatalf("expected %v, got %v", region, stateAfterCreate.Region.ValueString()) + } + if stateAfterCreate.BucketName.ValueString() != "bucket" { + t.Fatalf("expected %v, got %v", "bucket", stateAfterCreate.BucketName.ValueString()) + } } func TestCreate_ServiceEnablementFailure(t *testing.T) { @@ -182,13 +198,19 @@ func TestCreate_ServiceEnablementFailure(t *testing.T) { instanceRes.Create(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Create should not succeed, but got no errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Create should not succeed, but got no errors") + } // state should not be created var stateAfterCreate *instance.Model diags := resp.State.Get(tc.Ctx, &stateAfterCreate) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - require.Nil(t, stateAfterCreate, "State not nil") + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } + if stateAfterCreate != nil { + t.Fatalf("State not nil") + } } func TestCreate_GetInstanceFailure(t *testing.T) { @@ -207,7 +229,7 @@ func TestCreate_GetInstanceFailure(t *testing.T) { if r.Method == http.MethodGet { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) + _, _ = w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) } if r.Method == http.MethodPost { w.WriteHeader(http.StatusAccepted) @@ -266,20 +288,38 @@ func TestCreate_GetInstanceFailure(t *testing.T) { instanceRes.Create(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Create should succeed with errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Create should succeed with errors") + } var stateAfterCreate instance.Model diags := resp.State.Get(tc.Ctx, &stateAfterCreate) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } // state should be created even if get request failed - require.Equal(t, instanceId.String(), stateAfterCreate.InstanceId.ValueString()) - require.Equal(t, projectId.String(), stateAfterCreate.ProjectId.ValueString()) - require.Equal(t, instanceName, stateAfterCreate.Name.ValueString()) - require.Equal(t, url, stateAfterCreate.Url.ValueString()) - require.Equal(t, "unknown", stateAfterCreate.State.ValueString()) - require.Equal(t, region, stateAfterCreate.Region.ValueString()) - require.Equal(t, "", stateAfterCreate.BucketName.ValueString()) + if instanceId.String() != stateAfterCreate.InstanceId.ValueString() { + t.Fatalf("expected %v, got %v", instanceId.String(), stateAfterCreate.InstanceId.ValueString()) + } + if projectId.String() != stateAfterCreate.ProjectId.ValueString() { + t.Fatalf("expected %v, got %v", projectId.String(), stateAfterCreate.ProjectId.ValueString()) + } + if instanceName != stateAfterCreate.Name.ValueString() { + t.Fatalf("expected %v, got %v", instanceName, stateAfterCreate.Name.ValueString()) + } + if url != stateAfterCreate.Url.ValueString() { + t.Fatalf("expected %v, got %v", url, stateAfterCreate.Url.ValueString()) + } + if stateAfterCreate.State.ValueString() != "unknown" { + t.Fatalf("expected %v, got %v", "unknown", stateAfterCreate.State.ValueString()) + } + if region != stateAfterCreate.Region.ValueString() { + t.Fatalf("expected %v, got %v", region, stateAfterCreate.Region.ValueString()) + } + if stateAfterCreate.BucketName.ValueString() != "" { + t.Fatalf("expected %v, got %v", "", stateAfterCreate.BucketName.ValueString()) + } } func TestCreate_InstanceCreateFailure(t *testing.T) { @@ -297,7 +337,7 @@ func TestCreate_InstanceCreateFailure(t *testing.T) { if r.Method == http.MethodGet { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) + _, _ = w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) } if r.Method == http.MethodPost { w.WriteHeader(http.StatusAccepted) @@ -340,11 +380,17 @@ func TestCreate_InstanceCreateFailure(t *testing.T) { instanceRes.Create(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Create should not succeed, but got no errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Create should not succeed, but got no errors") + } // no state should be created var stateAfterCreate *instance.Model diags := resp.State.Get(tc.Ctx, &stateAfterCreate) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - require.Nil(t, stateAfterCreate, "State not nil") + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } + if stateAfterCreate != nil { + t.Fatalf("State not nil") + } } diff --git a/stackit/internal/services/modelexperiments/instance/resource_delete_test.go b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go index 9477efbe6..47c4e9bd4 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_delete_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go @@ -9,11 +9,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "go.uber.org/mock/gomock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestDelete_Success(t *testing.T) { @@ -53,7 +53,9 @@ func TestDelete_Success(t *testing.T) { resp := testutils.DeleteInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Delete(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } } func TestDelete_DeleteInstanceFailed(t *testing.T) { @@ -90,13 +92,19 @@ func TestDelete_DeleteInstanceFailed(t *testing.T) { resp := testutils.DeleteInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Delete(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed, but got no errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Delete should not succeed, but got no errors") + } var finalState instance.Model diags := resp.State.Get(tc.Ctx, &finalState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } - require.Equal(t, instanceId.String(), finalState.InstanceId.ValueString(), "state should not have been deleted") + if instanceId.String() != finalState.InstanceId.ValueString() { + t.Fatalf("state should not have been deleted - expected %v, got %v", instanceId.String(), finalState.InstanceId.ValueString()) + } } func TestDelete_InstanceAlreadyDeleted(t *testing.T) { @@ -133,12 +141,18 @@ func TestDelete_InstanceAlreadyDeleted(t *testing.T) { resp := testutils.DeleteInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Delete(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } var finalState *instance.Model diags := resp.State.Get(tc.Ctx, &finalState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - require.Nil(t, finalState, "state should have been deleted") + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } + if finalState != nil { + t.Fatalf("state should have been deleted - got %v", finalState) + } } func TestDelete_GetInstanceFailed(t *testing.T) { @@ -178,11 +192,17 @@ func TestDelete_GetInstanceFailed(t *testing.T) { resp := testutils.DeleteInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Delete(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed, but got no errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Delete should not succeed, but got no errors") + } var finalState instance.Model diags := resp.State.Get(tc.Ctx, &finalState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } - require.Equal(t, instanceId.String(), state.InstanceId.ValueString(), "state should not have been deleted") + if instanceId.String() != state.InstanceId.ValueString() { + t.Fatalf("state should not have been deleted - expected %v, got %v", instanceId.String(), state.InstanceId.ValueString()) + } } diff --git a/stackit/internal/services/modelexperiments/instance/resource_read_test.go b/stackit/internal/services/modelexperiments/instance/resource_read_test.go index b9b75af87..1d194da0e 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_read_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_read_test.go @@ -8,11 +8,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "go.uber.org/mock/gomock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestRead_Success(t *testing.T) { @@ -62,20 +62,38 @@ func TestRead_Success(t *testing.T) { instanceRes.Read(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } var refreshedState instance.Model diags := resp.State.Get(tc.Ctx, &refreshedState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } // state should be written according to GetInstance Response - require.Equal(t, instanceId.String(), refreshedState.InstanceId.ValueString()) - require.Equal(t, projectId.String(), refreshedState.ProjectId.ValueString()) - require.Equal(t, instanceNameUpdated, refreshedState.Name.ValueString()) - require.Equal(t, url, refreshedState.Url.ValueString()) - require.Equal(t, "active", refreshedState.State.ValueString()) - require.Equal(t, region, refreshedState.Region.ValueString()) - require.Equal(t, "bucket", refreshedState.BucketName.ValueString()) + if instanceId.String() != refreshedState.InstanceId.ValueString() { + t.Fatalf("expected %v, got %v", instanceId.String(), refreshedState.InstanceId.ValueString()) + } + if projectId.String() != refreshedState.ProjectId.ValueString() { + t.Fatalf("expected %v, got %v", projectId.String(), refreshedState.ProjectId.ValueString()) + } + if instanceNameUpdated != refreshedState.Name.ValueString() { + t.Fatalf("expected %v, got %v", instanceNameUpdated, refreshedState.Name.ValueString()) + } + if url != refreshedState.Url.ValueString() { + t.Fatalf("expected %v, got %v", url, refreshedState.Url.ValueString()) + } + if refreshedState.State.ValueString() != "active" { + t.Fatalf("expected %v, got %v", "active", refreshedState.State.ValueString()) + } + if region != refreshedState.Region.ValueString() { + t.Fatalf("expected %v, got %v", region, refreshedState.Region.ValueString()) + } + if refreshedState.BucketName.ValueString() != "bucket" { + t.Fatalf("expected %v, got %v", "bucket", refreshedState.BucketName.ValueString()) + } } func TestRead_InstanceIdEmptyFailure(t *testing.T) { @@ -104,13 +122,19 @@ func TestRead_InstanceIdEmptyFailure(t *testing.T) { resp := testutils.ReadInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Read(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Get should not succeed, but got no errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Get should not succeed, but got no errors") + } // state should be removed var refreshedState *instance.Model diags := resp.State.Get(tc.Ctx, &refreshedState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - require.Nil(t, refreshedState, "State not nil") + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } + if refreshedState != nil { + t.Fatalf("State not nil") + } } func TestRead_InstanceNotFound(t *testing.T) { @@ -147,13 +171,19 @@ func TestRead_InstanceNotFound(t *testing.T) { resp := testutils.ReadInstanceResponse(tc.Ctx, schemaResp, &state) instanceRes.Read(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } // state should be removed var refreshedState *instance.Model diags := resp.State.Get(tc.Ctx, &refreshedState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - require.Nil(t, refreshedState, "State not nil") + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } + if refreshedState != nil { + t.Fatalf("State not nil") + } } func TestRead_GetRequestFailed(t *testing.T) { @@ -190,11 +220,17 @@ func TestRead_GetRequestFailed(t *testing.T) { resp := testutils.ReadInstanceResponse(tc.Ctx, schemaResp, nil) instanceRes.Read(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Get should not succeed") + if !resp.Diagnostics.HasError() { + t.Fatalf("Get should not succeed") + } - //state should not be set + // state should not be set var refreshedState *instance.Model diags := resp.State.Get(tc.Ctx, &refreshedState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - require.Nil(t, refreshedState) + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } + if refreshedState != nil { + t.Fatalf("expected nil, got %v", refreshedState) + } } diff --git a/stackit/internal/services/modelexperiments/instance/resource_update_test.go b/stackit/internal/services/modelexperiments/instance/resource_update_test.go index 33c63db37..9c01cd28c 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_update_test.go @@ -9,11 +9,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "go.uber.org/mock/gomock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestUpdate_Success(t *testing.T) { @@ -77,21 +77,39 @@ func TestUpdate_Success(t *testing.T) { // Execute Update instanceRes.Update(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } // Extract final state var finalState instance.Model diags := resp.State.Get(tc.Ctx, &finalState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } // Verify all fields match the updated values from GetInstance, state should be updated - require.Equal(t, instanceId.String(), finalState.InstanceId.ValueString()) - require.Equal(t, projectId.String(), finalState.ProjectId.ValueString()) - require.Equal(t, instanceNameUpdated, finalState.Name.ValueString()) - require.Equal(t, descriptionUpdated, finalState.Description.ValueString()) - require.Equal(t, "active", finalState.State.ValueString()) - require.Equal(t, url, finalState.Url.ValueString()) - require.Equal(t, region, finalState.Region.ValueString()) + if instanceId.String() != finalState.InstanceId.ValueString() { + t.Fatalf("expected %v, got %v", instanceId.String(), finalState.InstanceId.ValueString()) + } + if projectId.String() != finalState.ProjectId.ValueString() { + t.Fatalf("expected %v, got %v", projectId.String(), finalState.ProjectId.ValueString()) + } + if instanceNameUpdated != finalState.Name.ValueString() { + t.Fatalf("expected %v, got %v", instanceNameUpdated, finalState.Name.ValueString()) + } + if descriptionUpdated != finalState.Description.ValueString() { + t.Fatalf("expected %v, got %v", descriptionUpdated, finalState.Description.ValueString()) + } + if finalState.State.ValueString() != "active" { + t.Fatalf("expected %v, got %v", "active", finalState.State.ValueString()) + } + if url != finalState.Url.ValueString() { + t.Fatalf("expected %v, got %v", url, finalState.Url.ValueString()) + } + if region != finalState.Region.ValueString() { + t.Fatalf("expected %v, got %v", region, finalState.Region.ValueString()) + } } func TestUpdate_InstanceNotFound(t *testing.T) { @@ -144,13 +162,19 @@ func TestUpdate_InstanceNotFound(t *testing.T) { // Execute Update instanceRes.Update(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } // Extract final state, state should be deleted var finalState *instance.Model diags := resp.State.Get(tc.Ctx, &finalState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - require.Nil(t, finalState, "State should not be written") + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } + if finalState != nil { + t.Fatalf("State should not be written") + } } func TestUpdate_InstanceUpdateError(t *testing.T) { @@ -200,17 +224,33 @@ func TestUpdate_InstanceUpdateError(t *testing.T) { // Execute Update instanceRes.Update(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Update should not succeed, but got no errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Update should not succeed, but got no errors") + } // Extract final state, instance should not be updated var finalState instance.Model diags := resp.State.Get(tc.Ctx, &finalState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - - require.Equal(t, description, finalState.Description.ValueString(), "value should not have changed") - require.Equal(t, instanceName, finalState.Name.ValueString(), "value should not have changed") - require.Equal(t, instanceId.String(), finalState.InstanceId.ValueString(), "value should not have changed") - require.Equal(t, region, finalState.Region.ValueString(), "value should not have changed") - require.Equal(t, projectId.String(), finalState.ProjectId.ValueString(), "value should not have changed") - require.Equal(t, fmt.Sprintf("%s,%s", projectId, instanceId), finalState.Id.ValueString(), "value should not have changed") + if diags.HasError() { + t.Fatalf("Failed to get state: %v", diags.Errors()) + } + + if description != finalState.Description.ValueString() { + t.Fatalf("value should not have changed - expected %v, got %v", description, finalState.Description.ValueString()) + } + if instanceName != finalState.Name.ValueString() { + t.Fatalf("value should not have changed - expected %v, got %v", instanceName, finalState.Name.ValueString()) + } + if instanceId.String() != finalState.InstanceId.ValueString() { + t.Fatalf("value should not have changed - expected %v, got %v", instanceId.String(), finalState.InstanceId.ValueString()) + } + if region != finalState.Region.ValueString() { + t.Fatalf("value should not have changed - expected %v, got %v", region, finalState.Region.ValueString()) + } + if projectId.String() != finalState.ProjectId.ValueString() { + t.Fatalf("value should not have changed - expected %v, got %v", projectId.String(), finalState.ProjectId.ValueString()) + } + if fmt.Sprintf("%s,%s", projectId, instanceId) != finalState.Id.ValueString() { + t.Fatalf("value should not have changed - expected %v, got %v", fmt.Sprintf("%s,%s", projectId, instanceId), finalState.Id.ValueString()) + } } diff --git a/stackit/internal/services/modelexperiments/testutils/test_utils.go b/stackit/internal/services/modelexperiments/testutils/test_utils.go index d64971aaf..c0188216c 100644 --- a/stackit/internal/services/modelexperiments/testutils/test_utils.go +++ b/stackit/internal/services/modelexperiments/testutils/test_utils.go @@ -8,10 +8,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" + "go.uber.org/mock/gomock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" mock_instance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance/mock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" - "go.uber.org/mock/gomock" ) type TestContext struct { @@ -42,7 +43,7 @@ func CreateInstanceTestModel(projectId, region, name, description string) instan } } -func CreateInstanceRequest(ctx context.Context, schema resource.SchemaResponse, model instance.Model) resource.CreateRequest { +func CreateInstanceRequest(ctx context.Context, schema resource.SchemaResponse, model instance.Model) resource.CreateRequest { //nolint:gocritic req := resource.CreateRequest{} req.Plan = tfsdk.Plan{ Schema: schema.Schema, @@ -52,7 +53,7 @@ func CreateInstanceRequest(ctx context.Context, schema resource.SchemaResponse, return req } -func CreateInstanceTokenRequest(ctx context.Context, schema resource.SchemaResponse, model token.Model) resource.CreateRequest { +func CreateInstanceTokenRequest(ctx context.Context, schema resource.SchemaResponse, model token.Model) resource.CreateRequest { //nolint:gocritic req := resource.CreateRequest{} req.Plan = tfsdk.Plan{ Schema: schema.Schema, @@ -62,7 +63,7 @@ func CreateInstanceTokenRequest(ctx context.Context, schema resource.SchemaRespo return req } -func CreateResponse(schema resource.SchemaResponse) *resource.CreateResponse { +func CreateResponse(schema resource.SchemaResponse) *resource.CreateResponse { //nolint:gocritic resp := &resource.CreateResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, @@ -71,7 +72,7 @@ func CreateResponse(schema resource.SchemaResponse) *resource.CreateResponse { return resp } -func UpdateInstanceRequest(ctx context.Context, schema resource.SchemaResponse, currentState, plannedState instance.Model) resource.UpdateRequest { +func UpdateInstanceRequest(ctx context.Context, schema resource.SchemaResponse, currentState, plannedState instance.Model) resource.UpdateRequest { //nolint:gocritic req := resource.UpdateRequest{} req.State = tfsdk.State{ Schema: schema.Schema, @@ -86,7 +87,7 @@ func UpdateInstanceRequest(ctx context.Context, schema resource.SchemaResponse, return req } -func UpdateTokenRequest(ctx context.Context, schema resource.SchemaResponse, currentState, plannedState token.Model) resource.UpdateRequest { +func UpdateTokenRequest(ctx context.Context, schema resource.SchemaResponse, currentState, plannedState token.Model) resource.UpdateRequest { //nolint:gocritic req := resource.UpdateRequest{} req.State = tfsdk.State{ Schema: schema.Schema, @@ -103,7 +104,7 @@ func UpdateTokenRequest(ctx context.Context, schema resource.SchemaResponse, cur // UpdateInstanceResponse creates a test Update response // Optionally initialize with current state to simulate Terraform framework behavior -func UpdateInstanceResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.UpdateResponse { +func UpdateInstanceResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.UpdateResponse { //nolint:gocritic resp := &resource.UpdateResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, @@ -117,7 +118,7 @@ func UpdateInstanceResponse(ctx context.Context, schema resource.SchemaResponse, return resp } -func UpdateTokenResponse(ctx context.Context, schema resource.SchemaResponse, currentState *token.Model) *resource.UpdateResponse { +func UpdateTokenResponse(ctx context.Context, schema resource.SchemaResponse, currentState *token.Model) *resource.UpdateResponse { //nolint:gocritic resp := &resource.UpdateResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, @@ -132,7 +133,7 @@ func UpdateTokenResponse(ctx context.Context, schema resource.SchemaResponse, cu } // DeleteInstanceRequest creates a test Delete request -func DeleteInstanceRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.DeleteRequest { +func DeleteInstanceRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.DeleteRequest { //nolint:gocritic req := resource.DeleteRequest{} req.State = tfsdk.State{ Schema: schema.Schema, @@ -142,7 +143,7 @@ func DeleteInstanceRequest(ctx context.Context, schema resource.SchemaResponse, return req } -func DeleteTokenRequest(ctx context.Context, schema resource.SchemaResponse, state token.Model) resource.DeleteRequest { +func DeleteTokenRequest(ctx context.Context, schema resource.SchemaResponse, state token.Model) resource.DeleteRequest { //nolint:gocritic req := resource.DeleteRequest{} req.State = tfsdk.State{ Schema: schema.Schema, @@ -154,7 +155,7 @@ func DeleteTokenRequest(ctx context.Context, schema resource.SchemaResponse, sta // DeleteInstanceResponse creates a test Delete response // Optionally initialize with current state to simulate Terraform framework behavior -func DeleteInstanceResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.DeleteResponse { +func DeleteInstanceResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.DeleteResponse { //nolint:gocritic resp := &resource.DeleteResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, @@ -168,7 +169,7 @@ func DeleteInstanceResponse(ctx context.Context, schema resource.SchemaResponse, return resp } -func DeleteTokenResponse(ctx context.Context, schema resource.SchemaResponse, currentState *token.Model) *resource.DeleteResponse { +func DeleteTokenResponse(ctx context.Context, schema resource.SchemaResponse, currentState *token.Model) *resource.DeleteResponse { //nolint:gocritic resp := &resource.DeleteResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, @@ -183,7 +184,7 @@ func DeleteTokenResponse(ctx context.Context, schema resource.SchemaResponse, cu } // ReadInstanceRequest creates a test Read request -func ReadInstanceRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.ReadRequest { +func ReadInstanceRequest(ctx context.Context, schema resource.SchemaResponse, state instance.Model) resource.ReadRequest { //nolint:gocritic req := resource.ReadRequest{} req.State = tfsdk.State{ Schema: schema.Schema, @@ -193,7 +194,7 @@ func ReadInstanceRequest(ctx context.Context, schema resource.SchemaResponse, st return req } -func ReadTokenRequest(ctx context.Context, schema resource.SchemaResponse, state token.Model) resource.ReadRequest { +func ReadTokenRequest(ctx context.Context, schema resource.SchemaResponse, state token.Model) resource.ReadRequest { //nolint:gocritic req := resource.ReadRequest{} req.State = tfsdk.State{ Schema: schema.Schema, @@ -204,7 +205,7 @@ func ReadTokenRequest(ctx context.Context, schema resource.SchemaResponse, state } // ReadInstanceResponse creates a test Read response -func ReadInstanceResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.ReadResponse { +func ReadInstanceResponse(ctx context.Context, schema resource.SchemaResponse, currentState *instance.Model) *resource.ReadResponse { //nolint:gocritic resp := &resource.ReadResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, @@ -218,7 +219,7 @@ func ReadInstanceResponse(ctx context.Context, schema resource.SchemaResponse, c return resp } -func ReadTokenResponse(ctx context.Context, schema resource.SchemaResponse, currentState *token.Model) *resource.ReadResponse { +func ReadTokenResponse(ctx context.Context, schema resource.SchemaResponse, currentState *token.Model) *resource.ReadResponse { //nolint:gocritic resp := &resource.ReadResponse{} resp.State = tfsdk.State{ Schema: schema.Schema, diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index e0a84f1d9..b89afefbb 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -56,7 +56,7 @@ func NewInstanceTokenResourceEmpty() resource.Resource { return &tokenResource{} } -func NewInstanceTokenResource(client modelexperiments.DefaultAPI, providerData core.ProviderData) resource.Resource { +func NewInstanceTokenResource(client modelexperiments.DefaultAPI, providerData core.ProviderData) resource.Resource { //nolint:gocritic return &tokenResource{ client: client, providerData: providerData, @@ -472,8 +472,8 @@ func (i *tokenResource) Delete(ctx context.Context, req resource.DeleteRequest, deleteTokenReq := i.client.DeleteInstanceToken(ctx, projectId, region, tokenId, instanceId) _, err := i.client.DeleteInstanceTokenExecute(deleteTokenReq) if err != nil { - oapiErr, ok := err.(*oapierror.GenericOpenAPIError) - if ok { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { if oapiErr.StatusCode == http.StatusNotFound { resp.State.RemoveResource(ctx) return @@ -623,8 +623,8 @@ func DeleteMExpTokenWaitHandler(ctx context.Context, a modelexperiments.DefaultA getTokenReq := a.GetInstanceToken(ctx, projectId, region, tokenId, instanceId) _, err = a.GetInstanceTokenExecute(getTokenReq) if err != nil { - oapiErr, ok := err.(*oapierror.GenericOpenAPIError) - if ok { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { if oapiErr.StatusCode == http.StatusNotFound { return true, nil, nil } diff --git a/stackit/internal/services/modelexperiments/token/resource_create_test.go b/stackit/internal/services/modelexperiments/token/resource_create_test.go index 8b2f4d791..2f4b9b021 100644 --- a/stackit/internal/services/modelexperiments/token/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_create_test.go @@ -9,12 +9,12 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "go.uber.org/mock/gomock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestCreate_Success(t *testing.T) { @@ -76,20 +76,40 @@ func TestCreate_Success(t *testing.T) { resp := testutils.CreateResponse(schemaResp) tokenRes.Create(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Create should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Create should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } var createdState token.Model diags := resp.State.Get(tc.Ctx, &createdState) - require.False(t, diags.HasError(), "Failed to get state") - - require.Equal(t, tokenId.String(), createdState.TokenId.ValueString(), "Should be equal") - require.Equal(t, projectId.String(), createdState.ProjectId.ValueString(), "Should be equal") - require.Equal(t, instanceId.String(), createdState.InstanceId.ValueString(), "Should be equal") - require.Equal(t, name, createdState.Name.ValueString(), "Should be equal") - require.Equal(t, "active", createdState.State.ValueString(), "Should be equal") - require.Equal(t, description, createdState.Description.ValueString(), "Should be equal") - require.Equal(t, "token", createdState.Token.ValueString(), "Should be equal") - require.Equal(t, utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString(), createdState.Id.ValueString(), "Should be equal") + if diags.HasError() { + t.Fatalf("failed to get state") + } + + if tokenId.String() != createdState.TokenId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", tokenId.String(), createdState.TokenId.ValueString()) + } + if projectId.String() != createdState.ProjectId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", projectId.String(), createdState.ProjectId.ValueString()) + } + if instanceId.String() != createdState.InstanceId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", instanceId.String(), createdState.InstanceId.ValueString()) + } + if name != createdState.Name.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", name, createdState.Name.ValueString()) + } + if createdState.State.ValueString() != "active" { + t.Fatalf("Should be equal - expected %v, got %v", "active", createdState.State.ValueString()) + } + if description != createdState.Description.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", description, createdState.Description.ValueString()) + } + if createdState.Token.ValueString() != "token" { + t.Fatalf("Should be equal - expected %v, got %v", "token", createdState.Token.ValueString()) + } + if utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString() != createdState.Id.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString(), createdState.Id.ValueString()) + } } func TestCreate_TokenIdEmpty(t *testing.T) { @@ -137,13 +157,19 @@ func TestCreate_TokenIdEmpty(t *testing.T) { resp := testutils.CreateResponse(schemaResp) tokenRes.Create(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Create should not succeed but got no errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Create should not succeed but got no errors") + } // state should not be created var createdState *token.Model diags := resp.State.Get(tc.Ctx, &createdState) - require.False(t, diags.HasError(), "Failed to get state") - require.Nil(t, createdState) + if diags.HasError() { + t.Fatalf("failed to get state") + } + if createdState != nil { + t.Fatalf("expected nil, got %v", createdState) + } } func TestCreate_CreateTokenFailure(t *testing.T) { @@ -182,13 +208,19 @@ func TestCreate_CreateTokenFailure(t *testing.T) { resp := testutils.CreateResponse(schemaResp) tokenRes.Create(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Create should not succeed but got no errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Create should not succeed but got no errors") + } // state should not be created var createdState *token.Model diags := resp.State.Get(tc.Ctx, &createdState) - require.False(t, diags.HasError(), "Failed to get state") - require.Nil(t, createdState) + if diags.HasError() { + t.Fatalf("failed to get state") + } + if createdState != nil { + t.Fatalf("expected nil, got %v", createdState) + } } func TestCreate_GetTokenFailure(t *testing.T) { @@ -243,19 +275,39 @@ func TestCreate_GetTokenFailure(t *testing.T) { resp := testutils.CreateResponse(schemaResp) tokenRes.Create(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Create should not succeed but got no errors") + if !resp.Diagnostics.HasError() { + t.Fatalf("Create should not succeed but got no errors") + } // state should be created var createdState token.Model diags := resp.State.Get(tc.Ctx, &createdState) - require.False(t, diags.HasError(), "Failed to get state") - - require.Equal(t, tokenId.String(), createdState.TokenId.ValueString(), "Should be equal") - require.Equal(t, projectId.String(), createdState.ProjectId.ValueString(), "Should be equal") - require.Equal(t, instanceId.String(), createdState.InstanceId.ValueString(), "Should be equal") - require.Equal(t, name, createdState.Name.ValueString(), "Should be equal") - require.Equal(t, "unknown", createdState.State.ValueString(), "Should be equal") - require.Equal(t, description, createdState.Description.ValueString(), "Should be equal") - require.Equal(t, "token", createdState.Token.ValueString(), "Should be equal") - require.Equal(t, utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString(), createdState.Id.ValueString(), "Should be equal") + if diags.HasError() { + t.Fatalf("failed to get state") + } + + if tokenId.String() != createdState.TokenId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", tokenId.String(), createdState.TokenId.ValueString()) + } + if projectId.String() != createdState.ProjectId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", projectId.String(), createdState.ProjectId.ValueString()) + } + if instanceId.String() != createdState.InstanceId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", instanceId.String(), createdState.InstanceId.ValueString()) + } + if name != createdState.Name.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", name, createdState.Name.ValueString()) + } + if createdState.State.ValueString() != "unknown" { + t.Fatalf("Should be equal - expected %v, got %v", "unknown", createdState.State.ValueString()) + } + if description != createdState.Description.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", description, createdState.Description.ValueString()) + } + if createdState.Token.ValueString() != "token" { + t.Fatalf("Should be equal - expected %v, got %v", "token", createdState.Token.ValueString()) + } + if utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString() != createdState.Id.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString(), createdState.Id.ValueString()) + } } diff --git a/stackit/internal/services/modelexperiments/token/resource_delete_test.go b/stackit/internal/services/modelexperiments/token/resource_delete_test.go index 178ff1f0a..e0a7a4bf6 100644 --- a/stackit/internal/services/modelexperiments/token/resource_delete_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_delete_test.go @@ -9,11 +9,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "go.uber.org/mock/gomock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestDelete_Success(t *testing.T) { @@ -55,7 +55,9 @@ func TestDelete_Success(t *testing.T) { resp := testutils.DeleteTokenResponse(tc.Ctx, schemaResp, nil) tokenRes.Delete(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } } func TestDelete_DeleteTokenFailed(t *testing.T) { @@ -94,14 +96,20 @@ func TestDelete_DeleteTokenFailed(t *testing.T) { resp := testutils.DeleteTokenResponse(tc.Ctx, schemaResp, &state) tokenRes.Delete(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed") + if !resp.Diagnostics.HasError() { + t.Fatalf("Delete should not succeed") + } - //state should not be removed + // state should not be removed var deletedState token.Model diags := resp.State.Get(tc.Ctx, &deletedState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + if diags.HasError() { + t.Fatalf("failed to get state") + } - require.Equal(t, instanceId.String(), deletedState.InstanceId.ValueString()) + if instanceId.String() != deletedState.InstanceId.ValueString() { + t.Fatalf("expected %v, got %v", instanceId.String(), deletedState.InstanceId.ValueString()) + } } func TestDelete_TokenNotFound(t *testing.T) { @@ -140,13 +148,19 @@ func TestDelete_TokenNotFound(t *testing.T) { resp := testutils.DeleteTokenResponse(tc.Ctx, schemaResp, &state) tokenRes.Delete(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) + } - //state should be removed + // state should be removed var deletedState *token.Model diags := resp.State.Get(tc.Ctx, &deletedState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) - require.Nil(t, deletedState) + if diags.HasError() { + t.Fatalf("failed to get state") + } + if deletedState != nil { + t.Fatalf("should be nil") + } } func TestDelete_GetTokenFailed(t *testing.T) { @@ -188,12 +202,18 @@ func TestDelete_GetTokenFailed(t *testing.T) { resp := testutils.DeleteTokenResponse(tc.Ctx, schemaResp, &state) tokenRes.Delete(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Delete should not succeed") + if !resp.Diagnostics.HasError() { + t.Fatalf("Delete should not succeed") + } - //state should not be removed + // state should not be removed var deletedState token.Model diags := resp.State.Get(tc.Ctx, &deletedState) - require.False(t, diags.HasError(), "Failed to get state: %v", diags.Errors()) + if diags.HasError() { + t.Fatalf("failed to get state") + } - require.Equal(t, instanceId.String(), deletedState.InstanceId.ValueString()) + if instanceId.String() != deletedState.InstanceId.ValueString() { + t.Fatalf("expected %v, got %v", instanceId.String(), deletedState.InstanceId.ValueString()) + } } diff --git a/stackit/internal/services/modelexperiments/token/resource_read_test.go b/stackit/internal/services/modelexperiments/token/resource_read_test.go index 3b4926850..87ad35910 100644 --- a/stackit/internal/services/modelexperiments/token/resource_read_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_read_test.go @@ -10,12 +10,12 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "go.uber.org/mock/gomock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestRead_Success(t *testing.T) { @@ -69,73 +69,19 @@ func TestRead_Success(t *testing.T) { req := testutils.ReadTokenRequest(tc.Ctx, schemaResp, model) resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, nil) - tokenRes.Read(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) - - // state should be set - var refreshedState token.Model - diags := resp.State.Get(tc.Ctx, &refreshedState) - require.False(t, diags.HasError(), "Failed to get state") - - require.Equal(t, tokenId.String(), refreshedState.TokenId.ValueString(), "Should be equal") - require.Equal(t, projectId.String(), refreshedState.ProjectId.ValueString(), "Should be equal") - require.Equal(t, instanceId.String(), refreshedState.InstanceId.ValueString(), "Should be equal") - require.Equal(t, name, refreshedState.Name.ValueString(), "Should be equal") - require.Equal(t, "active", refreshedState.State.ValueString(), "Should be equal") - require.Equal(t, description, refreshedState.Description.ValueString(), "Should be equal") - require.Equal(t, tokenContent, refreshedState.Token.ValueString(), "Should be equal") - require.Equal(t, id.ValueString(), refreshedState.Id.ValueString(), "Should be equal") - require.Equal(t, region, refreshedState.Region.ValueString(), "Should be equal") - require.Equal(t, "2099-01-01T00:00:00Z", refreshedState.ValidUntil.ValueString(), "Should be equal") -} - -func TestRead_TokenIdEmptyFailure(t *testing.T) { - tc := testutils.NewTestContext(t) - - projectId := uuid.New() - name := "token" - region := "eu01" - description := "token description" - instanceId := uuid.New() - tokenId := uuid.New() - tokenContent := "token" - state := "active" - id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) - - providerData := core.ProviderData{ - DefaultRegion: "eu01", - } - tokenRes := token.NewInstanceTokenResource(tc.MockInstanceCLient, providerData) - - schemaResp := resource.SchemaResponse{} - tokenRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) - - model := token.Model{ - ProjectId: types.StringValue(projectId.String()), - Name: types.StringValue(name), - Region: types.StringValue(region), - Description: types.StringValue(description), - InstanceId: types.StringValue(instanceId.String()), - Labels: types.MapNull(types.StringType), - Token: types.StringValue(tokenContent), - TokenId: types.StringValue(""), - Id: id, - State: types.StringValue(state), - ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), + if resp.Diagnostics.HasError() { + t.Fatalf("Get should succeed but got errors") } - - req := testutils.ReadTokenRequest(tc.Ctx, schemaResp, model) - resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, &model) - - tokenRes.Read(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) - // state should be removed var refreshedState *token.Model diags := resp.State.Get(tc.Ctx, &refreshedState) - require.False(t, diags.HasError(), "Failed to get state") - require.Nil(t, refreshedState) + if diags.HasError() { + t.Fatalf("failed to get state") + } + if refreshedState != nil { + t.Fatalf("should be nil") + } } func TestRead_TokenNotFound(t *testing.T) { @@ -183,14 +129,19 @@ func TestRead_TokenNotFound(t *testing.T) { resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, &model) tokenRes.Read(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Get should succeed but got errors") + } // state should be removed var refreshedState *token.Model diags := resp.State.Get(tc.Ctx, &refreshedState) - require.False(t, diags.HasError(), "Failed to get state") - require.Nil(t, refreshedState) - + if diags.HasError() { + t.Fatalf("failed to get state") + } + if refreshedState != nil { + t.Fatalf("should be nil") + } } func TestRead_GetTokenRequestFailed(t *testing.T) { @@ -238,23 +189,47 @@ func TestRead_GetTokenRequestFailed(t *testing.T) { resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, &model) tokenRes.Read(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Get should not succeed") + if !resp.Diagnostics.HasError() { + t.Fatalf("Get should not succeed") + } // state should not be edited var refreshedState token.Model diags := resp.State.Get(tc.Ctx, &refreshedState) - require.False(t, diags.HasError(), "Failed to get state") - - require.Equal(t, tokenId.String(), refreshedState.TokenId.ValueString(), "Should be equal") - require.Equal(t, projectId.String(), refreshedState.ProjectId.ValueString(), "Should be equal") - require.Equal(t, instanceId.String(), refreshedState.InstanceId.ValueString(), "Should be equal") - require.Equal(t, name, refreshedState.Name.ValueString(), "Should be equal") - require.Equal(t, "active", refreshedState.State.ValueString(), "Should be equal") - require.Equal(t, description, refreshedState.Description.ValueString(), "Should be equal") - require.Equal(t, tokenContent, refreshedState.Token.ValueString(), "Should be equal") - require.Equal(t, id.ValueString(), refreshedState.Id.ValueString(), "Should be equal") - require.Equal(t, region, refreshedState.Region.ValueString(), "Should be equal") - require.Equal(t, "2099-01-01T00:00:00Z", refreshedState.ValidUntil.ValueString(), "Should be equal") + if diags.HasError() { + t.Fatalf("failed to get state") + } + + if tokenId.String() != refreshedState.TokenId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", tokenId.String(), refreshedState.TokenId.ValueString()) + } + if projectId.String() != refreshedState.ProjectId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", projectId.String(), refreshedState.ProjectId.ValueString()) + } + if instanceId.String() != refreshedState.InstanceId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", instanceId.String(), refreshedState.InstanceId.ValueString()) + } + if name != refreshedState.Name.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", name, refreshedState.Name.ValueString()) + } + if refreshedState.State.ValueString() != "active" { + t.Fatalf("Should be equal - expected %v, got %v", "active", refreshedState.State.ValueString()) + } + if description != refreshedState.Description.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", description, refreshedState.Description.ValueString()) + } + if tokenContent != refreshedState.Token.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", tokenContent, refreshedState.Token.ValueString()) + } + if id.ValueString() != refreshedState.Id.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", id.ValueString(), refreshedState.Id.ValueString()) + } + if region != refreshedState.Region.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", region, refreshedState.Region.ValueString()) + } + if refreshedState.ValidUntil.ValueString() != "2099-01-01T00:00:00Z" { + t.Fatalf("Should be equal - expected %v, got %v", "2099-01-01T00:00:00Z", refreshedState.ValidUntil.ValueString()) + } } func TestRead_TokenInvalidError(t *testing.T) { @@ -310,11 +285,17 @@ func TestRead_TokenInvalidError(t *testing.T) { resp := testutils.ReadTokenResponse(tc.Ctx, schemaResp, &model) tokenRes.Read(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Get should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("Get should succeed but got errors") + } // state should be removed var refreshedState *token.Model diags := resp.State.Get(tc.Ctx, &refreshedState) - require.False(t, diags.HasError(), "Failed to get state") - require.Nil(t, refreshedState) + if diags.HasError() { + t.Fatalf("failed to get state") + } + if refreshedState != nil { + t.Fatalf("should be nil") + } } diff --git a/stackit/internal/services/modelexperiments/token/resource_update_test.go b/stackit/internal/services/modelexperiments/token/resource_update_test.go index d1d44a06b..d8bc5e608 100644 --- a/stackit/internal/services/modelexperiments/token/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_update_test.go @@ -10,12 +10,12 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "go.uber.org/mock/gomock" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestUpdate_Success(t *testing.T) { @@ -83,23 +83,22 @@ func TestUpdate_Success(t *testing.T) { // Execute Update tokenRes.Update(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("update should succeed") + } // state should be updated var updatedState token.Model diags := resp.State.Get(tc.Ctx, &updatedState) - require.False(t, diags.HasError(), "Failed to get state") - - require.Equal(t, tokenId.String(), updatedState.TokenId.ValueString(), "Should be equal") - require.Equal(t, projectId.String(), updatedState.ProjectId.ValueString(), "Should be equal") - require.Equal(t, instanceId.String(), updatedState.InstanceId.ValueString(), "Should be equal") - require.Equal(t, nameUpdated, updatedState.Name.ValueString(), "Should be equal") - require.Equal(t, "active", updatedState.State.ValueString(), "Should be equal") - require.Equal(t, descriptionUpdated, updatedState.Description.ValueString(), "Should be equal") - require.Equal(t, tokenContent, updatedState.Token.ValueString(), "Should be equal") - require.Equal(t, id.ValueString(), updatedState.Id.ValueString(), "Should be equal") - require.Equal(t, region, updatedState.Region.ValueString(), "Should be equal") - require.Equal(t, "2099-01-01T00:00:00Z", updatedState.ValidUntil.ValueString(), "Should be equal") + if diags.HasError() { + t.Fatalf("failed to get state") + } + if updatedState.Name.ValueString() != nameUpdated { + t.Fatalf("should be equal") + } + if updatedState.Description.ValueString() != descriptionUpdated { + t.Fatalf("should be equal") + } } func TestUpdate_TokenNotFound(t *testing.T) { @@ -159,13 +158,19 @@ func TestUpdate_TokenNotFound(t *testing.T) { // Execute Update tokenRes.Update(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) + if resp.Diagnostics.HasError() { + t.Fatalf("update should succeed") + } // state should be removed var updatedState *token.Model diags := resp.State.Get(tc.Ctx, &updatedState) - require.False(t, diags.HasError(), "Failed to get state") - require.Nil(t, updatedState) + if diags.HasError() { + t.Fatalf("failed to get state") + } + if updatedState != nil { + t.Fatalf("state should be nil") + } } func TestUpdate_TokenUpdateError(t *testing.T) { @@ -225,23 +230,23 @@ func TestUpdate_TokenUpdateError(t *testing.T) { // Execute Update tokenRes.Update(tc.Ctx, req, resp) - require.True(t, resp.Diagnostics.HasError(), "Update should not succeed") + if !resp.Diagnostics.HasError() { + t.Fatalf("update should not succeed") + } // state should not be changed var updatedState token.Model diags := resp.State.Get(tc.Ctx, &updatedState) - require.False(t, diags.HasError(), "Failed to get state") - - require.Equal(t, tokenId.String(), updatedState.TokenId.ValueString(), "Should be equal") - require.Equal(t, projectId.String(), updatedState.ProjectId.ValueString(), "Should be equal") - require.Equal(t, instanceId.String(), updatedState.InstanceId.ValueString(), "Should be equal") - require.Equal(t, name, updatedState.Name.ValueString(), "Should be equal") - require.Equal(t, "active", updatedState.State.ValueString(), "Should be equal") - require.Equal(t, description, updatedState.Description.ValueString(), "Should be equal") - require.Equal(t, tokenContent, updatedState.Token.ValueString(), "Should be equal") - require.Equal(t, id.ValueString(), updatedState.Id.ValueString(), "Should be equal") - require.Equal(t, region, updatedState.Region.ValueString(), "Should be equal") - require.Equal(t, "2099-01-01T00:00:00Z", updatedState.ValidUntil.ValueString(), "Should be equal") + if diags.HasError() { + t.Fatalf("failed to get state") + } + + if updatedState.Name.ValueString() != name { + t.Fatalf("should be equal") + } + if updatedState.Description.ValueString() != description { + t.Fatalf("should be equal") + } } func TestUpdate_TokenInvalidStateError(t *testing.T) { @@ -309,11 +314,18 @@ func TestUpdate_TokenInvalidStateError(t *testing.T) { // Execute Update tokenRes.Update(tc.Ctx, req, resp) - require.False(t, resp.Diagnostics.HasError(), "Update should succeed") + + if resp.Diagnostics.HasError() { + t.Fatalf("update should succeed") + } // state should not be removed var updatedState *token.Model diags := resp.State.Get(tc.Ctx, &updatedState) - require.False(t, diags.HasError(), "Failed to get state") - require.Nil(t, updatedState) + if diags.HasError() { + t.Fatalf("failed to get state") + } + if updatedState != nil { + t.Fatalf("state should be nil") + } } From 3bde33324170d9a44d56148d2965a5ee77229c5f Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Thu, 25 Jun 2026 13:08:11 +0200 Subject: [PATCH 23/30] feat: Change wait handler to public go sdk --- go.mod | 3 - go.sum | 2 - .../instance/mock/instance.go | 54 +++++++-------- .../modelexperiments/instance/resource.go | 65 +++---------------- .../instance/resource_create_test.go | 11 +++- .../instance/resource_delete_test.go | 10 ++- .../instance/resource_read_test.go | 2 +- .../instance/resource_test.go | 2 +- .../instance/resource_update_test.go | 2 +- .../modelexperiments_acc_test.go | 34 +--------- .../modelexperiments/token/resource.go | 55 ++-------------- .../token/resource_create_test.go | 18 +++-- .../token/resource_delete_test.go | 10 ++- .../token/resource_read_test.go | 6 +- .../modelexperiments/token/resource_test.go | 26 ++++---- .../token/resource_update_test.go | 6 +- .../services/modelexperiments/utils/util.go | 6 +- .../modelexperiments/utils/util_test.go | 2 +- 18 files changed, 103 insertions(+), 211 deletions(-) diff --git a/go.mod b/go.mod index d02ba81da..65bc981e9 100644 --- a/go.mod +++ b/go.mod @@ -52,8 +52,6 @@ require ( golang.org/x/mod v0.37.0 ) -require go.uber.org/mock v0.6.0 - require github.com/stretchr/testify v1.11.1 // indirect require ( @@ -61,7 +59,6 @@ require ( 4d63.com/gochecknoglobals v0.2.2 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect - dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments v0.41.0 dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect dev.gaijin.team/go/golib v0.6.0 // indirect github.com/4meepo/tagalign v1.4.3 // indirect diff --git a/go.sum b/go.sum index c73d31674..9ae4bc5e9 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,6 @@ codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments v0.41.0 h1:vu4HvuP03eT9UAl/3+44wFqm4Em8kVczMfPtF1Oh59c= -dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments v0.41.0/go.mod h1:gh67VQXccqlE/xsWWKZSt+brkbB8RHZymZ8dYo5FunU= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= diff --git a/stackit/internal/services/modelexperiments/instance/mock/instance.go b/stackit/internal/services/modelexperiments/instance/mock/instance.go index 08735830a..9c42629b0 100644 --- a/stackit/internal/services/modelexperiments/instance/mock/instance.go +++ b/stackit/internal/services/modelexperiments/instance/mock/instance.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api (interfaces: DefaultAPI) +// Source: github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api (interfaces: DefaultAPI) // // Generated by this command: // -// mockgen -destination=./mock/instance.go -package=mock_instance dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api DefaultAPI +// mockgen -destination=./mock/instance.go -package=mock_instance github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api DefaultAPI // // Package mock_instance is a generated GoMock package. @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - v1api "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" + v1api "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" gomock "go.uber.org/mock/gomock" ) @@ -85,10 +85,10 @@ func (mr *MockDefaultAPIMockRecorder) CreateInstanceToken(ctx, projectId, region } // CreateInstanceTokenExecute mocks base method. -func (m *MockDefaultAPI) CreateInstanceTokenExecute(r v1api.ApiCreateInstanceTokenRequest) (*v1api.CreateTokenResponse, error) { +func (m *MockDefaultAPI) CreateInstanceTokenExecute(r v1api.ApiCreateInstanceTokenRequest) (*v1api.CreateInstanceTokenResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateInstanceTokenExecute", r) - ret0, _ := ret[0].(*v1api.CreateTokenResponse) + ret0, _ := ret[0].(*v1api.CreateInstanceTokenResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -129,24 +129,24 @@ func (mr *MockDefaultAPIMockRecorder) DeleteInstanceExecute(r any) *gomock.Call } // DeleteInstanceToken mocks base method. -func (m *MockDefaultAPI) DeleteInstanceToken(ctx context.Context, projectId, regionId, tId, instanceId string) v1api.ApiDeleteInstanceTokenRequest { +func (m *MockDefaultAPI) DeleteInstanceToken(ctx context.Context, projectId, regionId, tokenId, instanceId string) v1api.ApiDeleteInstanceTokenRequest { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteInstanceToken", ctx, projectId, regionId, tId, instanceId) + ret := m.ctrl.Call(m, "DeleteInstanceToken", ctx, projectId, regionId, tokenId, instanceId) ret0, _ := ret[0].(v1api.ApiDeleteInstanceTokenRequest) return ret0 } // DeleteInstanceToken indicates an expected call of DeleteInstanceToken. -func (mr *MockDefaultAPIMockRecorder) DeleteInstanceToken(ctx, projectId, regionId, tId, instanceId any) *gomock.Call { +func (mr *MockDefaultAPIMockRecorder) DeleteInstanceToken(ctx, projectId, regionId, tokenId, instanceId any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).DeleteInstanceToken), ctx, projectId, regionId, tId, instanceId) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).DeleteInstanceToken), ctx, projectId, regionId, tokenId, instanceId) } // DeleteInstanceTokenExecute mocks base method. -func (m *MockDefaultAPI) DeleteInstanceTokenExecute(r v1api.ApiDeleteInstanceTokenRequest) (*v1api.DeleteTokenResponse, error) { +func (m *MockDefaultAPI) DeleteInstanceTokenExecute(r v1api.ApiDeleteInstanceTokenRequest) (*v1api.DeleteInstanceTokenResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeleteInstanceTokenExecute", r) - ret0, _ := ret[0].(*v1api.DeleteTokenResponse) + ret0, _ := ret[0].(*v1api.DeleteInstanceTokenResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -187,24 +187,24 @@ func (mr *MockDefaultAPIMockRecorder) GetInstanceExecute(r any) *gomock.Call { } // GetInstanceToken mocks base method. -func (m *MockDefaultAPI) GetInstanceToken(ctx context.Context, projectId, regionId, tId, instanceId string) v1api.ApiGetInstanceTokenRequest { +func (m *MockDefaultAPI) GetInstanceToken(ctx context.Context, projectId, regionId, tokenId, instanceId string) v1api.ApiGetInstanceTokenRequest { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetInstanceToken", ctx, projectId, regionId, tId, instanceId) + ret := m.ctrl.Call(m, "GetInstanceToken", ctx, projectId, regionId, tokenId, instanceId) ret0, _ := ret[0].(v1api.ApiGetInstanceTokenRequest) return ret0 } // GetInstanceToken indicates an expected call of GetInstanceToken. -func (mr *MockDefaultAPIMockRecorder) GetInstanceToken(ctx, projectId, regionId, tId, instanceId any) *gomock.Call { +func (mr *MockDefaultAPIMockRecorder) GetInstanceToken(ctx, projectId, regionId, tokenId, instanceId any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).GetInstanceToken), ctx, projectId, regionId, tId, instanceId) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).GetInstanceToken), ctx, projectId, regionId, tokenId, instanceId) } // GetInstanceTokenExecute mocks base method. -func (m *MockDefaultAPI) GetInstanceTokenExecute(r v1api.ApiGetInstanceTokenRequest) (*v1api.GetTokenResponse, error) { +func (m *MockDefaultAPI) GetInstanceTokenExecute(r v1api.ApiGetInstanceTokenRequest) (*v1api.GetInstanceTokenResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetInstanceTokenExecute", r) - ret0, _ := ret[0].(*v1api.GetTokenResponse) + ret0, _ := ret[0].(*v1api.GetInstanceTokenResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -230,10 +230,10 @@ func (mr *MockDefaultAPIMockRecorder) ListInstanceTokens(ctx, projectId, regionI } // ListInstanceTokensExecute mocks base method. -func (m *MockDefaultAPI) ListInstanceTokensExecute(r v1api.ApiListInstanceTokensRequest) (*v1api.ListTokenResponse, error) { +func (m *MockDefaultAPI) ListInstanceTokensExecute(r v1api.ApiListInstanceTokensRequest) (*v1api.ListInstanceTokensResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListInstanceTokensExecute", r) - ret0, _ := ret[0].(*v1api.ListTokenResponse) + ret0, _ := ret[0].(*v1api.ListInstanceTokensResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -259,10 +259,10 @@ func (mr *MockDefaultAPIMockRecorder) ListInstances(ctx, projectId, regionId any } // ListInstancesExecute mocks base method. -func (m *MockDefaultAPI) ListInstancesExecute(r v1api.ApiListInstancesRequest) (*v1api.ListInstanceResponse, error) { +func (m *MockDefaultAPI) ListInstancesExecute(r v1api.ApiListInstancesRequest) (*v1api.ListInstancesResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListInstancesExecute", r) - ret0, _ := ret[0].(*v1api.ListInstanceResponse) + ret0, _ := ret[0].(*v1api.ListInstancesResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -303,24 +303,24 @@ func (mr *MockDefaultAPIMockRecorder) PartialUpdateInstanceExecute(r any) *gomoc } // PartialUpdateInstanceToken mocks base method. -func (m *MockDefaultAPI) PartialUpdateInstanceToken(ctx context.Context, projectId, regionId, tId, instanceId string) v1api.ApiPartialUpdateInstanceTokenRequest { +func (m *MockDefaultAPI) PartialUpdateInstanceToken(ctx context.Context, projectId, regionId, tokenId, instanceId string) v1api.ApiPartialUpdateInstanceTokenRequest { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PartialUpdateInstanceToken", ctx, projectId, regionId, tId, instanceId) + ret := m.ctrl.Call(m, "PartialUpdateInstanceToken", ctx, projectId, regionId, tokenId, instanceId) ret0, _ := ret[0].(v1api.ApiPartialUpdateInstanceTokenRequest) return ret0 } // PartialUpdateInstanceToken indicates an expected call of PartialUpdateInstanceToken. -func (mr *MockDefaultAPIMockRecorder) PartialUpdateInstanceToken(ctx, projectId, regionId, tId, instanceId any) *gomock.Call { +func (mr *MockDefaultAPIMockRecorder) PartialUpdateInstanceToken(ctx, projectId, regionId, tokenId, instanceId any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PartialUpdateInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).PartialUpdateInstanceToken), ctx, projectId, regionId, tId, instanceId) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PartialUpdateInstanceToken", reflect.TypeOf((*MockDefaultAPI)(nil).PartialUpdateInstanceToken), ctx, projectId, regionId, tokenId, instanceId) } // PartialUpdateInstanceTokenExecute mocks base method. -func (m *MockDefaultAPI) PartialUpdateInstanceTokenExecute(r v1api.ApiPartialUpdateInstanceTokenRequest) (*v1api.PartialUpdateTokenResponse, error) { +func (m *MockDefaultAPI) PartialUpdateInstanceTokenExecute(r v1api.ApiPartialUpdateInstanceTokenRequest) (*v1api.PartialUpdateInstanceTokenResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PartialUpdateInstanceTokenExecute", r) - ret0, _ := ret[0].(*v1api.PartialUpdateTokenResponse) + ret0, _ := ret[0].(*v1api.PartialUpdateInstanceTokenResponse) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 5d2d94a0a..a293b0db6 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -6,9 +6,7 @@ import ( "errors" "fmt" "net/http" - "time" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -18,7 +16,8 @@ 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/stackit-sdk-go/core/wait" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api/wait" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" serviceEnablementWait "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api/wait" @@ -55,7 +54,7 @@ type Model struct { // NewInstanceResource is a helper function to simplify the provider implementation. // -//go:generate mockgen -destination=./mock/instance.go -package=mock_instance dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api DefaultAPI +//go:generate mockgen -destination=./mock/instance.go -package=mock_instance github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api DefaultAPI func NewInstanceResourceEmpty() resource.Resource { return &instanceResource{} } @@ -96,7 +95,7 @@ func (i *instanceResource) Configure(ctx context.Context, req resource.Configure if resp.Diagnostics.HasError() { return } - i.client = apiClient + i.client = apiClient.DefaultAPI i.serviceEnablementClient = serviceEnablementClient tflog.Info(ctx, "Model experiments client configured") } @@ -313,7 +312,7 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques } // If model experiments instance is impaired, write state avoid dangling resources and return - waitResp, err := CreateMExpInstanceWaitHandler(ctx, i.client, region, projectId, instanceId).WaitWithContext(ctx) + waitResp, err := wait.CreateInstanceWaitHandler(ctx, i.client, region, projectId, instanceId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance", fmt.Sprintf("Waiting for instance to be active: %v", err)) } @@ -493,7 +492,7 @@ func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteReques ctx = core.LogResponse(ctx) - _, err = DeleteMExpInstanceWaitHandler(ctx, i.client, region, projectId, instanceId). + _, err = wait.DeleteInstanceWaitHandler(ctx, i.client, region, projectId, instanceId). WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Waiting for instance to be deleted: %v", err)) @@ -526,7 +525,7 @@ func mapCreateResponse(ctx context.Context, instanceCreateResp *modelexperiments if waitResp == nil { model.State = types.StringValue("unknown") } else { - model.State = types.StringValue(waitResp.Instance.State) + model.State = types.StringValue(string(waitResp.Instance.State)) model.BucketName = types.StringValue(*waitResp.Instance.BucketName) } model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceCreateResp.Instance.Id) @@ -559,7 +558,7 @@ func mapInstance(ctx context.Context, instance *modelexperiments.Instance, model model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.Region.ValueString(), model.InstanceId.ValueString()) model.InstanceId = types.StringValue(instance.Id) model.Name = types.StringValue(instance.Name) - model.State = types.StringValue(instance.State) + model.State = types.StringValue(string(instance.State)) model.Description = types.StringPointerValue(instance.Description) model.DeletedExperimentRetention = types.StringPointerValue(instance.DeletedExperimentRetention) model.BucketName = types.StringPointerValue(instance.BucketName) @@ -605,51 +604,3 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstancePaylo DeletedExperimentRetention: conversion.StringValueToPointer(model.DeletedExperimentRetention), }, nil } - -func CreateMExpInstanceWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { - handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - getInstanceRequest := a.GetInstance(ctx, projectId, region, instanceId) - getInstanceResp, err := a.GetInstanceExecute(getInstanceRequest) - if err != nil { - return false, nil, err - } - if getInstanceResp.Instance.State == modelexperimentsutils.INSTANCESTATE_ACTIVE { - return true, getInstanceResp, nil - } - if getInstanceResp.Instance.State == modelexperimentsutils.INSTANCESTATE_IMPAIRED { - return true, getInstanceResp, fmt.Errorf("AI model experiments instance is impaired") - } - - return false, nil, nil - }) - - handler.SetTimeout(10 * time.Minute) - - return handler -} - -func DeleteMExpInstanceWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { - handler := wait.New( - func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - getInstanceReq := a.GetInstance(ctx, projectId, region, instanceId) - _, err = a.GetInstanceExecute(getInstanceReq) - if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if !errors.As(err, &oapiErr) { - return false, nil, err - } - if oapiErr.StatusCode != http.StatusNotFound { - return false, nil, err - } - - return true, nil, nil - } - - return false, nil, nil - }, - ) - - handler.SetTimeout(10 * time.Minute) - - return handler -} diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index e6483b56d..5bcd6b01c 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -6,10 +6,10 @@ import ( "net/http/httptest" "testing" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/stackitcloud/stackit-sdk-go/core/config" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" "go.uber.org/mock/gomock" @@ -98,7 +98,10 @@ func TestCreate_Success(t *testing.T) { BucketName: new("bucket"), }, } - tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(getResp, nil) schemaResp := resource.SchemaResponse{} @@ -276,7 +279,9 @@ func TestCreate_GetInstanceFailure(t *testing.T) { tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) tc.MockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(createResp, nil) - tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) schemaResp := resource.SchemaResponse{} diff --git a/stackit/internal/services/modelexperiments/instance/resource_delete_test.go b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go index 47c4e9bd4..77b1cd656 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_delete_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go @@ -4,11 +4,11 @@ import ( "net/http" "testing" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" "go.uber.org/mock/gomock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -30,7 +30,9 @@ func TestDelete_Success(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, } - tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -169,7 +171,9 @@ func TestDelete_GetInstanceFailed(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusInternalServerError, } - tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ diff --git a/stackit/internal/services/modelexperiments/instance/resource_read_test.go b/stackit/internal/services/modelexperiments/instance/resource_read_test.go index 1d194da0e..f56f3fc86 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_read_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_read_test.go @@ -3,11 +3,11 @@ package instance_test import ( "testing" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" "go.uber.org/mock/gomock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" diff --git a/stackit/internal/services/modelexperiments/instance/resource_test.go b/stackit/internal/services/modelexperiments/instance/resource_test.go index 6fc46178d..447466dac 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_test.go @@ -4,10 +4,10 @@ import ( "context" "testing" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" ) func TestMapInstanceFields(t *testing.T) { diff --git a/stackit/internal/services/modelexperiments/instance/resource_update_test.go b/stackit/internal/services/modelexperiments/instance/resource_update_test.go index 9c01cd28c..9f76a52e8 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_update_test.go @@ -4,11 +4,11 @@ import ( "fmt" "testing" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" "go.uber.org/mock/gomock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" diff --git a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go index 3c0dee01d..af87f79ba 100644 --- a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go +++ b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go @@ -2,20 +2,16 @@ package modelexperiments_test import ( "context" - "errors" "fmt" - "net/http" "slices" "strings" "testing" - "time" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - "github.com/stackitcloud/stackit-sdk-go/core/wait" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" @@ -175,7 +171,7 @@ func testAccCheckModelServingTokenDestroy(s *terraform.State) error { if err != nil { return fmt.Errorf("destroying instance %s during CheckDestroy: %w", items[i].Name, err) } - _, err = deleteModelExperimentsWaitHandler(ctx, client.DefaultAPI, testutil.Region, testutil.ProjectId, items[i].Id).WaitWithContext(ctx) + _, err = wait.DeleteInstanceWaitHandler(ctx, client.DefaultAPI, testutil.Region, testutil.ProjectId, items[i].Id).WaitWithContext(ctx) if err != nil { return fmt.Errorf("destroying token %s during CheckDestroy: waiting for deletion %w", items[i].Name, err) } @@ -183,27 +179,3 @@ func testAccCheckModelServingTokenDestroy(s *terraform.State) error { } return nil } - -func deleteModelExperimentsWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { - handler := wait.New( - func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - _, err = a.GetInstance(ctx, projectId, region, instanceId).Execute() - if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { - if oapiErr.StatusCode == http.StatusNotFound { - return true, nil, nil - } - } - - return false, nil, err - } - - return false, nil, nil - }, - ) - - handler.SetTimeout(10 * time.Minute) - - return handler -} diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index b89afefbb..8edf66740 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -8,7 +8,6 @@ import ( "net/http" "time" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -18,7 +17,8 @@ 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/stackit-sdk-go/core/wait" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" + "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -86,7 +86,7 @@ func (i *tokenResource) Configure(ctx context.Context, req resource.ConfigureReq if resp.Diagnostics.HasError() { return } - i.client = apiClient + i.client = apiClient.DefaultAPI tflog.Info(ctx, "Model experiments client configured") } @@ -271,7 +271,7 @@ func (i *tokenResource) Create(ctx context.Context, req resource.CreateRequest, return } - waitResp, err := CreateMExpTokenWaitHandler(ctx, i.client, region, projectId, instanceId, tokenId).WaitWithContext(ctx) + waitResp, err := wait.CreateInstanceTokenWaitHandler(ctx, i.client, region, projectId, instanceId, tokenId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating AI model experiments instance token", fmt.Sprintf("Waiting for instance to be active: %v", err)) } @@ -485,7 +485,7 @@ func (i *tokenResource) Delete(ctx context.Context, req resource.DeleteRequest, ctx = core.LogResponse(ctx) - _, err = DeleteMExpTokenWaitHandler(ctx, i.client, region, projectId, instanceId, tokenId). + _, err = wait.DeleteInstanceTokenWaitHandler(ctx, i.client, region, projectId, instanceId, tokenId). WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance token", fmt.Sprintf("Waiting for instance to be deleted: %v", err)) @@ -496,7 +496,7 @@ func (i *tokenResource) Delete(ctx context.Context, req resource.DeleteRequest, } // mapCreateResponse maps the instace creation response and GET instance response to the model -func mapCreateResponse(ctx context.Context, instanceTokenResp *modelexperiments.CreateTokenResponse, waitResp *modelexperiments.GetTokenResponse, model *Model, region string) error { +func mapCreateResponse(ctx context.Context, instanceTokenResp *modelexperiments.CreateInstanceTokenResponse, waitResp *modelexperiments.GetInstanceTokenResponse, model *Model, region string) error { if instanceTokenResp == nil { return fmt.Errorf("response input is nil") } @@ -597,46 +597,3 @@ func toUpdatePayload(model *Model) (*modelexperiments.PartialUpdateInstanceToken Labels: labels, }, nil } - -func CreateMExpTokenWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetTokenResponse] { - handler := wait.New(func() (waitFinished bool, response *modelexperiments.GetTokenResponse, err error) { - getTokenReq := a.GetInstanceToken(ctx, projectId, region, tokenId, instanceId) - getTokenResp, err := a.GetInstanceTokenExecute(getTokenReq) - if err != nil { - return false, nil, err - } - if getTokenResp.Token.State == modelexperimentsutils.TOKENSTATE_ACTIVE { - return true, getTokenResp, nil - } - - return false, nil, nil - }) - - handler.SetTimeout(10 * time.Minute) - - return handler -} - -func DeleteMExpTokenWaitHandler(ctx context.Context, a modelexperiments.DefaultAPI, region, projectId, instanceId, tokenId string) *wait.AsyncActionHandler[modelexperiments.GetInstanceResponse] { - handler := wait.New( - func() (waitFinished bool, response *modelexperiments.GetInstanceResponse, err error) { - getTokenReq := a.GetInstanceToken(ctx, projectId, region, tokenId, instanceId) - _, err = a.GetInstanceTokenExecute(getTokenReq) - if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { - if oapiErr.StatusCode == http.StatusNotFound { - return true, nil, nil - } - } - return false, nil, err - } - - return false, nil, nil - }, - ) - - handler.SetTimeout(10 * time.Minute) - - return handler -} diff --git a/stackit/internal/services/modelexperiments/token/resource_create_test.go b/stackit/internal/services/modelexperiments/token/resource_create_test.go index 2f4b9b021..d971f6e13 100644 --- a/stackit/internal/services/modelexperiments/token/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_create_test.go @@ -4,11 +4,11 @@ import ( "testing" "time" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" "go.uber.org/mock/gomock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -28,7 +28,7 @@ func TestCreate_Success(t *testing.T) { tokenId := uuid.New() validUntil := time.Now().Add(10 * time.Minute) - createTokenResp := &modelexperiments.CreateTokenResponse{ + createTokenResp := &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{ Content: "token", Description: &description, @@ -42,7 +42,7 @@ func TestCreate_Success(t *testing.T) { tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{}) tc.MockInstanceCLient.EXPECT().CreateInstanceTokenExecute(gomock.Any()).Return(createTokenResp, nil) - getTokenResp := &modelexperiments.GetTokenResponse{ + getTokenResp := &modelexperiments.GetInstanceTokenResponse{ Token: modelexperiments.TokenMetadata{ Description: &description, Id: tokenId.String(), @@ -52,7 +52,9 @@ func TestCreate_Success(t *testing.T) { ValidUntil: validUntil, }, } - tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(getTokenResp, nil) providerData := core.ProviderData{ @@ -122,7 +124,7 @@ func TestCreate_TokenIdEmpty(t *testing.T) { instanceId := uuid.New() validUntil := time.Now() - createTokenResp := &modelexperiments.CreateTokenResponse{ + createTokenResp := &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{ Content: "token", Description: &description, @@ -234,7 +236,7 @@ func TestCreate_GetTokenFailure(t *testing.T) { tokenId := uuid.New() validUntil := time.Now().Add(10 * time.Minute) - createTokenResp := &modelexperiments.CreateTokenResponse{ + createTokenResp := &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{ Content: "token", Description: &description, @@ -251,7 +253,9 @@ func TestCreate_GetTokenFailure(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: 404, } - tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ diff --git a/stackit/internal/services/modelexperiments/token/resource_delete_test.go b/stackit/internal/services/modelexperiments/token/resource_delete_test.go index e0a7a4bf6..ff2b4335c 100644 --- a/stackit/internal/services/modelexperiments/token/resource_delete_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_delete_test.go @@ -4,11 +4,11 @@ import ( "net/http" "testing" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" "go.uber.org/mock/gomock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -31,7 +31,9 @@ func TestDelete_Success(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, } - tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -178,7 +180,9 @@ func TestDelete_GetTokenFailed(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusInternalServerError, } - tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ diff --git a/stackit/internal/services/modelexperiments/token/resource_read_test.go b/stackit/internal/services/modelexperiments/token/resource_read_test.go index 87ad35910..3dcc7b0b6 100644 --- a/stackit/internal/services/modelexperiments/token/resource_read_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_read_test.go @@ -5,11 +5,11 @@ import ( "testing" "time" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" "go.uber.org/mock/gomock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -32,7 +32,7 @@ func TestRead_Success(t *testing.T) { state := "active" id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) - getTokenResp := &modelexperiments.GetTokenResponse{ + getTokenResp := &modelexperiments.GetInstanceTokenResponse{ Token: modelexperiments.TokenMetadata{ Description: &description, Id: tokenId.String(), @@ -246,7 +246,7 @@ func TestRead_TokenInvalidError(t *testing.T) { id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) validUntil := time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC) - getTokenResp := &modelexperiments.GetTokenResponse{ + getTokenResp := &modelexperiments.GetInstanceTokenResponse{ Token: modelexperiments.TokenMetadata{ Description: &description, Id: tokenId.String(), diff --git a/stackit/internal/services/modelexperiments/token/resource_test.go b/stackit/internal/services/modelexperiments/token/resource_test.go index a725b3f9d..4d707c5cd 100644 --- a/stackit/internal/services/modelexperiments/token/resource_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" ) func TestMapTokenFields(t *testing.T) { @@ -100,8 +100,8 @@ func TestMapCreateResponseFields(t *testing.T) { tests := []struct { description string state *Model - inputCreateResponse *modelexperiments.CreateTokenResponse - inputGetResponse *modelexperiments.GetTokenResponse + inputCreateResponse *modelexperiments.CreateInstanceTokenResponse + inputGetResponse *modelexperiments.GetInstanceTokenResponse expected Model isValid bool }{ @@ -109,25 +109,25 @@ func TestMapCreateResponseFields(t *testing.T) { description: "should error when token create response is nil", state: &Model{}, inputCreateResponse: nil, - inputGetResponse: &modelexperiments.GetTokenResponse{}, + inputGetResponse: &modelexperiments.GetInstanceTokenResponse{}, expected: Model{}, isValid: false, }, { description: "should error when state is nil", state: nil, - inputCreateResponse: &modelexperiments.CreateTokenResponse{}, - inputGetResponse: &modelexperiments.GetTokenResponse{}, + inputCreateResponse: &modelexperiments.CreateInstanceTokenResponse{}, + inputGetResponse: &modelexperiments.GetInstanceTokenResponse{}, expected: Model{}, isValid: false, }, { description: "should error when token id is not present", state: &Model{}, - inputCreateResponse: &modelexperiments.CreateTokenResponse{ + inputCreateResponse: &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{}, }, - inputGetResponse: &modelexperiments.GetTokenResponse{}, + inputGetResponse: &modelexperiments.GetInstanceTokenResponse{}, expected: Model{}, isValid: false, }, @@ -139,7 +139,7 @@ func TestMapCreateResponseFields(t *testing.T) { InstanceId: types.StringValue("id"), Region: types.StringValue("eu01"), }, - inputCreateResponse: &modelexperiments.CreateTokenResponse{ + inputCreateResponse: &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{ Id: "id", Content: "token", @@ -173,7 +173,7 @@ func TestMapCreateResponseFields(t *testing.T) { InstanceId: types.StringValue("id"), Region: types.StringValue("eu01"), }, - inputCreateResponse: &modelexperiments.CreateTokenResponse{ + inputCreateResponse: &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{ Id: "id", Content: "token", @@ -183,7 +183,7 @@ func TestMapCreateResponseFields(t *testing.T) { Name: "name", ValidUntil: time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC), }}, - inputGetResponse: &modelexperiments.GetTokenResponse{ + inputGetResponse: &modelexperiments.GetInstanceTokenResponse{ Token: modelexperiments.TokenMetadata{ State: "active", }, @@ -211,7 +211,7 @@ func TestMapCreateResponseFields(t *testing.T) { InstanceId: types.StringValue("id"), Region: types.StringValue("eu01"), }, - inputCreateResponse: &modelexperiments.CreateTokenResponse{ + inputCreateResponse: &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{ Id: "id", Content: "token", @@ -221,7 +221,7 @@ func TestMapCreateResponseFields(t *testing.T) { Name: "name", ValidUntil: time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC), }}, - inputGetResponse: &modelexperiments.GetTokenResponse{ + inputGetResponse: &modelexperiments.GetInstanceTokenResponse{ Token: modelexperiments.TokenMetadata{ State: "active", }, diff --git a/stackit/internal/services/modelexperiments/token/resource_update_test.go b/stackit/internal/services/modelexperiments/token/resource_update_test.go index d8bc5e608..de954f1cd 100644 --- a/stackit/internal/services/modelexperiments/token/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_update_test.go @@ -5,11 +5,11 @@ import ( "testing" "time" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" "go.uber.org/mock/gomock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -34,7 +34,7 @@ func TestUpdate_Success(t *testing.T) { state := "active" id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) - updateTokenResp := &modelexperiments.PartialUpdateTokenResponse{ + updateTokenResp := &modelexperiments.PartialUpdateInstanceTokenResponse{ Token: modelexperiments.TokenMetadata{ Description: &descriptionUpdated, Id: tokenId.String(), @@ -265,7 +265,7 @@ func TestUpdate_TokenInvalidStateError(t *testing.T) { id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) validUntil := time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC) - updateTokenResp := &modelexperiments.PartialUpdateTokenResponse{ + updateTokenResp := &modelexperiments.PartialUpdateInstanceTokenResponse{ Token: modelexperiments.TokenMetadata{ Description: &descriptionUpdated, Id: tokenId.String(), diff --git a/stackit/internal/services/modelexperiments/utils/util.go b/stackit/internal/services/modelexperiments/utils/util.go index 01ebec05a..018a6c7c8 100644 --- a/stackit/internal/services/modelexperiments/utils/util.go +++ b/stackit/internal/services/modelexperiments/utils/util.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - modelexperiment "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/stackitcloud/stackit-sdk-go/core/config" + modelexperiment "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -28,7 +28,7 @@ const ( TOKENSTATE_INACTIVE = "inactive" ) -func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) modelexperiment.DefaultAPI { +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *modelexperiment.APIClient { apiClientConfigOptions := []config.ConfigurationOption{ config.WithCustomAuth(providerData.RoundTripper), utils.UserAgentConfigOption(providerData.Version), @@ -42,7 +42,7 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags return nil } - return apiClient.DefaultAPI + return apiClient } func ConfigureServiceEnablementClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) serviceenablement.DefaultAPI { diff --git a/stackit/internal/services/modelexperiments/utils/util_test.go b/stackit/internal/services/modelexperiments/utils/util_test.go index 8f9c8b71c..1bbd7f242 100644 --- a/stackit/internal/services/modelexperiments/utils/util_test.go +++ b/stackit/internal/services/modelexperiments/utils/util_test.go @@ -6,10 +6,10 @@ import ( "reflect" "testing" - modelexperiments "dev.azure.com/schwarzit/schwarzit.stackit-public/stackit-sdk-go-internal.git/services/modelexperiments/v1api" "github.com/hashicorp/terraform-plugin-framework/diag" sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" "github.com/stackitcloud/stackit-sdk-go/core/config" + modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" From 98187a0d999af049a70c6d64210be78921d9e781 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Thu, 25 Jun 2026 14:47:03 +0200 Subject: [PATCH 24/30] feat: refractor unit tests and resources --- .../modelexperiments/instance/resource.go | 16 +- .../instance/resource_create_test.go | 204 ++++++------------ .../instance/resource_delete_test.go | 21 +- .../instance/resource_read_test.go | 12 +- .../instance/resource_update_test.go | 19 +- .../modelexperiments_acc_test.go | 4 +- .../modelexperiments/testutils/test_utils.go | 20 +- .../modelexperiments/token/resource.go | 12 +- .../token/resource_create_test.go | 16 +- .../token/resource_delete_test.go | 16 +- .../token/resource_read_test.go | 56 ++++- .../token/resource_update_test.go | 18 +- .../utils/mock/serviceenablement.go | 156 ++++++++++++++ .../services/modelexperiments/utils/util.go | 1 + 14 files changed, 364 insertions(+), 207 deletions(-) create mode 100644 stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index a293b0db6..249e55d1a 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -242,9 +242,7 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) - serviceEnablementReq := i.serviceEnablementClient.EnableServiceRegional(ctx, region, projectId, utils.ModelExperimentsServiceId) - err := i.serviceEnablementClient.EnableServiceRegionalExecute(serviceEnablementReq) - + err := i.serviceEnablementClient.EnableServiceRegional(ctx, region, projectId, utils.ModelExperimentsServiceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -283,8 +281,7 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - createInstanceRequest := i.client.CreateInstance(ctx, projectId, region).CreateInstancePayload(*payload) - createInstanceResp, err := i.client.CreateInstanceExecute(createInstanceRequest) + createInstanceResp, err := i.client.CreateInstance(ctx, projectId, region).CreateInstancePayload(*payload).Execute() if err != nil { core.LogAndAddError( ctx, @@ -358,8 +355,7 @@ func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - getInstanceReq := i.client.GetInstance(ctx, projectId, region, instanceId) - getInstanceResp, err := i.client.GetInstanceExecute(getInstanceReq) + getInstanceResp, err := i.client.GetInstance(ctx, projectId, region, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -424,8 +420,7 @@ func (i *instanceResource) Update(ctx context.Context, req resource.UpdateReques return } - updateInstanceReq := i.client.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload) - updateInstanceResp, err := i.client.PartialUpdateInstanceExecute(updateInstanceReq) + updateInstanceResp, err := i.client.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -476,8 +471,7 @@ func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteReques ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - deleteInstanceReq := i.client.DeleteInstance(ctx, projectId, region, instanceId) - _, err := i.client.DeleteInstanceExecute(deleteInstanceReq) + _, err := i.client.DeleteInstance(ctx, projectId, region, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index 5bcd6b01c..c0520fcfd 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -3,12 +3,11 @@ package instance_test import ( "fmt" "net/http" - "net/http/httptest" "testing" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" modelexperiments "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" "go.uber.org/mock/gomock" @@ -16,11 +15,6 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" -) - -const ( - modelServingServiceId = "cloud.stackit.model-serving" ) func TestCreate_Success(t *testing.T) { @@ -31,46 +25,27 @@ func TestCreate_Success(t *testing.T) { description := "description" region := "eu01" instanceId := uuid.New() - - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) - } - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusAccepted) - } - } - }, - ), - ) - defer server.Close() + url := "url" providerData := core.ProviderData{ - DefaultRegion: "eu01", - Version: "1.0.0", - ServiceEnablementCustomEndpoint: server.URL, + DefaultRegion: "eu01", + Version: "1.0.0", } - apiClientConfigOptions := []config.ConfigurationOption{ - config.WithoutAuthentication(), - config.WithHTTPClient(server.Client()), - utils.UserAgentConfigOption(providerData.Version), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), - } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, tc.MockServiceEnablementClient, providerData) - apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) - if err != nil { - fmt.Println(err) - } - - instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, apiClient.DefaultAPI, providerData) + tc.MockServiceEnablementClient.EXPECT().EnableServiceRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiEnableServiceRegionalRequest{ + ApiService: tc.MockServiceEnablementClient, + }) + tc.MockServiceEnablementClient.EXPECT().EnableServiceRegionalExecute(gomock.Any()).Return(nil) - url := "url" + serviceEnablementResp := &serviceenablement.ServiceStatus{ + State: new("ENABLED"), + } + tc.MockServiceEnablementClient.EXPECT().GetServiceStatusRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiGetServiceStatusRegionalRequest{ + ApiService: tc.MockServiceEnablementClient, + }) + tc.MockServiceEnablementClient.EXPECT().GetServiceStatusRegionalExecute(gomock.Any()).Return(serviceEnablementResp, nil) createResp := &modelexperiments.CreateInstanceResponse{ Instance: modelexperiments.Instance{ @@ -83,7 +58,9 @@ func TestCreate_Success(t *testing.T) { State: "pending", }, } - tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(createResp, nil) getResp := &modelexperiments.GetInstanceResponse{ @@ -155,42 +132,20 @@ func TestCreate_ServiceEnablementFailure(t *testing.T) { description := "description" region := "eu01" - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusNotFound) - } - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusNotFound) - } - } - }, - ), - ) - defer server.Close() - providerData := core.ProviderData{ - DefaultRegion: "eu01", - Version: "1.0.0", - ServiceEnablementCustomEndpoint: server.URL, + DefaultRegion: "eu01", + Version: "1.0.0", } - apiClientConfigOptions := []config.ConfigurationOption{ - config.WithoutAuthentication(), - config.WithHTTPClient(server.Client()), - utils.UserAgentConfigOption(providerData.Version), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), - } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, tc.MockServiceEnablementClient, providerData) - apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) - if err != nil { - fmt.Println(err) + oapiErr := &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusNotFound, } - - instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, apiClient.DefaultAPI, providerData) + tc.MockServiceEnablementClient.EXPECT().EnableServiceRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiEnableServiceRegionalRequest{ + ApiService: tc.MockServiceEnablementClient, + }) + tc.MockServiceEnablementClient.EXPECT().EnableServiceRegionalExecute(gomock.Any()).Return(oapiErr) schemaResp := resource.SchemaResponse{} instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) @@ -224,46 +179,27 @@ func TestCreate_GetInstanceFailure(t *testing.T) { description := "description" region := "eu01" instanceId := uuid.New() - - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) - } - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusAccepted) - } - } - }, - ), - ) - defer server.Close() + url := "url" providerData := core.ProviderData{ - DefaultRegion: "eu01", - Version: "1.0.0", - ServiceEnablementCustomEndpoint: server.URL, + DefaultRegion: "eu01", + Version: "1.0.0", } - apiClientConfigOptions := []config.ConfigurationOption{ - config.WithoutAuthentication(), - config.WithHTTPClient(server.Client()), - utils.UserAgentConfigOption(providerData.Version), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), - } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, tc.MockServiceEnablementClient, providerData) - apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) - if err != nil { - fmt.Println(err) - } - - instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, apiClient.DefaultAPI, providerData) + tc.MockServiceEnablementClient.EXPECT().EnableServiceRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiEnableServiceRegionalRequest{ + ApiService: tc.MockServiceEnablementClient, + }) + tc.MockServiceEnablementClient.EXPECT().EnableServiceRegionalExecute(gomock.Any()).Return(nil) - url := "url" + serviceEnablementResp := &serviceenablement.ServiceStatus{ + State: new("ENABLED"), + } + tc.MockServiceEnablementClient.EXPECT().GetServiceStatusRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiGetServiceStatusRegionalRequest{ + ApiService: tc.MockServiceEnablementClient, + }) + tc.MockServiceEnablementClient.EXPECT().GetServiceStatusRegionalExecute(gomock.Any()).Return(serviceEnablementResp, nil) createResp := &modelexperiments.CreateInstanceResponse{ Instance: modelexperiments.Instance{ @@ -276,7 +212,9 @@ func TestCreate_GetInstanceFailure(t *testing.T) { State: "pending", }, } - tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(createResp, nil) tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{ @@ -335,45 +273,29 @@ func TestCreate_InstanceCreateFailure(t *testing.T) { description := "description" region := "eu01" - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == fmt.Sprintf("/v2/projects/%s/regions/%s/services/%s", projectId, region, modelServingServiceId) { - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"state":"ENABLED","scope":"PUBLIC","serviceId":"cloud.stackit.model-serving"}`)) - } - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusAccepted) - } - } - }, - ), - ) - defer server.Close() - providerData := core.ProviderData{ - DefaultRegion: "eu01", - Version: "1.0.0", - ServiceEnablementCustomEndpoint: server.URL, + DefaultRegion: "eu01", + Version: "1.0.0", } - apiClientConfigOptions := []config.ConfigurationOption{ - config.WithoutAuthentication(), - config.WithHTTPClient(server.Client()), - utils.UserAgentConfigOption(providerData.Version), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), - } + instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, tc.MockServiceEnablementClient, providerData) - apiClient, err := serviceenablement.NewAPIClient(apiClientConfigOptions...) - if err != nil { - fmt.Println(err) - } + tc.MockServiceEnablementClient.EXPECT().EnableServiceRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiEnableServiceRegionalRequest{ + ApiService: tc.MockServiceEnablementClient, + }) + tc.MockServiceEnablementClient.EXPECT().EnableServiceRegionalExecute(gomock.Any()).Return(nil) - instanceRes := instance.NewInstanceResource(tc.MockInstanceCLient, apiClient.DefaultAPI, providerData) + serviceEnablementResp := &serviceenablement.ServiceStatus{ + State: new("ENABLED"), + } + tc.MockServiceEnablementClient.EXPECT().GetServiceStatusRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiGetServiceStatusRegionalRequest{ + ApiService: tc.MockServiceEnablementClient, + }) + tc.MockServiceEnablementClient.EXPECT().GetServiceStatusRegionalExecute(gomock.Any()).Return(serviceEnablementResp, nil) - tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().CreateInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) schemaResp := resource.SchemaResponse{} diff --git a/stackit/internal/services/modelexperiments/instance/resource_delete_test.go b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go index 77b1cd656..5649acc57 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_delete_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_delete_test.go @@ -24,7 +24,9 @@ func TestDelete_Success(t *testing.T) { region := "eu01" instanceId := uuid.New() - tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().DeleteInstanceExecute(gomock.Any()).Return(nil, nil) oapiErr := &oapierror.GenericOpenAPIError{ @@ -71,7 +73,9 @@ func TestDelete_DeleteInstanceFailed(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusInternalServerError, } - tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().DeleteInstanceExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -98,6 +102,7 @@ func TestDelete_DeleteInstanceFailed(t *testing.T) { t.Fatalf("Delete should not succeed, but got no errors") } + // state should not be removed var finalState instance.Model diags := resp.State.Get(tc.Ctx, &finalState) if diags.HasError() { @@ -120,7 +125,9 @@ func TestDelete_InstanceAlreadyDeleted(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, } - tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().DeleteInstanceExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -147,6 +154,7 @@ func TestDelete_InstanceAlreadyDeleted(t *testing.T) { t.Fatalf("Delete should succeed, but got errors: %v", resp.Diagnostics.Errors()) } + // state should be removed var finalState *instance.Model diags := resp.State.Get(tc.Ctx, &finalState) if diags.HasError() { @@ -165,7 +173,9 @@ func TestDelete_GetInstanceFailed(t *testing.T) { region := "eu01" instanceId := uuid.New() - tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().DeleteInstanceExecute(gomock.Any()).Return(nil, nil) oapiErr := &oapierror.GenericOpenAPIError{ @@ -200,13 +210,14 @@ func TestDelete_GetInstanceFailed(t *testing.T) { t.Fatalf("Delete should not succeed, but got no errors") } + // state should not be removed var finalState instance.Model diags := resp.State.Get(tc.Ctx, &finalState) if diags.HasError() { t.Fatalf("Failed to get state: %v", diags.Errors()) } - if instanceId.String() != state.InstanceId.ValueString() { + if instanceId.String() != finalState.InstanceId.ValueString() { t.Fatalf("state should not have been deleted - expected %v, got %v", instanceId.String(), state.InstanceId.ValueString()) } } diff --git a/stackit/internal/services/modelexperiments/instance/resource_read_test.go b/stackit/internal/services/modelexperiments/instance/resource_read_test.go index f56f3fc86..9e9c7bf42 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_read_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_read_test.go @@ -38,7 +38,9 @@ func TestRead_Success(t *testing.T) { BucketName: new("bucket"), }, } - tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(getResp, nil) providerData := core.ProviderData{ @@ -148,7 +150,9 @@ func TestRead_InstanceNotFound(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: 404, } - tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -197,7 +201,9 @@ func TestRead_GetRequestFailed(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: 400, } - tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ diff --git a/stackit/internal/services/modelexperiments/instance/resource_update_test.go b/stackit/internal/services/modelexperiments/instance/resource_update_test.go index 9f76a52e8..9f4669f66 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_update_test.go @@ -40,7 +40,9 @@ func TestUpdate_Success(t *testing.T) { }, } - tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceExecute(gomock.Any()).Return(updateResp, nil) providerData := core.ProviderData{ @@ -81,14 +83,13 @@ func TestUpdate_Success(t *testing.T) { t.Fatalf("Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) } - // Extract final state + // state should be updated var finalState instance.Model diags := resp.State.Get(tc.Ctx, &finalState) if diags.HasError() { t.Fatalf("Failed to get state: %v", diags.Errors()) } - // Verify all fields match the updated values from GetInstance, state should be updated if instanceId.String() != finalState.InstanceId.ValueString() { t.Fatalf("expected %v, got %v", instanceId.String(), finalState.InstanceId.ValueString()) } @@ -126,7 +127,9 @@ func TestUpdate_InstanceNotFound(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: 404, } - tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -166,7 +169,7 @@ func TestUpdate_InstanceNotFound(t *testing.T) { t.Fatalf("Update should succeed, but got errors: %v", resp.Diagnostics.Errors()) } - // Extract final state, state should be deleted + // state should be deleted var finalState *instance.Model diags := resp.State.Get(tc.Ctx, &finalState) if diags.HasError() { @@ -188,7 +191,9 @@ func TestUpdate_InstanceUpdateError(t *testing.T) { region := "eu01" instanceId := uuid.New() - tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceExecute(gomock.Any()).Return(nil, fmt.Errorf("server error")) providerData := core.ProviderData{ @@ -228,7 +233,7 @@ func TestUpdate_InstanceUpdateError(t *testing.T) { t.Fatalf("Update should not succeed, but got no errors") } - // Extract final state, instance should not be updated + // state should not be updated var finalState instance.Model diags := resp.State.Get(tc.Ctx, &finalState) if diags.HasError() { diff --git a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go index af87f79ba..dffafcb15 100644 --- a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go +++ b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go @@ -19,11 +19,11 @@ import ( var instanceResource = map[string]string{ "project_id": testutil.ProjectId, - "name": "instance01", + "name": "tf acc test instance01", "description": "my description", "description_updated": "my description updated", "region": testutil.Region, - "tokenName": "token01", + "tokenName": "tf acc test token01", "tokenDescription": "my token description", "tokenDescriptionUpdated": "my token description updated", } diff --git a/stackit/internal/services/modelexperiments/testutils/test_utils.go b/stackit/internal/services/modelexperiments/testutils/test_utils.go index c0188216c..610e2ae1d 100644 --- a/stackit/internal/services/modelexperiments/testutils/test_utils.go +++ b/stackit/internal/services/modelexperiments/testutils/test_utils.go @@ -13,23 +13,27 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" mock_instance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance/mock" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/token" + mock_serviceenablement "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils/mock" ) type TestContext struct { - T *testing.T - MockCtrl *gomock.Controller - MockInstanceCLient *mock_instance.MockDefaultAPI - Ctx context.Context + T *testing.T + MockCtrl *gomock.Controller + MockInstanceCLient *mock_instance.MockDefaultAPI + MockServiceEnablementClient *mock_serviceenablement.MockDefaultAPI + Ctx context.Context } func NewTestContext(t *testing.T) *TestContext { ctrl := gomock.NewController(t) mockClient := mock_instance.NewMockDefaultAPI(ctrl) + mockServiceClient := mock_serviceenablement.NewMockDefaultAPI(ctrl) return &TestContext{ - T: t, - MockCtrl: ctrl, - MockInstanceCLient: mockClient, - Ctx: context.Background(), + T: t, + MockCtrl: ctrl, + MockInstanceCLient: mockClient, + MockServiceEnablementClient: mockServiceClient, + Ctx: context.Background(), } } diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index 8edf66740..f3e56a159 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -242,8 +242,7 @@ func (i *tokenResource) Create(ctx context.Context, req resource.CreateRequest, return } - createTokenReq := i.client.CreateInstanceToken(ctx, projectId, region, instanceId).CreateInstanceTokenPayload(*payload) - createInstanceTokenResp, err := i.client.CreateInstanceTokenExecute(createTokenReq) + createInstanceTokenResp, err := i.client.CreateInstanceToken(ctx, projectId, region, instanceId).CreateInstanceTokenPayload(*payload).Execute() if err != nil { core.LogAndAddError( ctx, @@ -320,8 +319,7 @@ func (i *tokenResource) Read(ctx context.Context, req resource.ReadRequest, resp ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - getTokenReq := i.client.GetInstanceToken(ctx, projectId, region, tokenId, instanceId) - getInstanceTokenResp, err := i.client.GetInstanceTokenExecute(getTokenReq) + getInstanceTokenResp, err := i.client.GetInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -394,8 +392,7 @@ func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, return } - updateTokenReq := i.client.PartialUpdateInstanceToken(ctx, projectId, region, tokenId, instanceId).PartialUpdateInstanceTokenPayload(*payload) - updateInstanceTokenResp, err := i.client.PartialUpdateInstanceTokenExecute(updateTokenReq) + updateInstanceTokenResp, err := i.client.PartialUpdateInstanceToken(ctx, projectId, region, tokenId, instanceId).PartialUpdateInstanceTokenPayload(*payload).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { @@ -469,8 +466,7 @@ func (i *tokenResource) Delete(ctx context.Context, req resource.DeleteRequest, ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - deleteTokenReq := i.client.DeleteInstanceToken(ctx, projectId, region, tokenId, instanceId) - _, err := i.client.DeleteInstanceTokenExecute(deleteTokenReq) + _, err := i.client.DeleteInstanceToken(ctx, projectId, region, tokenId, instanceId).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { diff --git a/stackit/internal/services/modelexperiments/token/resource_create_test.go b/stackit/internal/services/modelexperiments/token/resource_create_test.go index d971f6e13..f391aa5fa 100644 --- a/stackit/internal/services/modelexperiments/token/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_create_test.go @@ -39,7 +39,9 @@ func TestCreate_Success(t *testing.T) { ValidUntil: validUntil, }, } - tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().CreateInstanceTokenExecute(gomock.Any()).Return(createTokenResp, nil) getTokenResp := &modelexperiments.GetInstanceTokenResponse{ @@ -135,7 +137,9 @@ func TestCreate_TokenIdEmpty(t *testing.T) { ValidUntil: validUntil, }, } - tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().CreateInstanceTokenExecute(gomock.Any()).Return(createTokenResp, nil) providerData := core.ProviderData{ @@ -186,7 +190,9 @@ func TestCreate_CreateTokenFailure(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: 400, } - tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().CreateInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -247,7 +253,9 @@ func TestCreate_GetTokenFailure(t *testing.T) { ValidUntil: validUntil, }, } - tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().CreateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiCreateInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().CreateInstanceTokenExecute(gomock.Any()).Return(createTokenResp, nil) oapiErr := &oapierror.GenericOpenAPIError{ diff --git a/stackit/internal/services/modelexperiments/token/resource_delete_test.go b/stackit/internal/services/modelexperiments/token/resource_delete_test.go index ff2b4335c..5ceef4985 100644 --- a/stackit/internal/services/modelexperiments/token/resource_delete_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_delete_test.go @@ -25,7 +25,9 @@ func TestDelete_Success(t *testing.T) { instanceId := uuid.New() tokenId := uuid.New() - tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().DeleteInstanceTokenExecute(gomock.Any()).Return(nil, nil) oapiErr := &oapierror.GenericOpenAPIError{ @@ -74,7 +76,9 @@ func TestDelete_DeleteTokenFailed(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusInternalServerError, } - tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().DeleteInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -126,7 +130,9 @@ func TestDelete_TokenNotFound(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, } - tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().DeleteInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -174,7 +180,9 @@ func TestDelete_GetTokenFailed(t *testing.T) { instanceId := uuid.New() tokenId := uuid.New() - tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().DeleteInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiDeleteInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().DeleteInstanceTokenExecute(gomock.Any()).Return(nil, nil) oapiErr := &oapierror.GenericOpenAPIError{ diff --git a/stackit/internal/services/modelexperiments/token/resource_read_test.go b/stackit/internal/services/modelexperiments/token/resource_read_test.go index 3dcc7b0b6..1e9a6a22b 100644 --- a/stackit/internal/services/modelexperiments/token/resource_read_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_read_test.go @@ -23,6 +23,7 @@ func TestRead_Success(t *testing.T) { projectId := uuid.New() name := "token" + newName := "new token name" region := "eu01" description := "token description" instanceId := uuid.New() @@ -36,13 +37,15 @@ func TestRead_Success(t *testing.T) { Token: modelexperiments.TokenMetadata{ Description: &description, Id: tokenId.String(), - Name: name, + Name: newName, Region: region, State: "active", ValidUntil: validUntil, }, } - tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(getTokenResp, nil) providerData := core.ProviderData{ @@ -73,14 +76,43 @@ func TestRead_Success(t *testing.T) { if resp.Diagnostics.HasError() { t.Fatalf("Get should succeed but got errors") } - // state should be removed - var refreshedState *token.Model + + // state should be written according to GetInstanceToken Response + var refreshedState token.Model diags := resp.State.Get(tc.Ctx, &refreshedState) if diags.HasError() { t.Fatalf("failed to get state") } - if refreshedState != nil { - t.Fatalf("should be nil") + + if tokenId.String() != refreshedState.TokenId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", tokenId.String(), refreshedState.TokenId.ValueString()) + } + if projectId.String() != refreshedState.ProjectId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", projectId.String(), refreshedState.ProjectId.ValueString()) + } + if instanceId.String() != refreshedState.InstanceId.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", instanceId.String(), refreshedState.InstanceId.ValueString()) + } + if newName != refreshedState.Name.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", name, refreshedState.Name.ValueString()) + } + if refreshedState.State.ValueString() != "active" { + t.Fatalf("Should be equal - expected %v, got %v", "active", refreshedState.State.ValueString()) + } + if description != refreshedState.Description.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", description, refreshedState.Description.ValueString()) + } + if tokenContent != refreshedState.Token.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", tokenContent, refreshedState.Token.ValueString()) + } + if id.ValueString() != refreshedState.Id.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", id.ValueString(), refreshedState.Id.ValueString()) + } + if region != refreshedState.Region.ValueString() { + t.Fatalf("Should be equal - expected %v, got %v", region, refreshedState.Region.ValueString()) + } + if refreshedState.ValidUntil.ValueString() != "2099-01-01T00:00:00Z" { + t.Fatalf("Should be equal - expected %v, got %v", "2099-01-01T00:00:00Z", refreshedState.ValidUntil.ValueString()) } } @@ -100,7 +132,9 @@ func TestRead_TokenNotFound(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, } - tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -160,7 +194,9 @@ func TestRead_GetTokenRequestFailed(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusInternalServerError, } - tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -256,7 +292,9 @@ func TestRead_TokenInvalidError(t *testing.T) { ValidUntil: validUntil, }, } - tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().GetInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().GetInstanceTokenExecute(gomock.Any()).Return(getTokenResp, nil) providerData := core.ProviderData{ diff --git a/stackit/internal/services/modelexperiments/token/resource_update_test.go b/stackit/internal/services/modelexperiments/token/resource_update_test.go index de954f1cd..0835f6263 100644 --- a/stackit/internal/services/modelexperiments/token/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_update_test.go @@ -44,7 +44,9 @@ func TestUpdate_Success(t *testing.T) { ValidUntil: validUntil, }, } - tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceTokenExecute(gomock.Any()).Return(updateTokenResp, nil) providerData := core.ProviderData{ @@ -119,7 +121,9 @@ func TestUpdate_TokenNotFound(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, } - tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -191,7 +195,9 @@ func TestUpdate_TokenUpdateError(t *testing.T) { oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusInternalServerError, } - tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceTokenExecute(gomock.Any()).Return(nil, oapiErr) providerData := core.ProviderData{ @@ -275,7 +281,9 @@ func TestUpdate_TokenInvalidStateError(t *testing.T) { ValidUntil: validUntil, }, } - tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{}) + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{ + ApiService: tc.MockInstanceCLient, + }) tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceTokenExecute(gomock.Any()).Return(updateTokenResp, nil) providerData := core.ProviderData{ @@ -319,7 +327,7 @@ func TestUpdate_TokenInvalidStateError(t *testing.T) { t.Fatalf("update should succeed") } - // state should not be removed + // state should be removed var updatedState *token.Model diags := resp.State.Get(tc.Ctx, &updatedState) if diags.HasError() { diff --git a/stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go b/stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go new file mode 100644 index 000000000..6d0fce345 --- /dev/null +++ b/stackit/internal/services/modelexperiments/utils/mock/serviceenablement.go @@ -0,0 +1,156 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api (interfaces: DefaultAPI) +// +// Generated by this command: +// +// mockgen -destination=./mock/serviceenablement.go -package=mock_serviceenablement github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api DefaultAPI +// + +// Package mock_serviceenablement is a generated GoMock package. +package mock_serviceenablement + +import ( + context "context" + reflect "reflect" + + v2api "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" + gomock "go.uber.org/mock/gomock" +) + +// MockDefaultAPI is a mock of DefaultAPI interface. +type MockDefaultAPI struct { + ctrl *gomock.Controller + recorder *MockDefaultAPIMockRecorder + isgomock struct{} +} + +// MockDefaultAPIMockRecorder is the mock recorder for MockDefaultAPI. +type MockDefaultAPIMockRecorder struct { + mock *MockDefaultAPI +} + +// NewMockDefaultAPI creates a new mock instance. +func NewMockDefaultAPI(ctrl *gomock.Controller) *MockDefaultAPI { + mock := &MockDefaultAPI{ctrl: ctrl} + mock.recorder = &MockDefaultAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDefaultAPI) EXPECT() *MockDefaultAPIMockRecorder { + return m.recorder +} + +// DisableServiceRegional mocks base method. +func (m *MockDefaultAPI) DisableServiceRegional(ctx context.Context, region, projectId, serviceId string) v2api.ApiDisableServiceRegionalRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DisableServiceRegional", ctx, region, projectId, serviceId) + ret0, _ := ret[0].(v2api.ApiDisableServiceRegionalRequest) + return ret0 +} + +// DisableServiceRegional indicates an expected call of DisableServiceRegional. +func (mr *MockDefaultAPIMockRecorder) DisableServiceRegional(ctx, region, projectId, serviceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableServiceRegional", reflect.TypeOf((*MockDefaultAPI)(nil).DisableServiceRegional), ctx, region, projectId, serviceId) +} + +// DisableServiceRegionalExecute mocks base method. +func (m *MockDefaultAPI) DisableServiceRegionalExecute(r v2api.ApiDisableServiceRegionalRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DisableServiceRegionalExecute", r) + ret0, _ := ret[0].(error) + return ret0 +} + +// DisableServiceRegionalExecute indicates an expected call of DisableServiceRegionalExecute. +func (mr *MockDefaultAPIMockRecorder) DisableServiceRegionalExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableServiceRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).DisableServiceRegionalExecute), r) +} + +// EnableServiceRegional mocks base method. +func (m *MockDefaultAPI) EnableServiceRegional(ctx context.Context, region, projectId, serviceId string) v2api.ApiEnableServiceRegionalRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableServiceRegional", ctx, region, projectId, serviceId) + ret0, _ := ret[0].(v2api.ApiEnableServiceRegionalRequest) + return ret0 +} + +// EnableServiceRegional indicates an expected call of EnableServiceRegional. +func (mr *MockDefaultAPIMockRecorder) EnableServiceRegional(ctx, region, projectId, serviceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableServiceRegional", reflect.TypeOf((*MockDefaultAPI)(nil).EnableServiceRegional), ctx, region, projectId, serviceId) +} + +// EnableServiceRegionalExecute mocks base method. +func (m *MockDefaultAPI) EnableServiceRegionalExecute(r v2api.ApiEnableServiceRegionalRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableServiceRegionalExecute", r) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnableServiceRegionalExecute indicates an expected call of EnableServiceRegionalExecute. +func (mr *MockDefaultAPIMockRecorder) EnableServiceRegionalExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableServiceRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).EnableServiceRegionalExecute), r) +} + +// GetServiceStatusRegional mocks base method. +func (m *MockDefaultAPI) GetServiceStatusRegional(ctx context.Context, region, projectId, serviceId string) v2api.ApiGetServiceStatusRegionalRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServiceStatusRegional", ctx, region, projectId, serviceId) + ret0, _ := ret[0].(v2api.ApiGetServiceStatusRegionalRequest) + return ret0 +} + +// GetServiceStatusRegional indicates an expected call of GetServiceStatusRegional. +func (mr *MockDefaultAPIMockRecorder) GetServiceStatusRegional(ctx, region, projectId, serviceId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceStatusRegional", reflect.TypeOf((*MockDefaultAPI)(nil).GetServiceStatusRegional), ctx, region, projectId, serviceId) +} + +// GetServiceStatusRegionalExecute mocks base method. +func (m *MockDefaultAPI) GetServiceStatusRegionalExecute(r v2api.ApiGetServiceStatusRegionalRequest) (*v2api.ServiceStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServiceStatusRegionalExecute", r) + ret0, _ := ret[0].(*v2api.ServiceStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServiceStatusRegionalExecute indicates an expected call of GetServiceStatusRegionalExecute. +func (mr *MockDefaultAPIMockRecorder) GetServiceStatusRegionalExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceStatusRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).GetServiceStatusRegionalExecute), r) +} + +// ListServiceStatusRegional mocks base method. +func (m *MockDefaultAPI) ListServiceStatusRegional(ctx context.Context, region, projectId string) v2api.ApiListServiceStatusRegionalRequest { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListServiceStatusRegional", ctx, region, projectId) + ret0, _ := ret[0].(v2api.ApiListServiceStatusRegionalRequest) + return ret0 +} + +// ListServiceStatusRegional indicates an expected call of ListServiceStatusRegional. +func (mr *MockDefaultAPIMockRecorder) ListServiceStatusRegional(ctx, region, projectId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServiceStatusRegional", reflect.TypeOf((*MockDefaultAPI)(nil).ListServiceStatusRegional), ctx, region, projectId) +} + +// ListServiceStatusRegionalExecute mocks base method. +func (m *MockDefaultAPI) ListServiceStatusRegionalExecute(r v2api.ApiListServiceStatusRegionalRequest) (*v2api.ListServiceStatusRegional200Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListServiceStatusRegionalExecute", r) + ret0, _ := ret[0].(*v2api.ListServiceStatusRegional200Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListServiceStatusRegionalExecute indicates an expected call of ListServiceStatusRegionalExecute. +func (mr *MockDefaultAPIMockRecorder) ListServiceStatusRegionalExecute(r any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServiceStatusRegionalExecute", reflect.TypeOf((*MockDefaultAPI)(nil).ListServiceStatusRegionalExecute), r) +} diff --git a/stackit/internal/services/modelexperiments/utils/util.go b/stackit/internal/services/modelexperiments/utils/util.go index 018a6c7c8..16926c9e7 100644 --- a/stackit/internal/services/modelexperiments/utils/util.go +++ b/stackit/internal/services/modelexperiments/utils/util.go @@ -28,6 +28,7 @@ const ( TOKENSTATE_INACTIVE = "inactive" ) +//go:generate mockgen -destination=./mock/serviceenablement.go -package=mock_serviceenablement github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api DefaultAPI func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *modelexperiment.APIClient { apiClientConfigOptions := []config.ConfigurationOption{ config.WithCustomAuth(providerData.RoundTripper), From 38368b59c96f5ad34ad582cd2bdb410d908d7c95 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Thu, 25 Jun 2026 16:09:28 +0200 Subject: [PATCH 25/30] feat: adapt to new serviceEnablementUtils --- go.mod | 2 + go.sum | 2 + .../modelexperiments/instance/resource.go | 5 ++- .../instance/resource_create_test.go | 6 +-- .../modelexperiments_acc_test.go | 39 ++++++++++--------- .../services/modelexperiments/utils/util.go | 20 ---------- 6 files changed, 30 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 65bc981e9..0286c8389 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/logme v1.0.0 github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.0 github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.0.0 + github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.2.0 github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.11.0 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.11.0 github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.9.0 @@ -49,6 +50,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/vpn v0.14.0 github.com/teambition/rrule-go v1.8.2 + go.uber.org/mock v0.6.0 golang.org/x/mod v0.37.0 ) diff --git a/go.sum b/go.sum index 9ae4bc5e9..782785602 100644 --- a/go.sum +++ b/go.sum @@ -700,6 +700,8 @@ github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.0 h1:g7zpfQFFq3UhAWrM github.com/stackitcloud/stackit-sdk-go/services/logs v0.10.0/go.mod h1:tvRejL8w5KpGBbLFPQ+dXOJURgZ3OMbZmwxlKQrGMuA= github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.0.0 h1:G/OqKHAgmH/GgqagGaow1aV6jkmVdTCexCM425PbXaE= github.com/stackitcloud/stackit-sdk-go/services/mariadb v1.0.0/go.mod h1:joa89Y1dyn0j22FstRcIKfW2ada3FDxNfttxSvq27uY= +github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.2.0 h1:4cM9P38lQkJRtz0ZdDRhMam/J2rlL0m3sa1JBcIPcdc= +github.com/stackitcloud/stackit-sdk-go/services/modelexperiments v0.2.0/go.mod h1:TW2PYG0kSrfAos3yY8wUxDem0J9ZYSXulnbzBZ4BTaE= github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.11.0 h1:LfcQ++Z8a13jrJ5NOaCY/hwToh/e+QJj0eS6rd6s6k8= github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.11.0/go.mod h1:u7T85YqoqncJevbPU1ODKthbmxxEh1zw+bVaAO8v0Sg= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.11.0 h1:mjcTktPsrqN/XvtuYs0O23n1lbbYkY5lvSxrNtzfaPs= diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 249e55d1a..dcde9bda6 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -24,6 +24,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" modelexperimentsutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/utils" + serviceEnablementUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/serviceenablement/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" ) @@ -91,12 +92,12 @@ func (i *instanceResource) Configure(ctx context.Context, req resource.Configure if resp.Diagnostics.HasError() { return } - serviceEnablementClient := modelexperimentsutils.ConfigureServiceEnablementClient(ctx, &i.providerData, &resp.Diagnostics) + serviceEnablementClient := serviceEnablementUtils.ConfigureClient(ctx, &i.providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } i.client = apiClient.DefaultAPI - i.serviceEnablementClient = serviceEnablementClient + i.serviceEnablementClient = serviceEnablementClient.DefaultAPI tflog.Info(ctx, "Model experiments client configured") } diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index c0520fcfd..c8bc2a5cf 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -40,7 +40,7 @@ func TestCreate_Success(t *testing.T) { tc.MockServiceEnablementClient.EXPECT().EnableServiceRegionalExecute(gomock.Any()).Return(nil) serviceEnablementResp := &serviceenablement.ServiceStatus{ - State: new("ENABLED"), + State: serviceenablement.SERVICESTATUSSTATE_ENABLED.Ptr(), } tc.MockServiceEnablementClient.EXPECT().GetServiceStatusRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiGetServiceStatusRegionalRequest{ ApiService: tc.MockServiceEnablementClient, @@ -194,7 +194,7 @@ func TestCreate_GetInstanceFailure(t *testing.T) { tc.MockServiceEnablementClient.EXPECT().EnableServiceRegionalExecute(gomock.Any()).Return(nil) serviceEnablementResp := &serviceenablement.ServiceStatus{ - State: new("ENABLED"), + State: serviceenablement.SERVICESTATUSSTATE_ENABLED.Ptr(), } tc.MockServiceEnablementClient.EXPECT().GetServiceStatusRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiGetServiceStatusRegionalRequest{ ApiService: tc.MockServiceEnablementClient, @@ -286,7 +286,7 @@ func TestCreate_InstanceCreateFailure(t *testing.T) { tc.MockServiceEnablementClient.EXPECT().EnableServiceRegionalExecute(gomock.Any()).Return(nil) serviceEnablementResp := &serviceenablement.ServiceStatus{ - State: new("ENABLED"), + State: serviceenablement.SERVICESTATUSSTATE_ENABLED.Ptr(), } tc.MockServiceEnablementClient.EXPECT().GetServiceStatusRegional(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(serviceenablement.ApiGetServiceStatusRegionalRequest{ ApiService: tc.MockServiceEnablementClient, diff --git a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go index dffafcb15..3467da374 100644 --- a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go +++ b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go @@ -17,18 +17,19 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" ) +//nolint:all var instanceResource = map[string]string{ - "project_id": testutil.ProjectId, - "name": "tf acc test instance01", - "description": "my description", - "description_updated": "my description updated", - "region": testutil.Region, - "tokenName": "tf acc test token01", - "tokenDescription": "my token description", - "tokenDescriptionUpdated": "my token description updated", + "project_id": testutil.ProjectId, + "name": "tf acc test instance01", + "description": "my description", + "description_updated": "my description updated", + "region": testutil.Region, + "token_name": "tf acc test token01", + "token_description": "my token description", + "token_description_updated": "my token description updated", } -func inputInstanceConfig(instanceName, instanceDescription, tokenName, tokenDescription string) string { +func inputInstanceConfig(instanceName, instanceDescription, token_name, token_description string) string { return fmt.Sprintf(` %s @@ -53,9 +54,9 @@ func inputInstanceConfig(instanceName, instanceDescription, tokenName, tokenDesc instanceResource["region"], instanceDescription, instanceResource["project_id"], - tokenName, + token_name, instanceResource["region"], - tokenDescription, + token_description, ) } @@ -69,8 +70,8 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { Config: inputInstanceConfig( instanceResource["name"], instanceResource["description"], - instanceResource["tokenName"], - instanceResource["tokenDescription"], + instanceResource["token_name"], + instanceResource["token_description"], ), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "project_id", instanceResource["project_id"]), @@ -84,8 +85,8 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "url"), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "project_id", instanceResource["project_id"]), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "region", instanceResource["region"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "name", instanceResource["tokenName"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "description", instanceResource["tokenDescription"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "name", instanceResource["token_name"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "description", instanceResource["token_description"]), resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "instance_id"), resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "token_id"), resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "state"), @@ -98,8 +99,8 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { Config: inputInstanceConfig( instanceResource["name"], instanceResource["description_updated"], - instanceResource["tokenName"], - instanceResource["tokenDescriptionUpdated"], + instanceResource["token_name"], + instanceResource["token_description_updated"], ), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "project_id", instanceResource["project_id"]), @@ -113,8 +114,8 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "url"), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "project_id", instanceResource["project_id"]), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "region", instanceResource["region"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "name", instanceResource["tokenName"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "description", instanceResource["tokenDescriptionUpdated"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "name", instanceResource["token_name"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "description", instanceResource["token_description_updated"]), resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "instance_id"), resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "token_id"), resource.TestCheckResourceAttrSet("stackit_modelexperiments_token.token", "state"), diff --git a/stackit/internal/services/modelexperiments/utils/util.go b/stackit/internal/services/modelexperiments/utils/util.go index 16926c9e7..9e22235d8 100644 --- a/stackit/internal/services/modelexperiments/utils/util.go +++ b/stackit/internal/services/modelexperiments/utils/util.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/stackitcloud/stackit-sdk-go/core/config" modelexperiment "github.com/stackitcloud/stackit-sdk-go/services/modelexperiments/v1api" - serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" @@ -45,22 +44,3 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags return apiClient } - -func ConfigureServiceEnablementClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) serviceenablement.DefaultAPI { - apiClientConfigOptions := []config.ConfigurationOption{ - config.WithCustomAuth(providerData.RoundTripper), - utils.UserAgentConfigOption(providerData.Version), - } - if providerData.ServiceEnablementCustomEndpoint != "" { - apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint)) - } else { - apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion())) - } - apiClient, err := serviceenablement.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.DefaultAPI -} From 430abe4455e024c84b6b8d509bf4965ae4957753 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Fri, 26 Jun 2026 08:25:00 +0200 Subject: [PATCH 26/30] feat: remove "unknown" resource state in creation --- .../internal/services/modelexperiments/instance/resource.go | 6 +++--- .../modelexperiments/instance/resource_create_test.go | 4 ++-- .../internal/services/modelexperiments/token/resource.go | 2 +- .../services/modelexperiments/token/resource_create_test.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index dcde9bda6..720d0e665 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -264,14 +264,14 @@ func (i *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - _, err = serviceEnablementWait.EnableServiceWaitHandler(ctx, i.serviceEnablementClient, region, projectId, utils.ModelServingServiceId). + _, err = serviceEnablementWait.EnableServiceWaitHandler(ctx, i.serviceEnablementClient, region, projectId, utils.ModelExperimentsServiceId). WaitWithContext(ctx) if err != nil { core.LogAndAddError( ctx, &resp.Diagnostics, "Error enabling AI model experiments", - fmt.Sprintf("Error enabling AI model serving: %v", err), + fmt.Sprintf("Error enabling AI model experiments: %v", err), ) return } @@ -518,7 +518,7 @@ func mapCreateResponse(ctx context.Context, instanceCreateResp *modelexperiments } if waitResp == nil { - model.State = types.StringValue("unknown") + model.State = types.StringValue(string(instanceCreateResp.Instance.State)) } else { model.State = types.StringValue(string(waitResp.Instance.State)) model.BucketName = types.StringValue(*waitResp.Instance.BucketName) diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index c8bc2a5cf..dacde9610 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -254,8 +254,8 @@ func TestCreate_GetInstanceFailure(t *testing.T) { if url != stateAfterCreate.Url.ValueString() { t.Fatalf("expected %v, got %v", url, stateAfterCreate.Url.ValueString()) } - if stateAfterCreate.State.ValueString() != "unknown" { - t.Fatalf("expected %v, got %v", "unknown", stateAfterCreate.State.ValueString()) + if stateAfterCreate.State.ValueString() != string(createResp.Instance.State) { + t.Fatalf("expected %v, got %v", "pending", stateAfterCreate.State.ValueString()) } if region != stateAfterCreate.Region.ValueString() { t.Fatalf("expected %v, got %v", region, stateAfterCreate.Region.ValueString()) diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index f3e56a159..fb0ee3ace 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -507,7 +507,7 @@ func mapCreateResponse(ctx context.Context, instanceTokenResp *modelexperiments. } if waitResp == nil { - model.State = types.StringValue("unknown") + model.State = types.StringValue(string(instanceTokenResp.Token.State)) } else { model.State = types.StringValue(string(waitResp.Token.State)) } diff --git a/stackit/internal/services/modelexperiments/token/resource_create_test.go b/stackit/internal/services/modelexperiments/token/resource_create_test.go index f391aa5fa..00aae4c60 100644 --- a/stackit/internal/services/modelexperiments/token/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_create_test.go @@ -310,8 +310,8 @@ func TestCreate_GetTokenFailure(t *testing.T) { if name != createdState.Name.ValueString() { t.Fatalf("Should be equal - expected %v, got %v", name, createdState.Name.ValueString()) } - if createdState.State.ValueString() != "unknown" { - t.Fatalf("Should be equal - expected %v, got %v", "unknown", createdState.State.ValueString()) + if createdState.State.ValueString() != string(createTokenResp.Token.State) { + t.Fatalf("Should be equal - expected %v, got %v", "creating", createdState.State.ValueString()) } if description != createdState.Description.ValueString() { t.Fatalf("Should be equal - expected %v, got %v", description, createdState.Description.ValueString()) From c0d049e992c2e44bf18303e94a0dd5e9873f1be5 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Fri, 26 Jun 2026 16:01:44 +0200 Subject: [PATCH 27/30] feat(mexp): refractor tests and resources --- .../modelexperiments/instance/resource.go | 33 ++--- .../instance/resource_create_test.go | 54 +++++--- .../instance/resource_read_test.go | 77 ++++++++---- .../instance/resource_update_test.go | 117 ++++++++++++------ .../modelexperiments_acc_test.go | 45 +++---- .../modelexperiments/token/resource.go | 37 ++++-- .../token/resource_create_test.go | 95 ++++++++------ .../token/resource_update_test.go | 77 ++++++++++-- 8 files changed, 365 insertions(+), 170 deletions(-) diff --git a/stackit/internal/services/modelexperiments/instance/resource.go b/stackit/internal/services/modelexperiments/instance/resource.go index 720d0e665..82b5b7420 100644 --- a/stackit/internal/services/modelexperiments/instance/resource.go +++ b/stackit/internal/services/modelexperiments/instance/resource.go @@ -146,10 +146,16 @@ func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "id": schema.StringAttribute{ Description: "Terraform's internal data source. ID. It is structured as \"`project_id`,`region`,`instance_id`\".", Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "project_id": schema.StringAttribute{ Description: "STACKIT project ID to which the AI model experiments instance is associated.", Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Validators: []validator.String{ validate.UUID(), validate.NoSeparator(), @@ -167,6 +173,9 @@ func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "instance_id": schema.StringAttribute{ Description: "The AI model experiments instance ID.", Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, Validators: []validator.String{ validate.UUID(), validate.NoSeparator(), @@ -175,13 +184,11 @@ func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "labels": schema.MapAttribute{ Description: "A map of arbitrary key/value pairs that can be attached to the AI model experiments instance", Optional: true, - Required: false, Computed: true, ElementType: types.StringType, }, "description": schema.StringAttribute{ Description: "The description of the AI model experiments instance.", - Required: false, Optional: true, Validators: []validator.String{ stringvalidator.LengthBetween(0, 160), @@ -208,7 +215,6 @@ func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "deleted_experiment_retention": schema.StringAttribute{ Description: "The deleted experiment retention of the AI model experiments instance.", Optional: true, - Required: false, Computed: true, }, "bucket_name": schema.StringAttribute{ @@ -217,8 +223,6 @@ func (i *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r }, "error_message": schema.StringAttribute{ Description: "Error messages of the AI model experiments instance.", - Optional: true, - Required: false, Computed: true, }, }, @@ -389,8 +393,8 @@ func (i *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r // Update updates the resource and sets the updated Terraform state on success. func (i *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform // Retrieve values from plan - var model Model - diags := req.Plan.Get(ctx, &model) + var plan Model + diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -409,13 +413,13 @@ func (i *instanceResource) Update(ctx context.Context, req resource.UpdateReques projectId := state.ProjectId.ValueString() instanceId := state.InstanceId.ValueString() - region := i.providerData.GetRegionWithOverride(model.Region) + region := i.providerData.GetRegionWithOverride(plan.Region) ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - payload, err := toUpdatePayload(&model) + payload, err := toUpdatePayload(&plan) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Creating API payload: %v", err)) return @@ -436,13 +440,13 @@ func (i *instanceResource) Update(ctx context.Context, req resource.UpdateReques ctx = core.LogResponse(ctx) - err = mapInstance(ctx, &updateInstanceResp.Instance, &model) + err = mapInstance(ctx, &updateInstanceResp.Instance, &plan) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Processing API payload: %v", err)) return } - diags = resp.State.Set(ctx, model) + diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -487,8 +491,7 @@ func (i *instanceResource) Delete(ctx context.Context, req resource.DeleteReques ctx = core.LogResponse(ctx) - _, err = wait.DeleteInstanceWaitHandler(ctx, i.client, region, projectId, instanceId). - WaitWithContext(ctx) + _, err = wait.DeleteInstanceWaitHandler(ctx, i.client, region, projectId, instanceId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting AI model experiments instance", fmt.Sprintf("Waiting for instance to be deleted: %v", err)) return @@ -550,7 +553,7 @@ func mapInstance(ctx context.Context, instance *modelexperiments.Instance, model return fmt.Errorf("failure in mapping labels") } - model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.Region.ValueString(), model.InstanceId.ValueString()) + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), model.Region.ValueString(), instance.Id) model.InstanceId = types.StringValue(instance.Id) model.Name = types.StringValue(instance.Name) model.State = types.StringValue(string(instance.State)) @@ -559,7 +562,7 @@ func mapInstance(ctx context.Context, instance *modelexperiments.Instance, model model.BucketName = types.StringPointerValue(instance.BucketName) model.ErrorMessage = types.StringPointerValue(instance.ErrorMessage) model.Labels = mapValue - model.Url = types.StringPointerValue(&instance.Url) + model.Url = types.StringValue(instance.Url) return nil } diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index dacde9610..44a43ea15 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -15,6 +15,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) func TestCreate_Success(t *testing.T) { @@ -26,6 +27,9 @@ func TestCreate_Success(t *testing.T) { region := "eu01" instanceId := uuid.New() url := "url" + bucketName := "bucket" + deletetExpRetention := "1m" + tfId := utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()) providerData := core.ProviderData{ DefaultRegion: "eu01", @@ -49,10 +53,10 @@ func TestCreate_Success(t *testing.T) { createResp := &modelexperiments.CreateInstanceResponse{ Instance: modelexperiments.Instance{ - DeletedExperimentRetention: new("1m"), + DeletedExperimentRetention: &deletetExpRetention, Description: &description, Name: instanceName, - Region: new("eu01"), + Region: ®ion, Url: url, Id: instanceId.String(), State: "pending", @@ -65,14 +69,14 @@ func TestCreate_Success(t *testing.T) { getResp := &modelexperiments.GetInstanceResponse{ Instance: modelexperiments.Instance{ - DeletedExperimentRetention: new("1m"), + DeletedExperimentRetention: &deletetExpRetention, + BucketName: &bucketName, Description: &description, Name: instanceName, - Region: new("eu01"), + Region: ®ion, Url: url, Id: instanceId.String(), State: "active", - BucketName: new("bucket"), }, } @@ -101,6 +105,9 @@ func TestCreate_Success(t *testing.T) { } // state should be created correctly + if tfId != stateAfterCreate.Id { + t.Fatalf("expected %v, got %v", tfId.String(), stateAfterCreate.Id.ValueString()) + } if instanceId.String() != stateAfterCreate.InstanceId.ValueString() { t.Fatalf("expected %v, got %v", instanceId.String(), stateAfterCreate.InstanceId.ValueString()) } @@ -110,17 +117,23 @@ func TestCreate_Success(t *testing.T) { if instanceName != stateAfterCreate.Name.ValueString() { t.Fatalf("expected %v, got %v", instanceName, stateAfterCreate.Name.ValueString()) } - if url != stateAfterCreate.Url.ValueString() { - t.Fatalf("expected %v, got %v", url, stateAfterCreate.Url.ValueString()) + if description != stateAfterCreate.Description.ValueString() { + t.Fatalf("expected %v, got %v", description, stateAfterCreate.Description.ValueString()) } if stateAfterCreate.State.ValueString() != "active" { t.Fatalf("expected %v, got %v", "active", stateAfterCreate.State.ValueString()) } + if url != stateAfterCreate.Url.ValueString() { + t.Fatalf("expected %v, got %v", url, stateAfterCreate.Url.ValueString()) + } if region != stateAfterCreate.Region.ValueString() { t.Fatalf("expected %v, got %v", region, stateAfterCreate.Region.ValueString()) } - if stateAfterCreate.BucketName.ValueString() != "bucket" { - t.Fatalf("expected %v, got %v", "bucket", stateAfterCreate.BucketName.ValueString()) + if bucketName != stateAfterCreate.BucketName.ValueString() { + t.Fatalf("expected %v, got %v", bucketName, stateAfterCreate.BucketName.ValueString()) + } + if deletetExpRetention != stateAfterCreate.DeletedExperimentRetention.ValueString() { + t.Fatalf("expected %v, got %v", deletetExpRetention, stateAfterCreate.DeletedExperimentRetention.ValueString()) } } @@ -180,6 +193,8 @@ func TestCreate_GetInstanceFailure(t *testing.T) { region := "eu01" instanceId := uuid.New() url := "url" + deletetExpRetention := "1m" + tfId := utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()) providerData := core.ProviderData{ DefaultRegion: "eu01", @@ -203,10 +218,10 @@ func TestCreate_GetInstanceFailure(t *testing.T) { createResp := &modelexperiments.CreateInstanceResponse{ Instance: modelexperiments.Instance{ - DeletedExperimentRetention: new("1m"), + DeletedExperimentRetention: &deletetExpRetention, Description: &description, Name: instanceName, - Region: new("eu01"), + Region: ®ion, Url: url, Id: instanceId.String(), State: "pending", @@ -242,6 +257,9 @@ func TestCreate_GetInstanceFailure(t *testing.T) { } // state should be created even if get request failed + if tfId != stateAfterCreate.Id { + t.Fatalf("expected %v, got %v", tfId.String(), stateAfterCreate.Id.ValueString()) + } if instanceId.String() != stateAfterCreate.InstanceId.ValueString() { t.Fatalf("expected %v, got %v", instanceId.String(), stateAfterCreate.InstanceId.ValueString()) } @@ -251,18 +269,24 @@ func TestCreate_GetInstanceFailure(t *testing.T) { if instanceName != stateAfterCreate.Name.ValueString() { t.Fatalf("expected %v, got %v", instanceName, stateAfterCreate.Name.ValueString()) } - if url != stateAfterCreate.Url.ValueString() { - t.Fatalf("expected %v, got %v", url, stateAfterCreate.Url.ValueString()) + if description != stateAfterCreate.Description.ValueString() { + t.Fatalf("expected %v, got %v", description, stateAfterCreate.Description.ValueString()) } - if stateAfterCreate.State.ValueString() != string(createResp.Instance.State) { + if stateAfterCreate.State.ValueString() != "pending" { t.Fatalf("expected %v, got %v", "pending", stateAfterCreate.State.ValueString()) } + if url != stateAfterCreate.Url.ValueString() { + t.Fatalf("expected %v, got %v", url, stateAfterCreate.Url.ValueString()) + } if region != stateAfterCreate.Region.ValueString() { t.Fatalf("expected %v, got %v", region, stateAfterCreate.Region.ValueString()) } - if stateAfterCreate.BucketName.ValueString() != "" { + if "" != stateAfterCreate.BucketName.ValueString() { t.Fatalf("expected %v, got %v", "", stateAfterCreate.BucketName.ValueString()) } + if deletetExpRetention != stateAfterCreate.DeletedExperimentRetention.ValueString() { + t.Fatalf("expected %v, got %v", deletetExpRetention, stateAfterCreate.DeletedExperimentRetention.ValueString()) + } } func TestCreate_InstanceCreateFailure(t *testing.T) { diff --git a/stackit/internal/services/modelexperiments/instance/resource_read_test.go b/stackit/internal/services/modelexperiments/instance/resource_read_test.go index 9e9c7bf42..110cb33ab 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_read_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_read_test.go @@ -13,6 +13,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) func TestRead_Success(t *testing.T) { @@ -25,17 +26,20 @@ func TestRead_Success(t *testing.T) { instanceId := uuid.New() url := "url" instanceNameUpdated := "updatedName" + bucketName := "bucket" + deletetExpRetention := "1m" + tfId := utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()) getResp := &modelexperiments.GetInstanceResponse{ Instance: modelexperiments.Instance{ - DeletedExperimentRetention: new("1m"), + DeletedExperimentRetention: &deletetExpRetention, + BucketName: &bucketName, Description: &description, Name: instanceNameUpdated, - Region: new("eu01"), + Region: ®ion, Url: url, Id: instanceId.String(), State: "active", - BucketName: new("bucket"), }, } tc.MockInstanceCLient.EXPECT().GetInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiGetInstanceRequest{ @@ -51,15 +55,21 @@ func TestRead_Success(t *testing.T) { schemaResp := resource.SchemaResponse{} instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) - state := instance.Model{ - ProjectId: types.StringValue(projectId.String()), - InstanceId: types.StringValue(instanceId.String()), - Region: types.StringValue(region), - Name: types.StringValue(instanceName), - Labels: types.MapNull(types.StringType), + currentState := instance.Model{ + Id: tfId, + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceName), + Region: types.StringValue(region), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + DeletedExperimentRetention: types.StringValue(deletetExpRetention), + BucketName: types.StringValue(bucketName), + State: types.StringValue("active"), + Url: types.StringValue(url), } - req := testutils.ReadInstanceRequest(tc.Ctx, schemaResp, state) + req := testutils.ReadInstanceRequest(tc.Ctx, schemaResp, currentState) resp := testutils.ReadInstanceResponse(tc.Ctx, schemaResp, nil) instanceRes.Read(tc.Ctx, req, resp) @@ -75,6 +85,9 @@ func TestRead_Success(t *testing.T) { } // state should be written according to GetInstance Response + if tfId != refreshedState.Id { + t.Fatalf("expected %v, got %v", tfId.String(), refreshedState.Id.ValueString()) + } if instanceId.String() != refreshedState.InstanceId.ValueString() { t.Fatalf("expected %v, got %v", instanceId.String(), refreshedState.InstanceId.ValueString()) } @@ -84,17 +97,23 @@ func TestRead_Success(t *testing.T) { if instanceNameUpdated != refreshedState.Name.ValueString() { t.Fatalf("expected %v, got %v", instanceNameUpdated, refreshedState.Name.ValueString()) } - if url != refreshedState.Url.ValueString() { - t.Fatalf("expected %v, got %v", url, refreshedState.Url.ValueString()) + if description != refreshedState.Description.ValueString() { + t.Fatalf("expected %v, got %v", description, refreshedState.Description.ValueString()) } if refreshedState.State.ValueString() != "active" { t.Fatalf("expected %v, got %v", "active", refreshedState.State.ValueString()) } + if url != refreshedState.Url.ValueString() { + t.Fatalf("expected %v, got %v", url, refreshedState.Url.ValueString()) + } if region != refreshedState.Region.ValueString() { t.Fatalf("expected %v, got %v", region, refreshedState.Region.ValueString()) } - if refreshedState.BucketName.ValueString() != "bucket" { - t.Fatalf("expected %v, got %v", "bucket", refreshedState.BucketName.ValueString()) + if bucketName != refreshedState.BucketName.ValueString() { + t.Fatalf("expected %v, got %v", bucketName, refreshedState.BucketName.ValueString()) + } + if deletetExpRetention != refreshedState.DeletedExperimentRetention.ValueString() { + t.Fatalf("expected %v, got %v", deletetExpRetention, refreshedState.DeletedExperimentRetention.ValueString()) } } @@ -195,8 +214,13 @@ func TestRead_GetRequestFailed(t *testing.T) { projectId := uuid.New() instanceName := "test" + description := "description" region := "eu01" instanceId := uuid.New() + url := "url" + bucketName := "bucket" + deletetExpRetention := "1m" + tfId := utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()) oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: 400, @@ -214,15 +238,21 @@ func TestRead_GetRequestFailed(t *testing.T) { schemaResp := resource.SchemaResponse{} instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) - state := instance.Model{ - ProjectId: types.StringValue(projectId.String()), - InstanceId: types.StringValue(instanceId.String()), - Region: types.StringValue(region), - Name: types.StringValue(instanceName), - Labels: types.MapNull(types.StringType), + currentState := instance.Model{ + Id: tfId, + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceName), + Region: types.StringValue(region), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + DeletedExperimentRetention: types.StringValue(deletetExpRetention), + BucketName: types.StringValue(bucketName), + State: types.StringValue("active"), + Url: types.StringValue(url), } - req := testutils.ReadInstanceRequest(tc.Ctx, schemaResp, state) + req := testutils.ReadInstanceRequest(tc.Ctx, schemaResp, currentState) resp := testutils.ReadInstanceResponse(tc.Ctx, schemaResp, nil) instanceRes.Read(tc.Ctx, req, resp) @@ -230,13 +260,14 @@ func TestRead_GetRequestFailed(t *testing.T) { t.Fatalf("Get should not succeed") } - // state should not be set + // resp state should not be set var refreshedState *instance.Model diags := resp.State.Get(tc.Ctx, &refreshedState) if diags.HasError() { t.Fatalf("Failed to get state: %v", diags.Errors()) } + if refreshedState != nil { - t.Fatalf("expected nil, got %v", refreshedState) + t.Fatalf("State not nil") } } diff --git a/stackit/internal/services/modelexperiments/instance/resource_update_test.go b/stackit/internal/services/modelexperiments/instance/resource_update_test.go index 9f4669f66..ccc0f1cc3 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_update_test.go @@ -14,6 +14,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/modelexperiments/testutils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) func TestUpdate_Success(t *testing.T) { @@ -27,13 +28,17 @@ func TestUpdate_Success(t *testing.T) { region := "eu01" instanceId := uuid.New() url := "url" + tfId := utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()) + bucketName := "bucket" + deletetExpRetention := "1m" updateResp := &modelexperiments.PartialUpdateInstanceResponse{ Instance: modelexperiments.Instance{ - DeletedExperimentRetention: new("1m"), + DeletedExperimentRetention: &deletetExpRetention, + BucketName: &bucketName, Description: &descriptionUpdated, Name: instanceNameUpdated, - Region: new("eu01"), + Region: ®ion, Url: url, Id: instanceId.String(), State: "active", @@ -54,20 +59,22 @@ func TestUpdate_Success(t *testing.T) { instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) currentState := instance.Model{ - Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), - ProjectId: types.StringValue(projectId.String()), - InstanceId: types.StringValue(instanceId.String()), - Name: types.StringValue(instanceName), - Region: types.StringValue(region), - Description: types.StringValue(description), - Labels: types.MapNull(types.StringType), + Id: tfId, + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceName), + Region: types.StringValue(region), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + DeletedExperimentRetention: types.StringValue(deletetExpRetention), + BucketName: types.StringValue(bucketName), + State: types.StringValue("active"), + Url: types.StringValue(url), } plannedState := instance.Model{ - Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), ProjectId: types.StringValue(projectId.String()), Region: types.StringValue(region), - InstanceId: types.StringValue(instanceId.String()), Name: types.StringValue(instanceNameUpdated), Description: types.StringValue(descriptionUpdated), Labels: types.MapNull(types.StringType), @@ -90,6 +97,9 @@ func TestUpdate_Success(t *testing.T) { t.Fatalf("Failed to get state: %v", diags.Errors()) } + if tfId != finalState.Id { + t.Fatalf("expected %v, got %v", tfId.String(), finalState.Id.ValueString()) + } if instanceId.String() != finalState.InstanceId.ValueString() { t.Fatalf("expected %v, got %v", instanceId.String(), finalState.InstanceId.ValueString()) } @@ -111,6 +121,12 @@ func TestUpdate_Success(t *testing.T) { if region != finalState.Region.ValueString() { t.Fatalf("expected %v, got %v", region, finalState.Region.ValueString()) } + if bucketName != finalState.BucketName.ValueString() { + t.Fatalf("expected %v, got %v", bucketName, finalState.BucketName.ValueString()) + } + if deletetExpRetention != finalState.DeletedExperimentRetention.ValueString() { + t.Fatalf("expected %v, got %v", deletetExpRetention, finalState.DeletedExperimentRetention.ValueString()) + } } func TestUpdate_InstanceNotFound(t *testing.T) { @@ -123,6 +139,10 @@ func TestUpdate_InstanceNotFound(t *testing.T) { descriptionUpdated := "description updated" region := "eu01" instanceId := uuid.New() + url := "url" + tfId := utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()) + bucketName := "bucket" + deletetExpRetention := "1m" oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: 404, @@ -141,19 +161,22 @@ func TestUpdate_InstanceNotFound(t *testing.T) { instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) currentState := instance.Model{ - Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), - ProjectId: types.StringValue(projectId.String()), - InstanceId: types.StringValue(instanceId.String()), - Name: types.StringValue(instanceName), - Description: types.StringValue(description), - Labels: types.MapNull(types.StringType), + Id: tfId, + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceName), + Region: types.StringValue(region), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + DeletedExperimentRetention: types.StringValue(deletetExpRetention), + BucketName: types.StringValue(bucketName), + State: types.StringValue("active"), + Url: types.StringValue(url), } plannedState := instance.Model{ - Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), ProjectId: types.StringValue(projectId.String()), Region: types.StringValue(region), - InstanceId: types.StringValue(instanceId.String()), Name: types.StringValue(instanceNameUpdated), Description: types.StringValue(descriptionUpdated), Labels: types.MapNull(types.StringType), @@ -190,6 +213,10 @@ func TestUpdate_InstanceUpdateError(t *testing.T) { descriptionUpdated := "description updated" region := "eu01" instanceId := uuid.New() + url := "url" + tfId := utils.BuildInternalTerraformId(projectId.String(), region, instanceId.String()) + bucketName := "bucket" + deletetExpRetention := "1m" tc.MockInstanceCLient.EXPECT().PartialUpdateInstance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceRequest{ ApiService: tc.MockInstanceCLient, @@ -205,20 +232,22 @@ func TestUpdate_InstanceUpdateError(t *testing.T) { instanceRes.Schema(tc.Ctx, resource.SchemaRequest{}, &schemaResp) currentState := instance.Model{ - Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), - ProjectId: types.StringValue(projectId.String()), - InstanceId: types.StringValue(instanceId.String()), - Region: types.StringValue(region), - Name: types.StringValue(instanceName), - Description: types.StringValue(description), - Labels: types.MapNull(types.StringType), + Id: tfId, + ProjectId: types.StringValue(projectId.String()), + InstanceId: types.StringValue(instanceId.String()), + Name: types.StringValue(instanceName), + Region: types.StringValue(region), + Description: types.StringValue(description), + Labels: types.MapNull(types.StringType), + DeletedExperimentRetention: types.StringValue(deletetExpRetention), + BucketName: types.StringValue(bucketName), + State: types.StringValue("active"), + Url: types.StringValue(url), } plannedState := instance.Model{ - Id: types.StringValue(fmt.Sprintf("%s,%s", projectId, instanceId)), ProjectId: types.StringValue(projectId.String()), Region: types.StringValue(region), - InstanceId: types.StringValue(instanceId.String()), Name: types.StringValue(instanceNameUpdated), Description: types.StringValue(descriptionUpdated), Labels: types.MapNull(types.StringType), @@ -240,22 +269,34 @@ func TestUpdate_InstanceUpdateError(t *testing.T) { t.Fatalf("Failed to get state: %v", diags.Errors()) } - if description != finalState.Description.ValueString() { - t.Fatalf("value should not have changed - expected %v, got %v", description, finalState.Description.ValueString()) + if tfId != finalState.Id { + t.Fatalf("expected %v, got %v", tfId.String(), finalState.Id.ValueString()) + } + if instanceId.String() != finalState.InstanceId.ValueString() { + t.Fatalf("expected %v, got %v", instanceId.String(), finalState.InstanceId.ValueString()) + } + if projectId.String() != finalState.ProjectId.ValueString() { + t.Fatalf("expected %v, got %v", projectId.String(), finalState.ProjectId.ValueString()) } if instanceName != finalState.Name.ValueString() { - t.Fatalf("value should not have changed - expected %v, got %v", instanceName, finalState.Name.ValueString()) + t.Fatalf("expected %v, got %v", instanceName, finalState.Name.ValueString()) } - if instanceId.String() != finalState.InstanceId.ValueString() { - t.Fatalf("value should not have changed - expected %v, got %v", instanceId.String(), finalState.InstanceId.ValueString()) + if description != finalState.Description.ValueString() { + t.Fatalf("expected %v, got %v", description, finalState.Description.ValueString()) + } + if finalState.State.ValueString() != "active" { + t.Fatalf("expected %v, got %v", "active", finalState.State.ValueString()) + } + if url != finalState.Url.ValueString() { + t.Fatalf("expected %v, got %v", url, finalState.Url.ValueString()) } if region != finalState.Region.ValueString() { - t.Fatalf("value should not have changed - expected %v, got %v", region, finalState.Region.ValueString()) + t.Fatalf("expected %v, got %v", region, finalState.Region.ValueString()) } - if projectId.String() != finalState.ProjectId.ValueString() { - t.Fatalf("value should not have changed - expected %v, got %v", projectId.String(), finalState.ProjectId.ValueString()) + if bucketName != finalState.BucketName.ValueString() { + t.Fatalf("expected %v, got %v", bucketName, finalState.BucketName.ValueString()) } - if fmt.Sprintf("%s,%s", projectId, instanceId) != finalState.Id.ValueString() { - t.Fatalf("value should not have changed - expected %v, got %v", fmt.Sprintf("%s,%s", projectId, instanceId), finalState.Id.ValueString()) + if deletetExpRetention != finalState.DeletedExperimentRetention.ValueString() { + t.Fatalf("expected %v, got %v", deletetExpRetention, finalState.DeletedExperimentRetention.ValueString()) } } diff --git a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go index 3467da374..1a152f00a 100644 --- a/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go +++ b/stackit/internal/services/modelexperiments/modelexperiments_acc_test.go @@ -33,7 +33,7 @@ func inputInstanceConfig(instanceName, instanceDescription, token_name, token_de return fmt.Sprintf(` %s - resource "stackit_modelexperiments_instance" "example" { + resource "stackit_modelexperiments_instance" "instance" { project_id = "%s" name = "%s" region = "%s" @@ -44,7 +44,7 @@ func inputInstanceConfig(instanceName, instanceDescription, token_name, token_de project_id = "%s" name = "%s" region = "%s" - instance_id = stackit_modelexperiments_instance.example.instance_id + instance_id = stackit_modelexperiments_instance.instance.instance_id description = "%s" } `, @@ -63,7 +63,7 @@ func inputInstanceConfig(instanceName, instanceDescription, token_name, token_de func TestAccModelExperimentsInstanceResource(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckModelServingTokenDestroy, + CheckDestroy: testAccCheckModelExperimentsInstanceDestroy, Steps: []resource.TestStep{ // Creation { @@ -74,15 +74,15 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { instanceResource["token_description"], ), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "project_id", instanceResource["project_id"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "region", instanceResource["region"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "name", instanceResource["name"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "description", instanceResource["description"]), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "instance_id"), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "state"), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "bucket_name"), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "deleted_experiment_retention"), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "url"), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.instance", "project_id", instanceResource["project_id"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.instance", "region", instanceResource["region"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.instance", "name", instanceResource["name"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.instance", "description", instanceResource["description"]), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "state"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "bucket_name"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "deleted_experiment_retention"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "url"), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "project_id", instanceResource["project_id"]), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "region", instanceResource["region"]), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "name", instanceResource["token_name"]), @@ -103,15 +103,15 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { instanceResource["token_description_updated"], ), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "project_id", instanceResource["project_id"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "region", instanceResource["region"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "name", instanceResource["name"]), - resource.TestCheckResourceAttr("stackit_modelexperiments_instance.example", "description", instanceResource["description_updated"]), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "instance_id"), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "state"), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "bucket_name"), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "deleted_experiment_retention"), - resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.example", "url"), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.instance", "project_id", instanceResource["project_id"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.instance", "region", instanceResource["region"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.instance", "name", instanceResource["name"]), + resource.TestCheckResourceAttr("stackit_modelexperiments_instance.instance", "description", instanceResource["description_updated"]), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "state"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "bucket_name"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "deleted_experiment_retention"), + resource.TestCheckResourceAttrSet("stackit_modelexperiments_instance.instance", "url"), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "project_id", instanceResource["project_id"]), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "region", instanceResource["region"]), resource.TestCheckResourceAttr("stackit_modelexperiments_token.token", "name", instanceResource["token_name"]), @@ -128,7 +128,8 @@ func TestAccModelExperimentsInstanceResource(t *testing.T) { }) } -func testAccCheckModelServingTokenDestroy(s *terraform.State) error { +func testAccCheckModelExperimentsInstanceDestroy(s *terraform.State) error { + fmt.Println("destroying resources") ctx := context.Background() client, err := modelexperiments.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.ModelExperimentsCustomEndpoint, false)...) if err != nil { diff --git a/stackit/internal/services/modelexperiments/token/resource.go b/stackit/internal/services/modelexperiments/token/resource.go index fb0ee3ace..df3b93cd6 100644 --- a/stackit/internal/services/modelexperiments/token/resource.go +++ b/stackit/internal/services/modelexperiments/token/resource.go @@ -135,10 +135,16 @@ func (i *tokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp "id": schema.StringAttribute{ Description: "Terraform's internal data source. ID. It is structured as \"`project_id`,`region`,`token_id`\".", Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "project_id": schema.StringAttribute{ Description: "STACKIT project ID to which the AI model experiments instance token is associated.", Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Validators: []validator.String{ validate.UUID(), validate.NoSeparator(), @@ -163,6 +169,9 @@ func (i *tokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp "instance_id": schema.StringAttribute{ Description: "The AI model experiments instance ID.", Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Validators: []validator.String{ validate.UUID(), validate.NoSeparator(), @@ -171,6 +180,9 @@ func (i *tokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp "token_id": schema.StringAttribute{ Description: "The AI model experiments instance token ID.", Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, Validators: []validator.String{ validate.UUID(), validate.NoSeparator(), @@ -179,13 +191,11 @@ func (i *tokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp "labels": schema.MapAttribute{ Description: "A map of arbitrary key/value pairs for the AI model experiments instance token.", Optional: true, - Required: false, Computed: true, ElementType: types.StringType, }, "description": schema.StringAttribute{ Description: "The description of the AI model experiments instance token.", - Required: false, Optional: true, Validators: []validator.String{ stringvalidator.LengthBetween(0, 160), @@ -203,11 +213,16 @@ func (i *tokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp "valid_until": schema.StringAttribute{ Description: "The time until the AI model experiments instance token is valid.", Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "ttl_duration": schema.StringAttribute{ Description: "The TTL duration of the AI model experiments instance token. E.g. 5h30m40s,5h,5h30m,30m,30s", - Required: false, Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Validators: []validator.String{ validate.ValidDurationString(), }, @@ -359,8 +374,8 @@ func (i *tokenResource) Read(ctx context.Context, req resource.ReadRequest, resp // Update updates the resource and sets the updated Terraform state on success. func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform // Retrieve values from plan - var model Model - diags := req.Plan.Get(ctx, &model) + var plan Model + diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -377,16 +392,16 @@ func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, ctx = core.InitProviderContext(ctx) projectId := state.ProjectId.ValueString() - instanceId := model.InstanceId.ValueString() + instanceId := plan.InstanceId.ValueString() tokenId := state.TokenId.ValueString() - region := i.providerData.GetRegionWithOverride(model.Region) + region := i.providerData.GetRegionWithOverride(plan.Region) ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "token_id", tokenId) ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - payload, err := toUpdatePayload(&model) + payload, err := toUpdatePayload(&plan) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance", fmt.Sprintf("Creating API payload: %v", err)) return @@ -427,14 +442,14 @@ func (i *tokenResource) Update(ctx context.Context, req resource.UpdateRequest, return } - model.Token = state.Token - err = mapToken(ctx, &updateInstanceTokenResp.Token, &model) + plan.Token = state.Token + err = mapToken(ctx, &updateInstanceTokenResp.Token, &plan) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating AI model experiments instance token", fmt.Sprintf("Processing API payload: %v", err)) return } - diags = resp.State.Set(ctx, model) + diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/stackit/internal/services/modelexperiments/token/resource_create_test.go b/stackit/internal/services/modelexperiments/token/resource_create_test.go index 00aae4c60..267ae0d52 100644 --- a/stackit/internal/services/modelexperiments/token/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_create_test.go @@ -26,11 +26,13 @@ func TestCreate_Success(t *testing.T) { description := "token description" instanceId := uuid.New() tokenId := uuid.New() - validUntil := time.Now().Add(10 * time.Minute) + validUntil := time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC) + content := "token" + tfId := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) createTokenResp := &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{ - Content: "token", + Content: content, Description: &description, Id: tokenId.String(), Name: name, @@ -90,29 +92,38 @@ func TestCreate_Success(t *testing.T) { t.Fatalf("failed to get state") } - if tokenId.String() != createdState.TokenId.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", tokenId.String(), createdState.TokenId.ValueString()) + if createdState.ProjectId.ValueString() != projectId.String() { + t.Fatalf("ProjectId mismatch: got %v, want %v", createdState.ProjectId.ValueString(), projectId.String()) } - if projectId.String() != createdState.ProjectId.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", projectId.String(), createdState.ProjectId.ValueString()) + if createdState.Region.ValueString() != region { + t.Fatalf("Region mismatch: got %v, want %v", createdState.Region.ValueString(), region) } - if instanceId.String() != createdState.InstanceId.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", instanceId.String(), createdState.InstanceId.ValueString()) + if createdState.Name.ValueString() != name { + t.Fatalf("Name mismatch: got %v, want %v", createdState.Name.ValueString(), name) } - if name != createdState.Name.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", name, createdState.Name.ValueString()) + if createdState.Description.ValueString() != description { + t.Fatalf("Description mismatch: got %v, want %v", createdState.Description.ValueString(), description) + } + if createdState.InstanceId.ValueString() != instanceId.String() { + t.Fatalf("InstanceId mismatch: got %v, want %v", createdState.InstanceId.ValueString(), instanceId.String()) + } + if createdState.TokenId.ValueString() != tokenId.String() { + t.Fatalf("TokenId mismatch: got %v, want %v", createdState.TokenId.ValueString(), tokenId.String()) + } + if createdState.Id != tfId { + t.Fatalf("Id mismatch: got %v, want %v", createdState.Id.ValueString(), tfId) } if createdState.State.ValueString() != "active" { - t.Fatalf("Should be equal - expected %v, got %v", "active", createdState.State.ValueString()) + t.Fatalf("State mismatch: got %v, want active", createdState.State.ValueString()) } - if description != createdState.Description.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", description, createdState.Description.ValueString()) + if createdState.ValidUntil.ValueString() != "2099-01-01T00:00:00Z" { + t.Fatalf("ValidUntil mismatch: got %v, want 2099-01-01T00:00:00Z", createdState.ValidUntil.ValueString()) } - if createdState.Token.ValueString() != "token" { - t.Fatalf("Should be equal - expected %v, got %v", "token", createdState.Token.ValueString()) + if !createdState.Labels.IsNull() { + t.Fatalf("Labels should be null") } - if utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString() != createdState.Id.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString(), createdState.Id.ValueString()) + if createdState.Token.ValueString() != content { + t.Fatalf("Token mismatch: got %v, want %v", createdState.Token.ValueString(), content) } } @@ -125,10 +136,11 @@ func TestCreate_TokenIdEmpty(t *testing.T) { description := "token description" instanceId := uuid.New() validUntil := time.Now() + content := "token" createTokenResp := &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{ - Content: "token", + Content: content, Description: &description, Id: "", Name: name, @@ -240,11 +252,13 @@ func TestCreate_GetTokenFailure(t *testing.T) { description := "token description" instanceId := uuid.New() tokenId := uuid.New() - validUntil := time.Now().Add(10 * time.Minute) + validUntil := time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC) + content := "token" + tfId := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) createTokenResp := &modelexperiments.CreateInstanceTokenResponse{ Token: modelexperiments.Token{ - Content: "token", + Content: content, Description: &description, Id: tokenId.String(), Name: name, @@ -298,28 +312,37 @@ func TestCreate_GetTokenFailure(t *testing.T) { t.Fatalf("failed to get state") } - if tokenId.String() != createdState.TokenId.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", tokenId.String(), createdState.TokenId.ValueString()) + if createdState.ProjectId.ValueString() != projectId.String() { + t.Fatalf("ProjectId mismatch: got %v, want %v", createdState.ProjectId.ValueString(), projectId.String()) + } + if createdState.Region.ValueString() != region { + t.Fatalf("Region mismatch: got %v, want %v", createdState.Region.ValueString(), region) + } + if createdState.Name.ValueString() != name { + t.Fatalf("Name mismatch: got %v, want %v", createdState.Name.ValueString(), name) + } + if createdState.Description.ValueString() != description { + t.Fatalf("Description mismatch: got %v, want %v", createdState.Description.ValueString(), description) } - if projectId.String() != createdState.ProjectId.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", projectId.String(), createdState.ProjectId.ValueString()) + if createdState.InstanceId.ValueString() != instanceId.String() { + t.Fatalf("InstanceId mismatch: got %v, want %v", createdState.InstanceId.ValueString(), instanceId.String()) } - if instanceId.String() != createdState.InstanceId.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", instanceId.String(), createdState.InstanceId.ValueString()) + if createdState.TokenId.ValueString() != tokenId.String() { + t.Fatalf("TokenId mismatch: got %v, want %v", createdState.TokenId.ValueString(), tokenId.String()) } - if name != createdState.Name.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", name, createdState.Name.ValueString()) + if createdState.Id != tfId { + t.Fatalf("Id mismatch: got %v, want %v", createdState.Id.ValueString(), tfId) } - if createdState.State.ValueString() != string(createTokenResp.Token.State) { - t.Fatalf("Should be equal - expected %v, got %v", "creating", createdState.State.ValueString()) + if createdState.State.ValueString() != "creating" { + t.Fatalf("State mismatch: got %v, want creating", createdState.State.ValueString()) } - if description != createdState.Description.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", description, createdState.Description.ValueString()) + if createdState.ValidUntil.ValueString() != "2099-01-01T00:00:00Z" { + t.Fatalf("ValidUntil mismatch: got %v, want 2099-01-01T00:00:00Z", createdState.ValidUntil.ValueString()) } - if createdState.Token.ValueString() != "token" { - t.Fatalf("Should be equal - expected %v, got %v", "token", createdState.Token.ValueString()) + if !createdState.Labels.IsNull() { + t.Fatalf("Labels should be null") } - if utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString() != createdState.Id.ValueString() { - t.Fatalf("Should be equal - expected %v, got %v", utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()).ValueString(), createdState.Id.ValueString()) + if createdState.Token.ValueString() != content { + t.Fatalf("Token mismatch: got %v, want %v", createdState.Token.ValueString(), content) } } diff --git a/stackit/internal/services/modelexperiments/token/resource_update_test.go b/stackit/internal/services/modelexperiments/token/resource_update_test.go index 0835f6263..28f8320be 100644 --- a/stackit/internal/services/modelexperiments/token/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_update_test.go @@ -32,7 +32,7 @@ func TestUpdate_Success(t *testing.T) { validUntil := time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC) tokenContent := "token" state := "active" - id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + tfId := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) updateTokenResp := &modelexperiments.PartialUpdateInstanceTokenResponse{ Token: modelexperiments.TokenMetadata{ @@ -44,6 +44,7 @@ func TestUpdate_Success(t *testing.T) { ValidUntil: validUntil, }, } + tc.MockInstanceCLient.EXPECT().PartialUpdateInstanceToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelexperiments.ApiPartialUpdateInstanceTokenRequest{ ApiService: tc.MockInstanceCLient, }) @@ -66,7 +67,7 @@ func TestUpdate_Success(t *testing.T) { Labels: types.MapNull(types.StringType), Token: types.StringValue(tokenContent), TokenId: types.StringValue(tokenId.String()), - Id: id, + Id: tfId, State: types.StringValue(state), ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), } @@ -86,6 +87,7 @@ func TestUpdate_Success(t *testing.T) { // Execute Update tokenRes.Update(tc.Ctx, req, resp) if resp.Diagnostics.HasError() { + t.Fatalf("%v", resp.Diagnostics.Errors()) t.Fatalf("update should succeed") } @@ -95,11 +97,39 @@ func TestUpdate_Success(t *testing.T) { if diags.HasError() { t.Fatalf("failed to get state") } + + if updatedState.ProjectId.ValueString() != projectId.String() { + t.Fatalf("ProjectId mismatch: got %v, want %v", updatedState.ProjectId.ValueString(), projectId.String()) + } + if updatedState.Region.ValueString() != region { + t.Fatalf("Region mismatch: got %v, want %v", updatedState.Region.ValueString(), region) + } if updatedState.Name.ValueString() != nameUpdated { - t.Fatalf("should be equal") + t.Fatalf("Name mismatch: got %v, want %v", updatedState.Name.ValueString(), nameUpdated) } if updatedState.Description.ValueString() != descriptionUpdated { - t.Fatalf("should be equal") + t.Fatalf("Description mismatch: got %v, want %v", updatedState.Description.ValueString(), descriptionUpdated) + } + if updatedState.InstanceId.ValueString() != instanceId.String() { + t.Fatalf("InstanceId mismatch: got %v, want %v", updatedState.InstanceId.ValueString(), instanceId.String()) + } + if updatedState.TokenId.ValueString() != tokenId.String() { + t.Fatalf("TokenId mismatch: got %v, want %v", updatedState.TokenId.ValueString(), tokenId.String()) + } + if updatedState.Id != tfId { + t.Fatalf("Id mismatch: got %v, want %v", updatedState.Id.ValueString(), tfId) + } + if updatedState.State.ValueString() != "active" { + t.Fatalf("State mismatch: got %v, want active", updatedState.State.ValueString()) + } + if updatedState.ValidUntil.ValueString() != "2099-01-01T00:00:00Z" { + t.Fatalf("ValidUntil mismatch: got %v, want 2099-01-01T00:00:00Z", updatedState.ValidUntil.ValueString()) + } + if !updatedState.Labels.IsNull() { + t.Fatalf("Labels should be null") + } + if updatedState.Token.ValueString() != tokenContent { + t.Fatalf("Token mismatch: got %v, want %v", updatedState.Token.ValueString(), tokenContent) } } @@ -116,7 +146,7 @@ func TestUpdate_TokenNotFound(t *testing.T) { tokenId := uuid.New() tokenContent := "token" state := "active" - id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + tfId := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, @@ -143,7 +173,7 @@ func TestUpdate_TokenNotFound(t *testing.T) { Labels: types.MapNull(types.StringType), Token: types.StringValue(tokenContent), TokenId: types.StringValue(tokenId.String()), - Id: id, + Id: tfId, State: types.StringValue(state), ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), } @@ -190,7 +220,7 @@ func TestUpdate_TokenUpdateError(t *testing.T) { tokenId := uuid.New() tokenContent := "token" state := "active" - id := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) + tfId := utils.BuildInternalTerraformId(projectId.String(), region, tokenId.String()) oapiErr := &oapierror.GenericOpenAPIError{ StatusCode: http.StatusInternalServerError, @@ -217,7 +247,7 @@ func TestUpdate_TokenUpdateError(t *testing.T) { Labels: types.MapNull(types.StringType), Token: types.StringValue(tokenContent), TokenId: types.StringValue(tokenId.String()), - Id: id, + Id: tfId, State: types.StringValue(state), ValidUntil: types.StringValue("2099-01-01T00:00:00Z"), } @@ -247,11 +277,38 @@ func TestUpdate_TokenUpdateError(t *testing.T) { t.Fatalf("failed to get state") } + if updatedState.ProjectId.ValueString() != projectId.String() { + t.Fatalf("ProjectId mismatch: got %v, want %v", updatedState.ProjectId.ValueString(), projectId.String()) + } + if updatedState.Region.ValueString() != region { + t.Fatalf("Region mismatch: got %v, want %v", updatedState.Region.ValueString(), region) + } if updatedState.Name.ValueString() != name { - t.Fatalf("should be equal") + t.Fatalf("Name mismatch: got %v, want %v", updatedState.Name.ValueString(), name) } if updatedState.Description.ValueString() != description { - t.Fatalf("should be equal") + t.Fatalf("Description mismatch: got %v, want %v", updatedState.Description.ValueString(), description) + } + if updatedState.InstanceId.ValueString() != instanceId.String() { + t.Fatalf("InstanceId mismatch: got %v, want %v", updatedState.InstanceId.ValueString(), instanceId.String()) + } + if updatedState.TokenId.ValueString() != tokenId.String() { + t.Fatalf("TokenId mismatch: got %v, want %v", updatedState.TokenId.ValueString(), tokenId.String()) + } + if updatedState.Id != tfId { + t.Fatalf("Id mismatch: got %v, want %v", updatedState.Id.ValueString(), tfId) + } + if updatedState.State.ValueString() != "active" { + t.Fatalf("State mismatch: got %v, want active", updatedState.State.ValueString()) + } + if updatedState.ValidUntil.ValueString() != "2099-01-01T00:00:00Z" { + t.Fatalf("ValidUntil mismatch: got %v, want 2099-01-01T00:00:00Z", updatedState.ValidUntil.ValueString()) + } + if !updatedState.Labels.IsNull() { + t.Fatalf("Labels should be null") + } + if updatedState.Token.ValueString() != tokenContent { + t.Fatalf("Token mismatch: got %v, want %v", updatedState.Token.ValueString(), tokenContent) } } From dc31d2328e944873af950cf5dee5aaf72fe03a56 Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Mon, 29 Jun 2026 11:12:31 +0200 Subject: [PATCH 28/30] chore(mexp): add docs and fix tests --- docs/resources/modelexperiments_instance.md | 27 ++++++++++++++----- docs/resources/modelexperiments_token.md | 25 +++++++++++++++++ .../resource.tf | 10 +++++++ .../resource.tf | 22 +++++++++++++++ .../modelexperiments/instance/description.md | 6 ++--- .../instance/resource_create_test.go | 2 +- .../instance/resource_test.go | 2 +- .../modelexperiments/token/resource_test.go | 2 +- .../token/resource_update_test.go | 1 - 9 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 examples/resources/stackit_modelexperiments_instance/resource.tf create mode 100644 examples/resources/stackit_modelexperiments_token/resource.tf diff --git a/docs/resources/modelexperiments_instance.md b/docs/resources/modelexperiments_instance.md index 9efca6760..986a3f11f 100644 --- a/docs/resources/modelexperiments_instance.md +++ b/docs/resources/modelexperiments_instance.md @@ -8,9 +8,9 @@ description: |- resource "stackit_modelexperiments_instance" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "Example instance" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" description = "Example description" } --- @@ -24,14 +24,27 @@ AI Model Experiment Instance Resource schema. ```terraform resource "stackit_modelexperiments_instance" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "Example instance" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" description = "Example description" } ``` +## Example Usage +```terraform +resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "example name" + description = "Example description" + deleted_experiment_retention = "30d" + labels = { + label = "example label" + } +} +``` ## Schema @@ -45,13 +58,13 @@ resource "stackit_modelexperiments_instance" "example" { - `deleted_experiment_retention` (String) The deleted experiment retention of the AI model experiments instance. - `description` (String) The description of the AI model experiments instance. -- `error_message` (String) Error messages of the AI model experiments instance. - `labels` (Map of String) A map of arbitrary key/value pairs that can be attached to the AI model experiments instance - `region` (String) Region to which the AI model experiments instance is associated. If not defined, the provider region is used ### Read-Only - `bucket_name` (String) The object storage bucket name of the AI model experiments instance. +- `error_message` (String) Error messages of the AI model experiments instance. - `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`instance_id`". - `instance_id` (String) The AI model experiments instance ID. - `state` (String) State of the AI model experiments instance. diff --git a/docs/resources/modelexperiments_token.md b/docs/resources/modelexperiments_token.md index 878564b91..bddcaa983 100644 --- a/docs/resources/modelexperiments_token.md +++ b/docs/resources/modelexperiments_token.md @@ -47,7 +47,32 @@ resource "stackit_modelexperiments_token" "token" { } ``` +## Example Usage + +```terraform +resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "Example name" + description = "Example description" + deleted_experiment_retention = "30d" + labels = { + label = "Example label" + } +} +resource "stackit_modelexperiments_token" "token" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example token nane" + region = "eu01" + instance_id = stackit_modelexperiments_instance.example.instance_id + description = "Example token description" + ttl_duration = "1h" + labels = { + label = "Example label" + } +} +``` ## Schema diff --git a/examples/resources/stackit_modelexperiments_instance/resource.tf b/examples/resources/stackit_modelexperiments_instance/resource.tf new file mode 100644 index 000000000..bc1182b49 --- /dev/null +++ b/examples/resources/stackit_modelexperiments_instance/resource.tf @@ -0,0 +1,10 @@ +resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "example name" + description = "Example description" + deleted_experiment_retention = "30d" + labels = { + label = "example label" + } +} \ No newline at end of file diff --git a/examples/resources/stackit_modelexperiments_token/resource.tf b/examples/resources/stackit_modelexperiments_token/resource.tf new file mode 100644 index 000000000..3f056af71 --- /dev/null +++ b/examples/resources/stackit_modelexperiments_token/resource.tf @@ -0,0 +1,22 @@ +resource "stackit_modelexperiments_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "Example name" + description = "Example description" + deleted_experiment_retention = "30d" + labels = { + label = "Example label" + } +} + +resource "stackit_modelexperiments_token" "token" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example token nane" + region = "eu01" + instance_id = stackit_modelexperiments_instance.example.instance_id + description = "Example token description" + ttl_duration = "1h" + labels = { + label = "Example label" + } +} \ No newline at end of file diff --git a/stackit/internal/services/modelexperiments/instance/description.md b/stackit/internal/services/modelexperiments/instance/description.md index 0b29ea11a..d3adab332 100644 --- a/stackit/internal/services/modelexperiments/instance/description.md +++ b/stackit/internal/services/modelexperiments/instance/description.md @@ -5,9 +5,9 @@ AI Model Experiment Instance Resource schema. ```terraform resource "stackit_modelexperiments_instance" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "Example instance" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" description = "Example description" } ``` \ No newline at end of file diff --git a/stackit/internal/services/modelexperiments/instance/resource_create_test.go b/stackit/internal/services/modelexperiments/instance/resource_create_test.go index 44a43ea15..9f983d232 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_create_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_create_test.go @@ -281,7 +281,7 @@ func TestCreate_GetInstanceFailure(t *testing.T) { if region != stateAfterCreate.Region.ValueString() { t.Fatalf("expected %v, got %v", region, stateAfterCreate.Region.ValueString()) } - if "" != stateAfterCreate.BucketName.ValueString() { + if stateAfterCreate.BucketName.ValueString() != "" { t.Fatalf("expected %v, got %v", "", stateAfterCreate.BucketName.ValueString()) } if deletetExpRetention != stateAfterCreate.DeletedExperimentRetention.ValueString() { diff --git a/stackit/internal/services/modelexperiments/instance/resource_test.go b/stackit/internal/services/modelexperiments/instance/resource_test.go index 447466dac..b6eb9cbe2 100644 --- a/stackit/internal/services/modelexperiments/instance/resource_test.go +++ b/stackit/internal/services/modelexperiments/instance/resource_test.go @@ -161,7 +161,7 @@ func TestMapCreateResponseFields(t *testing.T) { InstanceId: types.StringValue("id"), Name: types.StringValue("name"), Description: types.StringValue("description"), - State: types.StringValue("unknown"), + State: types.StringValue("pending"), DeletedExperimentRetention: types.StringValue("30d"), Url: types.StringValue("url"), ErrorMessage: types.StringNull(), diff --git a/stackit/internal/services/modelexperiments/token/resource_test.go b/stackit/internal/services/modelexperiments/token/resource_test.go index 4d707c5cd..fb75e5aa4 100644 --- a/stackit/internal/services/modelexperiments/token/resource_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_test.go @@ -157,7 +157,7 @@ func TestMapCreateResponseFields(t *testing.T) { InstanceId: types.StringValue("id"), Name: types.StringValue("name"), Description: types.StringValue("description"), - State: types.StringValue("unknown"), + State: types.StringValue("active"), Token: types.StringValue("token"), TokenId: types.StringValue("id"), Labels: types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}), diff --git a/stackit/internal/services/modelexperiments/token/resource_update_test.go b/stackit/internal/services/modelexperiments/token/resource_update_test.go index 28f8320be..2d8eeb575 100644 --- a/stackit/internal/services/modelexperiments/token/resource_update_test.go +++ b/stackit/internal/services/modelexperiments/token/resource_update_test.go @@ -87,7 +87,6 @@ func TestUpdate_Success(t *testing.T) { // Execute Update tokenRes.Update(tc.Ctx, req, resp) if resp.Diagnostics.HasError() { - t.Fatalf("%v", resp.Diagnostics.Errors()) t.Fatalf("update should succeed") } From 825ab15f9033220061da5bf2f2cd0350faf9a30f Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Mon, 29 Jun 2026 11:22:37 +0200 Subject: [PATCH 29/30] chore(mexp): fix description.md --- go.mod | 3 +-- .../services/modelexperiments/token/description.md | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 0286c8389..b220db31b 100644 --- a/go.mod +++ b/go.mod @@ -54,8 +54,6 @@ require ( golang.org/x/mod v0.37.0 ) -require github.com/stretchr/testify v1.11.1 // indirect - require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect @@ -221,6 +219,7 @@ require ( github.com/ssgreg/nlreturn/v2 v2.2.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 github.com/subosito/gotenv v1.4.1 // indirect github.com/tetafro/godot v1.5.4 // indirect github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect diff --git a/stackit/internal/services/modelexperiments/token/description.md b/stackit/internal/services/modelexperiments/token/description.md index ceb145d7b..6f4886e3d 100644 --- a/stackit/internal/services/modelexperiments/token/description.md +++ b/stackit/internal/services/modelexperiments/token/description.md @@ -5,17 +5,17 @@ AI Model Experiment Instance Token Resource schema. ```terraform resource "stackit_modelexperiments_instance" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "Example instance" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" description = "Example description" } resource "stackit_modelexperiments_token" "token" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "Example token" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example token" + region = "eu01" instance_id = stackit_modelexperiments_instance.example.instance_id - description = "Example description" + description = "Example description" } ``` \ No newline at end of file From 50e9dcf95f1d429fe64633217d00af5dfda5147f Mon Sep 17 00:00:00 2001 From: Peter Paul Schaffrath Date: Tue, 30 Jun 2026 08:23:09 +0200 Subject: [PATCH 30/30] chore(mexp): fix docs --- docs/resources/modelexperiments_token.md | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/resources/modelexperiments_token.md b/docs/resources/modelexperiments_token.md index bddcaa983..472a7bc11 100644 --- a/docs/resources/modelexperiments_token.md +++ b/docs/resources/modelexperiments_token.md @@ -8,18 +8,18 @@ description: |- resource "stackit_modelexperiments_instance" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "Example instance" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" description = "Example description" } resource "stackit_modelexperiments_token" "token" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "Example token" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example token" + region = "eu01" instance_id = stackit_modelexperiments_instance.example.instance_id - description = "Example description" + description = "Example description" } --- @@ -32,18 +32,18 @@ AI Model Experiment Instance Token Resource schema. ```terraform resource "stackit_modelexperiments_instance" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "Example instance" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example instance" + region = "eu01" description = "Example description" } resource "stackit_modelexperiments_token" "token" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "Example token" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "Example token" + region = "eu01" instance_id = stackit_modelexperiments_instance.example.instance_id - description = "Example description" + description = "Example description" } ```