From 3cd94381393aaa7f0555f544adb75e4d1b5687f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 15 May 2026 16:45:53 +0200 Subject: [PATCH 01/33] fix(Docs): Adding `services` to path in utils creation step description. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 267a95238..4c9cfce7f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,7 +83,7 @@ If you want to onboard resources of a STACKIT service `foo` that was not yet in ```go setStringField(providerConfig.FooCustomEndpoint, func(v string) { providerData.FooCustomEndpoint = v }) ``` -4. Create a utils package, for service `foo` it would be `stackit/internal/foo/utils`. Add a `ConfigureClient()` func and use it in your resource and datasource implementations. +4. Create a utils package, for service `foo` it would be `stackit/internal/services/foo/utils`. Add a `ConfigureClient()` func and use it in your resource and datasource implementations. https://github.com/stackitcloud/terraform-provider-stackit/blob/main/.github/docs/contribution-guide/utils/util.go From 723c02085825b06eba6d8ec3d71170904ba7ae89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 15 May 2026 16:48:53 +0200 Subject: [PATCH 02/33] feat(dremio): Initial Commit for onboarding of the STACKIT Dremio service. Adding: - Dremio SDK integration - `.../services/dremio/` directory with packages and stubs for both dremio instances and users --- go.mod | 1 + go.sum | 2 ++ stackit/internal/core/core.go | 1 + .../services/dremio/instance/resource.go | 1 + .../internal/services/dremio/user/resource.go | 1 + .../internal/services/dremio/utils/util.go | 30 +++++++++++++++++++ stackit/provider.go | 3 ++ 7 files changed, 39 insertions(+) create mode 100644 stackit/internal/services/dremio/instance/resource.go create mode 100644 stackit/internal/services/dremio/user/resource.go create mode 100644 stackit/internal/services/dremio/utils/util.go diff --git a/go.mod b/go.mod index 525872d8d..1c14b81fc 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/cdn v1.16.0 github.com/stackitcloud/stackit-sdk-go/services/certificates v1.7.0 github.com/stackitcloud/stackit-sdk-go/services/dns v0.20.2 + github.com/stackitcloud/stackit-sdk-go/services/dremio v0.1.0 github.com/stackitcloud/stackit-sdk-go/services/edge v0.11.0 github.com/stackitcloud/stackit-sdk-go/services/git v0.13.0 github.com/stackitcloud/stackit-sdk-go/services/iaas v1.12.0 diff --git a/go.sum b/go.sum index 13d31e3dc..26f2e5439 100644 --- a/go.sum +++ b/go.sum @@ -680,6 +680,8 @@ github.com/stackitcloud/stackit-sdk-go/services/certificates v1.7.0 h1:J7BVVHjRT github.com/stackitcloud/stackit-sdk-go/services/certificates v1.7.0/go.mod h1:eJpB3/pukz+KzVPVHQ4g3DVtQkxGga18VbFBhq9ugdY= github.com/stackitcloud/stackit-sdk-go/services/dns v0.20.2 h1:nMJRg1dKioOlMwXJnZZgIRwfTWYCksVA9GyfAVmib1g= github.com/stackitcloud/stackit-sdk-go/services/dns v0.20.2/go.mod h1:FiYSv3D9rzgEVzi8Mpq5oYZBosrasa5uUYqVdEIbM1U= +github.com/stackitcloud/stackit-sdk-go/services/dremio v0.1.0 h1:yNFIU1+1dA2uK8ERdBb1Ut74Kt2szn4qgelBbM93JXA= +github.com/stackitcloud/stackit-sdk-go/services/dremio v0.1.0/go.mod h1:iMoiM8fM1mXC1Nz8FBiiQ08Yh+0C3yN0GPCdAbOlRXo= github.com/stackitcloud/stackit-sdk-go/services/edge v0.11.0 h1:/JUxaJSGmg+PRj90e4fngWkXNQkRKHOYpVykJ3zoy7w= github.com/stackitcloud/stackit-sdk-go/services/edge v0.11.0/go.mod h1:Ylse6gqGJtsd5TVmvha+hoLd1QQHLKvhY5dO15+q5kg= github.com/stackitcloud/stackit-sdk-go/services/git v0.13.0 h1:BdamSnGYhDkDqUWQQcJ8Kqik90laTK1IlG5CQqyLVgA= diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go index 8fce15412..d71a0cfed 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -45,6 +45,7 @@ type ProviderData struct { AuthorizationCustomEndpoint string CdnCustomEndpoint string DnsCustomEndpoint string + DremioCustomEndpoint string EdgeCloudCustomEndpoint string GitCustomEndpoint string IaaSCustomEndpoint string diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go new file mode 100644 index 000000000..8dc0f480d --- /dev/null +++ b/stackit/internal/services/dremio/instance/resource.go @@ -0,0 +1 @@ +package dremio diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go new file mode 100644 index 000000000..8dc0f480d --- /dev/null +++ b/stackit/internal/services/dremio/user/resource.go @@ -0,0 +1 @@ +package dremio diff --git a/stackit/internal/services/dremio/utils/util.go b/stackit/internal/services/dremio/utils/util.go new file mode 100644 index 000000000..e32a25d7a --- /dev/null +++ b/stackit/internal/services/dremio/utils/util.go @@ -0,0 +1,30 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/stackitcloud/stackit-sdk-go/core/config" + dremio "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *dremio.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.DremioCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.DremioCustomEndpoint)) + } + apiClient, err := dremio.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/provider.go b/stackit/provider.go index f62e157e2..5e52728f7 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -177,6 +177,7 @@ type providerModel struct { CdnCustomEndpoint types.String `tfsdk:"cdn_custom_endpoint"` ALBCertificatesCustomEndpoint types.String `tfsdk:"alb_certificates_custom_endpoint"` DnsCustomEndpoint types.String `tfsdk:"dns_custom_endpoint"` + DremioCustomEndpoint types.String `tfsdk:"dremio_custom_endpoint"` EdgeCloudCustomEndpoint types.String `tfsdk:"edgecloud_custom_endpoint"` GitCustomEndpoint types.String `tfsdk:"git_custom_endpoint"` IaaSCustomEndpoint types.String `tfsdk:"iaas_custom_endpoint"` @@ -236,6 +237,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "alb_custom_endpoint": "Custom endpoint for the Application Load Balancer service", "cdn_custom_endpoint": "Custom endpoint for the CDN service", "dns_custom_endpoint": "Custom endpoint for the DNS service", + "dremio_custom_endpoint": "Custom endpoint for the Dremio service", "edgecloud_custom_endpoint": "Custom endpoint for the Edge Cloud service", "git_custom_endpoint": "Custom endpoint for the Git service", "iaas_custom_endpoint": "Custom endpoint for the IaaS service", @@ -550,6 +552,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, setStringField(providerConfig.AuthorizationCustomEndpoint, func(v string) { providerData.AuthorizationCustomEndpoint = v }) setStringField(providerConfig.CdnCustomEndpoint, func(v string) { providerData.CdnCustomEndpoint = v }) setStringField(providerConfig.DnsCustomEndpoint, func(v string) { providerData.DnsCustomEndpoint = v }) + setStringField(providerConfig.DremioCustomEndpoint, func(v string) { providerData.DremioCustomEndpoint = v }) setStringField(providerConfig.EdgeCloudCustomEndpoint, func(v string) { providerData.EdgeCloudCustomEndpoint = v }) setStringField(providerConfig.GitCustomEndpoint, func(v string) { providerData.GitCustomEndpoint = v }) setStringField(providerConfig.IaaSCustomEndpoint, func(v string) { providerData.IaaSCustomEndpoint = v }) From f608724d805858e54f0942c9cc55ab9a49063cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 22 May 2026 10:06:24 +0200 Subject: [PATCH 03/33] feat(dremio): Adding Dremio instance resource. First draft for the implementation of a Dremio instance resource. --- .../services/dremio/instance/resource.go | 780 ++++++++++++++++++ .../services/dremio/instance/resource_test.go | 341 ++++++++ stackit/provider.go | 6 + 3 files changed, 1127 insertions(+) create mode 100644 stackit/internal/services/dremio/instance/resource_test.go diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index 8dc0f480d..de604fa71 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -1 +1,781 @@ package dremio + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "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/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + + dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" + dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait/wait" + dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" +) + +var ( + _ resource.Resource = &instanceResource{} + _ resource.ResourceWithConfigure = &instanceResource{} + _ resource.ResourceWithImportState = &instanceResource{} + _ resource.ResourceWithModifyPlan = &instanceResource{} // not needed for global APIs +) + +// InstanceModel maps the resource schema data. +type InstanceModel struct { + Id types.String `tfsdk:"id"` + + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + InstanceId types.String `tfsdk:"instance_id"` + + // Required Fields + DisplayName types.String `tfsdk:"display_name"` + Authentication *AuthenticationModel `tfsdk:"authentication"` + + // Optional Fields + Description types.String `tfsdk:"description"` + + // Read-only Fields + State types.String `tfsdk:"state"` + ErrorMessage types.String `tfsdk:"error_message"` + Endpoints types.Object `tfsdk:"endpoints"` // see endpointsTypes below + + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +// AuthenticationModel maps the nested authentication block. +type AuthenticationModel struct { + // Required Fields + Type types.String `tfsdk:"type"` + + // Optional Fields + AzureAD *AzureADModel `tfsdk:"azuread"` + OAuth *OAuthModel `tfsdk:"oauth"` +} + +type AzureADModel struct { + // Required Fields + AuthorityUrl types.String `tfsdk:"authority_url"` + ClientId types.String `tfsdk:"client_id"` + ClientSecret types.String `tfsdk:"client_secret"` + + RedirectUrl types.String `tfsdk:"redirect_url"` +} + +type OAuthModel struct { + // Required Fields + AuthorityUrl types.String `tfsdk:"authority_url"` + ClientId types.String `tfsdk:"client_id"` + ClientSecret types.String `tfsdk:"client_secret"` + JwtClaims *JwtClaimsModel `tfsdk:"jwt_claims"` + + // Optional Fields + Scope types.String `tfsdk:"scope"` + Parameters []AuthParameterModel `tfsdk:"parameters"` + + // Read-only Fields + RedirectUrl types.String `tfsdk:"redirect_url"` +} + +type JwtClaimsModel struct { + // Required Fields + UserName types.String `tfsdk:"user_name"` +} + +type AuthParameterModel struct { + // Required Fields + Name types.String `tfsdk:"name"` + Value types.String `tfsdk:"value"` +} + +var endpointsTypes = map[string]attr.Type{ + "arrow_flight": basetypes.StringType{}, + "catalog": basetypes.StringType{}, + "ui": basetypes.StringType{}, +} + +func NewInstanceResource() resource.Resource { + return &instanceResource{} +} + +type instanceResource struct { + client *dremioSdk.APIClient + providerData core.ProviderData // not needed for global APIs +} + +func (r *instanceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dremio_instance" +} + +func (r *instanceResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform + var configModel InstanceModel + // 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 InstanceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) + if resp.Diagnostics.HasError() { + return + } + + utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *instanceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := dremioUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + r.client = apiClient + tflog.Info(ctx, "Dremio instance client configured") +} + +func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + descriptions := map[string]string{ + "main": "Manages a STACKIT Dremio instance.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`dremio_id`\".", + "project_id": "STACKIT Project ID to which the resource is associated.", + "instance_id": "The Dremio instance ID.", + "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", + "display_name": "The display name is a short name chosen by the user to identify the resource.", + "description": "The description is a longer text chosen by the user to provide more context for the resource.", + "state": "The current state of the resource.", + "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", + "endpoints": "The available endpoints of the Dremio instance.", + "endpoints_arrow_flight": "The arrow flight endpoint of the Dremio instance.", + "endpoints_catalog": "The Apache Iceberg endpoint of the Dremio instance.", + "endpoints_ui": "The UI endpoint of the Dremio instance.", + "authentication": "Dremio instance authentication settings. A change here triggers a Dremio restart and will incur downtime.", + "authentication_type": "Type of authentication (local-only, azuread, oauth).", + "azuread": "Azure Active Directory authentication configuration.", + "azuread_authority_url": "The Azure AD authority URL.", + "azuread_client_id": "The Azure AD client ID.", + "azuread_client_secret": "The Azure AD client secret.", + "azuread_redirect_url": "The Azure AD redirect URL.", + "oauth": "OIDC authentication configuration.", + "oauth_authority_url": "The Issuer location URI, where the OIDC provider configuration can be found.", + "oauth_client_id": "The client ID assigned by the Identity Provider.", + "oauth_client_secret": "The client secret generated by the Identity Provider.", + "oauth_scope": "A list of space-separated scopes. The `openid` scope is always required; other scopes can vary by provider.", + "oauth_redirect_url": "The URL where the Dremio instance is hosted. The URL must match the redirect URL set in the Identity Provider.", + "oauth_jwt_claims": "Maps fields from the JWT token to fields Dremio requires.", + "oauth_jwt_claims_user_name": "Mapped user name claim (e.g. email).", + "oauth_parameters": "Any additional parameters the Identity Provider requires.", + "oauth_parameters_name": "Parameter name.", + "oauth_parameters_value": "Parameter value.", + } + + resp.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "instance_id": schema.StringAttribute{ + Description: descriptions["instance_id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "region": schema.StringAttribute{ + Required: true, + Description: descriptions["region"], + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["display_name"], + Required: true, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "state": schema.StringAttribute{ + Description: descriptions["state"], + Computed: true, + }, + "error_message": schema.StringAttribute{ + Description: descriptions["error_message"], + Optional: true, + Computed: true, + }, + "endpoints": schema.SingleNestedAttribute{ + Description: descriptions["endpoints"], + Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + Attributes: map[string]schema.Attribute{ + "arrow_flight": schema.StringAttribute{ + Description: descriptions["endpoints_arrow_flight"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "catalog": schema.StringAttribute{ + Description: descriptions["endpoints_catalog"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "ui": schema.StringAttribute{ + Description: descriptions["endpoints_ui"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + "authentication": schema.SingleNestedAttribute{ + Description: descriptions["authentication"], + Required: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Description: descriptions["authentication_type"], + Required: true, + }, + "azuread": schema.SingleNestedAttribute{ + Description: descriptions["azuread"], + Optional: true, + Attributes: map[string]schema.Attribute{ + "authority_url": schema.StringAttribute{ + Description: descriptions["azuread_authority_url"], + Required: true, + }, + "client_id": schema.StringAttribute{ + Description: descriptions["azuread_client_id"], + Required: true, + }, + "client_secret": schema.StringAttribute{ + Description: descriptions["azuread_client_secret"], + Required: true, + Sensitive: true, + }, + "redirect_url": schema.StringAttribute{ + Description: descriptions["azuread_redirect_url"], + Computed: true, + }, + }, + }, + "oauth": schema.SingleNestedAttribute{ + Description: descriptions["oauth"], + Optional: true, + Attributes: map[string]schema.Attribute{ + "authority_url": schema.StringAttribute{ + Description: descriptions["oauth_authority_url"], + Required: true, + }, + "client_id": schema.StringAttribute{ + Description: descriptions["oauth_client_id"], + Required: true, + }, + "client_secret": schema.StringAttribute{ + Description: descriptions["oauth_client_secret"], + Required: true, + Sensitive: true, + }, + "scope": schema.StringAttribute{ + Description: descriptions["oauth_scope"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "redirect_url": schema.StringAttribute{ + Description: descriptions["oauth_redirect_url"], + Computed: true, + }, + "jwt_claims": schema.SingleNestedAttribute{ + Description: descriptions["oauth_jwt_claims"], + Required: true, + Attributes: map[string]schema.Attribute{ + "user_name": schema.StringAttribute{ + Description: descriptions["oauth_jwt_claims_user_name"], + Required: true, + }, + }, + }, + "parameters": schema.ListNestedAttribute{ + Description: descriptions["oauth_parameters"], + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: descriptions["oauth_parameters_name"], + Required: true, + }, + "value": schema.StringAttribute{ + Description: descriptions["oauth_parameters_value"], + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + "timeouts": timeouts.AttributesAll(ctx), + }, + } +} + +func (r *instanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform + var model InstanceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + waiterTimeout := dremioWaiter.CreateDremioWaitHandler(ctx, r.client.DefaultAPI, "", "", "").GetTimeout() + createTimeout, diags := model.Timeouts.Create(ctx, waiterTimeout+core.DefaultTimeoutMargin) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, createTimeout) + defer cancel() + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() // not needed for global APIs + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + // prepare the payload struct for the create instance request + payload, err := toCreatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + // Create new Dremio instance + instanceResp, err := r.client.DefaultAPI.CreateDremioInstance(ctx, projectId, region).CreateDremioInstancePayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]interface{}{ + "project_id": projectId, + "region": region, + "instance_id": instanceResp.Id, + }) + if resp.Diagnostics.HasError() { + return + } + + _, err = dremioWaiter.CreateDremioWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceResp.Id).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Dremio instance", fmt.Sprintf("Dremio instance creation waiting: %v", err)) + return + } + + err = mapFields(instanceResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Dremio instance created") +} + +func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model InstanceModel + resp.Diagnostics.Append(req.State.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + readTimeout, diags := model.Timeouts.Read(ctx, core.DefaultOperationTimeout) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + instanceId := model.InstanceId.ValueString() + if instanceId == "" { + // Resource not yet created; ID is unknown. + resp.State.RemoveResource(ctx) + return + } + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + + instanceResp, err := r.client.DefaultAPI.GetDremioInstance(ctx, projectId, region, instanceId).Execute() + if err != nil { + if oapiErr, ok := errors.AsType[*oapierror.GenericOpenAPIError](err); ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading dremio instance", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(instanceResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading dremio instance", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Dremio instance read") +} + +func (r *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform + var model, state InstanceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + waiterTimeout := dremioWaiter.UpdateDremioWaitHandler(ctx, r.client.DefaultAPI, "", "", "").GetTimeout() + updateTimeout, diags := model.Timeouts.Update(ctx, waiterTimeout+core.DefaultTimeoutMargin) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, updateTimeout) + defer cancel() + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() // not needed for global APIs + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + payload, err := toUpdatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + instanceId := state.InstanceId.ValueString() + instanceResp, err := r.client.DefaultAPI.UpdateDremioInstance(ctx, projectId, region, instanceId).UpdateDremioInstancePayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]interface{}{ + "project_id": projectId, + "region": region, + "instance_id": instanceResp.Id, + }) + if resp.Diagnostics.HasError() { + return + } + + _, err = dremioWaiter.UpdateDremioWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceResp.Id).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating Dremio instance", fmt.Sprintf("Dremio instance updating waiting: %v", err)) + return + } + + err = mapFields(instanceResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Dremio instance updated") +} + +func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform + var model InstanceModel + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + waiterTimeout := dremioWaiter.DeleteDremioWaitHandler(ctx, r.client.DefaultAPI, "", "", "").GetTimeout() + deleteTimeout, diags := model.Timeouts.Delete(ctx, waiterTimeout+core.DefaultTimeoutMargin) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, deleteTimeout) + defer cancel() + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() + instanceId := model.InstanceId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + + err := r.client.DefaultAPI.DeleteDremioInstance(ctx, projectId, region, instanceId).Execute() + if err != nil { + if oapiErr, ok := errors.AsType[*oapierror.GenericOpenAPIError](err); ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting Dremio instance", fmt.Sprintf("Calling API: %v", err)) + } + + ctx = core.LogResponse(ctx) + + _, err = dremioWaiter.DeleteDremioWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting Dremio instance", fmt.Sprintf("Dremio instance deletion waiting: %v", err)) + return + } + + tflog.Info(ctx, "Dremio instance deleted") +} + +func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, core.Separator) + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, + "Error importing dremio instance", + fmt.Sprintf("Expected import identifier with format [project_id],[region],[instance_id], got %q", req.ID), + ) + return + } + + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": idParts[0], + "region": idParts[1], + "instance_id": idParts[2], + }) + + tflog.Info(ctx, "Dremio instance state imported") +} + +// Maps instance fields to the provider's internal model +func mapFields(instanceResp *dremioSdk.DremioResponse, model *InstanceModel) error { + if instanceResp == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + model.InstanceId = types.StringValue(instanceResp.Id) + + model.Id = utils.BuildInternalTerraformId( + model.ProjectId.ValueString(), + model.Region.ValueString(), + model.InstanceId.ValueString(), + ) + + model.DisplayName = types.StringValue(instanceResp.DisplayName) + model.State = types.StringValue(instanceResp.State) + + model.Description = types.StringPointerValue(instanceResp.Description) + model.ErrorMessage = types.StringPointerValue(instanceResp.ErrorMessage) + + endpoints, diags := types.ObjectValue(endpointsTypes, map[string]attr.Value{ + "arrow_flight": types.StringValue(instanceResp.Endpoints.ArrowFlight), + "catalog": types.StringValue(instanceResp.Endpoints.Catalog), + "ui": types.StringValue(instanceResp.Endpoints.Ui), + }) + if diags.HasError() { + return fmt.Errorf("error mapping endpoints: %v", diags) + } + model.Endpoints = endpoints + + authModel := &AuthenticationModel{ + Type: types.StringValue(instanceResp.Authentication.Type), + } + + if instanceResp.Authentication.Azuread != nil { + azureADResp := instanceResp.Authentication.Azuread + authModel.AzureAD = &AzureADModel{ + AuthorityUrl: types.StringValue(azureADResp.AuthorityUrl), + ClientId: types.StringValue(azureADResp.ClientId), + ClientSecret: types.StringValue(azureADResp.ClientSecret), + RedirectUrl: types.StringPointerValue(azureADResp.RedirectUrl), + } + } + + if instanceResp.Authentication.Oauth != nil { + oauthResp := instanceResp.Authentication.Oauth + oauthModel := &OAuthModel{ + AuthorityUrl: types.StringValue(oauthResp.AuthorityUrl), + ClientId: types.StringValue(oauthResp.ClientId), + ClientSecret: types.StringValue(oauthResp.ClientSecret), + Scope: types.StringPointerValue(oauthResp.Scope), + RedirectUrl: types.StringPointerValue(oauthResp.RedirectUrl), + JwtClaims: &JwtClaimsModel{ + UserName: types.StringValue(oauthResp.JwtClaims.UserName), + }, + } + + if len(oauthResp.Parameters) > 0 { + var params []AuthParameterModel + for _, p := range oauthResp.Parameters { + params = append(params, AuthParameterModel{ + Name: types.StringValue(p.Name), + Value: types.StringValue(p.Value), + }) + } + oauthModel.Parameters = params + } + + authModel.OAuth = oauthModel + } + + model.Authentication = authModel + + return nil +} + +// Build UpdateDremioInstancePayload from provider's model +func toUpdatePayload(model *InstanceModel) (*dremioSdk.UpdateDremioInstancePayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + + return &dremioSdk.UpdateDremioInstancePayload{ + Authentication: parseAuthentication(model), + Description: model.Description.ValueStringPointer(), + DisplayName: model.DisplayName.ValueStringPointer(), + }, nil +} + +// Build CreateDremioInstancePayload from provider's model +func toCreatePayload(model *InstanceModel) (*dremioSdk.CreateDremioInstancePayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + + return &dremioSdk.CreateDremioInstancePayload{ + Authentication: parseAuthentication(model), + Description: model.Description.ValueStringPointer(), + DisplayName: model.DisplayName.ValueString(), + }, nil +} + +func parseAuthentication(model *InstanceModel) *dremioSdk.Authentication { + var azureAdPayload *dremioSdk.Azuread + if model.Authentication.AzureAD != nil { + azureAdPayload = &dremioSdk.Azuread{ + AuthorityUrl: model.Authentication.AzureAD.AuthorityUrl.ValueString(), + ClientId: model.Authentication.AzureAD.ClientId.ValueString(), + ClientSecret: model.Authentication.AzureAD.ClientSecret.ValueString(), + RedirectUrl: model.Authentication.AzureAD.RedirectUrl.ValueStringPointer(), + } + } + + var oAuthPayload *dremioSdk.Oauth + if model.Authentication.OAuth != nil { + oAuthParams := []dremioSdk.AuthParameters{} + if len(model.Authentication.OAuth.Parameters) > 0 { + parameters := model.Authentication.OAuth.Parameters + for _, param := range parameters { + oAuthParams = append(oAuthParams, dremioSdk.AuthParameters{ + Name: param.Name.ValueString(), + Value: param.Value.ValueString(), + }) + } + } + + oAuthPayload = &dremioSdk.Oauth{ + AuthorityUrl: model.Authentication.OAuth.AuthorityUrl.ValueString(), + ClientId: model.Authentication.OAuth.ClientId.ValueString(), + ClientSecret: model.Authentication.OAuth.ClientSecret.ValueString(), + JwtClaims: dremioSdk.OauthJwtClaims{ + UserName: model.Authentication.OAuth.JwtClaims.UserName.ValueString(), + }, + RedirectUrl: model.Authentication.OAuth.RedirectUrl.ValueStringPointer(), + Scope: model.Authentication.OAuth.Scope.ValueStringPointer(), + Parameters: oAuthParams, + } + } + + return &dremioSdk.Authentication{ + Azuread: azureAdPayload, + Oauth: oAuthPayload, + Type: model.Authentication.Type.ValueString(), + } +} diff --git a/stackit/internal/services/dremio/instance/resource_test.go b/stackit/internal/services/dremio/instance/resource_test.go new file mode 100644 index 000000000..d26aee738 --- /dev/null +++ b/stackit/internal/services/dremio/instance/resource_test.go @@ -0,0 +1,341 @@ +package dremio + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" +) + +func TestMapFields(t *testing.T) { + instanceId := uuid.New().String() + tests := []struct { + description string + state *InstanceModel + input *dremioSdk.DremioResponse + expected *InstanceModel + wantErr bool + }{ + { + "all_fields_filled", + &InstanceModel{ + Region: types.StringValue("rid"), + ProjectId: types.StringValue("pid"), + }, + &dremioSdk.DremioResponse{ + Id: instanceId, + CreateTime: time.Now(), + Description: utils.Ptr("minimal-required-values"), + DisplayName: "greatName", + Authentication: dremioSdk.Authentication{ + Azuread: &dremioSdk.Azuread{ + AuthorityUrl: "azure-authority", + ClientId: "azure-client", + ClientSecret: "azure-secret", + RedirectUrl: utils.Ptr("azure-redirect"), + }, + Oauth: &dremioSdk.Oauth{ + AuthorityUrl: "oauth-authority", + ClientId: "oauth-client", + ClientSecret: "oauth-secret", + JwtClaims: dremioSdk.OauthJwtClaims{ + UserName: "oauth-username", + }, + Parameters: []dremioSdk.AuthParameters{ + { + Name: "oauth-parameter", + Value: "oauth-value", + }, + }, + RedirectUrl: utils.Ptr("oauth-redirect"), + Scope: utils.Ptr("oauth-scope"), + }, + Type: "local-only", + }, + Endpoints: dremioSdk.Endpoints{ + ArrowFlight: "flight", + Catalog: "catalog", + Ui: "ui", + }, + State: "active", + }, + &InstanceModel{ + Id: types.StringValue("pid,rid," + instanceId), + + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + InstanceId: types.StringValue(instanceId), + + DisplayName: types.StringValue("greatName"), + Authentication: &AuthenticationModel{ + AzureAD: &AzureADModel{ + AuthorityUrl: types.StringValue("azure-authority"), + ClientId: types.StringValue("azure-client"), + ClientSecret: types.StringValue("azure-secret"), + RedirectUrl: types.StringValue("azure-redirect"), + }, + OAuth: &OAuthModel{ + AuthorityUrl: types.StringValue("oauth-authority"), + ClientId: types.StringValue("oauth-client"), + ClientSecret: types.StringValue("oauth-secret"), + JwtClaims: &JwtClaimsModel{ + UserName: types.StringValue("oauth-username"), + }, + Parameters: []AuthParameterModel{ + { + Name: types.StringValue("oauth-parameter"), + Value: types.StringValue("oauth-value"), + }, + }, + RedirectUrl: types.StringValue("oauth-redirect"), + Scope: types.StringValue("oauth-scope"), + }, + Type: types.StringValue("local-only"), + }, + Description: types.StringValue("minimal-required-values"), + + State: types.StringValue("active"), + ErrorMessage: types.StringNull(), + Endpoints: types.ObjectValueMust( + map[string]attr.Type{ + "arrow_flight": types.StringType, + "catalog": types.StringType, + "ui": types.StringType, + }, + map[string]attr.Value{ + "arrow_flight": types.StringValue("flight"), + "catalog": types.StringValue("catalog"), + "ui": types.StringValue("ui"), + }, + ), + }, + false, + }, + { + "nil response", + &InstanceModel{ + Region: types.StringValue("rid"), + ProjectId: types.StringValue("pid"), + }, + nil, + &InstanceModel{ + Id: types.StringValue("pid,rid,"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + }, + true, + }, + { + "nil state", + nil, + &dremioSdk.DremioResponse{}, + nil, + true, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + err := mapFields(tt.input, tt.state) + if (err != nil) != tt.wantErr { + t.Errorf("mapFields error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if diff := cmp.Diff(tt.expected, tt.state); diff != "" { + t.Errorf("mapFields mismatch (-want +got):\n%s", diff) + } + } + }) + } +} + +func TestToCreatePayload(t *testing.T) { + tests := []struct { + description string + state *InstanceModel + expected *dremioSdk.CreateDremioInstancePayload + wantErr bool + }{ + { + "success", + &InstanceModel{ + Authentication: &AuthenticationModel{ + AzureAD: &AzureADModel{ + AuthorityUrl: types.StringValue("azure-authority"), + ClientId: types.StringValue("azure-client"), + ClientSecret: types.StringValue("azure-secret"), + RedirectUrl: types.StringValue("azure-redirect"), + }, + OAuth: &OAuthModel{ + AuthorityUrl: types.StringValue("oauth-authority"), + ClientId: types.StringValue("oauth-client"), + ClientSecret: types.StringValue("oauth-secret"), + JwtClaims: &JwtClaimsModel{ + UserName: types.StringValue("oauth-username"), + }, + Parameters: []AuthParameterModel{ + { + Name: types.StringValue("oauth-parameter"), + Value: types.StringValue("oauth-value"), + }, + }, + RedirectUrl: types.StringValue("oauth-redirect"), + Scope: types.StringValue("oauth-scope"), + }, + Type: types.StringValue("oauth"), + }, + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + &dremioSdk.CreateDremioInstancePayload{ + Authentication: &dremioSdk.Authentication{ + Azuread: &dremioSdk.Azuread{ + AuthorityUrl: "azure-authority", + ClientId: "azure-client", + ClientSecret: "azure-secret", + RedirectUrl: utils.Ptr("azure-redirect"), + }, + Oauth: &dremioSdk.Oauth{ + AuthorityUrl: "oauth-authority", + ClientId: "oauth-client", + ClientSecret: "oauth-secret", + JwtClaims: dremioSdk.OauthJwtClaims{ + UserName: "oauth-username", + }, + Parameters: []dremioSdk.AuthParameters{ + { + Name: "oauth-parameter", + Value: "oauth-value", + }, + }, + RedirectUrl: utils.Ptr("oauth-redirect"), + Scope: utils.Ptr("oauth-scope"), + }, + Type: "oauth", + }, + Description: utils.Ptr("test description"), + DisplayName: "displayName", + }, + false, + }, + { + "nil model", + nil, + nil, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + payload, err := toCreatePayload(tt.state) + if (err != nil) != tt.wantErr { + t.Errorf("toCreatePayload error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if diff := cmp.Diff(tt.expected, payload); diff != "" { + t.Errorf("toCreatePayload mismatch (-want +got):\n%s", diff) + } + } + }) + } +} + +func TestToUpdatePayload(t *testing.T) { + tests := []struct { + description string + state *InstanceModel + expected *dremioSdk.UpdateDremioInstancePayload + wantErr bool + }{ + { + "success", + &InstanceModel{ + Authentication: &AuthenticationModel{ + AzureAD: &AzureADModel{ + AuthorityUrl: types.StringValue("azure-authority"), + ClientId: types.StringValue("azure-client"), + ClientSecret: types.StringValue("azure-secret"), + RedirectUrl: types.StringValue("azure-redirect"), + }, + OAuth: &OAuthModel{ + AuthorityUrl: types.StringValue("oauth-authority"), + ClientId: types.StringValue("oauth-client"), + ClientSecret: types.StringValue("oauth-secret"), + JwtClaims: &JwtClaimsModel{ + UserName: types.StringValue("oauth-username"), + }, + Parameters: []AuthParameterModel{ + { + Name: types.StringValue("oauth-parameter"), + Value: types.StringValue("oauth-value"), + }, + }, + RedirectUrl: types.StringValue("oauth-redirect"), + Scope: types.StringValue("oauth-scope"), + }, + Type: types.StringValue("oauth"), + }, + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + &dremioSdk.UpdateDremioInstancePayload{ + Authentication: &dremioSdk.Authentication{ + Azuread: &dremioSdk.Azuread{ + AuthorityUrl: "azure-authority", + ClientId: "azure-client", + ClientSecret: "azure-secret", + RedirectUrl: utils.Ptr("azure-redirect"), + }, + Oauth: &dremioSdk.Oauth{ + AuthorityUrl: "oauth-authority", + ClientId: "oauth-client", + ClientSecret: "oauth-secret", + JwtClaims: dremioSdk.OauthJwtClaims{ + UserName: "oauth-username", + }, + Parameters: []dremioSdk.AuthParameters{ + { + Name: "oauth-parameter", + Value: "oauth-value", + }, + }, + RedirectUrl: utils.Ptr("oauth-redirect"), + Scope: utils.Ptr("oauth-scope"), + }, + Type: "oauth", + }, + Description: utils.Ptr("test description"), + DisplayName: utils.Ptr("displayName"), + }, + false, + }, + { + "nil model", + nil, + nil, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + payload, err := toUpdatePayload(tt.state) + if (err != nil) != tt.wantErr { + t.Errorf("toUpdatePayload error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if diff := cmp.Diff(tt.expected, payload); diff != "" { + t.Errorf("toUpdatePayload mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/stackit/provider.go b/stackit/provider.go index 5e52728f7..cb26c13ca 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -30,6 +30,7 @@ import ( cdn "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/distribution" dnsRecordSet "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/recordset" dnsZone "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/zone" + dremioInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/instance" edgeCloudInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/instance" edgeCloudInstances "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/instances" edgeCloudKubeconfig "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/kubeconfig" @@ -368,6 +369,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["dns_custom_endpoint"], }, + "dremio_custom_endpoint": schema.StringAttribute{ + Optional: true, + Description: descriptions["dremio_custom_endpoint"], + }, "edgecloud_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["edgecloud_custom_endpoint"], @@ -766,6 +771,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { cdnCustomDomain.NewCustomDomainResource, dnsZone.NewZoneResource, dnsRecordSet.NewRecordSetResource, + dremioInstance.NewInstanceResource, edgeCloudInstance.NewInstanceResource, edgeCloudKubeconfig.NewKubeconfigResource, edgeCloudToken.NewTokenResource, From 32ca5c84efada3526196fe5e689779f617b76c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 22 May 2026 10:10:20 +0200 Subject: [PATCH 04/33] chore(dremio): Linting --- stackit/internal/services/dremio/instance/resource.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index de604fa71..d1c82471d 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -19,6 +19,7 @@ import ( "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/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" @@ -26,6 +27,7 @@ import ( dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait/wait" + dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" ) From 86989ead4b3835f2ddccb22b58acd846cad009cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 22 May 2026 10:38:31 +0200 Subject: [PATCH 05/33] fix(dremio): Providing default region in SDK config. Not propagating it renders the provider unusable for Dremio, because Dremio is no global STACKIT API yet. --- stackit/internal/services/dremio/utils/util.go | 1 + 1 file changed, 1 insertion(+) diff --git a/stackit/internal/services/dremio/utils/util.go b/stackit/internal/services/dremio/utils/util.go index e32a25d7a..912af6e39 100644 --- a/stackit/internal/services/dremio/utils/util.go +++ b/stackit/internal/services/dremio/utils/util.go @@ -16,6 +16,7 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags apiClientConfigOptions := []config.ConfigurationOption{ config.WithCustomAuth(providerData.RoundTripper), utils.UserAgentConfigOption(providerData.Version), + config.WithRegion(providerData.DefaultRegion), } if providerData.DremioCustomEndpoint != "" { apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.DremioCustomEndpoint)) From 7f06e2d7b2fb1dff6677bd60596f0b8fee262202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 22 May 2026 14:23:12 +0200 Subject: [PATCH 06/33] feat(dremio): Adding example for Dremio instance --- .../stackit_dremio_instance/resource.tf | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 examples/resources/stackit_dremio_instance/resource.tf diff --git a/examples/resources/stackit_dremio_instance/resource.tf b/examples/resources/stackit_dremio_instance/resource.tf new file mode 100644 index 000000000..5cf5d6c1a --- /dev/null +++ b/examples/resources/stackit_dremio_instance/resource.tf @@ -0,0 +1,34 @@ +resource "stackit_dremio_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "exampleName" + description = "Example description" + authentication = { + type = "local-only" // "oauth" or "azuread" for IDP config + + oauth = { // only needed if "oauth" is given as type + authority_url = "authority" + client_id = "client-id" + client_secret = "client-secret" + jwt_claims = { + user_name = "example" + } + scope = "idp-scope" + parameters = [ + {"name": "example", "value": "example-value"} + ] + } + + azuread = { // only needed if "azuread" is given as type + authority_url = "authority" + client_id = "client-id" + client_secret = "client-secret" + } + } +} + +# Only use the import statement, if you want to import an existing dns zone +import { + to = stackit_dremio_instance.import_example + id = "${var.project_id},${var.region},${var.instance_id}" +} \ No newline at end of file From 894102e4a89bab3315777648970910702e179294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 22 May 2026 19:43:41 +0200 Subject: [PATCH 07/33] feat(dremio): Preparing resource methods for data source usage. The aim of this commit was to dry up and atomize common fields which are both used by the instance resource and data resource. By drying up the methods and model constellation we can make use of one implementation. Also removed the endpoints object. --- .../services/dremio/instance/resource.go | 74 +++++++++++++------ .../services/dremio/instance/resource_test.go | 72 +++++++++--------- 2 files changed, 88 insertions(+), 58 deletions(-) diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index d1c82471d..d95fa35d7 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" @@ -16,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" @@ -38,8 +36,7 @@ var ( _ resource.ResourceWithModifyPlan = &instanceResource{} // not needed for global APIs ) -// InstanceModel maps the resource schema data. -type InstanceModel struct { +type Model struct { Id types.String `tfsdk:"id"` ProjectId types.String `tfsdk:"project_id"` @@ -47,16 +44,23 @@ type InstanceModel struct { InstanceId types.String `tfsdk:"instance_id"` // Required Fields - DisplayName types.String `tfsdk:"display_name"` - Authentication *AuthenticationModel `tfsdk:"authentication"` + DisplayName types.String `tfsdk:"display_name"` // Optional Fields Description types.String `tfsdk:"description"` // Read-only Fields - State types.String `tfsdk:"state"` - ErrorMessage types.String `tfsdk:"error_message"` - Endpoints types.Object `tfsdk:"endpoints"` // see endpointsTypes below + State types.String `tfsdk:"state"` + ErrorMessage types.String `tfsdk:"error_message"` + Endpoints *EndpointsModel `tfsdk:"endpoints"` +} + +// InstanceModel maps the resource schema data. +type InstanceModel struct { + Model + + // Required Fields + Authentication *AuthenticationModel `tfsdk:"authentication"` Timeouts timeouts.Value `tfsdk:"timeouts"` } @@ -106,10 +110,10 @@ type AuthParameterModel struct { Value types.String `tfsdk:"value"` } -var endpointsTypes = map[string]attr.Type{ - "arrow_flight": basetypes.StringType{}, - "catalog": basetypes.StringType{}, - "ui": basetypes.StringType{}, +type EndpointsModel struct { + ArrowFlight types.String `tfsdk:"arrow_flight"` + Catalog types.String `tfsdk:"catalog"` + Ui types.String `tfsdk:"ui"` } func NewInstanceResource() resource.Resource { @@ -561,6 +565,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) return } + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) if resp.Diagnostics.HasError() { return @@ -633,7 +638,6 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS tflog.Info(ctx, "Dremio instance state imported") } -// Maps instance fields to the provider's internal model func mapFields(instanceResp *dremioSdk.DremioResponse, model *InstanceModel) error { if instanceResp == nil { return fmt.Errorf("response input is nil") @@ -642,6 +646,24 @@ func mapFields(instanceResp *dremioSdk.DremioResponse, model *InstanceModel) err return fmt.Errorf("model input is nil") } + err := mapModelFields(instanceResp, &model.Model) + if err != nil { + return fmt.Errorf("failed to map Model fields") + } + err = mapAuthentication(instanceResp, model) + if err != nil { + return fmt.Errorf("failed to map Authentication fields") + } + + return nil +} + +// Maps instance fields to the provider's internal model +func mapModelFields(instanceResp *dremioSdk.DremioResponse, model *Model) error { + if model == nil { + return fmt.Errorf("model input is nil") + } + model.InstanceId = types.StringValue(instanceResp.Id) model.Id = utils.BuildInternalTerraformId( @@ -656,17 +678,21 @@ func mapFields(instanceResp *dremioSdk.DremioResponse, model *InstanceModel) err model.Description = types.StringPointerValue(instanceResp.Description) model.ErrorMessage = types.StringPointerValue(instanceResp.ErrorMessage) - endpoints, diags := types.ObjectValue(endpointsTypes, map[string]attr.Value{ - "arrow_flight": types.StringValue(instanceResp.Endpoints.ArrowFlight), - "catalog": types.StringValue(instanceResp.Endpoints.Catalog), - "ui": types.StringValue(instanceResp.Endpoints.Ui), - }) - if diags.HasError() { - return fmt.Errorf("error mapping endpoints: %v", diags) + model.Endpoints = &EndpointsModel{ + ArrowFlight: types.StringValue(instanceResp.Endpoints.ArrowFlight), + Catalog: types.StringValue(instanceResp.Endpoints.Catalog), + Ui: types.StringValue(instanceResp.Endpoints.Ui), + } + + return nil +} + +func mapAuthentication(instanceResp *dremioSdk.DremioResponse, model *InstanceModel) error { + if model == nil { + return fmt.Errorf("model input is nil") } - model.Endpoints = endpoints - authModel := &AuthenticationModel{ + authModel := AuthenticationModel{ Type: types.StringValue(instanceResp.Authentication.Type), } @@ -707,7 +733,7 @@ func mapFields(instanceResp *dremioSdk.DremioResponse, model *InstanceModel) err authModel.OAuth = oauthModel } - model.Authentication = authModel + model.Authentication = &authModel return nil } diff --git a/stackit/internal/services/dremio/instance/resource_test.go b/stackit/internal/services/dremio/instance/resource_test.go index d26aee738..accdb6174 100644 --- a/stackit/internal/services/dremio/instance/resource_test.go +++ b/stackit/internal/services/dremio/instance/resource_test.go @@ -6,7 +6,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/utils" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" @@ -24,8 +23,10 @@ func TestMapFields(t *testing.T) { { "all_fields_filled", &InstanceModel{ - Region: types.StringValue("rid"), - ProjectId: types.StringValue("pid"), + Model: Model{ + Region: types.StringValue("rid"), + ProjectId: types.StringValue("pid"), + }, }, &dremioSdk.DremioResponse{ Id: instanceId, @@ -65,13 +66,24 @@ func TestMapFields(t *testing.T) { State: "active", }, &InstanceModel{ - Id: types.StringValue("pid,rid," + instanceId), + Model: Model{ + Id: types.StringValue("pid,rid," + instanceId), + + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + InstanceId: types.StringValue(instanceId), - ProjectId: types.StringValue("pid"), - Region: types.StringValue("rid"), - InstanceId: types.StringValue(instanceId), + DisplayName: types.StringValue("greatName"), + Description: types.StringValue("minimal-required-values"), - DisplayName: types.StringValue("greatName"), + State: types.StringValue("active"), + ErrorMessage: types.StringNull(), + Endpoints: &EndpointsModel{ + ArrowFlight: types.StringValue("flight"), + Catalog: types.StringValue("catalog"), + Ui: types.StringValue("ui"), + }, + }, Authentication: &AuthenticationModel{ AzureAD: &AzureADModel{ AuthorityUrl: types.StringValue("azure-authority"), @@ -97,36 +109,24 @@ func TestMapFields(t *testing.T) { }, Type: types.StringValue("local-only"), }, - Description: types.StringValue("minimal-required-values"), - - State: types.StringValue("active"), - ErrorMessage: types.StringNull(), - Endpoints: types.ObjectValueMust( - map[string]attr.Type{ - "arrow_flight": types.StringType, - "catalog": types.StringType, - "ui": types.StringType, - }, - map[string]attr.Value{ - "arrow_flight": types.StringValue("flight"), - "catalog": types.StringValue("catalog"), - "ui": types.StringValue("ui"), - }, - ), }, false, }, { "nil response", &InstanceModel{ - Region: types.StringValue("rid"), - ProjectId: types.StringValue("pid"), + Model: Model{ + Region: types.StringValue("rid"), + ProjectId: types.StringValue("pid"), + }, }, nil, &InstanceModel{ - Id: types.StringValue("pid,rid,"), - ProjectId: types.StringValue("pid"), - Region: types.StringValue("rid"), + Model: Model{ + Id: types.StringValue("pid,rid,"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + }, }, true, }, @@ -147,7 +147,7 @@ func TestMapFields(t *testing.T) { } if !tt.wantErr { if diff := cmp.Diff(tt.expected, tt.state); diff != "" { - t.Errorf("mapFields mismatch (-want +got):\n%s", diff) + t.Errorf("mapping mismatch (-want +got):\n%s", diff) } } }) @@ -164,6 +164,10 @@ func TestToCreatePayload(t *testing.T) { { "success", &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, Authentication: &AuthenticationModel{ AzureAD: &AzureADModel{ AuthorityUrl: types.StringValue("azure-authority"), @@ -189,8 +193,6 @@ func TestToCreatePayload(t *testing.T) { }, Type: types.StringValue("oauth"), }, - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), }, &dremioSdk.CreateDremioInstancePayload{ Authentication: &dremioSdk.Authentication{ @@ -257,6 +259,10 @@ func TestToUpdatePayload(t *testing.T) { { "success", &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, Authentication: &AuthenticationModel{ AzureAD: &AzureADModel{ AuthorityUrl: types.StringValue("azure-authority"), @@ -282,8 +288,6 @@ func TestToUpdatePayload(t *testing.T) { }, Type: types.StringValue("oauth"), }, - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), }, &dremioSdk.UpdateDremioInstancePayload{ Authentication: &dremioSdk.Authentication{ From 1aa2a789da3a3441d249a0a80b700ae079f2d715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 22 May 2026 19:46:16 +0200 Subject: [PATCH 08/33] feat(dremio): Adding dremio instance data resource. The data resource does not read all the fields. There is only one authentication setup read, because only one can be used at a time. Also we are not reading the client secret here. --- .../stackit_dremio_instance/data-source.tf | 5 + .../services/dremio/instance/datasource.go | 338 ++++++++++++++++++ stackit/provider.go | 2 + 3 files changed, 345 insertions(+) create mode 100644 examples/data-sources/stackit_dremio_instance/data-source.tf create mode 100644 stackit/internal/services/dremio/instance/datasource.go diff --git a/examples/data-sources/stackit_dremio_instance/data-source.tf b/examples/data-sources/stackit_dremio_instance/data-source.tf new file mode 100644 index 000000000..4f17e8514 --- /dev/null +++ b/examples/data-sources/stackit_dremio_instance/data-source.tf @@ -0,0 +1,5 @@ +data "stackit_dremio_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "example-instance-id" +} \ No newline at end of file diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go new file mode 100644 index 000000000..0e5d3b8c2 --- /dev/null +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -0,0 +1,338 @@ +package dremio + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = &instanceDataSource{} + _ datasource.DataSourceWithConfigure = &instanceDataSource{} +) + +type InstanceDataSourceModel struct { + Model + + // Required Fields + Authentication *DataSourceAuthenticationModel `tfsdk:"authentication"` +} + +type DataSourceAuthenticationModel struct { + Type types.String `tfsdk:"type"` + AuthorityUrl types.String `tfsdk:"authority_url"` + ClientId types.String `tfsdk:"client_id"` + JwtClaims *JwtClaimsModel `tfsdk:"jwt_claims"` + Scope types.String `tfsdk:"scope"` + Parameters []AuthParameterModel `tfsdk:"parameters"` + RedirectUrl types.String `tfsdk:"redirect_url"` +} + +type instanceDataSource struct { + client *dremioSdk.APIClient +} + +func NewInstanceDataSource() datasource.DataSource { + return &instanceDataSource{} +} + +// Metadata should return the full name of the data source, such as +// examplecloud_thing. +func (d *instanceDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dremio_instance" +} + +// Configure enables provider-level data or clients to be set in the +// provider-defined DataSource type. It is separately executed for each +// ReadDataSource RPC. +func (d *instanceDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := dremioUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + d.client = apiClient + tflog.Info(ctx, "Dremio instance client configured for data source") +} + +// Schema should return the schema for this data source. +func (d *instanceDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + descriptions := map[string]string{ + "main": "Manages a STACKIT Dremio instance.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`dremio_id`\".", + "project_id": "STACKIT Project ID to which the resource is associated.", + "instance_id": "The Dremio instance ID.", + "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", + "display_name": "The display name is a short name chosen by the user to identify the resource.", + "description": "The description is a longer text chosen by the user to provide more context for the resource.", + "state": "The current state of the resource.", + "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", + "endpoints": "The available endpoints of the Dremio instance.", + "endpoints_arrow_flight": "The arrow flight endpoint of the Dremio instance.", + "endpoints_catalog": "The Apache Iceberg endpoint of the Dremio instance.", + "endpoints_ui": "The UI endpoint of the Dremio instance.", + "authentication": "Dremio instance authentication settings. A change here triggers a Dremio restart and will incur downtime.", + "authentication_type": "Type of authentication (local-only, azuread, oauth).", + "authentication_authority_url": "The Issuer location URI, where the OIDC provider configuration can be found.", + "authentication_client_id": "The client ID assigned by the Identity Provider.", + "authentication_scope": "A list of space-separated scopes. The `openid` scope is always required; other scopes can vary by provider.", + "authentication_redirect_url": "The URL where the Dremio instance is hosted. The URL must match the redirect URL set in the Identity Provider.", + "authentication_jwt_claims": "Maps fields from the JWT token to fields Dremio requires.", + "authentication_jwt_claims_user_name": "Mapped user name claim (e.g. email).", + "authentication_parameters": "Any additional parameters the Identity Provider requires.", + "authentication_parameters_name": "Parameter name.", + "authentication_parameters_value": "Parameter value.", + } + + resp.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "instance_id": schema.StringAttribute{ + Description: descriptions["instance_id"], + Required: true, + }, + "region": schema.StringAttribute{ + Description: descriptions["region"], + Required: true, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["display_name"], + Computed: true, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Computed: true, + Optional: true, + }, + "state": schema.StringAttribute{ + Description: descriptions["state"], + Computed: true, + }, + "error_message": schema.StringAttribute{ + Description: descriptions["error_message"], + Computed: true, + Optional: true, + }, + "endpoints": schema.SingleNestedAttribute{ + Description: descriptions["endpoints"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "arrow_flight": schema.StringAttribute{ + Description: descriptions["endpoints_arrow_flight"], + Computed: true, + }, + "catalog": schema.StringAttribute{ + Description: descriptions["endpoints_catalog"], + Computed: true, + }, + "ui": schema.StringAttribute{ + Description: descriptions["endpoints_ui"], + Computed: true, + }, + }, + }, + "authentication": schema.SingleNestedAttribute{ + Description: descriptions["authentication"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Description: descriptions["authentication_type"], + Computed: true, + }, + "authority_url": schema.StringAttribute{ + Description: descriptions["oauth_authority_url"], + Computed: true, + Optional: true, + }, + "client_id": schema.StringAttribute{ + Description: descriptions["oauth_client_id"], + Computed: true, + Optional: true, + }, + "scope": schema.StringAttribute{ + Description: descriptions["oauth_scope"], + Computed: true, + Optional: true, + }, + "redirect_url": schema.StringAttribute{ + Description: descriptions["oauth_redirect_url"], + Computed: true, + Optional: true, + }, + "jwt_claims": schema.SingleNestedAttribute{ + Description: descriptions["oauth_jwt_claims"], + Computed: true, + Optional: true, + Attributes: map[string]schema.Attribute{ + "user_name": schema.StringAttribute{ + Description: descriptions["oauth_jwt_claims_user_name"], + Computed: true, + }, + }, + }, + "parameters": schema.ListNestedAttribute{ + Description: descriptions["oauth_parameters"], + Computed: true, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: descriptions["oauth_parameters_name"], + Computed: true, + }, + "value": schema.StringAttribute{ + Description: descriptions["oauth_parameters_value"], + Computed: true, + }, + }, + }, + }, + }, + }, + }, + } +} + +// Read is called when the provider must read data source values in +// order to update state. Config values should be read from the +// ReadRequest and new state values set on the ReadResponse. +func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + // nolint:gocritic // function signature required by Terraform + var model InstanceDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() + instanceId := model.InstanceId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + + instanceResp, err := d.client.DefaultAPI.GetDremioInstance(ctx, projectId, region, instanceId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Dremio instance with ID %s not found in project %s and region %s", instanceId, projectId, region)) + return + } + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Dremio instance", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + err = mapDataSourceFields(instanceResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Dremio instance read") +} + +func mapDataSourceFields(instanceResp *dremioSdk.DremioResponse, model *InstanceDataSourceModel) error { + if instanceResp == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + err := mapModelFields(instanceResp, &model.Model) + if err != nil { + return fmt.Errorf("failed to map Model fields") + } + err = mapDataSourceAuthentication(instanceResp, model) + if err != nil { + return fmt.Errorf("failed to map Authentication fields") + } + + return nil +} + +func mapDataSourceAuthentication(instanceResp *dremioSdk.DremioResponse, model *InstanceDataSourceModel) error { + authResp := instanceResp.Authentication + + authModel := DataSourceAuthenticationModel{} + + authModel.Type = types.StringValue(authResp.Type) + + if instanceResp.Authentication.Type == "local-only" { + // On local auth we don't need to map IDP fields + return nil + } + + if authResp.Type == "azuread" { + azureADResp := authResp.Azuread + authModel.AuthorityUrl = types.StringValue(azureADResp.AuthorityUrl) + authModel.ClientId = types.StringValue(azureADResp.ClientId) + authModel.RedirectUrl = types.StringPointerValue(azureADResp.RedirectUrl) + } + + if authResp.Type == "oauth" { + oauthResp := authResp.Oauth + authModel.AuthorityUrl = types.StringValue(oauthResp.AuthorityUrl) + authModel.ClientId = types.StringValue(oauthResp.ClientId) + authModel.Scope = types.StringPointerValue(oauthResp.Scope) + authModel.RedirectUrl = types.StringPointerValue(oauthResp.RedirectUrl) + authModel.JwtClaims = &JwtClaimsModel{ + UserName: types.StringValue(oauthResp.JwtClaims.UserName), + } + + if len(oauthResp.Parameters) > 0 { + var params []AuthParameterModel + for _, p := range oauthResp.Parameters { + params = append(params, AuthParameterModel{ + Name: types.StringValue(p.Name), + Value: types.StringValue(p.Value), + }) + } + authModel.Parameters = params + } + } + + model.Authentication = &authModel + + return nil +} diff --git a/stackit/provider.go b/stackit/provider.go index cb26c13ca..8b2ae428c 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -30,6 +30,7 @@ import ( cdn "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/distribution" dnsRecordSet "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/recordset" dnsZone "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/zone" + dremio "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/instance" dremioInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/instance" edgeCloudInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/instance" edgeCloudInstances "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/instances" @@ -669,6 +670,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource cdnCustomDomain.NewCustomDomainDataSource, dnsZone.NewZoneDataSource, dnsRecordSet.NewRecordSetDataSource, + dremio.NewInstanceDataSource, edgeCloudInstances.NewInstancesDataSource, edgeCloudPlans.NewPlansDataSource, gitInstance.NewGitDataSource, From 1d1fc6e344e685e7f9cd78789516096f1398f828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 22 May 2026 19:54:19 +0200 Subject: [PATCH 09/33] fix(dremio): Linting & Formatting --- .../stackit_dremio_instance/data-source.tf | 6 +++--- .../resources/stackit_dremio_instance/resource.tf | 12 ++++++------ .../internal/services/dremio/instance/datasource.go | 13 ++++++++----- .../internal/services/dremio/instance/resource.go | 2 +- stackit/provider.go | 3 +-- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/examples/data-sources/stackit_dremio_instance/data-source.tf b/examples/data-sources/stackit_dremio_instance/data-source.tf index 4f17e8514..e39553403 100644 --- a/examples/data-sources/stackit_dremio_instance/data-source.tf +++ b/examples/data-sources/stackit_dremio_instance/data-source.tf @@ -1,5 +1,5 @@ data "stackit_dremio_instance" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - region = "eu01" - instance_id = "example-instance-id" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "example-instance-id" } \ No newline at end of file diff --git a/examples/resources/stackit_dremio_instance/resource.tf b/examples/resources/stackit_dremio_instance/resource.tf index 5cf5d6c1a..7f8042576 100644 --- a/examples/resources/stackit_dremio_instance/resource.tf +++ b/examples/resources/stackit_dremio_instance/resource.tf @@ -1,27 +1,27 @@ resource "stackit_dremio_instance" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - region = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" display_name = "exampleName" - description = "Example description" + description = "Example description" authentication = { type = "local-only" // "oauth" or "azuread" for IDP config oauth = { // only needed if "oauth" is given as type authority_url = "authority" - client_id = "client-id" + client_id = "client-id" client_secret = "client-secret" jwt_claims = { user_name = "example" } scope = "idp-scope" parameters = [ - {"name": "example", "value": "example-value"} + { "name" : "example", "value" : "example-value" } ] } azuread = { // only needed if "azuread" is given as type authority_url = "authority" - client_id = "client-id" + client_id = "client-id" client_secret = "client-secret" } } diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go index 0e5d3b8c2..ed64c3b36 100644 --- a/stackit/internal/services/dremio/instance/datasource.go +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -12,11 +12,14 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" - dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + + dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" + + dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" ) var ( @@ -51,7 +54,7 @@ func NewInstanceDataSource() datasource.DataSource { // Metadata should return the full name of the data source, such as // examplecloud_thing. -func (d *instanceDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { +func (d *instanceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_dremio_instance" } @@ -73,7 +76,7 @@ func (d *instanceDataSource) Configure(ctx context.Context, req datasource.Confi } // Schema should return the schema for this data source. -func (d *instanceDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { +func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { descriptions := map[string]string{ "main": "Manages a STACKIT Dremio instance.", "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`dremio_id`\".", @@ -225,7 +228,7 @@ func (d *instanceDataSource) Schema(ctx context.Context, req datasource.SchemaRe // Read is called when the provider must read data source values in // order to update state. Config values should be read from the // ReadRequest and new state values set on the ReadResponse. -func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { +func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform // nolint:gocritic // function signature required by Terraform var model InstanceDataSourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &model)...) diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index d95fa35d7..85874ee5e 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -172,7 +172,7 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure } func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - descriptions := map[string]string{ + descriptions := map[string]string{ //nolint:gosec // no hardcoded credentials in here "main": "Manages a STACKIT Dremio instance.", "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`dremio_id`\".", "project_id": "STACKIT Project ID to which the resource is associated.", diff --git a/stackit/provider.go b/stackit/provider.go index 8b2ae428c..9d5b98dc0 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -30,7 +30,6 @@ import ( cdn "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/distribution" dnsRecordSet "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/recordset" dnsZone "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/zone" - dremio "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/instance" dremioInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/instance" edgeCloudInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/instance" edgeCloudInstances "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/instances" @@ -670,7 +669,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource cdnCustomDomain.NewCustomDomainDataSource, dnsZone.NewZoneDataSource, dnsRecordSet.NewRecordSetDataSource, - dremio.NewInstanceDataSource, + dremioInstance.NewInstanceDataSource, edgeCloudInstances.NewInstancesDataSource, edgeCloudPlans.NewPlansDataSource, gitInstance.NewGitDataSource, From 494fa390fe8a67c8dc33b18ea28791cea7ff0f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Mon, 1 Jun 2026 11:14:55 +0200 Subject: [PATCH 10/33] feat(dremio): Adding acceptance tests for Dremio instance. --- .../services/dremio/dremio_acc_test.go | 374 ++++++++++++++++++ .../services/dremio/instance/datasource.go | 5 - .../services/dremio/instance/resource.go | 106 +++-- .../services/dremio/instance/resource_test.go | 244 ++++++++++-- .../services/dremio/testdata/resource-max.tf | 60 +++ .../services/dremio/testdata/resource-min.tf | 20 + stackit/internal/testutil/testutil.go | 1 + 7 files changed, 750 insertions(+), 60 deletions(-) create mode 100644 stackit/internal/services/dremio/dremio_acc_test.go create mode 100644 stackit/internal/services/dremio/testdata/resource-max.tf create mode 100644 stackit/internal/services/dremio/testdata/resource-min.tf diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go new file mode 100644 index 000000000..8550bde99 --- /dev/null +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -0,0 +1,374 @@ +package dremio + +import ( + "context" + _ "embed" + "fmt" + "maps" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/stackitcloud/stackit-sdk-go/core/utils" + dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" + dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait/wait" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +//go:embed testdata/resource-min.tf +var resourceDremioInstanceMin string + +//go:embed testdata/resource-max.tf +var resourceDremioInstanceMax string + +const dremioInstanceResource = "stackit_dremio_instance.example" +const dremioInstanceDataResource = "data.stackit_dremio_instance.example" + +var testDremioInstanceConfigVarsMin = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("dremioMinInstance"), + "authentication_type": config.StringVariable("local-only"), +} + +var testDremioInstanceConfigVarsMax = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable("eu01"), + "display_name": config.StringVariable("dremioMaxInstance"), + "description": config.StringVariable("description"), + + "authentication_type": config.StringVariable("oauth"), + + "authentication_oauth_authority_url": config.StringVariable("oauth-authority-url"), + "authentication_oauth_client_id": config.StringVariable("oauth-client-id"), + "authentication_oauth_client_secret": config.StringVariable("oauth-client-secret"), + "authentication_oauth_client_jwt_claims_user_name": config.StringVariable("oauth-jwt-claim-user"), + "authentication_oauth_scope": config.StringVariable("oauth-scope"), + "authentication_oauth_parameter_name": config.StringVariable("oauth-parameter-name"), + "authentication_oauth_parameter_value": config.StringVariable("oauth-parameter-value"), +} + +func testDremioInstanceConfigVarsMinUpdated() config.Variables { + tempConfig := make(config.Variables, len(testDremioInstanceConfigVarsMin)) + maps.Copy(tempConfig, testDremioInstanceConfigVarsMin) + tempConfig["display_name"] = config.StringVariable("dremioMinInstanceUpd") + return tempConfig +} + +func testDremioInstanceConfigVarsMaxUpdated() config.Variables { + tempConfig := make(config.Variables, len(testDremioInstanceConfigVarsMax)) + maps.Copy(tempConfig, testDremioInstanceConfigVarsMax) + tempConfig["display_name"] = config.StringVariable("dremioMaxInstanceUpd") + tempConfig["description"] = config.StringVariable("description-upd") + + // switching idp to azuread + tempConfig["authentication_type"] = config.StringVariable("azuread") + + tempConfig["authentication_azuread_authority_url"] = config.StringVariable("azuread-authority-url-upd") + tempConfig["authentication_azuread_client_id"] = config.StringVariable("azuread-client-id-upd") + tempConfig["authentication_azuread_client_secret"] = config.StringVariable("azuread-client-secret-upd") + + return tempConfig +} + +func TestDremioInstanceMin(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccDremioInstanceDestroy, + Steps: []resource.TestStep{ + // 1) Creation + { + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceDremioInstanceMin, + ConfigVariables: testDremioInstanceConfigVarsMin, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "region", testutil.Region), + resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["display_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["authentication_type"])), + + resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "state"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), + ), + }, + // 2) Data Source + { + Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMin, + ConfigVariables: testDremioInstanceConfigVarsMin, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "project_id", + dremioInstanceDataResource, "project_id", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "region", + dremioInstanceDataResource, "region", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "instance_id", + dremioInstanceDataResource, "instance_id", + ), + + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "display_name", + dremioInstanceDataResource, "display_name", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "authentication.type", + dremioInstanceDataResource, "authentication.type", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "endpoints.arrow_flight", + dremioInstanceDataResource, "endpoints.arrow_flight", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "catalog", + dremioInstanceDataResource, "catalog", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "endpoints.ui", + dremioInstanceDataResource, "endpoints.ui", + ), + ), + }, + // 3) Import + { + ConfigVariables: testDremioInstanceConfigVarsMin, + ResourceName: dremioInstanceResource, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources[dremioInstanceResource] + if !ok { + return "", fmt.Errorf("couldn't find resource %s", dremioInstanceResource) + } + instanceId, ok := r.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute instanceId") + } + + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId), nil + }, + }, + // 4) Update + { + Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMin, + ConfigVariables: testDremioInstanceConfigVarsMinUpdated(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "region", testutil.Region), + resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["display_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["authentication_type"])), + + resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "state"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), + ), + }, + }, + }) +} + +func TestDremioInstanceMax(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccDremioInstanceDestroy, + Steps: []resource.TestStep{ + // 1) Creation + { + ConfigVariables: testDremioInstanceConfigVarsMax, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceDremioInstanceMax, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["project_id"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "region", testutil.Region), + resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["display_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "description", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["description"])), + + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_type"])), + + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.authority_url", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_authority_url"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.client_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_client_id"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.client_secret", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_client_secret"])), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "authentication.oauth.redirect_url"), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.jwt_claims.user_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_client_jwt_claims_user_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.scope", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_scope"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.parameters.0.name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_parameter_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.parameters.0.value", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_parameter_value"])), + + resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "state"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), + ), + }, + // 2) Data Source + { + Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMax, + ConfigVariables: testDremioInstanceConfigVarsMax, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "project_id", + dremioInstanceDataResource, "project_id", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "region", + dremioInstanceDataResource, "region", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "instance_id", + dremioInstanceDataResource, "instance_id", + ), + + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "display_name", + dremioInstanceDataResource, "display_name", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "description", + dremioInstanceDataResource, "description", + ), + + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "authentication.type", + dremioInstanceDataResource, "authentication.type", + ), + // Authentication on the data source only shows the currently set IDP config, + // which is oauth for the config here. Hence why we test for the oauth value here. + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "authentication.oauth.authority_url", + dremioInstanceDataResource, "authentication.authority_url", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "authentication.oauth.client_id", + dremioInstanceDataResource, "authentication.client_id", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "authentication.oauth.scope", + dremioInstanceDataResource, "authentication.scope", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "authentication.oauth.parameters", + dremioInstanceDataResource, "authentication.parameters", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "authentication.oauth.redirect_url", + dremioInstanceDataResource, "authentication.redirect_url", + ), + + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "endpoints.arrow_flight", + dremioInstanceDataResource, "endpoints.arrow_flight", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "catalog", + dremioInstanceDataResource, "catalog", + ), + resource.TestCheckResourceAttrPair( + dremioInstanceResource, "endpoints.ui", + dremioInstanceDataResource, "endpoints.ui", + ), + ), + }, + // 3) Import + { + ConfigVariables: testDremioInstanceConfigVarsMax, + ResourceName: dremioInstanceResource, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources[dremioInstanceResource] + if !ok { + return "", fmt.Errorf("couldn't find resource %s", dremioInstanceResource) + } + instanceId, ok := r.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute instanceId") + } + + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId), nil + }}, + // 4) Update + { + Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMax, + ConfigVariables: testDremioInstanceConfigVarsMaxUpdated(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMaxUpdated()["project_id"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "region", testutil.Region), + resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMaxUpdated()["display_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "description", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMaxUpdated()["description"])), + + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMaxUpdated()["authentication_type"])), + + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.azuread.authority_url", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMaxUpdated()["authentication_azuread_authority_url"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.azuread.client_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMaxUpdated()["authentication_azuread_client_id"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.azuread.client_secret", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMaxUpdated()["authentication_azuread_client_secret"])), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "authentication.azuread.redirect_url"), + + resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "state"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), + ), + }, + }, + }) +} + +func testAccDremioInstanceDestroy(s *terraform.State) error { + ctx := context.Background() + client, err := dremioSdk.NewAPIClient( + testutil.NewConfigBuilder().BuildClientOptions(testutil.DremioCustomEndpoint, true)...) + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + instancesToDestroy := []string{} + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_dremio_instance" { + continue + } + // Dremio internal ID: "[project_id],[region],[instance_id]" + instanceId := strings.Split(rs.Primary.ID, core.Separator)[2] + instancesToDestroy = append(instancesToDestroy, instanceId) + } + + // List all resources in the project/region to see what's left + instancesResp, err := client.DefaultAPI.ListDremioInstances(ctx, testutil.ProjectId, testutil.Region).Execute() + if err != nil { + return fmt.Errorf("getting instancesResp: %w", err) + } + + // If the API returns a list of runners, check if our deleted ones are still there + items := instancesResp.Dremios + for i := range items { + // If a runner we thought we deleted is found in the list + if utils.Contains(instancesToDestroy, items[i].Id) { + // Attempt a final delete and wait, just like Postgres + err := client.DefaultAPI.DeleteDremioInstance(ctx, testutil.ProjectId, testutil.Region, items[i].Id).Execute() + if err != nil { + return fmt.Errorf("deleting Dremio instance %s during CheckDestroy: %w", items[i].Id, err) + } + + // Using the wait handler for destruction verification + _, err = dremioWaiter.DeleteDremioWaitHandler(ctx, client.DefaultAPI, testutil.ProjectId, testutil.Region, items[i].Id).WaitWithContext(ctx) + if err != nil { + return fmt.Errorf("deleting Dremio instance %s during CheckDestroy: waiting for deletion %w", items[i].Id, err) + } + } + } + return nil +} diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go index ed64c3b36..410776b08 100644 --- a/stackit/internal/services/dremio/instance/datasource.go +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -301,11 +301,6 @@ func mapDataSourceAuthentication(instanceResp *dremioSdk.DremioResponse, model * authModel.Type = types.StringValue(authResp.Type) - if instanceResp.Authentication.Type == "local-only" { - // On local auth we don't need to map IDP fields - return nil - } - if authResp.Type == "azuread" { azureADResp := authResp.Azuread authModel.AuthorityUrl = types.StringValue(azureADResp.AuthorityUrl) diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index 85874ee5e..91b16e95a 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -8,10 +8,12 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -50,9 +52,9 @@ type Model struct { Description types.String `tfsdk:"description"` // Read-only Fields - State types.String `tfsdk:"state"` - ErrorMessage types.String `tfsdk:"error_message"` - Endpoints *EndpointsModel `tfsdk:"endpoints"` + State types.String `tfsdk:"state"` + ErrorMessage types.String `tfsdk:"error_message"` + Endpoints types.Object `tfsdk:"endpoints"` // see EdnpointsModel } // InstanceModel maps the resource schema data. @@ -116,6 +118,12 @@ type EndpointsModel struct { Ui types.String `tfsdk:"ui"` } +var endpointsAttrTypes = map[string]attr.Type{ + "arrow_flight": types.StringType, + "catalog": types.StringType, + "ui": types.StringType, +} + func NewInstanceResource() resource.Resource { return &instanceResource{} } @@ -248,6 +256,8 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, "description": schema.StringAttribute{ Description: descriptions["description"], Optional: true, + Computed: true, // Must be computed if a default is applied + Default: stringdefault.StaticString(""), PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -314,7 +324,7 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, "client_secret": schema.StringAttribute{ Description: descriptions["azuread_client_secret"], Required: true, - Sensitive: true, + // Sensitive: true, }, "redirect_url": schema.StringAttribute{ Description: descriptions["azuread_redirect_url"], @@ -337,7 +347,7 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, "client_secret": schema.StringAttribute{ Description: descriptions["oauth_client_secret"], Required: true, - Sensitive: true, + // Sensitive: true, }, "scope": schema.StringAttribute{ Description: descriptions["oauth_scope"], @@ -678,11 +688,16 @@ func mapModelFields(instanceResp *dremioSdk.DremioResponse, model *Model) error model.Description = types.StringPointerValue(instanceResp.Description) model.ErrorMessage = types.StringPointerValue(instanceResp.ErrorMessage) - model.Endpoints = &EndpointsModel{ + endpoints := &EndpointsModel{ ArrowFlight: types.StringValue(instanceResp.Endpoints.ArrowFlight), Catalog: types.StringValue(instanceResp.Endpoints.Catalog), Ui: types.StringValue(instanceResp.Endpoints.Ui), } + endpointsObj, diags := types.ObjectValueFrom(context.Background(), endpointsAttrTypes, endpoints) + if diags.HasError() { + return fmt.Errorf("failed to parse endpoints") + } + model.Endpoints = endpointsObj return nil } @@ -744,8 +759,13 @@ func toUpdatePayload(model *InstanceModel) (*dremioSdk.UpdateDremioInstancePaylo return nil, fmt.Errorf("nil model") } + authentication, err := parseAuthentication(model) + if err != nil { + return nil, fmt.Errorf("failed to parse authentication: %v", err) + } + return &dremioSdk.UpdateDremioInstancePayload{ - Authentication: parseAuthentication(model), + Authentication: authentication, Description: model.Description.ValueStringPointer(), DisplayName: model.DisplayName.ValueStringPointer(), }, nil @@ -757,26 +777,41 @@ func toCreatePayload(model *InstanceModel) (*dremioSdk.CreateDremioInstancePaylo return nil, fmt.Errorf("nil model") } + authentication, err := parseAuthentication(model) + if err != nil { + return nil, fmt.Errorf("failed to parse authentication: %v", err) + } + return &dremioSdk.CreateDremioInstancePayload{ - Authentication: parseAuthentication(model), + Authentication: authentication, Description: model.Description.ValueStringPointer(), DisplayName: model.DisplayName.ValueString(), }, nil } -func parseAuthentication(model *InstanceModel) *dremioSdk.Authentication { - var azureAdPayload *dremioSdk.Azuread - if model.Authentication.AzureAD != nil { - azureAdPayload = &dremioSdk.Azuread{ - AuthorityUrl: model.Authentication.AzureAD.AuthorityUrl.ValueString(), - ClientId: model.Authentication.AzureAD.ClientId.ValueString(), - ClientSecret: model.Authentication.AzureAD.ClientSecret.ValueString(), - RedirectUrl: model.Authentication.AzureAD.RedirectUrl.ValueStringPointer(), +func parseAuthentication(model *InstanceModel) (*dremioSdk.Authentication, error) { + // API only saves the block of the stated type. The other one is omitted. + // Keeping the block in TF leads to inconsistent state. Therefore we have + // make sure the type matches the existing block. + + switch model.Authentication.Type.ValueString() { + case "local-only": + if !(model.Authentication.OAuth == nil) || !(model.Authentication.AzureAD == nil) { + return nil, fmt.Errorf("can't state idp config if auth type is local-only") + } + return &dremioSdk.Authentication{ + Azuread: nil, + Oauth: nil, + Type: model.Authentication.Type.ValueString(), + }, nil + case "oauth": + if !(model.Authentication.AzureAD == nil) { + return nil, fmt.Errorf("can't state azure idp config if auth type is oauth") + } + if model.Authentication.OAuth == nil { + return nil, fmt.Errorf("missing oauth idp config") } - } - var oAuthPayload *dremioSdk.Oauth - if model.Authentication.OAuth != nil { oAuthParams := []dremioSdk.AuthParameters{} if len(model.Authentication.OAuth.Parameters) > 0 { parameters := model.Authentication.OAuth.Parameters @@ -788,7 +823,7 @@ func parseAuthentication(model *InstanceModel) *dremioSdk.Authentication { } } - oAuthPayload = &dremioSdk.Oauth{ + oAuthPayload := &dremioSdk.Oauth{ AuthorityUrl: model.Authentication.OAuth.AuthorityUrl.ValueString(), ClientId: model.Authentication.OAuth.ClientId.ValueString(), ClientSecret: model.Authentication.OAuth.ClientSecret.ValueString(), @@ -799,11 +834,32 @@ func parseAuthentication(model *InstanceModel) *dremioSdk.Authentication { Scope: model.Authentication.OAuth.Scope.ValueStringPointer(), Parameters: oAuthParams, } - } - return &dremioSdk.Authentication{ - Azuread: azureAdPayload, - Oauth: oAuthPayload, - Type: model.Authentication.Type.ValueString(), + return &dremioSdk.Authentication{ + Azuread: nil, + Oauth: oAuthPayload, + Type: model.Authentication.Type.ValueString(), + }, nil + case "azuread": + if !(model.Authentication.OAuth == nil) { + return nil, fmt.Errorf("can't state oauth idp config if auth type is azuread") + } + if model.Authentication.AzureAD == nil { + return nil, fmt.Errorf("missing azuread config") + } + + azureAdPayload := &dremioSdk.Azuread{ + AuthorityUrl: model.Authentication.AzureAD.AuthorityUrl.ValueString(), + ClientId: model.Authentication.AzureAD.ClientId.ValueString(), + ClientSecret: model.Authentication.AzureAD.ClientSecret.ValueString(), + RedirectUrl: model.Authentication.AzureAD.RedirectUrl.ValueStringPointer(), + } + return &dremioSdk.Authentication{ + Azuread: azureAdPayload, + Oauth: nil, + Type: model.Authentication.Type.ValueString(), + }, nil + default: + return nil, fmt.Errorf("unknown authentication type: %s", model.Authentication.Type) } } diff --git a/stackit/internal/services/dremio/instance/resource_test.go b/stackit/internal/services/dremio/instance/resource_test.go index accdb6174..1bd1845e1 100644 --- a/stackit/internal/services/dremio/instance/resource_test.go +++ b/stackit/internal/services/dremio/instance/resource_test.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/utils" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" @@ -78,11 +79,18 @@ func TestMapFields(t *testing.T) { State: types.StringValue("active"), ErrorMessage: types.StringNull(), - Endpoints: &EndpointsModel{ - ArrowFlight: types.StringValue("flight"), - Catalog: types.StringValue("catalog"), - Ui: types.StringValue("ui"), - }, + Endpoints: types.ObjectValueMust( + map[string]attr.Type{ + "arrow_flight": types.StringType, + "catalog": types.StringType, + "ui": types.StringType, + }, + map[string]attr.Value{ + "arrow_flight": types.StringValue("flight"), + "catalog": types.StringValue("catalog"), + "ui": types.StringValue("ui"), + }, + ), }, Authentication: &AuthenticationModel{ AzureAD: &AzureADModel{ @@ -162,19 +170,33 @@ func TestToCreatePayload(t *testing.T) { wantErr bool }{ { - "success", + "success-local", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ + Type: types.StringValue("local-only"), + }, + }, + &dremioSdk.CreateDremioInstancePayload{ + Authentication: &dremioSdk.Authentication{ + Type: "local-only", + }, + Description: utils.Ptr("test description"), + DisplayName: "displayName", + }, + false, + }, + { + "success-oauth", &InstanceModel{ Model: Model{ Description: types.StringValue("test description"), DisplayName: types.StringValue("displayName"), }, Authentication: &AuthenticationModel{ - AzureAD: &AzureADModel{ - AuthorityUrl: types.StringValue("azure-authority"), - ClientId: types.StringValue("azure-client"), - ClientSecret: types.StringValue("azure-secret"), - RedirectUrl: types.StringValue("azure-redirect"), - }, OAuth: &OAuthModel{ AuthorityUrl: types.StringValue("oauth-authority"), ClientId: types.StringValue("oauth-client"), @@ -196,12 +218,6 @@ func TestToCreatePayload(t *testing.T) { }, &dremioSdk.CreateDremioInstancePayload{ Authentication: &dremioSdk.Authentication{ - Azuread: &dremioSdk.Azuread{ - AuthorityUrl: "azure-authority", - ClientId: "azure-client", - ClientSecret: "azure-secret", - RedirectUrl: utils.Ptr("azure-redirect"), - }, Oauth: &dremioSdk.Oauth{ AuthorityUrl: "oauth-authority", ClientId: "oauth-client", @@ -225,6 +241,86 @@ func TestToCreatePayload(t *testing.T) { }, false, }, + { + "success-azuread", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ + AzureAD: &AzureADModel{ + AuthorityUrl: types.StringValue("azure-authority"), + ClientId: types.StringValue("azure-client"), + ClientSecret: types.StringValue("azure-secret"), + RedirectUrl: types.StringValue("azure-redirect"), + }, + Type: types.StringValue("azuread"), + }, + }, + &dremioSdk.CreateDremioInstancePayload{ + Authentication: &dremioSdk.Authentication{ + Azuread: &dremioSdk.Azuread{ + AuthorityUrl: "azure-authority", + ClientId: "azure-client", + ClientSecret: "azure-secret", + RedirectUrl: utils.Ptr("azure-redirect"), + }, + Type: "azuread", + }, + Description: utils.Ptr("test description"), + DisplayName: "displayName", + }, + false, + }, + { + "idp-config-mismatch-local", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ + AzureAD: &AzureADModel{ + AuthorityUrl: types.StringValue("azure-authority"), + ClientId: types.StringValue("azure-client"), + ClientSecret: types.StringValue("azure-secret"), + RedirectUrl: types.StringValue("azure-redirect"), + }, + Type: types.StringValue("local-only"), + }, + }, + nil, + true, + }, + { + "missing-idp-config-oauth", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ + Type: types.StringValue("oauth"), + }, + }, + nil, + true, + }, + { + "missing-idp-config-azuread", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ + Type: types.StringValue("azuread"), + }, + }, + nil, + true, + }, { "nil model", nil, @@ -264,12 +360,26 @@ func TestToUpdatePayload(t *testing.T) { DisplayName: types.StringValue("displayName"), }, Authentication: &AuthenticationModel{ - AzureAD: &AzureADModel{ - AuthorityUrl: types.StringValue("azure-authority"), - ClientId: types.StringValue("azure-client"), - ClientSecret: types.StringValue("azure-secret"), - RedirectUrl: types.StringValue("azure-redirect"), - }, + Type: types.StringValue("local-only"), + }, + }, + &dremioSdk.UpdateDremioInstancePayload{ + Authentication: &dremioSdk.Authentication{ + Type: "local-only", + }, + Description: utils.Ptr("test description"), + DisplayName: utils.Ptr("displayName"), + }, + false, + }, + { + "success-oauth", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ OAuth: &OAuthModel{ AuthorityUrl: types.StringValue("oauth-authority"), ClientId: types.StringValue("oauth-client"), @@ -291,12 +401,6 @@ func TestToUpdatePayload(t *testing.T) { }, &dremioSdk.UpdateDremioInstancePayload{ Authentication: &dremioSdk.Authentication{ - Azuread: &dremioSdk.Azuread{ - AuthorityUrl: "azure-authority", - ClientId: "azure-client", - ClientSecret: "azure-secret", - RedirectUrl: utils.Ptr("azure-redirect"), - }, Oauth: &dremioSdk.Oauth{ AuthorityUrl: "oauth-authority", ClientId: "oauth-client", @@ -320,6 +424,86 @@ func TestToUpdatePayload(t *testing.T) { }, false, }, + { + "success-azuread", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ + AzureAD: &AzureADModel{ + AuthorityUrl: types.StringValue("azure-authority"), + ClientId: types.StringValue("azure-client"), + ClientSecret: types.StringValue("azure-secret"), + RedirectUrl: types.StringValue("azure-redirect"), + }, + Type: types.StringValue("azuread"), + }, + }, + &dremioSdk.UpdateDremioInstancePayload{ + Authentication: &dremioSdk.Authentication{ + Azuread: &dremioSdk.Azuread{ + AuthorityUrl: "azure-authority", + ClientId: "azure-client", + ClientSecret: "azure-secret", + RedirectUrl: utils.Ptr("azure-redirect"), + }, + Type: "azuread", + }, + Description: utils.Ptr("test description"), + DisplayName: utils.Ptr("displayName"), + }, + false, + }, + { + "idp-config-mismatch-local", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ + AzureAD: &AzureADModel{ + AuthorityUrl: types.StringValue("azure-authority"), + ClientId: types.StringValue("azure-client"), + ClientSecret: types.StringValue("azure-secret"), + RedirectUrl: types.StringValue("azure-redirect"), + }, + Type: types.StringValue("local-only"), + }, + }, + nil, + true, + }, + { + "missing-idp-config-oauth", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ + Type: types.StringValue("oauth"), + }, + }, + nil, + true, + }, + { + "missing-idp-config-azuread", + &InstanceModel{ + Model: Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + }, + Authentication: &AuthenticationModel{ + Type: types.StringValue("azuread"), + }, + }, + nil, + true, + }, { "nil model", nil, diff --git a/stackit/internal/services/dremio/testdata/resource-max.tf b/stackit/internal/services/dremio/testdata/resource-max.tf new file mode 100644 index 000000000..57943490b --- /dev/null +++ b/stackit/internal/services/dremio/testdata/resource-max.tf @@ -0,0 +1,60 @@ + +variable "project_id"{} +variable "region" {} +variable "display_name" {} +variable "description" {} + +// authentication +variable "authentication_type" {} + +// oauth +variable "authentication_oauth_authority_url" {} +variable "authentication_oauth_client_id" {} +variable "authentication_oauth_client_secret" {} +variable "authentication_oauth_client_jwt_claims_user_name" {} +variable "authentication_oauth_scope" {} +variable "authentication_oauth_parameter_name" {} +variable "authentication_oauth_parameter_value" {} + +// azuread +variable "authentication_type_azuread" {default=null} +variable "authentication_azuread_authority_url" {default=null} +variable "authentication_azuread_client_id" {default=null} +variable "authentication_azuread_client_secret" {default=null} + +resource "stackit_dremio_instance" "example" { + project_id = var.project_id + region = var.region + display_name = var.display_name + description = var.description + authentication = { + type = var.authentication_type + + oauth = var.authentication_type == "oauth" ? { + authority_url = var.authentication_oauth_authority_url + client_id = var.authentication_oauth_client_id + client_secret = var.authentication_oauth_client_secret + jwt_claims = { + user_name = var.authentication_oauth_client_jwt_claims_user_name + } + scope = var.authentication_oauth_scope + parameters = [ + { + "name": var.authentication_oauth_parameter_name, + "value": var.authentication_oauth_parameter_value + } + ] + } : null + azuread = var.authentication_type == "azuread" ? { + authority_url = var.authentication_azuread_authority_url + client_id = var.authentication_azuread_client_id + client_secret = var.authentication_azuread_client_secret + } : null + } +} + +data "stackit_dremio_instance" "example" { + project_id = var.project_id + region = var.region + instance_id = stackit_dremio_instance.example.instance_id +} \ No newline at end of file diff --git a/stackit/internal/services/dremio/testdata/resource-min.tf b/stackit/internal/services/dremio/testdata/resource-min.tf new file mode 100644 index 000000000..5a3bf4fb3 --- /dev/null +++ b/stackit/internal/services/dremio/testdata/resource-min.tf @@ -0,0 +1,20 @@ + +variable "project_id"{} +variable "region" {} +variable "display_name" {} +variable "authentication_type" {} + +resource "stackit_dremio_instance" "example" { + project_id = var.project_id + region = var.region + display_name = var.display_name + authentication = { + type = var.authentication_type + } +} + +data "stackit_dremio_instance" "example" { + project_id = var.project_id + region = var.region + instance_id = stackit_dremio_instance.example.instance_id +} \ No newline at end of file diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index 25f7be11c..8ade0d85f 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -71,6 +71,7 @@ var ( 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"} From 763d72e487be21ce4a5e9f5f1819e740b2fa53a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 3 Jun 2026 18:22:22 +0200 Subject: [PATCH 11/33] feat(dremio): First draft for Dremio user resource --- .../internal/services/dremio/user/resource.go | 449 ++++++++++++++++++ 1 file changed, 449 insertions(+) diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index 8dc0f480d..528733a64 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -1 +1,450 @@ package dremio + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "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/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" + dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" + + dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait/wait" +) + +var ( + _ resource.Resource = &userResource{} + _ resource.ResourceWithConfigure = &userResource{} + _ resource.ResourceWithImportState = &userResource{} + _ resource.ResourceWithModifyPlan = &userResource{} +) + +type Model struct { + ID types.String `tfsdk:"id"` + + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + InstanceId types.String `tfsdk:"instance_id"` + UserId types.String `tfsdk:"user_id"` + + Description types.String `tfsdk:"description"` + Email types.String `tfsdk:"email"` + FirstName types.String `tfsdk:"first_name"` + LastName types.String `tfsdk:"last_name"` + Name types.String `tfsdk:"name"` + Password types.String `tfsdk:"password"` +} + +type UserModel struct { + Model + + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type userResource struct { + client *dremioSdk.APIClient + providerData core.ProviderData +} + +func NewUserResource() resource.Resource { + return &userResource{} +} + +func (r *userResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dremio_user" +} + +func (r *userResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform + var configModel UserModel + // skip initial empty configuration to avoid follow-up errors + if req.Config.Raw.IsNull() { + return + } + resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) + if resp.Diagnostics.HasError() { + return + } + + var planModel Model + resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) + if resp.Diagnostics.HasError() { + return + } + + utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := dremioUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + r.client = apiClient + tflog.Info(ctx, "Dremio user client configured") +} + +func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + descriptions := map[string]string{ + "main": "Manages a STACKIT Dremio instances user.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".", + "project_id": "STACKIT Project ID to which the resource is associated.", + "instance_id": "The Dremio instance ID.", + "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", + "user_id": "The Dremio user ID.", + "description": "The description of the user.", + "email": "The email address of the user.", + "first_name": "The first name of the user.", + "last_name": "The last name of the user.", + "name": "The username of the user.", + "password": "The password of the user. Only used for creation and updates. Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character.", + } + + resp.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "instance_id": schema.StringAttribute{ + Description: descriptions["instance_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "region": schema.StringAttribute{ + Description: descriptions["region"], + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "user_id": schema.StringAttribute{ + Description: descriptions["user_id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + }, + "email": schema.StringAttribute{ + Description: descriptions["email"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "first_name": schema.StringAttribute{ + Description: descriptions["first_name"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "last_name": schema.StringAttribute{ + Description: descriptions["last_name"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Description: descriptions["name"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "password": schema.StringAttribute{ + Description: descriptions["password"], + Optional: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform + var model UserModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + waiterTimeout := dremioWaiter.CreateDremioUserWaitHandler(ctx, r.client.DefaultAPI, "", "", "", "").GetTimeout() + createTimeout, diags := model.Timeouts.Create(ctx, waiterTimeout+core.DefaultTimeoutMargin) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, createTimeout) + defer cancel() + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() // not needed for global APIs + instanceId := model.InstanceId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + + // prepare the payload struct for the create user request + payload, err := toCreatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + // Create new Dremio user + userResp, err := r.client.DefaultAPI.CreateDremioUser(ctx, projectId, region, instanceId).CreateDremioUserPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]interface{}{ + "project_id": projectId, + "region": region, + "user_id": userResp.Id, + }) + if resp.Diagnostics.HasError() { + return + } + + _, err = dremioWaiter.CreateDremioUserWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId, userResp.Id).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Dremio user", fmt.Sprintf("Dremio user creation waiting: %v", err)) + return + } + + err = mapFields(userResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Dremio user", fmt.Sprintf("Processing API payload: %v", err)) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Dremio user created") +} + +func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model UserModel + resp.Diagnostics.Append(req.State.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + readTimeout, diags := model.Timeouts.Read(ctx, core.DefaultOperationTimeout) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + instanceId := model.InstanceId.ValueString() + userId := model.UserId.ValueString() + if userId == "" { + // Resource not yet created; ID is unknown. + resp.State.RemoveResource(ctx) + return + } + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "user_id", userId) + + userResp, err := r.client.DefaultAPI.GetDremioUser(ctx, projectId, region, instanceId, userId).Execute() + if err != nil { + if oapiErr, ok := errors.AsType[*oapierror.GenericOpenAPIError](err); ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading dremio user", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(userResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading dremio user", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Dremio user read") +} + +func (r *userResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // We don't allow updates on Dremio users. +} + +func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform + var model UserModel + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + waiterTimeout := dremioWaiter.DeleteDremioUserWaitHandler(ctx, r.client.DefaultAPI, "", "", "", "").GetTimeout() + deleteTimeout, diags := model.Timeouts.Delete(ctx, waiterTimeout+core.DefaultTimeoutMargin) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, deleteTimeout) + defer cancel() + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() + instanceId := model.InstanceId.ValueString() + userId := model.UserId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "user_id", userId) + + err := r.client.DefaultAPI.DeleteDremioUser(ctx, projectId, region, instanceId, userId).Execute() + if err != nil { + if oapiErr, ok := errors.AsType[*oapierror.GenericOpenAPIError](err); ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting Dremio user", fmt.Sprintf("Calling API: %v", err)) + } + + ctx = core.LogResponse(ctx) + + _, err = dremioWaiter.DeleteDremioUserWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId, userId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting Dremio user", fmt.Sprintf("Dremio user deletion waiting: %v", err)) + return + } + + tflog.Info(ctx, "Dremio user deleted") +} + +func (r *userResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, core.Separator) + if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, + "Error importing dremio user", + fmt.Sprintf("Expected import identifier with format [project_id],[region],[instance_id],[user_id] got %q", req.ID), + ) + return + } + + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": idParts[0], + "region": idParts[1], + "instance_id": idParts[2], + "user_id": idParts[3], + }) + + tflog.Info(ctx, "Dremio user state imported") +} + +func mapFields(userResp *dremioSdk.DremioUserResponse, model *UserModel) error { + if userResp == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + model.UserId = types.StringValue(userResp.Id) + model.Description = types.StringPointerValue(userResp.Description) + model.Email = types.StringValue(userResp.Email) + model.FirstName = types.StringValue(userResp.FirstName) + model.LastName = types.StringValue(userResp.LastName) + model.Name = types.StringValue(userResp.Name) + + return nil +} + +func toCreatePayload(model *UserModel) (*dremioSdk.CreateDremioUserPayload, error) { + if model == nil { + return nil, fmt.Errorf("model input is nil") + } + + payload := &dremioSdk.CreateDremioUserPayload{ + Description: model.Description.ValueStringPointer(), + Email: model.Email.ValueString(), + FirstName: model.FirstName.ValueString(), + LastName: model.LastName.ValueString(), + Name: model.Name.ValueString(), + Password: model.Password.ValueString(), + } + + return payload, nil +} + +// func toUpdatePayload(model *UserModel) (*dremioSdk.UpdateDremioUserPayload, error) { +// return nil, nil +// } From 1bc40eea5589bd8eec64b5218aaf6199d8e62fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 3 Jun 2026 18:23:07 +0200 Subject: [PATCH 12/33] fix(dremio): Removing some obsolete comments & fixing typos in Dremio instance. --- stackit/internal/services/dremio/instance/resource.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index 91b16e95a..8d870defd 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -35,7 +35,7 @@ var ( _ resource.Resource = &instanceResource{} _ resource.ResourceWithConfigure = &instanceResource{} _ resource.ResourceWithImportState = &instanceResource{} - _ resource.ResourceWithModifyPlan = &instanceResource{} // not needed for global APIs + _ resource.ResourceWithModifyPlan = &instanceResource{} ) type Model struct { @@ -130,7 +130,7 @@ func NewInstanceResource() resource.Resource { type instanceResource struct { client *dremioSdk.APIClient - providerData core.ProviderData // not needed for global APIs + providerData core.ProviderData } func (r *instanceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -182,7 +182,7 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { descriptions := map[string]string{ //nolint:gosec // no hardcoded credentials in here "main": "Manages a STACKIT Dremio instance.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`dremio_id`\".", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`\".", "project_id": "STACKIT Project ID to which the resource is associated.", "instance_id": "The Dremio instance ID.", "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", @@ -422,7 +422,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques // prepare the payload struct for the create instance request payload, err := toCreatePayload(&model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err)) return } From e6af5ef3eebc69935276441d76a59b273484ec11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Thu, 4 Jun 2026 13:05:49 +0200 Subject: [PATCH 13/33] fix(dremio): Fully align instance data source with resource. Opted for fully aligning the status quo with the possiblity for improvement later on. It is the more practical and aligned solution. On changes of the sdk we will update the TFP accordingly. --- .../services/dremio/instance/datasource.go | 248 ++++++++---------- .../services/dremio/instance/resource.go | 28 +- .../services/dremio/instance/resource_test.go | 175 +++++------- 3 files changed, 188 insertions(+), 263 deletions(-) diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go index 410776b08..52591200c 100644 --- a/stackit/internal/services/dremio/instance/datasource.go +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -8,14 +8,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" @@ -29,19 +26,6 @@ var ( type InstanceDataSourceModel struct { Model - - // Required Fields - Authentication *DataSourceAuthenticationModel `tfsdk:"authentication"` -} - -type DataSourceAuthenticationModel struct { - Type types.String `tfsdk:"type"` - AuthorityUrl types.String `tfsdk:"authority_url"` - ClientId types.String `tfsdk:"client_id"` - JwtClaims *JwtClaimsModel `tfsdk:"jwt_claims"` - Scope types.String `tfsdk:"scope"` - Parameters []AuthParameterModel `tfsdk:"parameters"` - RedirectUrl types.String `tfsdk:"redirect_url"` } type instanceDataSource struct { @@ -77,31 +61,38 @@ func (d *instanceDataSource) Configure(ctx context.Context, req datasource.Confi // Schema should return the schema for this data source. func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - descriptions := map[string]string{ - "main": "Manages a STACKIT Dremio instance.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`dremio_id`\".", - "project_id": "STACKIT Project ID to which the resource is associated.", - "instance_id": "The Dremio instance ID.", - "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", - "display_name": "The display name is a short name chosen by the user to identify the resource.", - "description": "The description is a longer text chosen by the user to provide more context for the resource.", - "state": "The current state of the resource.", - "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", - "endpoints": "The available endpoints of the Dremio instance.", - "endpoints_arrow_flight": "The arrow flight endpoint of the Dremio instance.", - "endpoints_catalog": "The Apache Iceberg endpoint of the Dremio instance.", - "endpoints_ui": "The UI endpoint of the Dremio instance.", - "authentication": "Dremio instance authentication settings. A change here triggers a Dremio restart and will incur downtime.", - "authentication_type": "Type of authentication (local-only, azuread, oauth).", - "authentication_authority_url": "The Issuer location URI, where the OIDC provider configuration can be found.", - "authentication_client_id": "The client ID assigned by the Identity Provider.", - "authentication_scope": "A list of space-separated scopes. The `openid` scope is always required; other scopes can vary by provider.", - "authentication_redirect_url": "The URL where the Dremio instance is hosted. The URL must match the redirect URL set in the Identity Provider.", - "authentication_jwt_claims": "Maps fields from the JWT token to fields Dremio requires.", - "authentication_jwt_claims_user_name": "Mapped user name claim (e.g. email).", - "authentication_parameters": "Any additional parameters the Identity Provider requires.", - "authentication_parameters_name": "Parameter name.", - "authentication_parameters_value": "Parameter value.", + descriptions := map[string]string{ //nolint:gosec // no hardcoded credentials in here + "main": "Manages a STACKIT Dremio instance.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`\".", + "project_id": "STACKIT Project ID to which the resource is associated.", + "instance_id": "The Dremio instance ID.", + "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", + "display_name": "The display name is a short name chosen by the user to identify the resource.", + "description": "The description is a longer text chosen by the user to provide more context for the resource.", + "state": "The current state of the resource.", + "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", + "endpoints": "The available endpoints of the Dremio instance.", + "endpoints_arrow_flight": "The arrow flight endpoint of the Dremio instance.", + "endpoints_catalog": "The Apache Iceberg endpoint of the Dremio instance.", + "endpoints_ui": "The UI endpoint of the Dremio instance.", + "authentication": "Dremio instance authentication settings. A change here triggers a Dremio restart and will incur downtime.", + "authentication_type": "Type of authentication (local-only, azuread, oauth).", + "azuread": "Azure Active Directory authentication configuration.", + "azuread_authority_url": "The Azure AD authority URL.", + "azuread_client_id": "The Azure AD client ID.", + "azuread_client_secret": "The Azure AD client secret.", + "azuread_redirect_url": "The Azure AD redirect URL.", + "oauth": "OIDC authentication configuration.", + "oauth_authority_url": "The Issuer location URI, where the OIDC provider configuration can be found.", + "oauth_client_id": "The client ID assigned by the Identity Provider.", + "oauth_client_secret": "The client secret generated by the Identity Provider.", + "oauth_scope": "A list of space-separated scopes. The `openid` scope is always required; other scopes can vary by provider.", + "oauth_redirect_url": "The URL where the Dremio instance is hosted. The URL must match the redirect URL set in the Identity Provider.", + "oauth_jwt_claims": "Maps fields from the JWT token to fields Dremio requires.", + "oauth_jwt_claims_user_name": "Mapped user name claim (e.g. email).", + "oauth_parameters": "Any additional parameters the Identity Provider requires.", + "oauth_parameters_name": "Parameter name.", + "oauth_parameters_value": "Parameter value.", } resp.Schema = schema.Schema{ @@ -114,18 +105,14 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques "project_id": schema.StringAttribute{ Description: descriptions["project_id"], Required: true, - Validators: []validator.String{ - validate.UUID(), - validate.NoSeparator(), - }, }, "instance_id": schema.StringAttribute{ Description: descriptions["instance_id"], Required: true, }, "region": schema.StringAttribute{ - Description: descriptions["region"], Required: true, + Description: descriptions["region"], }, "display_name": schema.StringAttribute{ Description: descriptions["display_name"], @@ -133,8 +120,8 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques }, "description": schema.StringAttribute{ Description: descriptions["description"], - Computed: true, Optional: true, + Computed: true, }, "state": schema.StringAttribute{ Description: descriptions["state"], @@ -142,8 +129,8 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques }, "error_message": schema.StringAttribute{ Description: descriptions["error_message"], - Computed: true, Optional: true, + Computed: true, }, "endpoints": schema.SingleNestedAttribute{ Description: descriptions["endpoints"], @@ -171,50 +158,82 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques Description: descriptions["authentication_type"], Computed: true, }, - "authority_url": schema.StringAttribute{ - Description: descriptions["oauth_authority_url"], - Computed: true, - Optional: true, - }, - "client_id": schema.StringAttribute{ - Description: descriptions["oauth_client_id"], - Computed: true, - Optional: true, - }, - "scope": schema.StringAttribute{ - Description: descriptions["oauth_scope"], - Computed: true, - Optional: true, - }, - "redirect_url": schema.StringAttribute{ - Description: descriptions["oauth_redirect_url"], - Computed: true, + "azuread": schema.SingleNestedAttribute{ + Description: descriptions["azuread"], Optional: true, - }, - "jwt_claims": schema.SingleNestedAttribute{ - Description: descriptions["oauth_jwt_claims"], Computed: true, - Optional: true, Attributes: map[string]schema.Attribute{ - "user_name": schema.StringAttribute{ - Description: descriptions["oauth_jwt_claims_user_name"], + "authority_url": schema.StringAttribute{ + Description: descriptions["azuread_authority_url"], + Computed: true, + }, + "client_id": schema.StringAttribute{ + Description: descriptions["azuread_client_id"], + Computed: true, + }, + "client_secret": schema.StringAttribute{ + Description: descriptions["azuread_client_secret"], + Computed: true, + Sensitive: true, + }, + "redirect_url": schema.StringAttribute{ + Description: descriptions["azuread_redirect_url"], Computed: true, }, }, }, - "parameters": schema.ListNestedAttribute{ - Description: descriptions["oauth_parameters"], - Computed: true, + "oauth": schema.SingleNestedAttribute{ + Description: descriptions["oauth"], Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "name": schema.StringAttribute{ - Description: descriptions["oauth_parameters_name"], - Computed: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "authority_url": schema.StringAttribute{ + Description: descriptions["oauth_authority_url"], + Computed: true, + }, + "client_id": schema.StringAttribute{ + Description: descriptions["oauth_client_id"], + Computed: true, + }, + "client_secret": schema.StringAttribute{ + Description: descriptions["oauth_client_secret"], + Computed: true, + Sensitive: true, + }, + "scope": schema.StringAttribute{ + Description: descriptions["oauth_scope"], + Optional: true, + Computed: true, + }, + "redirect_url": schema.StringAttribute{ + Description: descriptions["oauth_redirect_url"], + Computed: true, + }, + "jwt_claims": schema.SingleNestedAttribute{ + Description: descriptions["oauth_jwt_claims"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "user_name": schema.StringAttribute{ + Description: descriptions["oauth_jwt_claims_user_name"], + Computed: true, + }, }, - "value": schema.StringAttribute{ - Description: descriptions["oauth_parameters_value"], - Computed: true, + }, + "parameters": schema.ListNestedAttribute{ + Description: descriptions["oauth_parameters"], + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: descriptions["oauth_parameters_name"], + Computed: true, + }, + "value": schema.StringAttribute{ + Description: descriptions["oauth_parameters_value"], + Computed: true, + }, + }, }, }, }, @@ -260,7 +279,7 @@ func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques ctx = core.LogResponse(ctx) - err = mapDataSourceFields(instanceResp, &model) + err = mapFields(instanceResp, &model.Model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -273,64 +292,3 @@ func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques } tflog.Info(ctx, "Dremio instance read") } - -func mapDataSourceFields(instanceResp *dremioSdk.DremioResponse, model *InstanceDataSourceModel) error { - if instanceResp == nil { - return fmt.Errorf("response input is nil") - } - if model == nil { - return fmt.Errorf("model input is nil") - } - - err := mapModelFields(instanceResp, &model.Model) - if err != nil { - return fmt.Errorf("failed to map Model fields") - } - err = mapDataSourceAuthentication(instanceResp, model) - if err != nil { - return fmt.Errorf("failed to map Authentication fields") - } - - return nil -} - -func mapDataSourceAuthentication(instanceResp *dremioSdk.DremioResponse, model *InstanceDataSourceModel) error { - authResp := instanceResp.Authentication - - authModel := DataSourceAuthenticationModel{} - - authModel.Type = types.StringValue(authResp.Type) - - if authResp.Type == "azuread" { - azureADResp := authResp.Azuread - authModel.AuthorityUrl = types.StringValue(azureADResp.AuthorityUrl) - authModel.ClientId = types.StringValue(azureADResp.ClientId) - authModel.RedirectUrl = types.StringPointerValue(azureADResp.RedirectUrl) - } - - if authResp.Type == "oauth" { - oauthResp := authResp.Oauth - authModel.AuthorityUrl = types.StringValue(oauthResp.AuthorityUrl) - authModel.ClientId = types.StringValue(oauthResp.ClientId) - authModel.Scope = types.StringPointerValue(oauthResp.Scope) - authModel.RedirectUrl = types.StringPointerValue(oauthResp.RedirectUrl) - authModel.JwtClaims = &JwtClaimsModel{ - UserName: types.StringValue(oauthResp.JwtClaims.UserName), - } - - if len(oauthResp.Parameters) > 0 { - var params []AuthParameterModel - for _, p := range oauthResp.Parameters { - params = append(params, AuthParameterModel{ - Name: types.StringValue(p.Name), - Value: types.StringValue(p.Value), - }) - } - authModel.Parameters = params - } - } - - model.Authentication = &authModel - - return nil -} diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index 8d870defd..c69a51400 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -46,7 +46,8 @@ type Model struct { InstanceId types.String `tfsdk:"instance_id"` // Required Fields - DisplayName types.String `tfsdk:"display_name"` + DisplayName types.String `tfsdk:"display_name"` + Authentication *AuthenticationModel `tfsdk:"authentication"` // Optional Fields Description types.String `tfsdk:"description"` @@ -61,9 +62,6 @@ type Model struct { type InstanceModel struct { Model - // Required Fields - Authentication *AuthenticationModel `tfsdk:"authentication"` - Timeouts timeouts.Value `tfsdk:"timeouts"` } @@ -420,7 +418,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques ctx = tflog.SetField(ctx, "region", region) // prepare the payload struct for the create instance request - payload, err := toCreatePayload(&model) + payload, err := toCreatePayload(&model.Model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err)) return @@ -450,7 +448,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - err = mapFields(instanceResp, &model) + err = mapFields(instanceResp, &model.Model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -503,7 +501,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r ctx = core.LogResponse(ctx) - err = mapFields(instanceResp, &model) + err = mapFields(instanceResp, &model.Model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading dremio instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -540,7 +538,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) - payload, err := toUpdatePayload(&model) + payload, err := toUpdatePayload(&model.Model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err)) return @@ -570,7 +568,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques return } - err = mapFields(instanceResp, &model) + err = mapFields(instanceResp, &model.Model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -648,7 +646,7 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS tflog.Info(ctx, "Dremio instance state imported") } -func mapFields(instanceResp *dremioSdk.DremioResponse, model *InstanceModel) error { +func mapFields(instanceResp *dremioSdk.DremioResponse, model *Model) error { if instanceResp == nil { return fmt.Errorf("response input is nil") } @@ -656,7 +654,7 @@ func mapFields(instanceResp *dremioSdk.DremioResponse, model *InstanceModel) err return fmt.Errorf("model input is nil") } - err := mapModelFields(instanceResp, &model.Model) + err := mapModelFields(instanceResp, model) if err != nil { return fmt.Errorf("failed to map Model fields") } @@ -702,7 +700,7 @@ func mapModelFields(instanceResp *dremioSdk.DremioResponse, model *Model) error return nil } -func mapAuthentication(instanceResp *dremioSdk.DremioResponse, model *InstanceModel) error { +func mapAuthentication(instanceResp *dremioSdk.DremioResponse, model *Model) error { if model == nil { return fmt.Errorf("model input is nil") } @@ -754,7 +752,7 @@ func mapAuthentication(instanceResp *dremioSdk.DremioResponse, model *InstanceMo } // Build UpdateDremioInstancePayload from provider's model -func toUpdatePayload(model *InstanceModel) (*dremioSdk.UpdateDremioInstancePayload, error) { +func toUpdatePayload(model *Model) (*dremioSdk.UpdateDremioInstancePayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } @@ -772,7 +770,7 @@ func toUpdatePayload(model *InstanceModel) (*dremioSdk.UpdateDremioInstancePaylo } // Build CreateDremioInstancePayload from provider's model -func toCreatePayload(model *InstanceModel) (*dremioSdk.CreateDremioInstancePayload, error) { +func toCreatePayload(model *Model) (*dremioSdk.CreateDremioInstancePayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } @@ -789,7 +787,7 @@ func toCreatePayload(model *InstanceModel) (*dremioSdk.CreateDremioInstancePaylo }, nil } -func parseAuthentication(model *InstanceModel) (*dremioSdk.Authentication, error) { +func parseAuthentication(model *Model) (*dremioSdk.Authentication, error) { // API only saves the block of the stated type. The other one is omitted. // Keeping the block in TF leads to inconsistent state. Therefore we have // make sure the type matches the existing block. diff --git a/stackit/internal/services/dremio/instance/resource_test.go b/stackit/internal/services/dremio/instance/resource_test.go index 1bd1845e1..fc725f817 100644 --- a/stackit/internal/services/dremio/instance/resource_test.go +++ b/stackit/internal/services/dremio/instance/resource_test.go @@ -16,18 +16,16 @@ func TestMapFields(t *testing.T) { instanceId := uuid.New().String() tests := []struct { description string - state *InstanceModel + state *Model input *dremioSdk.DremioResponse - expected *InstanceModel + expected *Model wantErr bool }{ { "all_fields_filled", - &InstanceModel{ - Model: Model{ - Region: types.StringValue("rid"), - ProjectId: types.StringValue("pid"), - }, + &Model{ + Region: types.StringValue("rid"), + ProjectId: types.StringValue("pid"), }, &dremioSdk.DremioResponse{ Id: instanceId, @@ -66,32 +64,16 @@ func TestMapFields(t *testing.T) { }, State: "active", }, - &InstanceModel{ - Model: Model{ - Id: types.StringValue("pid,rid," + instanceId), + &Model{ + Id: types.StringValue("pid,rid," + instanceId), - ProjectId: types.StringValue("pid"), - Region: types.StringValue("rid"), - InstanceId: types.StringValue(instanceId), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + InstanceId: types.StringValue(instanceId), - DisplayName: types.StringValue("greatName"), - Description: types.StringValue("minimal-required-values"), + DisplayName: types.StringValue("greatName"), + Description: types.StringValue("minimal-required-values"), - State: types.StringValue("active"), - ErrorMessage: types.StringNull(), - Endpoints: types.ObjectValueMust( - map[string]attr.Type{ - "arrow_flight": types.StringType, - "catalog": types.StringType, - "ui": types.StringType, - }, - map[string]attr.Value{ - "arrow_flight": types.StringValue("flight"), - "catalog": types.StringValue("catalog"), - "ui": types.StringValue("ui"), - }, - ), - }, Authentication: &AuthenticationModel{ AzureAD: &AzureADModel{ AuthorityUrl: types.StringValue("azure-authority"), @@ -117,24 +99,35 @@ func TestMapFields(t *testing.T) { }, Type: types.StringValue("local-only"), }, + + State: types.StringValue("active"), + ErrorMessage: types.StringNull(), + Endpoints: types.ObjectValueMust( + map[string]attr.Type{ + "arrow_flight": types.StringType, + "catalog": types.StringType, + "ui": types.StringType, + }, + map[string]attr.Value{ + "arrow_flight": types.StringValue("flight"), + "catalog": types.StringValue("catalog"), + "ui": types.StringValue("ui"), + }, + ), }, false, }, { "nil response", - &InstanceModel{ - Model: Model{ - Region: types.StringValue("rid"), - ProjectId: types.StringValue("pid"), - }, + &Model{ + Region: types.StringValue("rid"), + ProjectId: types.StringValue("pid"), }, nil, - &InstanceModel{ - Model: Model{ - Id: types.StringValue("pid,rid,"), - ProjectId: types.StringValue("pid"), - Region: types.StringValue("rid"), - }, + &Model{ + Id: types.StringValue("pid,rid,"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), }, true, }, @@ -165,17 +158,15 @@ func TestMapFields(t *testing.T) { func TestToCreatePayload(t *testing.T) { tests := []struct { description string - state *InstanceModel + state *Model expected *dremioSdk.CreateDremioInstancePayload wantErr bool }{ { "success-local", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ Type: types.StringValue("local-only"), }, @@ -191,11 +182,9 @@ func TestToCreatePayload(t *testing.T) { }, { "success-oauth", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ OAuth: &OAuthModel{ AuthorityUrl: types.StringValue("oauth-authority"), @@ -243,11 +232,9 @@ func TestToCreatePayload(t *testing.T) { }, { "success-azuread", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ AzureAD: &AzureADModel{ AuthorityUrl: types.StringValue("azure-authority"), @@ -275,11 +262,9 @@ func TestToCreatePayload(t *testing.T) { }, { "idp-config-mismatch-local", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ AzureAD: &AzureADModel{ AuthorityUrl: types.StringValue("azure-authority"), @@ -295,11 +280,9 @@ func TestToCreatePayload(t *testing.T) { }, { "missing-idp-config-oauth", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ Type: types.StringValue("oauth"), }, @@ -309,11 +292,9 @@ func TestToCreatePayload(t *testing.T) { }, { "missing-idp-config-azuread", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ Type: types.StringValue("azuread"), }, @@ -348,17 +329,15 @@ func TestToCreatePayload(t *testing.T) { func TestToUpdatePayload(t *testing.T) { tests := []struct { description string - state *InstanceModel + state *Model expected *dremioSdk.UpdateDremioInstancePayload wantErr bool }{ { "success", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ Type: types.StringValue("local-only"), }, @@ -374,11 +353,9 @@ func TestToUpdatePayload(t *testing.T) { }, { "success-oauth", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ OAuth: &OAuthModel{ AuthorityUrl: types.StringValue("oauth-authority"), @@ -426,11 +403,9 @@ func TestToUpdatePayload(t *testing.T) { }, { "success-azuread", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ AzureAD: &AzureADModel{ AuthorityUrl: types.StringValue("azure-authority"), @@ -458,11 +433,9 @@ func TestToUpdatePayload(t *testing.T) { }, { "idp-config-mismatch-local", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ AzureAD: &AzureADModel{ AuthorityUrl: types.StringValue("azure-authority"), @@ -478,11 +451,9 @@ func TestToUpdatePayload(t *testing.T) { }, { "missing-idp-config-oauth", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ Type: types.StringValue("oauth"), }, @@ -492,11 +463,9 @@ func TestToUpdatePayload(t *testing.T) { }, { "missing-idp-config-azuread", - &InstanceModel{ - Model: Model{ - Description: types.StringValue("test description"), - DisplayName: types.StringValue("displayName"), - }, + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ Type: types.StringValue("azuread"), }, From 9775d712220c36f9664f074635b14bfb3080af3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Thu, 4 Jun 2026 13:07:48 +0200 Subject: [PATCH 14/33] fix(dremio): Fixing acceptance tests. --- .../services/dremio/dremio_acc_test.go | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go index 8550bde99..94540aecb 100644 --- a/stackit/internal/services/dremio/dremio_acc_test.go +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -129,8 +129,8 @@ func TestDremioInstanceMin(t *testing.T) { dremioInstanceDataResource, "endpoints.arrow_flight", ), resource.TestCheckResourceAttrPair( - dremioInstanceResource, "catalog", - dremioInstanceDataResource, "catalog", + dremioInstanceResource, "endpoints.catalog", + dremioInstanceDataResource, "endpoints.catalog", ), resource.TestCheckResourceAttrPair( dremioInstanceResource, "endpoints.ui", @@ -162,10 +162,10 @@ func TestDremioInstanceMin(t *testing.T) { Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMin, ConfigVariables: testDremioInstanceConfigVarsMinUpdated(), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMinUpdated()["project_id"])), resource.TestCheckResourceAttr(dremioInstanceResource, "region", testutil.Region), - resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["display_name"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["authentication_type"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMinUpdated()["display_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMinUpdated()["authentication_type"])), resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), @@ -248,23 +248,23 @@ func TestDremioInstanceMax(t *testing.T) { // which is oauth for the config here. Hence why we test for the oauth value here. resource.TestCheckResourceAttrPair( dremioInstanceResource, "authentication.oauth.authority_url", - dremioInstanceDataResource, "authentication.authority_url", + dremioInstanceDataResource, "authentication.oauth.authority_url", ), resource.TestCheckResourceAttrPair( dremioInstanceResource, "authentication.oauth.client_id", - dremioInstanceDataResource, "authentication.client_id", + dremioInstanceDataResource, "authentication.oauth.client_id", ), resource.TestCheckResourceAttrPair( dremioInstanceResource, "authentication.oauth.scope", - dremioInstanceDataResource, "authentication.scope", + dremioInstanceDataResource, "authentication.oauth.scope", ), resource.TestCheckResourceAttrPair( dremioInstanceResource, "authentication.oauth.parameters", - dremioInstanceDataResource, "authentication.parameters", + dremioInstanceDataResource, "authentication.oauth.parameters", ), resource.TestCheckResourceAttrPair( dremioInstanceResource, "authentication.oauth.redirect_url", - dremioInstanceDataResource, "authentication.redirect_url", + dremioInstanceDataResource, "authentication.oauth.redirect_url", ), resource.TestCheckResourceAttrPair( @@ -272,8 +272,8 @@ func TestDremioInstanceMax(t *testing.T) { dremioInstanceDataResource, "endpoints.arrow_flight", ), resource.TestCheckResourceAttrPair( - dremioInstanceResource, "catalog", - dremioInstanceDataResource, "catalog", + dremioInstanceResource, "endpoints.catalog", + dremioInstanceDataResource, "endpoints.catalog", ), resource.TestCheckResourceAttrPair( dremioInstanceResource, "endpoints.ui", From e4335628339b2f944131b3084acfdbb40045aa54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Fri, 5 Jun 2026 14:48:24 +0200 Subject: [PATCH 15/33] fix(dremio): Make Dremio users actually creatable --- .../internal/services/dremio/user/resource.go | 17 +++++++++++------ stackit/provider.go | 2 ++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index 528733a64..35993d030 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -35,7 +35,7 @@ var ( ) type Model struct { - ID types.String `tfsdk:"id"` + Id types.String `tfsdk:"id"` ProjectId types.String `tfsdk:"project_id"` Region types.String `tfsdk:"region"` @@ -80,7 +80,7 @@ func (r *userResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe return } - var planModel Model + var planModel UserModel resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) if resp.Diagnostics.HasError() { return @@ -210,6 +210,7 @@ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, res stringplanmodifier.RequiresReplace(), }, }, + "timeouts": timeouts.AttributesAll(ctx), }, } } @@ -419,6 +420,14 @@ func mapFields(userResp *dremioSdk.DremioUserResponse, model *UserModel) error { } model.UserId = types.StringValue(userResp.Id) + + model.Id = utils.BuildInternalTerraformId( + model.ProjectId.ValueString(), + model.Region.ValueString(), + model.InstanceId.ValueString(), + model.Id.ValueString(), + ) + model.Description = types.StringPointerValue(userResp.Description) model.Email = types.StringValue(userResp.Email) model.FirstName = types.StringValue(userResp.FirstName) @@ -444,7 +453,3 @@ func toCreatePayload(model *UserModel) (*dremioSdk.CreateDremioUserPayload, erro return payload, nil } - -// func toUpdatePayload(model *UserModel) (*dremioSdk.UpdateDremioUserPayload, error) { -// return nil, nil -// } diff --git a/stackit/provider.go b/stackit/provider.go index 9d5b98dc0..ce9c386c3 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -31,6 +31,7 @@ import ( dnsRecordSet "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/recordset" dnsZone "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/zone" dremioInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/instance" + dremioUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/user" edgeCloudInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/instance" edgeCloudInstances "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/instances" edgeCloudKubeconfig "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/edgecloud/kubeconfig" @@ -773,6 +774,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { dnsZone.NewZoneResource, dnsRecordSet.NewRecordSetResource, dremioInstance.NewInstanceResource, + dremioUser.NewUserResource, edgeCloudInstance.NewInstanceResource, edgeCloudKubeconfig.NewKubeconfigResource, edgeCloudToken.NewTokenResource, From 768dfeca5504111db5eefcc0b9ad3705bd83bf34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Mon, 8 Jun 2026 10:32:21 +0200 Subject: [PATCH 16/33] feat(dremio): Adding user unit tests --- .../internal/services/dremio/user/resource.go | 67 +++++--- .../services/dremio/user/resource_test.go | 151 ++++++++++++++++++ 2 files changed, 195 insertions(+), 23 deletions(-) create mode 100644 stackit/internal/services/dremio/user/resource_test.go diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index 35993d030..b393de7c7 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -42,12 +42,19 @@ type Model struct { InstanceId types.String `tfsdk:"instance_id"` UserId types.String `tfsdk:"user_id"` + // Required Fields + Email types.String `tfsdk:"email"` + FirstName types.String `tfsdk:"first_name"` + LastName types.String `tfsdk:"last_name"` + Name types.String `tfsdk:"name"` + Password types.String `tfsdk:"password"` + + // Optional Fields Description types.String `tfsdk:"description"` - Email types.String `tfsdk:"email"` - FirstName types.String `tfsdk:"first_name"` - LastName types.String `tfsdk:"last_name"` - Name types.String `tfsdk:"name"` - Password types.String `tfsdk:"password"` + + // Read-only Fields + State types.String `tfsdk:"state"` + ErrorMessage types.String `tfsdk:"error_message"` } type UserModel struct { @@ -113,18 +120,20 @@ func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { descriptions := map[string]string{ - "main": "Manages a STACKIT Dremio instances user.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".", - "project_id": "STACKIT Project ID to which the resource is associated.", - "instance_id": "The Dremio instance ID.", - "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", - "user_id": "The Dremio user ID.", - "description": "The description of the user.", - "email": "The email address of the user.", - "first_name": "The first name of the user.", - "last_name": "The last name of the user.", - "name": "The username of the user.", - "password": "The password of the user. Only used for creation and updates. Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character.", + "main": "Manages a STACKIT Dremio instances user.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".", + "project_id": "STACKIT Project ID to which the resource is associated.", + "instance_id": "The Dremio instance ID.", + "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", + "user_id": "The Dremio user ID.", + "description": "The description of the user.", + "email": "The email address of the user.", + "first_name": "The first name of the user.", + "last_name": "The last name of the user.", + "name": "The username of the user.", + "password": "The password of the user. Only used for creation and updates. Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character.", + "state": "The current state of the resource.", + "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", } resp.Schema = schema.Schema{ @@ -210,6 +219,15 @@ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, res stringplanmodifier.RequiresReplace(), }, }, + "state": schema.StringAttribute{ + Description: descriptions["state"], + Computed: true, + }, + "error_message": schema.StringAttribute{ + Description: descriptions["error_message"], + Optional: true, + Computed: true, + }, "timeouts": timeouts.AttributesAll(ctx), }, } @@ -241,7 +259,7 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r ctx = tflog.SetField(ctx, "instance_id", instanceId) // prepare the payload struct for the create user request - payload, err := toCreatePayload(&model) + payload, err := toCreatePayload(&model.Model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Creating API payload: %v", err)) return @@ -271,7 +289,7 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r return } - err = mapFields(userResp, &model) + err = mapFields(userResp, &model.Model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Dremio user", fmt.Sprintf("Processing API payload: %v", err)) return @@ -326,7 +344,7 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp ctx = core.LogResponse(ctx) - err = mapFields(userResp, &model) + err = mapFields(userResp, &model.Model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading dremio user", fmt.Sprintf("Processing API payload: %v", err)) return @@ -411,7 +429,7 @@ func (r *userResource) ImportState(ctx context.Context, req resource.ImportState tflog.Info(ctx, "Dremio user state imported") } -func mapFields(userResp *dremioSdk.DremioUserResponse, model *UserModel) error { +func mapFields(userResp *dremioSdk.DremioUserResponse, model *Model) error { if userResp == nil { return fmt.Errorf("response input is nil") } @@ -425,7 +443,7 @@ func mapFields(userResp *dremioSdk.DremioUserResponse, model *UserModel) error { model.ProjectId.ValueString(), model.Region.ValueString(), model.InstanceId.ValueString(), - model.Id.ValueString(), + model.UserId.ValueString(), ) model.Description = types.StringPointerValue(userResp.Description) @@ -434,10 +452,13 @@ func mapFields(userResp *dremioSdk.DremioUserResponse, model *UserModel) error { model.LastName = types.StringValue(userResp.LastName) model.Name = types.StringValue(userResp.Name) + model.State = types.StringValue(userResp.State) + model.ErrorMessage = types.StringPointerValue(userResp.ErrorMessage) + return nil } -func toCreatePayload(model *UserModel) (*dremioSdk.CreateDremioUserPayload, error) { +func toCreatePayload(model *Model) (*dremioSdk.CreateDremioUserPayload, error) { if model == nil { return nil, fmt.Errorf("model input is nil") } diff --git a/stackit/internal/services/dremio/user/resource_test.go b/stackit/internal/services/dremio/user/resource_test.go new file mode 100644 index 000000000..5357a6f7c --- /dev/null +++ b/stackit/internal/services/dremio/user/resource_test.go @@ -0,0 +1,151 @@ +package dremio + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" +) + +func TestMapFields(t *testing.T) { + instanceId := uuid.New().String() + userId := uuid.New().String() + tests := []struct { + description string + state *Model + input *dremioSdk.DremioUserResponse + expected *Model + wantErr bool + }{ + { + "all_fields_filled", + &Model{ + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + InstanceId: types.StringValue(instanceId), + }, + &dremioSdk.DremioUserResponse{ + Id: userId, + Description: utils.Ptr("test description"), + Email: "test-user@example.com", + FirstName: "Test", + LastName: "User", + Name: "testUser", + State: "active", + ErrorMessage: utils.Ptr("test error message"), + }, + &Model{ + Id: types.StringValue(fmt.Sprintf("pid,rid,%s,%s", instanceId, userId)), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + InstanceId: types.StringValue(instanceId), + UserId: types.StringValue(userId), + Description: types.StringPointerValue(utils.Ptr("test description")), + Email: types.StringValue("test-user@example.com"), + FirstName: types.StringValue("Test"), + LastName: types.StringValue("User"), + Name: types.StringValue("testUser"), + State: types.StringValue("active"), + ErrorMessage: types.StringPointerValue(utils.Ptr("test error message")), + }, + false, + }, + { + "nil response", + &Model{ + Region: types.StringValue("rid"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue(instanceId), + }, + nil, + &Model{ + Id: types.StringValue(fmt.Sprintf("pid,rid,%s,", instanceId)), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + InstanceId: types.StringValue(instanceId), + }, + true, + }, + { + "nil state", + nil, + &dremioSdk.DremioUserResponse{}, + nil, + true, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + err := mapFields(tt.input, tt.state) + if (err != nil) != tt.wantErr { + t.Errorf("mapFields error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if diff := cmp.Diff(tt.expected, tt.state); diff != "" { + t.Errorf("mapping mismatch (-want +got):\n%s", diff) + } + } + }) + } +} + +func TestToCreatePayload(t *testing.T) { + instanceId := uuid.New().String() + tests := []struct { + description string + state *Model + expected *dremioSdk.CreateDremioUserPayload + wantErr bool + }{ + { + "success", + &Model{ + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + InstanceId: types.StringValue(instanceId), + + Email: types.StringValue("example@stackit.cloud"), + Description: types.StringValue("test description"), + FirstName: types.StringValue("Test"), + LastName: types.StringValue("User"), + Name: types.StringValue("testUser"), + Password: types.StringValue("test-password"), + }, + &dremioSdk.CreateDremioUserPayload{ + Email: "example@stackit.cloud", + Description: utils.Ptr("test description"), + FirstName: "Test", + LastName: "User", + Name: "testUser", + Password: "test-password", + }, + false, + }, + { + "nil model", + nil, + nil, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + payload, err := toCreatePayload(tt.state) + if (err != nil) != tt.wantErr { + t.Errorf("toCreatePayload error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if diff := cmp.Diff(tt.expected, payload); diff != "" { + t.Errorf("toCreatePayload mismatch (-want +got):\n%s", diff) + } + } + }) + } +} From 6ff89afd400dd6e69d33c6c7381475bd2cc63c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Mon, 8 Jun 2026 13:36:40 +0200 Subject: [PATCH 17/33] feat(dremio): Adding Dremio user data source. Moving Password field to UserModel, so mapFields() can be used both for the resource and the data source. --- .../services/dremio/user/datasource.go | 185 ++++++++++++++++++ .../internal/services/dremio/user/resource.go | 10 +- .../services/dremio/user/resource_test.go | 24 +-- stackit/provider.go | 1 + 4 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 stackit/internal/services/dremio/user/datasource.go diff --git a/stackit/internal/services/dremio/user/datasource.go b/stackit/internal/services/dremio/user/datasource.go new file mode 100644 index 000000000..419484945 --- /dev/null +++ b/stackit/internal/services/dremio/user/datasource.go @@ -0,0 +1,185 @@ +package dremio + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + + dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" + + dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" +) + +var ( + _ datasource.DataSource = &userDataSource{} + _ datasource.DataSourceWithConfigure = &userDataSource{} +) + +type UserDataSourceModel struct { + Model +} + +type userDataSource struct { + client *dremioSdk.APIClient +} + +func NewInstanceDataSource() datasource.DataSource { + return &userDataSource{} +} + +// Metadata should return the full name of the data source, such as +// examplecloud_thing. +func (d *userDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dremio_user" +} + +// Configure enables provider-level data or clients to be set in the +// provider-defined DataSource type. It is separately executed for each +// ReadDataSource RPC. +func (d *userDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := dremioUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + d.client = apiClient + tflog.Info(ctx, "Dremio user client configured for data source") +} + +func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + descriptions := map[string]string{ + "main": "Manages a STACKIT Dremio instances user.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".", + "project_id": "STACKIT Project ID to which the resource is associated.", + "instance_id": "The Dremio instance ID.", + "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", + "user_id": "The Dremio user ID.", + "description": "The description of the user.", + "email": "The email address of the user.", + "first_name": "The first name of the user.", + "last_name": "The last name of the user.", + "name": "The username of the user.", + "state": "The current state of the resource.", + "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", + } + + resp.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + }, + "instance_id": schema.StringAttribute{ + Description: descriptions["instance_id"], + Required: true, + }, + "region": schema.StringAttribute{ + Description: descriptions["region"], + Required: true, + }, + "user_id": schema.StringAttribute{ + Description: descriptions["user_id"], + Required: true, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + Computed: true, + }, + "email": schema.StringAttribute{ + Description: descriptions["email"], + Computed: true, + }, + "first_name": schema.StringAttribute{ + Description: descriptions["first_name"], + Computed: true, + }, + "last_name": schema.StringAttribute{ + Description: descriptions["last_name"], + Computed: true, + }, + "name": schema.StringAttribute{ + Description: descriptions["name"], + Computed: true, + }, + "state": schema.StringAttribute{ + Description: descriptions["state"], + Computed: true, + }, + "error_message": schema.StringAttribute{ + Description: descriptions["error_message"], + Optional: true, + Computed: true, + }, + }, + } +} + +// Read is called when the provider must read data source values in +// order to update state. Config values should be read from the +// ReadRequest and new state values set on the ReadResponse. +func (d *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + // nolint:gocritic // function signature required by Terraform + var model UserDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() + instanceId := model.InstanceId.ValueString() + userId := model.UserId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "user_id", userId) + + userResp, err := d.client.DefaultAPI.GetDremioUser(ctx, projectId, region, instanceId, userId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Dremio user with ID %s not found in project %s and region %s in instance %s", userId, projectId, region, instanceId)) + return + } + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Dremio user", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(userResp, &model.Model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Dremio user", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Dremio user read") +} diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index b393de7c7..fd88f8e4e 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" @@ -22,6 +23,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/oapierror" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" + dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait/wait" @@ -47,7 +49,6 @@ type Model struct { FirstName types.String `tfsdk:"first_name"` LastName types.String `tfsdk:"last_name"` Name types.String `tfsdk:"name"` - Password types.String `tfsdk:"password"` // Optional Fields Description types.String `tfsdk:"description"` @@ -60,6 +61,9 @@ type Model struct { type UserModel struct { Model + // Required Fields + Password types.String `tfsdk:"password"` + Timeouts timeouts.Value `tfsdk:"timeouts"` } @@ -259,7 +263,7 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r ctx = tflog.SetField(ctx, "instance_id", instanceId) // prepare the payload struct for the create user request - payload, err := toCreatePayload(&model.Model) + payload, err := toCreatePayload(&model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Creating API payload: %v", err)) return @@ -458,7 +462,7 @@ func mapFields(userResp *dremioSdk.DremioUserResponse, model *Model) error { return nil } -func toCreatePayload(model *Model) (*dremioSdk.CreateDremioUserPayload, error) { +func toCreatePayload(model *UserModel) (*dremioSdk.CreateDremioUserPayload, error) { if model == nil { return nil, fmt.Errorf("model input is nil") } diff --git a/stackit/internal/services/dremio/user/resource_test.go b/stackit/internal/services/dremio/user/resource_test.go index 5357a6f7c..ab4b6dade 100644 --- a/stackit/internal/services/dremio/user/resource_test.go +++ b/stackit/internal/services/dremio/user/resource_test.go @@ -98,23 +98,25 @@ func TestToCreatePayload(t *testing.T) { instanceId := uuid.New().String() tests := []struct { description string - state *Model + state *UserModel expected *dremioSdk.CreateDremioUserPayload wantErr bool }{ { "success", - &Model{ - ProjectId: types.StringValue("pid"), - Region: types.StringValue("rid"), - InstanceId: types.StringValue(instanceId), + &UserModel{ + Model: Model{ + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + InstanceId: types.StringValue(instanceId), - Email: types.StringValue("example@stackit.cloud"), - Description: types.StringValue("test description"), - FirstName: types.StringValue("Test"), - LastName: types.StringValue("User"), - Name: types.StringValue("testUser"), - Password: types.StringValue("test-password"), + Email: types.StringValue("example@stackit.cloud"), + Description: types.StringValue("test description"), + FirstName: types.StringValue("Test"), + LastName: types.StringValue("User"), + Name: types.StringValue("testUser"), + }, + Password: types.StringValue("test-password"), }, &dremioSdk.CreateDremioUserPayload{ Email: "example@stackit.cloud", diff --git a/stackit/provider.go b/stackit/provider.go index ce9c386c3..0470af21a 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -671,6 +671,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource dnsZone.NewZoneDataSource, dnsRecordSet.NewRecordSetDataSource, dremioInstance.NewInstanceDataSource, + dremioUser.NewUserDataSource, edgeCloudInstances.NewInstancesDataSource, edgeCloudPlans.NewPlansDataSource, gitInstance.NewGitDataSource, From 8fb1781410f92d708a095affd532187d9ec267a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Mon, 8 Jun 2026 13:40:01 +0200 Subject: [PATCH 18/33] feat(dremio): Adding Dremio user to acceptance tests (draft). --- .../services/dremio/dremio_acc_test.go | 223 ++++++++++++++---- .../services/dremio/instance/datasource.go | 2 +- .../services/dremio/testdata/resource-max.tf | 108 ++++++--- .../services/dremio/testdata/resource-min.tf | 50 +++- 4 files changed, 291 insertions(+), 92 deletions(-) diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go index 94540aecb..536d09bdf 100644 --- a/stackit/internal/services/dremio/dremio_acc_test.go +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -15,6 +15,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/utils" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait/wait" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" ) @@ -28,21 +29,30 @@ var resourceDremioInstanceMax string const dremioInstanceResource = "stackit_dremio_instance.example" const dremioInstanceDataResource = "data.stackit_dremio_instance.example" -var testDremioInstanceConfigVarsMin = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "region": config.StringVariable(testutil.Region), +const dremioUserResource = "stackit_dremio_user.example" +const dremioUserDataResource = "data.stackit_dremio_user.example" + +var testDremioConfigVarsMin = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + //Instance "display_name": config.StringVariable("dremioMinInstance"), "authentication_type": config.StringVariable("local-only"), + //User + "email": config.StringVariable("minInstanceUser@example.com"), + "first_name": config.StringVariable("Min"), + "last_name": config.StringVariable("InstanceUser"), + "name": config.StringVariable("minInstanceUser"), + "password": config.StringVariable("minInstanceUserPassword!23"), } -var testDremioInstanceConfigVarsMax = config.Variables{ - "project_id": config.StringVariable(testutil.ProjectId), - "region": config.StringVariable("eu01"), - "display_name": config.StringVariable("dremioMaxInstance"), - "description": config.StringVariable("description"), - - "authentication_type": config.StringVariable("oauth"), - +var testDremioConfigVarsMax = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable("eu01"), + //Instance + "display_name": config.StringVariable("dremioMaxInstance"), + "description": config.StringVariable("description"), + "authentication_type": config.StringVariable("oauth"), "authentication_oauth_authority_url": config.StringVariable("oauth-authority-url"), "authentication_oauth_client_id": config.StringVariable("oauth-client-id"), "authentication_oauth_client_secret": config.StringVariable("oauth-client-secret"), @@ -50,18 +60,25 @@ var testDremioInstanceConfigVarsMax = config.Variables{ "authentication_oauth_scope": config.StringVariable("oauth-scope"), "authentication_oauth_parameter_name": config.StringVariable("oauth-parameter-name"), "authentication_oauth_parameter_value": config.StringVariable("oauth-parameter-value"), + //User + "email": config.StringVariable("maxInstanceUser@example.com"), + "user_description": config.StringVariable("Max Instance User Description"), + "first_name": config.StringVariable("Max"), + "last_name": config.StringVariable("InstanceUser"), + "name": config.StringVariable("maxInstanceUser"), + "password": config.StringVariable("maxInstanceUserPassword!23"), } func testDremioInstanceConfigVarsMinUpdated() config.Variables { - tempConfig := make(config.Variables, len(testDremioInstanceConfigVarsMin)) - maps.Copy(tempConfig, testDremioInstanceConfigVarsMin) + tempConfig := make(config.Variables, len(testDremioConfigVarsMin)) + maps.Copy(tempConfig, testDremioConfigVarsMin) tempConfig["display_name"] = config.StringVariable("dremioMinInstanceUpd") return tempConfig } func testDremioInstanceConfigVarsMaxUpdated() config.Variables { - tempConfig := make(config.Variables, len(testDremioInstanceConfigVarsMax)) - maps.Copy(tempConfig, testDremioInstanceConfigVarsMax) + tempConfig := make(config.Variables, len(testDremioConfigVarsMax)) + maps.Copy(tempConfig, testDremioConfigVarsMax) tempConfig["display_name"] = config.StringVariable("dremioMaxInstanceUpd") tempConfig["description"] = config.StringVariable("description-upd") @@ -83,26 +100,44 @@ func TestDremioInstanceMin(t *testing.T) { // 1) Creation { Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceDremioInstanceMin, - ConfigVariables: testDremioInstanceConfigVarsMin, + ConfigVariables: testDremioConfigVarsMin, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["project_id"])), + // Instance + resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioConfigVarsMin["project_id"])), resource.TestCheckResourceAttr(dremioInstanceResource, "region", testutil.Region), - resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["display_name"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMin["authentication_type"])), - resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), + + resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioConfigVarsMin["display_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioConfigVarsMin["authentication_type"])), + resource.TestCheckResourceAttrSet(dremioInstanceResource, "state"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), + + // User + resource.TestCheckResourceAttr(dremioUserResource, "project_id", testutil.ConvertConfigVariable(testDremioConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr(dremioUserResource, "region", testutil.Region), + resource.TestCheckResourceAttrSet(dremioUserResource, "instance_id"), + resource.TestCheckResourceAttrSet(dremioUserResource, "user_id"), + resource.TestCheckResourceAttrSet(dremioUserResource, "id"), + + resource.TestCheckResourceAttr(dremioUserResource, "email", testutil.ConvertConfigVariable(testDremioConfigVarsMin["email"])), + resource.TestCheckResourceAttr(dremioUserResource, "first_name", testutil.ConvertConfigVariable(testDremioConfigVarsMin["first_name"])), + resource.TestCheckResourceAttr(dremioUserResource, "last_name", testutil.ConvertConfigVariable(testDremioConfigVarsMin["last_name"])), + resource.TestCheckResourceAttr(dremioUserResource, "name", testutil.ConvertConfigVariable(testDremioConfigVarsMin["name"])), + resource.TestCheckResourceAttr(dremioUserResource, "password", testutil.ConvertConfigVariable(testDremioConfigVarsMin["password"])), + + resource.TestCheckResourceAttrSet(dremioUserResource, "state"), ), }, // 2) Data Source { Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMin, - ConfigVariables: testDremioInstanceConfigVarsMin, + ConfigVariables: testDremioConfigVarsMin, Check: resource.ComposeAggregateTestCheckFunc( + // Instance resource.TestCheckResourceAttrPair( dremioInstanceResource, "project_id", dremioInstanceDataResource, "project_id", @@ -115,7 +150,6 @@ func TestDremioInstanceMin(t *testing.T) { dremioInstanceResource, "instance_id", dremioInstanceDataResource, "instance_id", ), - resource.TestCheckResourceAttrPair( dremioInstanceResource, "display_name", dremioInstanceDataResource, "display_name", @@ -136,11 +170,44 @@ func TestDremioInstanceMin(t *testing.T) { dremioInstanceResource, "endpoints.ui", dremioInstanceDataResource, "endpoints.ui", ), + // User + resource.TestCheckResourceAttrPair( + dremioUserResource, "project_id", + dremioUserDataResource, "project_id", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "region", + dremioUserDataResource, "region", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "instance_id", + dremioUserDataResource, "instance_id", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "user_id", + dremioUserDataResource, "user_id", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "email", + dremioUserDataResource, "email", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "first_name", + dremioUserDataResource, "first_name", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "last_name", + dremioUserDataResource, "last_name", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "name", + dremioUserDataResource, "name", + ), ), }, // 3) Import { - ConfigVariables: testDremioInstanceConfigVarsMin, + ConfigVariables: testDremioConfigVarsMin, ResourceName: dremioInstanceResource, ImportState: true, ImportStateVerify: true, @@ -157,6 +224,28 @@ func TestDremioInstanceMin(t *testing.T) { return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId), nil }, }, + { + ConfigVariables: testDremioConfigVarsMin, + ResourceName: dremioUserResource, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources[dremioUserResource] + if !ok { + return "", fmt.Errorf("couldn't find resource %s", dremioUserResource) + } + instanceId, ok := r.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute instanceId") + } + userId, ok := r.Primary.Attributes["user_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute userId") + } + + return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId, userId), nil + }, + }, // 4) Update { Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMin, @@ -186,24 +275,25 @@ func TestDremioInstanceMax(t *testing.T) { Steps: []resource.TestStep{ // 1) Creation { - ConfigVariables: testDremioInstanceConfigVarsMax, + ConfigVariables: testDremioConfigVarsMax, Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceDremioInstanceMax, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["project_id"])), + // Instance + resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioConfigVarsMax["project_id"])), resource.TestCheckResourceAttr(dremioInstanceResource, "region", testutil.Region), - resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["display_name"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "description", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["description"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["display_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "description", testutil.ConvertConfigVariable(testDremioConfigVarsMax["description"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_type"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioConfigVarsMax["authentication_type"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.authority_url", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_authority_url"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.client_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_client_id"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.client_secret", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_client_secret"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.authority_url", testutil.ConvertConfigVariable(testDremioConfigVarsMax["authentication_oauth_authority_url"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.client_id", testutil.ConvertConfigVariable(testDremioConfigVarsMax["authentication_oauth_client_id"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.client_secret", testutil.ConvertConfigVariable(testDremioConfigVarsMax["authentication_oauth_client_secret"])), resource.TestCheckResourceAttrSet(dremioInstanceResource, "authentication.oauth.redirect_url"), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.jwt_claims.user_name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_client_jwt_claims_user_name"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.scope", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_scope"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.parameters.0.name", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_parameter_name"])), - resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.parameters.0.value", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMax["authentication_oauth_parameter_value"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.jwt_claims.user_name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["authentication_oauth_client_jwt_claims_user_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.scope", testutil.ConvertConfigVariable(testDremioConfigVarsMax["authentication_oauth_scope"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.parameters.0.name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["authentication_oauth_parameter_name"])), + resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.oauth.parameters.0.value", testutil.ConvertConfigVariable(testDremioConfigVarsMax["authentication_oauth_parameter_value"])), resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), @@ -211,13 +301,30 @@ func TestDremioInstanceMax(t *testing.T) { resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), + + // User + resource.TestCheckResourceAttr(dremioUserResource, "project_id", testutil.ConvertConfigVariable(testDremioConfigVarsMax["project_id"])), + resource.TestCheckResourceAttr(dremioUserResource, "region", testutil.Region), + resource.TestCheckResourceAttrSet(dremioUserResource, "instance_id"), + resource.TestCheckResourceAttrSet(dremioUserResource, "user_id"), + resource.TestCheckResourceAttrSet(dremioUserResource, "id"), + + resource.TestCheckResourceAttr(dremioUserResource, "email", testutil.ConvertConfigVariable(testDremioConfigVarsMax["email"])), + resource.TestCheckResourceAttr(dremioUserResource, "user_description", testutil.ConvertConfigVariable(testDremioConfigVarsMax["user_description"])), + resource.TestCheckResourceAttr(dremioUserResource, "first_name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["first_name"])), + resource.TestCheckResourceAttr(dremioUserResource, "last_name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["last_name"])), + resource.TestCheckResourceAttr(dremioUserResource, "name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["name"])), + resource.TestCheckResourceAttr(dremioUserResource, "password", testutil.ConvertConfigVariable(testDremioConfigVarsMax["password"])), + + resource.TestCheckResourceAttrSet(dremioUserResource, "state"), ), }, // 2) Data Source { Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMax, - ConfigVariables: testDremioInstanceConfigVarsMax, + ConfigVariables: testDremioConfigVarsMax, Check: resource.ComposeAggregateTestCheckFunc( + // Instance resource.TestCheckResourceAttrPair( dremioInstanceResource, "project_id", dremioInstanceDataResource, "project_id", @@ -230,7 +337,6 @@ func TestDremioInstanceMax(t *testing.T) { dremioInstanceResource, "instance_id", dremioInstanceDataResource, "instance_id", ), - resource.TestCheckResourceAttrPair( dremioInstanceResource, "display_name", dremioInstanceDataResource, "display_name", @@ -239,13 +345,10 @@ func TestDremioInstanceMax(t *testing.T) { dremioInstanceResource, "description", dremioInstanceDataResource, "description", ), - resource.TestCheckResourceAttrPair( dremioInstanceResource, "authentication.type", dremioInstanceDataResource, "authentication.type", ), - // Authentication on the data source only shows the currently set IDP config, - // which is oauth for the config here. Hence why we test for the oauth value here. resource.TestCheckResourceAttrPair( dremioInstanceResource, "authentication.oauth.authority_url", dremioInstanceDataResource, "authentication.oauth.authority_url", @@ -266,7 +369,6 @@ func TestDremioInstanceMax(t *testing.T) { dremioInstanceResource, "authentication.oauth.redirect_url", dremioInstanceDataResource, "authentication.oauth.redirect_url", ), - resource.TestCheckResourceAttrPair( dremioInstanceResource, "endpoints.arrow_flight", dremioInstanceDataResource, "endpoints.arrow_flight", @@ -279,11 +381,48 @@ func TestDremioInstanceMax(t *testing.T) { dremioInstanceResource, "endpoints.ui", dremioInstanceDataResource, "endpoints.ui", ), + // User + resource.TestCheckResourceAttrPair( + dremioUserResource, "project_id", + dremioUserDataResource, "project_id", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "region", + dremioUserDataResource, "region", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "instance_id", + dremioUserDataResource, "instance_id", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "user_id", + dremioUserDataResource, "user_id", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "email", + dremioUserDataResource, "email", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "user_description", + dremioUserDataResource, "user_description", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "first_name", + dremioUserDataResource, "first_name", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "last_name", + dremioUserDataResource, "last_name", + ), + resource.TestCheckResourceAttrPair( + dremioUserResource, "name", + dremioUserDataResource, "name", + ), ), }, // 3) Import { - ConfigVariables: testDremioInstanceConfigVarsMax, + ConfigVariables: testDremioConfigVarsMax, ResourceName: dremioInstanceResource, ImportState: true, ImportStateVerify: true, diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go index 52591200c..f357c9963 100644 --- a/stackit/internal/services/dremio/instance/datasource.go +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -269,7 +269,7 @@ func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques var oapiErr *oapierror.GenericOpenAPIError if errors.As(err, &oapiErr) { if oapiErr.StatusCode == http.StatusNotFound { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Dremio instance with ID %s not found in project %s and region %s", instanceId, projectId, region)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Dremio instance with ID %s not found in project %s and region %s", instanceId, projectId, region)) return } } diff --git a/stackit/internal/services/dremio/testdata/resource-max.tf b/stackit/internal/services/dremio/testdata/resource-max.tf index 57943490b..7966c246d 100644 --- a/stackit/internal/services/dremio/testdata/resource-max.tf +++ b/stackit/internal/services/dremio/testdata/resource-max.tf @@ -1,13 +1,13 @@ -variable "project_id"{} +variable "project_id" {} variable "region" {} + +// Instance Variables variable "display_name" {} variable "description" {} -// authentication variable "authentication_type" {} -// oauth variable "authentication_oauth_authority_url" {} variable "authentication_oauth_client_id" {} variable "authentication_oauth_client_secret" {} @@ -16,45 +16,75 @@ variable "authentication_oauth_scope" {} variable "authentication_oauth_parameter_name" {} variable "authentication_oauth_parameter_value" {} -// azuread -variable "authentication_type_azuread" {default=null} -variable "authentication_azuread_authority_url" {default=null} -variable "authentication_azuread_client_id" {default=null} -variable "authentication_azuread_client_secret" {default=null} +variable "authentication_type_azuread" { default = null } +variable "authentication_azuread_authority_url" { default = null } +variable "authentication_azuread_client_id" { default = null } +variable "authentication_azuread_client_secret" { default = null } + + +// User Variables +variable "email" {} +variable "user_description" {} +variable "first_name" {} +variable "last_name" {} +variable "name" {} +variable "password" {} +// Instance Resources resource "stackit_dremio_instance" "example" { - project_id = var.project_id - region = var.region - display_name = var.display_name - description = var.description - authentication = { - type = var.authentication_type - - oauth = var.authentication_type == "oauth" ? { - authority_url = var.authentication_oauth_authority_url - client_id = var.authentication_oauth_client_id - client_secret = var.authentication_oauth_client_secret - jwt_claims = { - user_name = var.authentication_oauth_client_jwt_claims_user_name - } - scope = var.authentication_oauth_scope - parameters = [ - { - "name": var.authentication_oauth_parameter_name, - "value": var.authentication_oauth_parameter_value - } - ] - } : null - azuread = var.authentication_type == "azuread" ? { - authority_url = var.authentication_azuread_authority_url - client_id = var.authentication_azuread_client_id - client_secret = var.authentication_azuread_client_secret - } : null - } + project_id = var.project_id + region = var.region + display_name = var.display_name + description = var.description + authentication = { + type = var.authentication_type + + oauth = var.authentication_type == "oauth" ? { + authority_url = var.authentication_oauth_authority_url + client_id = var.authentication_oauth_client_id + client_secret = var.authentication_oauth_client_secret + jwt_claims = { + user_name = var.authentication_oauth_client_jwt_claims_user_name + } + scope = var.authentication_oauth_scope + parameters = [ + { + "name" : var.authentication_oauth_parameter_name, + "value" : var.authentication_oauth_parameter_value + } + ] + } : null + azuread = var.authentication_type == "azuread" ? { + authority_url = var.authentication_azuread_authority_url + client_id = var.authentication_azuread_client_id + client_secret = var.authentication_azuread_client_secret + } : null + } } data "stackit_dremio_instance" "example" { - project_id = var.project_id - region = var.region - instance_id = stackit_dremio_instance.example.instance_id + project_id = var.project_id + region = var.region + instance_id = stackit_dremio_instance.example.instance_id +} + +// User Resources +resource "stackit_dremio_user" "example" { + project_id = var.project_id + region = var.region + instance_id = stackit_dremio_instance.example.instance_id + + email = var.email + description = var.user_description + first_name = var.first_name + last_name = var.last_name + name = var.name + password = var.password +} + +data "stackit_dremio_user" "example" { + project_id = var.project_id + region = var.region + instance_id = stackit_dremio_instance.example.instance_id + user_id = stackit_dremio_user.example.user_id } \ No newline at end of file diff --git a/stackit/internal/services/dremio/testdata/resource-min.tf b/stackit/internal/services/dremio/testdata/resource-min.tf index 5a3bf4fb3..964170819 100644 --- a/stackit/internal/services/dremio/testdata/resource-min.tf +++ b/stackit/internal/services/dremio/testdata/resource-min.tf @@ -1,20 +1,50 @@ -variable "project_id"{} +variable "project_id" {} variable "region" {} + +// Instance Variables variable "display_name" {} variable "authentication_type" {} +// User Variables +variable "email" {} +variable "first_name" {} +variable "last_name" {} +variable "name" {} +variable "password" {} + +// Instance Resources resource "stackit_dremio_instance" "example" { - project_id = var.project_id - region = var.region - display_name = var.display_name - authentication = { - type = var.authentication_type - } + project_id = var.project_id + region = var.region + display_name = var.display_name + authentication = { + type = var.authentication_type + } } data "stackit_dremio_instance" "example" { - project_id = var.project_id - region = var.region - instance_id = stackit_dremio_instance.example.instance_id + project_id = var.project_id + region = var.region + instance_id = stackit_dremio_instance.example.instance_id +} + +// User Resources +resource "stackit_dremio_user" "example" { + project_id = var.project_id + region = var.region + instance_id = stackit_dremio_instance.example.instance_id + + email = var.email + first_name = var.first_name + last_name = var.last_name + name = var.name + password = var.password +} + +data "stackit_dremio_user" "example" { + project_id = var.project_id + region = var.region + instance_id = stackit_dremio_instance.example.instance_id + user_id = stackit_dremio_user.example.user_id } \ No newline at end of file From e54f25b65cee6e1936823383cf1c233bf0afe28b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Tue, 9 Jun 2026 10:18:26 +0200 Subject: [PATCH 19/33] fix(dremio): Implementing review suggestions for datasources. --- .../services/dremio/instance/datasource.go | 30 ++++++++++--------- .../services/dremio/user/datasource.go | 30 ++++++++++--------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go index f357c9963..75a9ce9f9 100644 --- a/stackit/internal/services/dremio/instance/datasource.go +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -2,17 +2,16 @@ package dremio import ( "context" - "errors" "fmt" "net/http" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" @@ -29,15 +28,15 @@ type InstanceDataSourceModel struct { } type instanceDataSource struct { - client *dremioSdk.APIClient + client *dremioSdk.APIClient + providerData core.ProviderData } func NewInstanceDataSource() datasource.DataSource { return &instanceDataSource{} } -// Metadata should return the full name of the data source, such as -// examplecloud_thing. +// Metadata returns the data source type name. func (d *instanceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_dremio_instance" } @@ -258,7 +257,7 @@ func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() - region := model.Region.ValueString() + region := d.providerData.GetRegionWithOverride(model.Region) instanceId := model.InstanceId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) @@ -266,14 +265,17 @@ func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques instanceResp, err := d.client.DefaultAPI.GetDremioInstance(ctx, projectId, region, instanceId).Execute() if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { - if oapiErr.StatusCode == http.StatusNotFound { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Dremio instance with ID %s not found in project %s and region %s", instanceId, projectId, region)) - return - } - } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Dremio instance", fmt.Sprintf("Calling API: %v", err)) + utils.LogError( + ctx, + &resp.Diagnostics, + err, + "Error reading Dremio instance", + fmt.Sprintf("Dremio instance with ID %q does not exist in project %q and region %q", instanceId, projectId, region), + map[int]string{ + http.StatusNotFound: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId), + }, + ) + resp.State.RemoveResource(ctx) return } diff --git a/stackit/internal/services/dremio/user/datasource.go b/stackit/internal/services/dremio/user/datasource.go index 419484945..445450961 100644 --- a/stackit/internal/services/dremio/user/datasource.go +++ b/stackit/internal/services/dremio/user/datasource.go @@ -2,17 +2,16 @@ package dremio import ( "context" - "errors" "fmt" "net/http" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" @@ -29,15 +28,15 @@ type UserDataSourceModel struct { } type userDataSource struct { - client *dremioSdk.APIClient + client *dremioSdk.APIClient + providerData core.ProviderData } func NewInstanceDataSource() datasource.DataSource { return &userDataSource{} } -// Metadata should return the full name of the data source, such as -// examplecloud_thing. +// Metadata returns the data source type name. func (d *userDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_dremio_user" } @@ -147,7 +146,7 @@ func (d *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() - region := model.Region.ValueString() + region := d.providerData.GetRegionWithOverride(model.Region) instanceId := model.InstanceId.ValueString() userId := model.UserId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) @@ -157,14 +156,17 @@ func (d *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r userResp, err := d.client.DefaultAPI.GetDremioUser(ctx, projectId, region, instanceId, userId).Execute() if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - if errors.As(err, &oapiErr) { - if oapiErr.StatusCode == http.StatusNotFound { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Dremio user with ID %s not found in project %s and region %s in instance %s", userId, projectId, region, instanceId)) - return - } - } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Dremio user", fmt.Sprintf("Calling API: %v", err)) + utils.LogError( + ctx, + &resp.Diagnostics, + err, + "Error reading Dremio user", + fmt.Sprintf("Dremio user with ID %q does not exist in project %q and region %q in instance %q", userId, projectId, region, instanceId), + map[int]string{ + http.StatusNotFound: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId), + }, + ) + resp.State.RemoveResource(ctx) return } From 0a5b63e90697957450dcfe078745011a0ee715a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Tue, 9 Jun 2026 12:32:19 +0200 Subject: [PATCH 20/33] feat(dremio): Updating dremio SDK containing enums. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1c14b81fc..b10a60da3 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/cdn v1.16.0 github.com/stackitcloud/stackit-sdk-go/services/certificates v1.7.0 github.com/stackitcloud/stackit-sdk-go/services/dns v0.20.2 - github.com/stackitcloud/stackit-sdk-go/services/dremio v0.1.0 + github.com/stackitcloud/stackit-sdk-go/services/dremio v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/edge v0.11.0 github.com/stackitcloud/stackit-sdk-go/services/git v0.13.0 github.com/stackitcloud/stackit-sdk-go/services/iaas v1.12.0 diff --git a/go.sum b/go.sum index 26f2e5439..88d84aa12 100644 --- a/go.sum +++ b/go.sum @@ -680,8 +680,8 @@ github.com/stackitcloud/stackit-sdk-go/services/certificates v1.7.0 h1:J7BVVHjRT github.com/stackitcloud/stackit-sdk-go/services/certificates v1.7.0/go.mod h1:eJpB3/pukz+KzVPVHQ4g3DVtQkxGga18VbFBhq9ugdY= github.com/stackitcloud/stackit-sdk-go/services/dns v0.20.2 h1:nMJRg1dKioOlMwXJnZZgIRwfTWYCksVA9GyfAVmib1g= github.com/stackitcloud/stackit-sdk-go/services/dns v0.20.2/go.mod h1:FiYSv3D9rzgEVzi8Mpq5oYZBosrasa5uUYqVdEIbM1U= -github.com/stackitcloud/stackit-sdk-go/services/dremio v0.1.0 h1:yNFIU1+1dA2uK8ERdBb1Ut74Kt2szn4qgelBbM93JXA= -github.com/stackitcloud/stackit-sdk-go/services/dremio v0.1.0/go.mod h1:iMoiM8fM1mXC1Nz8FBiiQ08Yh+0C3yN0GPCdAbOlRXo= +github.com/stackitcloud/stackit-sdk-go/services/dremio v0.3.0 h1:LKreLAR425+EMYbKrPIAzeoTEQlDF8EpIMTVBpOhjmA= +github.com/stackitcloud/stackit-sdk-go/services/dremio v0.3.0/go.mod h1:iMoiM8fM1mXC1Nz8FBiiQ08Yh+0C3yN0GPCdAbOlRXo= github.com/stackitcloud/stackit-sdk-go/services/edge v0.11.0 h1:/JUxaJSGmg+PRj90e4fngWkXNQkRKHOYpVykJ3zoy7w= github.com/stackitcloud/stackit-sdk-go/services/edge v0.11.0/go.mod h1:Ylse6gqGJtsd5TVmvha+hoLd1QQHLKvhY5dO15+q5kg= github.com/stackitcloud/stackit-sdk-go/services/git v0.13.0 h1:BdamSnGYhDkDqUWQQcJ8Kqik90laTK1IlG5CQqyLVgA= From 0bec9cae27221a10f7b49aadcabb506c34671f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Tue, 9 Jun 2026 12:35:42 +0200 Subject: [PATCH 21/33] feat(dremio): Implementing review feedback from PR. - Propagating region to mapping methods. - Using region override from provider. - Ordering schemas. - Moving descriptions to packet level. - Additional safety checks for nil. - Additional unit tests for auth-config. - Making use of newly added enums. --- .../services/dremio/dremio_acc_test.go | 8 +- .../services/dremio/instance/datasource.go | 38 +-- .../services/dremio/instance/resource.go | 240 +++++++++--------- .../services/dremio/instance/resource_test.go | 134 ++++++++-- .../services/dremio/user/datasource.go | 32 +-- .../internal/services/dremio/user/resource.go | 94 +++---- .../services/dremio/user/resource_test.go | 2 +- 7 files changed, 297 insertions(+), 251 deletions(-) diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go index 536d09bdf..41b73aa56 100644 --- a/stackit/internal/services/dremio/dremio_acc_test.go +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -14,7 +14,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/utils" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" - dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait/wait" + dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" @@ -37,7 +37,7 @@ var testDremioConfigVarsMin = config.Variables{ "region": config.StringVariable(testutil.Region), //Instance "display_name": config.StringVariable("dremioMinInstance"), - "authentication_type": config.StringVariable("local-only"), + "authentication_type": config.StringVariable(string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY)), //User "email": config.StringVariable("minInstanceUser@example.com"), "first_name": config.StringVariable("Min"), @@ -52,7 +52,7 @@ var testDremioConfigVarsMax = config.Variables{ //Instance "display_name": config.StringVariable("dremioMaxInstance"), "description": config.StringVariable("description"), - "authentication_type": config.StringVariable("oauth"), + "authentication_type": config.StringVariable(string(dremioSdk.AUTHENTICATIONTYPE_OAUTH)), "authentication_oauth_authority_url": config.StringVariable("oauth-authority-url"), "authentication_oauth_client_id": config.StringVariable("oauth-client-id"), "authentication_oauth_client_secret": config.StringVariable("oauth-client-secret"), @@ -83,7 +83,7 @@ func testDremioInstanceConfigVarsMaxUpdated() config.Variables { tempConfig["description"] = config.StringVariable("description-upd") // switching idp to azuread - tempConfig["authentication_type"] = config.StringVariable("azuread") + tempConfig["authentication_type"] = config.StringVariable(string(dremioSdk.AUTHENTICATIONTYPE_AZUREAD)) tempConfig["authentication_azuread_authority_url"] = config.StringVariable("azuread-authority-url-upd") tempConfig["authentication_azuread_client_id"] = config.StringVariable("azuread-client-id-upd") diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go index 75a9ce9f9..14267b450 100644 --- a/stackit/internal/services/dremio/instance/datasource.go +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -60,40 +60,6 @@ func (d *instanceDataSource) Configure(ctx context.Context, req datasource.Confi // Schema should return the schema for this data source. func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - descriptions := map[string]string{ //nolint:gosec // no hardcoded credentials in here - "main": "Manages a STACKIT Dremio instance.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`\".", - "project_id": "STACKIT Project ID to which the resource is associated.", - "instance_id": "The Dremio instance ID.", - "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", - "display_name": "The display name is a short name chosen by the user to identify the resource.", - "description": "The description is a longer text chosen by the user to provide more context for the resource.", - "state": "The current state of the resource.", - "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", - "endpoints": "The available endpoints of the Dremio instance.", - "endpoints_arrow_flight": "The arrow flight endpoint of the Dremio instance.", - "endpoints_catalog": "The Apache Iceberg endpoint of the Dremio instance.", - "endpoints_ui": "The UI endpoint of the Dremio instance.", - "authentication": "Dremio instance authentication settings. A change here triggers a Dremio restart and will incur downtime.", - "authentication_type": "Type of authentication (local-only, azuread, oauth).", - "azuread": "Azure Active Directory authentication configuration.", - "azuread_authority_url": "The Azure AD authority URL.", - "azuread_client_id": "The Azure AD client ID.", - "azuread_client_secret": "The Azure AD client secret.", - "azuread_redirect_url": "The Azure AD redirect URL.", - "oauth": "OIDC authentication configuration.", - "oauth_authority_url": "The Issuer location URI, where the OIDC provider configuration can be found.", - "oauth_client_id": "The client ID assigned by the Identity Provider.", - "oauth_client_secret": "The client secret generated by the Identity Provider.", - "oauth_scope": "A list of space-separated scopes. The `openid` scope is always required; other scopes can vary by provider.", - "oauth_redirect_url": "The URL where the Dremio instance is hosted. The URL must match the redirect URL set in the Identity Provider.", - "oauth_jwt_claims": "Maps fields from the JWT token to fields Dremio requires.", - "oauth_jwt_claims_user_name": "Mapped user name claim (e.g. email).", - "oauth_parameters": "Any additional parameters the Identity Provider requires.", - "oauth_parameters_name": "Parameter name.", - "oauth_parameters_value": "Parameter value.", - } - resp.Schema = schema.Schema{ Description: descriptions["main"], Attributes: map[string]schema.Attribute{ @@ -110,7 +76,7 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques Required: true, }, "region": schema.StringAttribute{ - Required: true, + Optional: true, Description: descriptions["region"], }, "display_name": schema.StringAttribute{ @@ -281,7 +247,7 @@ func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques ctx = core.LogResponse(ctx) - err = mapFields(instanceResp, &model.Model) + err = mapFields(instanceResp, &model.Model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) return diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index c69a51400..de7ad1752 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -26,7 +26,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" - dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait/wait" + dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait" dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" ) @@ -122,6 +122,40 @@ var endpointsAttrTypes = map[string]attr.Type{ "ui": types.StringType, } +var descriptions = map[string]string{ //nolint:gosec // no hardcoded credentials in here + "main": "Manages a STACKIT Dremio instance.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`\".", + "project_id": "STACKIT Project ID to which the resource is associated.", + "instance_id": "The Dremio instance ID.", + "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", + "display_name": "The display name is a short name chosen by the user to identify the resource.", + "description": "The description is a longer text chosen by the user to provide more context for the resource.", + "state": "The current state of the resource.", + "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", + "endpoints": "The available endpoints of the Dremio instance.", + "endpoints_arrow_flight": "The arrow flight endpoint of the Dremio instance.", + "endpoints_catalog": "The Apache Iceberg endpoint of the Dremio instance.", + "endpoints_ui": "The UI endpoint of the Dremio instance.", + "authentication": "Dremio instance authentication settings. A change here triggers a Dremio restart and will incur downtime.", + "authentication_type": "Type of authentication (local-only, azuread, oauth).", + "azuread": "Azure Active Directory authentication configuration.", + "azuread_authority_url": "The Azure AD authority URL.", + "azuread_client_id": "The Azure AD client ID.", + "azuread_client_secret": "The Azure AD client secret.", + "azuread_redirect_url": "The Azure AD redirect URL.", + "oauth": "OIDC authentication configuration.", + "oauth_authority_url": "The Issuer location URI, where the OIDC provider configuration can be found.", + "oauth_client_id": "The client ID assigned by the Identity Provider.", + "oauth_client_secret": "The client secret generated by the Identity Provider.", + "oauth_scope": "A list of space-separated scopes. The `openid` scope is always required; other scopes can vary by provider.", + "oauth_redirect_url": "The URL where the Dremio instance is hosted. The URL must match the redirect URL set in the Identity Provider.", + "oauth_jwt_claims": "Maps fields from the JWT token to fields Dremio requires.", + "oauth_jwt_claims_user_name": "Mapped user name claim (e.g. email).", + "oauth_parameters": "Any additional parameters the Identity Provider requires.", + "oauth_parameters_name": "Parameter name.", + "oauth_parameters_value": "Parameter value.", +} + func NewInstanceResource() resource.Resource { return &instanceResource{} } @@ -178,39 +212,6 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure } func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - descriptions := map[string]string{ //nolint:gosec // no hardcoded credentials in here - "main": "Manages a STACKIT Dremio instance.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`\".", - "project_id": "STACKIT Project ID to which the resource is associated.", - "instance_id": "The Dremio instance ID.", - "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", - "display_name": "The display name is a short name chosen by the user to identify the resource.", - "description": "The description is a longer text chosen by the user to provide more context for the resource.", - "state": "The current state of the resource.", - "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", - "endpoints": "The available endpoints of the Dremio instance.", - "endpoints_arrow_flight": "The arrow flight endpoint of the Dremio instance.", - "endpoints_catalog": "The Apache Iceberg endpoint of the Dremio instance.", - "endpoints_ui": "The UI endpoint of the Dremio instance.", - "authentication": "Dremio instance authentication settings. A change here triggers a Dremio restart and will incur downtime.", - "authentication_type": "Type of authentication (local-only, azuread, oauth).", - "azuread": "Azure Active Directory authentication configuration.", - "azuread_authority_url": "The Azure AD authority URL.", - "azuread_client_id": "The Azure AD client ID.", - "azuread_client_secret": "The Azure AD client secret.", - "azuread_redirect_url": "The Azure AD redirect URL.", - "oauth": "OIDC authentication configuration.", - "oauth_authority_url": "The Issuer location URI, where the OIDC provider configuration can be found.", - "oauth_client_id": "The client ID assigned by the Identity Provider.", - "oauth_client_secret": "The client secret generated by the Identity Provider.", - "oauth_scope": "A list of space-separated scopes. The `openid` scope is always required; other scopes can vary by provider.", - "oauth_redirect_url": "The URL where the Dremio instance is hosted. The URL must match the redirect URL set in the Identity Provider.", - "oauth_jwt_claims": "Maps fields from the JWT token to fields Dremio requires.", - "oauth_jwt_claims_user_name": "Mapped user name claim (e.g. email).", - "oauth_parameters": "Any additional parameters the Identity Provider requires.", - "oauth_parameters_name": "Parameter name.", - "oauth_parameters_value": "Parameter value.", - } resp.Schema = schema.Schema{ Description: descriptions["main"], @@ -241,7 +242,7 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, }, }, "region": schema.StringAttribute{ - Required: true, + Optional: true, Description: descriptions["region"], PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), @@ -251,54 +252,6 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, Description: descriptions["display_name"], Required: true, }, - "description": schema.StringAttribute{ - Description: descriptions["description"], - Optional: true, - Computed: true, // Must be computed if a default is applied - Default: stringdefault.StaticString(""), - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "state": schema.StringAttribute{ - Description: descriptions["state"], - Computed: true, - }, - "error_message": schema.StringAttribute{ - Description: descriptions["error_message"], - Optional: true, - Computed: true, - }, - "endpoints": schema.SingleNestedAttribute{ - Description: descriptions["endpoints"], - Computed: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - Attributes: map[string]schema.Attribute{ - "arrow_flight": schema.StringAttribute{ - Description: descriptions["endpoints_arrow_flight"], - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "catalog": schema.StringAttribute{ - Description: descriptions["endpoints_catalog"], - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "ui": schema.StringAttribute{ - Description: descriptions["endpoints_ui"], - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - }, - }, "authentication": schema.SingleNestedAttribute{ Description: descriptions["authentication"], Required: true, @@ -322,7 +275,7 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, "client_secret": schema.StringAttribute{ Description: descriptions["azuread_client_secret"], Required: true, - // Sensitive: true, + Sensitive: true, }, "redirect_url": schema.StringAttribute{ Description: descriptions["azuread_redirect_url"], @@ -345,19 +298,7 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, "client_secret": schema.StringAttribute{ Description: descriptions["oauth_client_secret"], Required: true, - // Sensitive: true, - }, - "scope": schema.StringAttribute{ - Description: descriptions["oauth_scope"], - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "redirect_url": schema.StringAttribute{ - Description: descriptions["oauth_redirect_url"], - Computed: true, + Sensitive: true, }, "jwt_claims": schema.SingleNestedAttribute{ Description: descriptions["oauth_jwt_claims"], @@ -385,6 +326,66 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, }, }, }, + "redirect_url": schema.StringAttribute{ + Description: descriptions["oauth_redirect_url"], + Computed: true, + }, + "scope": schema.StringAttribute{ + Description: descriptions["oauth_scope"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + }, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + Computed: true, // Must be computed if a default is applied + Default: stringdefault.StaticString(""), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "error_message": schema.StringAttribute{ + Description: descriptions["error_message"], + Optional: true, + Computed: true, + }, + "state": schema.StringAttribute{ + Description: descriptions["state"], + Computed: true, + }, + "endpoints": schema.SingleNestedAttribute{ + Description: descriptions["endpoints"], + Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + Attributes: map[string]schema.Attribute{ + "arrow_flight": schema.StringAttribute{ + Description: descriptions["endpoints_arrow_flight"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "catalog": schema.StringAttribute{ + Description: descriptions["endpoints_catalog"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "ui": schema.StringAttribute{ + Description: descriptions["endpoints_ui"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), }, }, }, @@ -413,7 +414,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() - region := model.Region.ValueString() // not needed for global APIs + region := r.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) @@ -448,7 +449,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques return } - err = mapFields(instanceResp, &model.Model) + err = mapFields(instanceResp, &model.Model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -501,7 +502,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r ctx = core.LogResponse(ctx) - err = mapFields(instanceResp, &model.Model) + err = mapFields(instanceResp, &model.Model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading dremio instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -534,7 +535,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() - region := model.Region.ValueString() // not needed for global APIs + region := r.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) @@ -568,7 +569,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques return } - err = mapFields(instanceResp, &model.Model) + err = mapFields(instanceResp, &model.Model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating Dremio instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -601,7 +602,7 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() - region := model.Region.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) instanceId := model.InstanceId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) @@ -646,7 +647,7 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS tflog.Info(ctx, "Dremio instance state imported") } -func mapFields(instanceResp *dremioSdk.DremioResponse, model *Model) error { +func mapFields(instanceResp *dremioSdk.DremioResponse, model *Model, region string) error { if instanceResp == nil { return fmt.Errorf("response input is nil") } @@ -654,7 +655,7 @@ func mapFields(instanceResp *dremioSdk.DremioResponse, model *Model) error { return fmt.Errorf("model input is nil") } - err := mapModelFields(instanceResp, model) + err := mapModelFields(instanceResp, model, region) if err != nil { return fmt.Errorf("failed to map Model fields") } @@ -667,7 +668,10 @@ func mapFields(instanceResp *dremioSdk.DremioResponse, model *Model) error { } // Maps instance fields to the provider's internal model -func mapModelFields(instanceResp *dremioSdk.DremioResponse, model *Model) error { +func mapModelFields(instanceResp *dremioSdk.DremioResponse, model *Model, region string) error { + if instanceResp == nil { + return fmt.Errorf("response input is nil") + } if model == nil { return fmt.Errorf("model input is nil") } @@ -676,12 +680,12 @@ func mapModelFields(instanceResp *dremioSdk.DremioResponse, model *Model) error model.Id = utils.BuildInternalTerraformId( model.ProjectId.ValueString(), - model.Region.ValueString(), + region, model.InstanceId.ValueString(), ) model.DisplayName = types.StringValue(instanceResp.DisplayName) - model.State = types.StringValue(instanceResp.State) + model.State = types.StringValue(string(instanceResp.State)) model.Description = types.StringPointerValue(instanceResp.Description) model.ErrorMessage = types.StringPointerValue(instanceResp.ErrorMessage) @@ -706,7 +710,7 @@ func mapAuthentication(instanceResp *dremioSdk.DremioResponse, model *Model) err } authModel := AuthenticationModel{ - Type: types.StringValue(instanceResp.Authentication.Type), + Type: types.StringValue(string(instanceResp.Authentication.Type)), } if instanceResp.Authentication.Azuread != nil { @@ -793,18 +797,18 @@ func parseAuthentication(model *Model) (*dremioSdk.Authentication, error) { // make sure the type matches the existing block. switch model.Authentication.Type.ValueString() { - case "local-only": + case string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY): if !(model.Authentication.OAuth == nil) || !(model.Authentication.AzureAD == nil) { - return nil, fmt.Errorf("can't state idp config if auth type is local-only") + return nil, fmt.Errorf("can't state idp config if auth type is %q", dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY) } return &dremioSdk.Authentication{ Azuread: nil, Oauth: nil, - Type: model.Authentication.Type.ValueString(), + Type: dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY, }, nil - case "oauth": + case string(dremioSdk.AUTHENTICATIONTYPE_OAUTH): if !(model.Authentication.AzureAD == nil) { - return nil, fmt.Errorf("can't state azure idp config if auth type is oauth") + return nil, fmt.Errorf("can't state azure idp config if auth type is %q", dremioSdk.AUTHENTICATIONTYPE_OAUTH) } if model.Authentication.OAuth == nil { return nil, fmt.Errorf("missing oauth idp config") @@ -836,11 +840,11 @@ func parseAuthentication(model *Model) (*dremioSdk.Authentication, error) { return &dremioSdk.Authentication{ Azuread: nil, Oauth: oAuthPayload, - Type: model.Authentication.Type.ValueString(), + Type: dremioSdk.AUTHENTICATIONTYPE_OAUTH, }, nil - case "azuread": + case string(dremioSdk.AUTHENTICATIONTYPE_AZUREAD): if !(model.Authentication.OAuth == nil) { - return nil, fmt.Errorf("can't state oauth idp config if auth type is azuread") + return nil, fmt.Errorf("can't state oauth idp config if auth type is %q", dremioSdk.AUTHENTICATIONTYPE_AZUREAD) } if model.Authentication.AzureAD == nil { return nil, fmt.Errorf("missing azuread config") @@ -855,7 +859,7 @@ func parseAuthentication(model *Model) (*dremioSdk.Authentication, error) { return &dremioSdk.Authentication{ Azuread: azureAdPayload, Oauth: nil, - Type: model.Authentication.Type.ValueString(), + Type: dremioSdk.AUTHENTICATIONTYPE_AZUREAD, }, nil default: return nil, fmt.Errorf("unknown authentication type: %s", model.Authentication.Type) diff --git a/stackit/internal/services/dremio/instance/resource_test.go b/stackit/internal/services/dremio/instance/resource_test.go index fc725f817..c2cb550c9 100644 --- a/stackit/internal/services/dremio/instance/resource_test.go +++ b/stackit/internal/services/dremio/instance/resource_test.go @@ -55,7 +55,7 @@ func TestMapFields(t *testing.T) { RedirectUrl: utils.Ptr("oauth-redirect"), Scope: utils.Ptr("oauth-scope"), }, - Type: "local-only", + Type: dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY, }, Endpoints: dremioSdk.Endpoints{ ArrowFlight: "flight", @@ -97,7 +97,7 @@ func TestMapFields(t *testing.T) { RedirectUrl: types.StringValue("oauth-redirect"), Scope: types.StringValue("oauth-scope"), }, - Type: types.StringValue("local-only"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY)), }, State: types.StringValue("active"), @@ -141,7 +141,7 @@ func TestMapFields(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - err := mapFields(tt.input, tt.state) + err := mapFields(tt.input, tt.state, "rid") if (err != nil) != tt.wantErr { t.Errorf("mapFields error = %v, wantErr %v", err, tt.wantErr) return @@ -168,12 +168,12 @@ func TestToCreatePayload(t *testing.T) { Description: types.StringValue("test description"), DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ - Type: types.StringValue("local-only"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY)), }, }, &dremioSdk.CreateDremioInstancePayload{ Authentication: &dremioSdk.Authentication{ - Type: "local-only", + Type: dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY, }, Description: utils.Ptr("test description"), DisplayName: "displayName", @@ -202,7 +202,7 @@ func TestToCreatePayload(t *testing.T) { RedirectUrl: types.StringValue("oauth-redirect"), Scope: types.StringValue("oauth-scope"), }, - Type: types.StringValue("oauth"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_OAUTH)), }, }, &dremioSdk.CreateDremioInstancePayload{ @@ -223,7 +223,7 @@ func TestToCreatePayload(t *testing.T) { RedirectUrl: utils.Ptr("oauth-redirect"), Scope: utils.Ptr("oauth-scope"), }, - Type: "oauth", + Type: dremioSdk.AUTHENTICATIONTYPE_OAUTH, }, Description: utils.Ptr("test description"), DisplayName: "displayName", @@ -242,7 +242,7 @@ func TestToCreatePayload(t *testing.T) { ClientSecret: types.StringValue("azure-secret"), RedirectUrl: types.StringValue("azure-redirect"), }, - Type: types.StringValue("azuread"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_AZUREAD)), }, }, &dremioSdk.CreateDremioInstancePayload{ @@ -253,7 +253,7 @@ func TestToCreatePayload(t *testing.T) { ClientSecret: "azure-secret", RedirectUrl: utils.Ptr("azure-redirect"), }, - Type: "azuread", + Type: dremioSdk.AUTHENTICATIONTYPE_AZUREAD, }, Description: utils.Ptr("test description"), DisplayName: "displayName", @@ -272,7 +272,53 @@ func TestToCreatePayload(t *testing.T) { ClientSecret: types.StringValue("azure-secret"), RedirectUrl: types.StringValue("azure-redirect"), }, - Type: types.StringValue("local-only"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY)), + }, + }, + nil, + true, + }, + { + "idp-config-mismatch-oauth", + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + Authentication: &AuthenticationModel{ + AzureAD: &AzureADModel{ + AuthorityUrl: types.StringValue("azure-authority"), + ClientId: types.StringValue("azure-client"), + ClientSecret: types.StringValue("azure-secret"), + RedirectUrl: types.StringValue("azure-redirect"), + }, + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_OAUTH)), + }, + }, + nil, + true, + }, + { + "idp-config-mismatch-azuread", + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + Authentication: &AuthenticationModel{ + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_AZUREAD)), + OAuth: &OAuthModel{ + AuthorityUrl: types.StringValue("oauth-authority"), + ClientId: types.StringValue("oauth-client"), + ClientSecret: types.StringValue("oauth-secret"), + JwtClaims: &JwtClaimsModel{ + UserName: types.StringValue("oauth-username"), + }, + Parameters: []AuthParameterModel{ + { + Name: types.StringValue("oauth-parameter"), + Value: types.StringValue("oauth-value"), + }, + }, + RedirectUrl: types.StringValue("oauth-redirect"), + Scope: types.StringValue("oauth-scope"), + }, }, }, nil, @@ -284,7 +330,7 @@ func TestToCreatePayload(t *testing.T) { Description: types.StringValue("test description"), DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ - Type: types.StringValue("oauth"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_OAUTH)), }, }, nil, @@ -296,7 +342,7 @@ func TestToCreatePayload(t *testing.T) { Description: types.StringValue("test description"), DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ - Type: types.StringValue("azuread"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_AZUREAD)), }, }, nil, @@ -339,12 +385,12 @@ func TestToUpdatePayload(t *testing.T) { Description: types.StringValue("test description"), DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ - Type: types.StringValue("local-only"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY)), }, }, &dremioSdk.UpdateDremioInstancePayload{ Authentication: &dremioSdk.Authentication{ - Type: "local-only", + Type: dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY, }, Description: utils.Ptr("test description"), DisplayName: utils.Ptr("displayName"), @@ -373,7 +419,7 @@ func TestToUpdatePayload(t *testing.T) { RedirectUrl: types.StringValue("oauth-redirect"), Scope: types.StringValue("oauth-scope"), }, - Type: types.StringValue("oauth"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_OAUTH)), }, }, &dremioSdk.UpdateDremioInstancePayload{ @@ -394,7 +440,7 @@ func TestToUpdatePayload(t *testing.T) { RedirectUrl: utils.Ptr("oauth-redirect"), Scope: utils.Ptr("oauth-scope"), }, - Type: "oauth", + Type: dremioSdk.AUTHENTICATIONTYPE_OAUTH, }, Description: utils.Ptr("test description"), DisplayName: utils.Ptr("displayName"), @@ -413,7 +459,7 @@ func TestToUpdatePayload(t *testing.T) { ClientSecret: types.StringValue("azure-secret"), RedirectUrl: types.StringValue("azure-redirect"), }, - Type: types.StringValue("azuread"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_AZUREAD)), }, }, &dremioSdk.UpdateDremioInstancePayload{ @@ -424,7 +470,7 @@ func TestToUpdatePayload(t *testing.T) { ClientSecret: "azure-secret", RedirectUrl: utils.Ptr("azure-redirect"), }, - Type: "azuread", + Type: dremioSdk.AUTHENTICATIONTYPE_AZUREAD, }, Description: utils.Ptr("test description"), DisplayName: utils.Ptr("displayName"), @@ -443,7 +489,53 @@ func TestToUpdatePayload(t *testing.T) { ClientSecret: types.StringValue("azure-secret"), RedirectUrl: types.StringValue("azure-redirect"), }, - Type: types.StringValue("local-only"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY)), + }, + }, + nil, + true, + }, + { + "idp-config-mismatch-oauth", + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + Authentication: &AuthenticationModel{ + AzureAD: &AzureADModel{ + AuthorityUrl: types.StringValue("azure-authority"), + ClientId: types.StringValue("azure-client"), + ClientSecret: types.StringValue("azure-secret"), + RedirectUrl: types.StringValue("azure-redirect"), + }, + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_OAUTH)), + }, + }, + nil, + true, + }, + { + "idp-config-mismatch-azuread", + &Model{ + Description: types.StringValue("test description"), + DisplayName: types.StringValue("displayName"), + Authentication: &AuthenticationModel{ + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_AZUREAD)), + OAuth: &OAuthModel{ + AuthorityUrl: types.StringValue("oauth-authority"), + ClientId: types.StringValue("oauth-client"), + ClientSecret: types.StringValue("oauth-secret"), + JwtClaims: &JwtClaimsModel{ + UserName: types.StringValue("oauth-username"), + }, + Parameters: []AuthParameterModel{ + { + Name: types.StringValue("oauth-parameter"), + Value: types.StringValue("oauth-value"), + }, + }, + RedirectUrl: types.StringValue("oauth-redirect"), + Scope: types.StringValue("oauth-scope"), + }, }, }, nil, @@ -455,7 +547,7 @@ func TestToUpdatePayload(t *testing.T) { Description: types.StringValue("test description"), DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ - Type: types.StringValue("oauth"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_OAUTH)), }, }, nil, @@ -467,7 +559,7 @@ func TestToUpdatePayload(t *testing.T) { Description: types.StringValue("test description"), DisplayName: types.StringValue("displayName"), Authentication: &AuthenticationModel{ - Type: types.StringValue("azuread"), + Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_AZUREAD)), }, }, nil, diff --git a/stackit/internal/services/dremio/user/datasource.go b/stackit/internal/services/dremio/user/datasource.go index 445450961..90caa83a1 100644 --- a/stackit/internal/services/dremio/user/datasource.go +++ b/stackit/internal/services/dremio/user/datasource.go @@ -32,7 +32,7 @@ type userDataSource struct { providerData core.ProviderData } -func NewInstanceDataSource() datasource.DataSource { +func NewUserDataSource() datasource.DataSource { return &userDataSource{} } @@ -59,22 +59,6 @@ func (d *userDataSource) Configure(ctx context.Context, req datasource.Configure } func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - descriptions := map[string]string{ - "main": "Manages a STACKIT Dremio instances user.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".", - "project_id": "STACKIT Project ID to which the resource is associated.", - "instance_id": "The Dremio instance ID.", - "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", - "user_id": "The Dremio user ID.", - "description": "The description of the user.", - "email": "The email address of the user.", - "first_name": "The first name of the user.", - "last_name": "The last name of the user.", - "name": "The username of the user.", - "state": "The current state of the resource.", - "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", - } - resp.Schema = schema.Schema{ Description: descriptions["main"], Attributes: map[string]schema.Attribute{ @@ -92,7 +76,7 @@ func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r }, "region": schema.StringAttribute{ Description: descriptions["region"], - Required: true, + Optional: true, }, "user_id": schema.StringAttribute{ Description: descriptions["user_id"], @@ -103,6 +87,11 @@ func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r Optional: true, Computed: true, }, + "error_message": schema.StringAttribute{ + Description: descriptions["error_message"], + Optional: true, + Computed: true, + }, "email": schema.StringAttribute{ Description: descriptions["email"], Computed: true, @@ -123,11 +112,6 @@ func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r Description: descriptions["state"], Computed: true, }, - "error_message": schema.StringAttribute{ - Description: descriptions["error_message"], - Optional: true, - Computed: true, - }, }, } } @@ -172,7 +156,7 @@ func (d *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r ctx = core.LogResponse(ctx) - err = mapFields(userResp, &model.Model) + err = mapFields(userResp, &model.Model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Dremio user", fmt.Sprintf("Processing API payload: %v", err)) return diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index fd88f8e4e..537a52564 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -26,7 +26,7 @@ import ( dremioUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dremio/utils" - dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait/wait" + dremioWaiter "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi/wait" ) var ( @@ -67,6 +67,23 @@ type UserModel struct { Timeouts timeouts.Value `tfsdk:"timeouts"` } +var descriptions = map[string]string{ + "main": "Manages a STACKIT Dremio instances user.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".", + "project_id": "STACKIT Project ID to which the resource is associated.", + "instance_id": "The Dremio instance ID.", + "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", + "user_id": "The Dremio user ID.", + "email": "The email address of the user.", + "first_name": "The first name of the user.", + "last_name": "The last name of the user.", + "name": "The username of the user.", + "password": "The password of the user. Only used for creation and updates. Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character.", + "description": "The description of the user.", + "state": "The current state of the resource.", + "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", +} + type userResource struct { client *dremioSdk.APIClient providerData core.ProviderData @@ -123,23 +140,6 @@ func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequ } func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - descriptions := map[string]string{ - "main": "Manages a STACKIT Dremio instances user.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".", - "project_id": "STACKIT Project ID to which the resource is associated.", - "instance_id": "The Dremio instance ID.", - "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", - "user_id": "The Dremio user ID.", - "description": "The description of the user.", - "email": "The email address of the user.", - "first_name": "The first name of the user.", - "last_name": "The last name of the user.", - "name": "The username of the user.", - "password": "The password of the user. Only used for creation and updates. Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character.", - "state": "The current state of the resource.", - "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", - } - resp.Schema = schema.Schema{ Description: descriptions["main"], Attributes: map[string]schema.Attribute{ @@ -168,24 +168,6 @@ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, res stringplanmodifier.RequiresReplace(), }, }, - "region": schema.StringAttribute{ - Description: descriptions["region"], - Optional: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "user_id": schema.StringAttribute{ - Description: descriptions["user_id"], - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "description": schema.StringAttribute{ - Description: descriptions["description"], - Optional: true, - }, "email": schema.StringAttribute{ Description: descriptions["email"], Required: true, @@ -216,22 +198,40 @@ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, res }, "password": schema.StringAttribute{ Description: descriptions["password"], - Optional: true, + Required: true, Sensitive: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), stringplanmodifier.RequiresReplace(), }, }, - "state": schema.StringAttribute{ - Description: descriptions["state"], - Computed: true, + "region": schema.StringAttribute{ + Description: descriptions["region"], + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, }, "error_message": schema.StringAttribute{ Description: descriptions["error_message"], Optional: true, Computed: true, }, + "state": schema.StringAttribute{ + Description: descriptions["state"], + Computed: true, + }, + "user_id": schema.StringAttribute{ + Description: descriptions["user_id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, "timeouts": timeouts.AttributesAll(ctx), }, } @@ -256,7 +256,7 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() - region := model.Region.ValueString() // not needed for global APIs + region := r.providerData.GetRegionWithOverride(model.Region) instanceId := model.InstanceId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) @@ -293,7 +293,7 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r return } - err = mapFields(userResp, &model.Model) + err = mapFields(userResp, &model.Model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Dremio user", fmt.Sprintf("Processing API payload: %v", err)) return @@ -348,7 +348,7 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp ctx = core.LogResponse(ctx) - err = mapFields(userResp, &model.Model) + err = mapFields(userResp, &model.Model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading dremio user", fmt.Sprintf("Processing API payload: %v", err)) return @@ -385,7 +385,7 @@ func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, r ctx = core.InitProviderContext(ctx) projectId := model.ProjectId.ValueString() - region := model.Region.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) instanceId := model.InstanceId.ValueString() userId := model.UserId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) @@ -433,7 +433,7 @@ func (r *userResource) ImportState(ctx context.Context, req resource.ImportState tflog.Info(ctx, "Dremio user state imported") } -func mapFields(userResp *dremioSdk.DremioUserResponse, model *Model) error { +func mapFields(userResp *dremioSdk.DremioUserResponse, model *Model, region string) error { if userResp == nil { return fmt.Errorf("response input is nil") } @@ -445,7 +445,7 @@ func mapFields(userResp *dremioSdk.DremioUserResponse, model *Model) error { model.Id = utils.BuildInternalTerraformId( model.ProjectId.ValueString(), - model.Region.ValueString(), + region, model.InstanceId.ValueString(), model.UserId.ValueString(), ) @@ -456,7 +456,7 @@ func mapFields(userResp *dremioSdk.DremioUserResponse, model *Model) error { model.LastName = types.StringValue(userResp.LastName) model.Name = types.StringValue(userResp.Name) - model.State = types.StringValue(userResp.State) + model.State = types.StringValue(string(userResp.State)) model.ErrorMessage = types.StringPointerValue(userResp.ErrorMessage) return nil diff --git a/stackit/internal/services/dremio/user/resource_test.go b/stackit/internal/services/dremio/user/resource_test.go index ab4b6dade..25cb225d9 100644 --- a/stackit/internal/services/dremio/user/resource_test.go +++ b/stackit/internal/services/dremio/user/resource_test.go @@ -80,7 +80,7 @@ func TestMapFields(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - err := mapFields(tt.input, tt.state) + err := mapFields(tt.input, tt.state, "rid") if (err != nil) != tt.wantErr { t.Errorf("mapFields error = %v, wantErr %v", err, tt.wantErr) return From 96d4dabd82af02d1afe28ae79b2b6ab212dcab1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Tue, 9 Jun 2026 15:05:09 +0200 Subject: [PATCH 22/33] fix(dremio): Adding default description for user, so state does not break on omit --- stackit/internal/services/dremio/user/resource.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index 537a52564..301a85b8f 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -11,6 +11,7 @@ import ( "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/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -215,6 +216,11 @@ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, res "description": schema.StringAttribute{ Description: descriptions["description"], Optional: true, + Computed: true, // Must be computed if a default is applied + Default: stringdefault.StaticString(""), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "error_message": schema.StringAttribute{ Description: descriptions["error_message"], From 889974143a3a2cc29a9dc89e91785ff12d08ba32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 10 Jun 2026 08:29:06 +0200 Subject: [PATCH 23/33] fix(dremio): Fixing acceptance tests. --- .../services/dremio/dremio_acc_test.go | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go index 41b73aa56..32aaad0e0 100644 --- a/stackit/internal/services/dremio/dremio_acc_test.go +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -245,6 +245,7 @@ func TestDremioInstanceMin(t *testing.T) { return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId, userId), nil }, + ImportStateVerifyIgnore: []string{"password"}, }, // 4) Update { @@ -310,7 +311,7 @@ func TestDremioInstanceMax(t *testing.T) { resource.TestCheckResourceAttrSet(dremioUserResource, "id"), resource.TestCheckResourceAttr(dremioUserResource, "email", testutil.ConvertConfigVariable(testDremioConfigVarsMax["email"])), - resource.TestCheckResourceAttr(dremioUserResource, "user_description", testutil.ConvertConfigVariable(testDremioConfigVarsMax["user_description"])), + resource.TestCheckResourceAttr(dremioUserResource, "description", testutil.ConvertConfigVariable(testDremioConfigVarsMax["user_description"])), resource.TestCheckResourceAttr(dremioUserResource, "first_name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["first_name"])), resource.TestCheckResourceAttr(dremioUserResource, "last_name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["last_name"])), resource.TestCheckResourceAttr(dremioUserResource, "name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["name"])), @@ -403,8 +404,8 @@ func TestDremioInstanceMax(t *testing.T) { dremioUserDataResource, "email", ), resource.TestCheckResourceAttrPair( - dremioUserResource, "user_description", - dremioUserDataResource, "user_description", + dremioUserResource, "description", + dremioUserDataResource, "description", ), resource.TestCheckResourceAttrPair( dremioUserResource, "first_name", @@ -437,7 +438,31 @@ func TestDremioInstanceMax(t *testing.T) { } return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId), nil - }}, + }, + }, + { + ConfigVariables: testDremioConfigVarsMax, + ResourceName: dremioUserResource, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources[dremioUserResource] + if !ok { + return "", fmt.Errorf("couldn't find resource %s", dremioUserResource) + } + instanceId, ok := r.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute instanceId") + } + userId, ok := r.Primary.Attributes["user_id"] + if !ok { + return "", fmt.Errorf("couldn't find attribute userId") + } + + return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId, userId), nil + }, + ImportStateVerifyIgnore: []string{"password"}, + }, // 4) Update { Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMax, From 713ca04f3a67b5e6a74b7bd4c3a617cd695e9006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 10 Jun 2026 08:36:06 +0200 Subject: [PATCH 24/33] fix(dremio): Linter --- stackit/internal/services/dremio/dremio_acc_test.go | 8 ++++---- stackit/internal/services/dremio/instance/resource.go | 5 ++--- stackit/internal/services/dremio/user/resource.go | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go index 32aaad0e0..d694c0df8 100644 --- a/stackit/internal/services/dremio/dremio_acc_test.go +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -35,10 +35,10 @@ const dremioUserDataResource = "data.stackit_dremio_user.example" var testDremioConfigVarsMin = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "region": config.StringVariable(testutil.Region), - //Instance + // Instance "display_name": config.StringVariable("dremioMinInstance"), "authentication_type": config.StringVariable(string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY)), - //User + // User "email": config.StringVariable("minInstanceUser@example.com"), "first_name": config.StringVariable("Min"), "last_name": config.StringVariable("InstanceUser"), @@ -49,7 +49,7 @@ var testDremioConfigVarsMin = config.Variables{ var testDremioConfigVarsMax = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "region": config.StringVariable("eu01"), - //Instance + // Instance "display_name": config.StringVariable("dremioMaxInstance"), "description": config.StringVariable("description"), "authentication_type": config.StringVariable(string(dremioSdk.AUTHENTICATIONTYPE_OAUTH)), @@ -60,7 +60,7 @@ var testDremioConfigVarsMax = config.Variables{ "authentication_oauth_scope": config.StringVariable("oauth-scope"), "authentication_oauth_parameter_name": config.StringVariable("oauth-parameter-name"), "authentication_oauth_parameter_value": config.StringVariable("oauth-parameter-value"), - //User + // User "email": config.StringVariable("maxInstanceUser@example.com"), "user_description": config.StringVariable("Max Instance User Description"), "first_name": config.StringVariable("Max"), diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index de7ad1752..e9564154a 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -212,7 +212,6 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure } func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ Description: descriptions["main"], Attributes: map[string]schema.Attribute{ @@ -763,7 +762,7 @@ func toUpdatePayload(model *Model) (*dremioSdk.UpdateDremioInstancePayload, erro authentication, err := parseAuthentication(model) if err != nil { - return nil, fmt.Errorf("failed to parse authentication: %v", err) + return nil, fmt.Errorf("failed to parse authentication: %w", err) } return &dremioSdk.UpdateDremioInstancePayload{ @@ -781,7 +780,7 @@ func toCreatePayload(model *Model) (*dremioSdk.CreateDremioInstancePayload, erro authentication, err := parseAuthentication(model) if err != nil { - return nil, fmt.Errorf("failed to parse authentication: %v", err) + return nil, fmt.Errorf("failed to parse authentication: %w", err) } return &dremioSdk.CreateDremioInstancePayload{ diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index 301a85b8f..bb210d7e1 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -367,7 +367,7 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp tflog.Info(ctx, "Dremio user read") } -func (r *userResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r *userResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform // We don't allow updates on Dremio users. } From aca56f65509d7b9090d23886b41bc9675418b23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 10 Jun 2026 10:17:54 +0200 Subject: [PATCH 25/33] feat(dremio): Adding example for Dremio users --- .../stackit_dremio_user/data-source.tf | 6 ++++++ .../stackit_dremio_instance/resource.tf | 1 - .../resources/stackit_dremio_user/resource.tf | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 examples/data-sources/stackit_dremio_user/data-source.tf create mode 100644 examples/resources/stackit_dremio_user/resource.tf diff --git a/examples/data-sources/stackit_dremio_user/data-source.tf b/examples/data-sources/stackit_dremio_user/data-source.tf new file mode 100644 index 000000000..ef32f191b --- /dev/null +++ b/examples/data-sources/stackit_dremio_user/data-source.tf @@ -0,0 +1,6 @@ +data "stackit_dremio_user" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "example-instance-id" + user_id = "example-user-id" +} \ No newline at end of file diff --git a/examples/resources/stackit_dremio_instance/resource.tf b/examples/resources/stackit_dremio_instance/resource.tf index 7f8042576..c48a0acae 100644 --- a/examples/resources/stackit_dremio_instance/resource.tf +++ b/examples/resources/stackit_dremio_instance/resource.tf @@ -27,7 +27,6 @@ resource "stackit_dremio_instance" "example" { } } -# Only use the import statement, if you want to import an existing dns zone import { to = stackit_dremio_instance.import_example id = "${var.project_id},${var.region},${var.instance_id}" diff --git a/examples/resources/stackit_dremio_user/resource.tf b/examples/resources/stackit_dremio_user/resource.tf new file mode 100644 index 000000000..6fd1be1e0 --- /dev/null +++ b/examples/resources/stackit_dremio_user/resource.tf @@ -0,0 +1,16 @@ +resource "stackit_dremio_user" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "example-instance-id" + + description = "STACKIT Terraform example" + email = "example@example.com" + first_name = "Test" + last_name = "User" + name = "testUser" +} + +import { + to = stackit_dremio_user.import_example + id = "${var.project_id},${var.region},${var.instance_id},${var.user_id}" +} \ No newline at end of file From dfb901987ecec574a2fc7d9b13a6a26fecbc430d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 10 Jun 2026 13:04:57 +0200 Subject: [PATCH 26/33] fix(dremio): Implementing PR feedback --- stackit/internal/services/dremio/dremio_acc_test.go | 12 ++++++------ .../internal/services/dremio/instance/resource.go | 10 +++++++++- stackit/internal/services/dremio/user/resource.go | 4 ++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go index d694c0df8..cb0d8d58c 100644 --- a/stackit/internal/services/dremio/dremio_acc_test.go +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -36,7 +36,7 @@ var testDremioConfigVarsMin = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "region": config.StringVariable(testutil.Region), // Instance - "display_name": config.StringVariable("dremioMinInstance"), + "display_name": config.StringVariable("tfAccDremioMinInstance"), "authentication_type": config.StringVariable(string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY)), // User "email": config.StringVariable("minInstanceUser@example.com"), @@ -50,7 +50,7 @@ var testDremioConfigVarsMax = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "region": config.StringVariable("eu01"), // Instance - "display_name": config.StringVariable("dremioMaxInstance"), + "display_name": config.StringVariable("tfAccDremioMaxInstance"), "description": config.StringVariable("description"), "authentication_type": config.StringVariable(string(dremioSdk.AUTHENTICATIONTYPE_OAUTH)), "authentication_oauth_authority_url": config.StringVariable("oauth-authority-url"), @@ -72,14 +72,14 @@ var testDremioConfigVarsMax = config.Variables{ func testDremioInstanceConfigVarsMinUpdated() config.Variables { tempConfig := make(config.Variables, len(testDremioConfigVarsMin)) maps.Copy(tempConfig, testDremioConfigVarsMin) - tempConfig["display_name"] = config.StringVariable("dremioMinInstanceUpd") + tempConfig["display_name"] = config.StringVariable("tfAccDremioMinInstanceUpd") return tempConfig } func testDremioInstanceConfigVarsMaxUpdated() config.Variables { tempConfig := make(config.Variables, len(testDremioConfigVarsMax)) maps.Copy(tempConfig, testDremioConfigVarsMax) - tempConfig["display_name"] = config.StringVariable("dremioMaxInstanceUpd") + tempConfig["display_name"] = config.StringVariable("tfAccDremioMaxInstanceUpd") tempConfig["description"] = config.StringVariable("description-upd") // switching idp to azuread @@ -92,7 +92,7 @@ func testDremioInstanceConfigVarsMaxUpdated() config.Variables { return tempConfig } -func TestDremioInstanceMin(t *testing.T) { +func TestAccDremioInstanceMin(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, CheckDestroy: testAccDremioInstanceDestroy, @@ -269,7 +269,7 @@ func TestDremioInstanceMin(t *testing.T) { }) } -func TestDremioInstanceMax(t *testing.T) { +func TestAccDremioInstanceMax(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, CheckDestroy: testAccDremioInstanceDestroy, diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index e9564154a..1bf591e6c 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -55,7 +55,7 @@ type Model struct { // Read-only Fields State types.String `tfsdk:"state"` ErrorMessage types.String `tfsdk:"error_message"` - Endpoints types.Object `tfsdk:"endpoints"` // see EdnpointsModel + Endpoints types.Object `tfsdk:"endpoints"` // see EndpointsModel } // InstanceModel maps the resource schema data. @@ -430,6 +430,10 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err)) return } + if instanceResp == nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Empty response", fmt.Sprintf("Calling API: %v", err)) + return + } ctx = core.LogResponse(ctx) @@ -550,6 +554,10 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Calling API: %v", err)) return } + if instanceResp == nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Empty response", fmt.Sprintf("Calling API: %v", err)) + return + } ctx = core.LogResponse(ctx) diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index bb210d7e1..12d93fdec 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -281,6 +281,10 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err)) return } + if userResp == nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Empty response", fmt.Sprintf("Calling API: %v", err)) + return + } ctx = core.LogResponse(ctx) From 395ff96c01cfc3a607617ed5c6f90181add13a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 10 Jun 2026 13:05:51 +0200 Subject: [PATCH 27/33] feat(dremio): Specifying Dremio as experimental --- stackit/internal/features/experiments.go | 1 + stackit/internal/services/dremio/dremio_acc_test.go | 4 ++-- stackit/internal/services/dremio/instance/datasource.go | 8 +++++++- stackit/internal/services/dremio/instance/resource.go | 8 +++++++- stackit/internal/services/dremio/user/datasource.go | 8 +++++++- stackit/internal/services/dremio/user/resource.go | 8 +++++++- stackit/internal/testutil/testutil.go | 1 + 7 files changed, 32 insertions(+), 6 deletions(-) diff --git a/stackit/internal/features/experiments.go b/stackit/internal/features/experiments.go index 553c2bfdf..77b45f8f5 100644 --- a/stackit/internal/features/experiments.go +++ b/stackit/internal/features/experiments.go @@ -16,6 +16,7 @@ const ( RoutingTablesExperiment = "routing-tables" NetworkExperiment = "network" IamExperiment = "iam" + DremioExperiment = "dremio" ) var AvailableExperiments = []string{IamExperiment, RoutingTablesExperiment, NetworkExperiment} diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go index cb0d8d58c..1a5f64a13 100644 --- a/stackit/internal/services/dremio/dremio_acc_test.go +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -99,7 +99,7 @@ func TestAccDremioInstanceMin(t *testing.T) { Steps: []resource.TestStep{ // 1) Creation { - Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceDremioInstanceMin, + Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentDremio).BuildProviderConfig() + resourceDremioInstanceMin, ConfigVariables: testDremioConfigVarsMin, Check: resource.ComposeAggregateTestCheckFunc( // Instance @@ -277,7 +277,7 @@ func TestAccDremioInstanceMax(t *testing.T) { // 1) Creation { ConfigVariables: testDremioConfigVarsMax, - Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceDremioInstanceMax, + Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentDremio).BuildProviderConfig() + resourceDremioInstanceMax, Check: resource.ComposeAggregateTestCheckFunc( // Instance resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioConfigVarsMax["project_id"])), diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go index 14267b450..6e3cdd227 100644 --- a/stackit/internal/services/dremio/instance/datasource.go +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -11,6 +11,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" @@ -50,6 +51,11 @@ func (d *instanceDataSource) Configure(ctx context.Context, req datasource.Confi return } + features.CheckExperimentEnabled(ctx, &d.providerData, features.DremioExperiment, "stackit_dremio_instance", core.Datasource, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + apiClient := dremioUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return @@ -61,7 +67,7 @@ func (d *instanceDataSource) Configure(ctx context.Context, req datasource.Confi // Schema should return the schema for this data source. func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: descriptions["main"], + Description: features.AddExperimentDescription(descriptions["main"], features.DremioExperiment, core.Datasource), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: descriptions["id"], diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index 1bf591e6c..57b3bbf4b 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -22,6 +22,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" @@ -203,6 +204,11 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure return } + features.CheckExperimentEnabled(ctx, &r.providerData, features.DremioExperiment, "stackit_dremio_instance", core.Resource, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + apiClient := dremioUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return @@ -213,7 +219,7 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: descriptions["main"], + Description: features.AddExperimentDescription(descriptions["main"], features.DremioExperiment, core.Resource), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: descriptions["id"], diff --git a/stackit/internal/services/dremio/user/datasource.go b/stackit/internal/services/dremio/user/datasource.go index 90caa83a1..7a3ca0e11 100644 --- a/stackit/internal/services/dremio/user/datasource.go +++ b/stackit/internal/services/dremio/user/datasource.go @@ -11,6 +11,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1alphaapi" @@ -50,6 +51,11 @@ func (d *userDataSource) Configure(ctx context.Context, req datasource.Configure return } + features.CheckExperimentEnabled(ctx, &d.providerData, features.DremioExperiment, "stackit_dremio_user", core.Datasource, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + apiClient := dremioUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return @@ -60,7 +66,7 @@ func (d *userDataSource) Configure(ctx context.Context, req datasource.Configure func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: descriptions["main"], + Description: features.AddExperimentDescription(descriptions["main"], features.DremioExperiment, core.Datasource), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: descriptions["id"], diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index 12d93fdec..64710f43a 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -19,6 +19,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" @@ -132,6 +133,11 @@ func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequ return } + features.CheckExperimentEnabled(ctx, &r.providerData, features.DremioExperiment, "stackit_dremio_user", core.Resource, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + apiClient := dremioUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return @@ -142,7 +148,7 @@ func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: descriptions["main"], + Description: features.AddExperimentDescription(descriptions["main"], features.DremioExperiment, core.Resource), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: descriptions["id"], diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index 8ade0d85f..e119666ad 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -148,6 +148,7 @@ const ( ExperimentRoutingTables Experiment = "routing-tables" ExperimentNetwork Experiment = "network" ExperimentIAM Experiment = "iam" + ExperimentDremio Experiment = "dremio" ) type customEndpointConfig struct { From d29ed9bfb12740e9ef52653adda63c8ac6c04224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 10 Jun 2026 13:06:40 +0200 Subject: [PATCH 28/33] fix(dremo): Formatting --- .../stackit_dremio_user/data-source.tf | 2 +- examples/resources/stackit_dremio_user/resource.tf | 14 +++++++------- stackit/internal/features/experiments.go | 2 +- .../internal/services/dremio/dremio_acc_test.go | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/data-sources/stackit_dremio_user/data-source.tf b/examples/data-sources/stackit_dremio_user/data-source.tf index ef32f191b..cbcf9750b 100644 --- a/examples/data-sources/stackit_dremio_user/data-source.tf +++ b/examples/data-sources/stackit_dremio_user/data-source.tf @@ -2,5 +2,5 @@ data "stackit_dremio_user" "example" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" region = "eu01" instance_id = "example-instance-id" - user_id = "example-user-id" + user_id = "example-user-id" } \ No newline at end of file diff --git a/examples/resources/stackit_dremio_user/resource.tf b/examples/resources/stackit_dremio_user/resource.tf index 6fd1be1e0..d317cf483 100644 --- a/examples/resources/stackit_dremio_user/resource.tf +++ b/examples/resources/stackit_dremio_user/resource.tf @@ -1,13 +1,13 @@ resource "stackit_dremio_user" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - region = "eu01" - instance_id = "example-instance-id" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "example-instance-id" description = "STACKIT Terraform example" - email = "example@example.com" - first_name = "Test" - last_name = "User" - name = "testUser" + email = "example@example.com" + first_name = "Test" + last_name = "User" + name = "testUser" } import { diff --git a/stackit/internal/features/experiments.go b/stackit/internal/features/experiments.go index 77b45f8f5..aea97d293 100644 --- a/stackit/internal/features/experiments.go +++ b/stackit/internal/features/experiments.go @@ -19,7 +19,7 @@ const ( DremioExperiment = "dremio" ) -var AvailableExperiments = []string{IamExperiment, RoutingTablesExperiment, NetworkExperiment} +var AvailableExperiments = []string{DremioExperiment, IamExperiment, RoutingTablesExperiment, NetworkExperiment} // Check if an experiment is valid. func ValidExperiment(experiment string, diags *diag.Diagnostics) bool { diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go index 1a5f64a13..17965da57 100644 --- a/stackit/internal/services/dremio/dremio_acc_test.go +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -134,7 +134,7 @@ func TestAccDremioInstanceMin(t *testing.T) { }, // 2) Data Source { - Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMin, + Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentDremio).BuildProviderConfig() + resourceDremioInstanceMin, ConfigVariables: testDremioConfigVarsMin, Check: resource.ComposeAggregateTestCheckFunc( // Instance @@ -249,7 +249,7 @@ func TestAccDremioInstanceMin(t *testing.T) { }, // 4) Update { - Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMin, + Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentDremio).BuildProviderConfig() + resourceDremioInstanceMin, ConfigVariables: testDremioInstanceConfigVarsMinUpdated(), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMinUpdated()["project_id"])), @@ -322,7 +322,7 @@ func TestAccDremioInstanceMax(t *testing.T) { }, // 2) Data Source { - Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMax, + Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentDremio).BuildProviderConfig() + resourceDremioInstanceMax, ConfigVariables: testDremioConfigVarsMax, Check: resource.ComposeAggregateTestCheckFunc( // Instance @@ -465,7 +465,7 @@ func TestAccDremioInstanceMax(t *testing.T) { }, // 4) Update { - Config: testutil.NewConfigBuilder().BuildProviderConfig() + resourceDremioInstanceMax, + Config: testutil.NewConfigBuilder().Experiments(testutil.ExperimentDremio).BuildProviderConfig() + resourceDremioInstanceMax, ConfigVariables: testDremioInstanceConfigVarsMaxUpdated(), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(dremioInstanceResource, "project_id", testutil.ConvertConfigVariable(testDremioInstanceConfigVarsMaxUpdated()["project_id"])), From 2d616fa4d76ffd030d51fc8fbfbcea262ae4a75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 10 Jun 2026 13:56:39 +0200 Subject: [PATCH 29/33] feat(dremio): Adding description for experimental state in README. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 7b7da2891..2f3a58e2c 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,12 @@ The `stackit_network` provides the fields `region` and `routing_table_id` when t The underlying API is not stable yet and could change in the future. If you don't need these fields, don't set the experiment flag `network`, to use the stable api. +#### `dremio` + +Enables the usage and provisioning of STACKIT Dremio resources. +The STACKIT Dremio API is currently in alpha state. +The fields of the resources are still subject to change. + ## Acceptance Tests > [!WARNING] From 1be2f8a700885a68e82a54d09bafdfdef8b7806e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Wed, 10 Jun 2026 15:38:36 +0200 Subject: [PATCH 30/33] fix(dremio): Fix experimental state check --- stackit/internal/services/dremio/instance/datasource.go | 2 +- stackit/internal/services/dremio/instance/resource.go | 2 +- stackit/internal/services/dremio/user/datasource.go | 2 +- stackit/internal/services/dremio/user/resource.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go index 6e3cdd227..266501a7b 100644 --- a/stackit/internal/services/dremio/instance/datasource.go +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -51,7 +51,7 @@ func (d *instanceDataSource) Configure(ctx context.Context, req datasource.Confi return } - features.CheckExperimentEnabled(ctx, &d.providerData, features.DremioExperiment, "stackit_dremio_instance", core.Datasource, &resp.Diagnostics) + features.CheckExperimentEnabled(ctx, &providerData, features.DremioExperiment, "stackit_dremio_instance", core.Datasource, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index 57b3bbf4b..8ea558ac4 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -204,7 +204,7 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure return } - features.CheckExperimentEnabled(ctx, &r.providerData, features.DremioExperiment, "stackit_dremio_instance", core.Resource, &resp.Diagnostics) + features.CheckExperimentEnabled(ctx, &providerData, features.DremioExperiment, "stackit_dremio_instance", core.Resource, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } diff --git a/stackit/internal/services/dremio/user/datasource.go b/stackit/internal/services/dremio/user/datasource.go index 7a3ca0e11..cb2ed96f0 100644 --- a/stackit/internal/services/dremio/user/datasource.go +++ b/stackit/internal/services/dremio/user/datasource.go @@ -51,7 +51,7 @@ func (d *userDataSource) Configure(ctx context.Context, req datasource.Configure return } - features.CheckExperimentEnabled(ctx, &d.providerData, features.DremioExperiment, "stackit_dremio_user", core.Datasource, &resp.Diagnostics) + features.CheckExperimentEnabled(ctx, &providerData, features.DremioExperiment, "stackit_dremio_user", core.Datasource, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index 64710f43a..4217f934b 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -133,7 +133,7 @@ func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequ return } - features.CheckExperimentEnabled(ctx, &r.providerData, features.DremioExperiment, "stackit_dremio_user", core.Resource, &resp.Diagnostics) + features.CheckExperimentEnabled(ctx, &providerData, features.DremioExperiment, "stackit_dremio_user", core.Resource, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } From 3d894825e6be630e570548a6ed22d12bd58d9e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Thu, 11 Jun 2026 08:54:16 +0200 Subject: [PATCH 31/33] fix(dremio): Generate docs. --- docs/data-sources/dremio_instance.md | 113 +++++++++++++++++++ docs/data-sources/dremio_user.md | 49 +++++++++ docs/index.md | 3 +- docs/resources/dremio_instance.md | 159 +++++++++++++++++++++++++++ docs/resources/dremio_user.md | 71 ++++++++++++ 5 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 docs/data-sources/dremio_instance.md create mode 100644 docs/data-sources/dremio_user.md create mode 100644 docs/resources/dremio_instance.md create mode 100644 docs/resources/dremio_user.md diff --git a/docs/data-sources/dremio_instance.md b/docs/data-sources/dremio_instance.md new file mode 100644 index 000000000..67d6afbff --- /dev/null +++ b/docs/data-sources/dremio_instance.md @@ -0,0 +1,113 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_dremio_instance Data Source - stackit" +subcategory: "" +description: |- + Manages a STACKIT Dremio instance. + ~> This datasource is part of the dremio experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion. +--- + +# stackit_dremio_instance (Data Source) + +Manages a STACKIT Dremio instance. + +~> This datasource is part of the dremio experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion. + +## Example Usage + +```terraform +data "stackit_dremio_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "example-instance-id" +} +``` + + +## Schema + +### Required + +- `instance_id` (String) The Dremio instance ID. +- `project_id` (String) STACKIT Project ID to which the resource is associated. + +### Optional + +- `description` (String) The description is a longer text chosen by the user to provide more context for the resource. +- `error_message` (String) A message describing an actionable error the user can resolve. This field is empty if no such error exists. +- `region` (String) The STACKIT region name the resource is located in. If not defined, the provider region is used. + +### Read-Only + +- `authentication` (Attributes) Dremio instance authentication settings. A change here triggers a Dremio restart and will incur downtime. (see [below for nested schema](#nestedatt--authentication)) +- `display_name` (String) The display name is a short name chosen by the user to identify the resource. +- `endpoints` (Attributes) The available endpoints of the Dremio instance. (see [below for nested schema](#nestedatt--endpoints)) +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`". +- `state` (String) The current state of the resource. + + +### Nested Schema for `authentication` + +Optional: + +- `azuread` (Attributes) Azure Active Directory authentication configuration. (see [below for nested schema](#nestedatt--authentication--azuread)) +- `oauth` (Attributes) OIDC authentication configuration. (see [below for nested schema](#nestedatt--authentication--oauth)) + +Read-Only: + +- `type` (String) Type of authentication (local-only, azuread, oauth). + + +### Nested Schema for `authentication.azuread` + +Read-Only: + +- `authority_url` (String) The Azure AD authority URL. +- `client_id` (String) The Azure AD client ID. +- `client_secret` (String, Sensitive) The Azure AD client secret. +- `redirect_url` (String) The Azure AD redirect URL. + + + +### Nested Schema for `authentication.oauth` + +Optional: + +- `parameters` (Attributes List) Any additional parameters the Identity Provider requires. (see [below for nested schema](#nestedatt--authentication--oauth--parameters)) +- `scope` (String) A list of space-separated scopes. The `openid` scope is always required; other scopes can vary by provider. + +Read-Only: + +- `authority_url` (String) The Issuer location URI, where the OIDC provider configuration can be found. +- `client_id` (String) The client ID assigned by the Identity Provider. +- `client_secret` (String, Sensitive) The client secret generated by the Identity Provider. +- `jwt_claims` (Attributes) Maps fields from the JWT token to fields Dremio requires. (see [below for nested schema](#nestedatt--authentication--oauth--jwt_claims)) +- `redirect_url` (String) The URL where the Dremio instance is hosted. The URL must match the redirect URL set in the Identity Provider. + + +### Nested Schema for `authentication.oauth.parameters` + +Read-Only: + +- `name` (String) Parameter name. +- `value` (String) Parameter value. + + + +### Nested Schema for `authentication.oauth.jwt_claims` + +Read-Only: + +- `user_name` (String) Mapped user name claim (e.g. email). + + + + + +### Nested Schema for `endpoints` + +Read-Only: + +- `arrow_flight` (String) The arrow flight endpoint of the Dremio instance. +- `catalog` (String) The Apache Iceberg endpoint of the Dremio instance. +- `ui` (String) The UI endpoint of the Dremio instance. diff --git a/docs/data-sources/dremio_user.md b/docs/data-sources/dremio_user.md new file mode 100644 index 000000000..2965c4ad3 --- /dev/null +++ b/docs/data-sources/dremio_user.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_dremio_user Data Source - stackit" +subcategory: "" +description: |- + Manages a STACKIT Dremio instances user. + ~> This datasource is part of the dremio experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion. +--- + +# stackit_dremio_user (Data Source) + +Manages a STACKIT Dremio instances user. + +~> This datasource is part of the dremio experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion. + +## Example Usage + +```terraform +data "stackit_dremio_user" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "example-instance-id" + user_id = "example-user-id" +} +``` + + +## Schema + +### Required + +- `instance_id` (String) The Dremio instance ID. +- `project_id` (String) STACKIT Project ID to which the resource is associated. +- `user_id` (String) The Dremio user ID. + +### Optional + +- `description` (String) The description of the user. +- `error_message` (String) A message describing an actionable error the user can resolve. This field is empty if no such error exists. +- `region` (String) The STACKIT region name the resource is located in. If not defined, the provider region is used. + +### Read-Only + +- `email` (String) The email address of the user. +- `first_name` (String) The first name of the user. +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`,`user_id`". +- `last_name` (String) The last name of the user. +- `name` (String) The username of the user. +- `state` (String) The current state of the resource. diff --git a/docs/index.md b/docs/index.md index f1aed20ab..368beb47e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -170,9 +170,10 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de - `credentials_path` (String) Path of JSON from where the credentials are read. Takes precedence over the env var `STACKIT_CREDENTIALS_PATH`. Default value is `~/.stackit/credentials.json`. - `default_region` (String) Region will be used as the default location for regional services. Not all services require a region, some are global - `dns_custom_endpoint` (String) Custom endpoint for the DNS service +- `dremio_custom_endpoint` (String) Custom endpoint for the Dremio service - `edgecloud_custom_endpoint` (String) Custom endpoint for the Edge Cloud service - `enable_beta_resources` (Boolean) Enable beta resources. Default is false. -- `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: iam, routing-tables, network +- `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: dremio, iam, routing-tables, network - `git_custom_endpoint` (String) Custom endpoint for the Git service - `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service - `intake_custom_endpoint` (String) Custom endpoint for the Intake service diff --git a/docs/resources/dremio_instance.md b/docs/resources/dremio_instance.md new file mode 100644 index 000000000..11f1cfce5 --- /dev/null +++ b/docs/resources/dremio_instance.md @@ -0,0 +1,159 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_dremio_instance Resource - stackit" +subcategory: "" +description: |- + Manages a STACKIT Dremio instance. + ~> This resource is part of the dremio experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion. +--- + +# stackit_dremio_instance (Resource) + +Manages a STACKIT Dremio instance. + +~> This resource is part of the dremio experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion. + +## Example Usage + +```terraform +resource "stackit_dremio_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "exampleName" + description = "Example description" + authentication = { + type = "local-only" // "oauth" or "azuread" for IDP config + + oauth = { // only needed if "oauth" is given as type + authority_url = "authority" + client_id = "client-id" + client_secret = "client-secret" + jwt_claims = { + user_name = "example" + } + scope = "idp-scope" + parameters = [ + { "name" : "example", "value" : "example-value" } + ] + } + + azuread = { // only needed if "azuread" is given as type + authority_url = "authority" + client_id = "client-id" + client_secret = "client-secret" + } + } +} + +import { + to = stackit_dremio_instance.import_example + id = "${var.project_id},${var.region},${var.instance_id}" +} +``` + + +## Schema + +### Required + +- `authentication` (Attributes) Dremio instance authentication settings. A change here triggers a Dremio restart and will incur downtime. (see [below for nested schema](#nestedatt--authentication)) +- `display_name` (String) The display name is a short name chosen by the user to identify the resource. +- `project_id` (String) STACKIT Project ID to which the resource is associated. + +### Optional + +- `description` (String) The description is a longer text chosen by the user to provide more context for the resource. +- `error_message` (String) A message describing an actionable error the user can resolve. This field is empty if no such error exists. +- `region` (String) The STACKIT region name the resource is located in. If not defined, the provider region is used. +- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) + +### Read-Only + +- `endpoints` (Attributes) The available endpoints of the Dremio instance. (see [below for nested schema](#nestedatt--endpoints)) +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`". +- `instance_id` (String) The Dremio instance ID. +- `state` (String) The current state of the resource. + + +### Nested Schema for `authentication` + +Required: + +- `type` (String) Type of authentication (local-only, azuread, oauth). + +Optional: + +- `azuread` (Attributes) Azure Active Directory authentication configuration. (see [below for nested schema](#nestedatt--authentication--azuread)) +- `oauth` (Attributes) OIDC authentication configuration. (see [below for nested schema](#nestedatt--authentication--oauth)) + + +### Nested Schema for `authentication.azuread` + +Required: + +- `authority_url` (String) The Azure AD authority URL. +- `client_id` (String) The Azure AD client ID. +- `client_secret` (String, Sensitive) The Azure AD client secret. + +Read-Only: + +- `redirect_url` (String) The Azure AD redirect URL. + + + +### Nested Schema for `authentication.oauth` + +Required: + +- `authority_url` (String) The Issuer location URI, where the OIDC provider configuration can be found. +- `client_id` (String) The client ID assigned by the Identity Provider. +- `client_secret` (String, Sensitive) The client secret generated by the Identity Provider. +- `jwt_claims` (Attributes) Maps fields from the JWT token to fields Dremio requires. (see [below for nested schema](#nestedatt--authentication--oauth--jwt_claims)) + +Optional: + +- `parameters` (Attributes List) Any additional parameters the Identity Provider requires. (see [below for nested schema](#nestedatt--authentication--oauth--parameters)) +- `scope` (String) A list of space-separated scopes. The `openid` scope is always required; other scopes can vary by provider. + +Read-Only: + +- `redirect_url` (String) The URL where the Dremio instance is hosted. The URL must match the redirect URL set in the Identity Provider. + + +### Nested Schema for `authentication.oauth.jwt_claims` + +Required: + +- `user_name` (String) Mapped user name claim (e.g. email). + + + +### Nested Schema for `authentication.oauth.parameters` + +Required: + +- `name` (String) Parameter name. +- `value` (String) Parameter value. + + + + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). +- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs. +- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled. +- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). + + + +### Nested Schema for `endpoints` + +Read-Only: + +- `arrow_flight` (String) The arrow flight endpoint of the Dremio instance. +- `catalog` (String) The Apache Iceberg endpoint of the Dremio instance. +- `ui` (String) The UI endpoint of the Dremio instance. diff --git a/docs/resources/dremio_user.md b/docs/resources/dremio_user.md new file mode 100644 index 000000000..c891c6d09 --- /dev/null +++ b/docs/resources/dremio_user.md @@ -0,0 +1,71 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_dremio_user Resource - stackit" +subcategory: "" +description: |- + Manages a STACKIT Dremio instances user. + ~> This resource is part of the dremio experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion. +--- + +# stackit_dremio_user (Resource) + +Manages a STACKIT Dremio instances user. + +~> This resource is part of the dremio experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion. + +## Example Usage + +```terraform +resource "stackit_dremio_user" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "example-instance-id" + + description = "STACKIT Terraform example" + email = "example@example.com" + first_name = "Test" + last_name = "User" + name = "testUser" +} + +import { + to = stackit_dremio_user.import_example + id = "${var.project_id},${var.region},${var.instance_id},${var.user_id}" +} +``` + + +## Schema + +### Required + +- `email` (String) The email address of the user. +- `first_name` (String) The first name of the user. +- `instance_id` (String) The Dremio instance ID. +- `last_name` (String) The last name of the user. +- `name` (String) The username of the user. +- `password` (String, Sensitive) The password of the user. Only used for creation and updates. Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character. +- `project_id` (String) STACKIT Project ID to which the resource is associated. + +### Optional + +- `description` (String) The description of the user. +- `error_message` (String) A message describing an actionable error the user can resolve. This field is empty if no such error exists. +- `region` (String) The STACKIT region name the resource is located in. If not defined, the provider region is used. +- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) + +### Read-Only + +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`,`user_id`". +- `state` (String) The current state of the resource. +- `user_id` (String) The Dremio user ID. + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). +- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs. +- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled. +- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). From e41f6ceecc29fdd08c426f3812e29f0f0ddadd3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Thu, 11 Jun 2026 18:24:12 +0200 Subject: [PATCH 32/33] feat(dremio): Removing state & error_message from tf state --- .../services/dremio/dremio_acc_test.go | 12 +---- .../services/dremio/instance/datasource.go | 9 ---- .../services/dremio/instance/resource.go | 22 ++-------- .../services/dremio/instance/resource_test.go | 3 -- .../services/dremio/user/datasource.go | 9 ---- .../internal/services/dremio/user/resource.go | 44 ++++++------------- .../services/dremio/user/resource_test.go | 36 +++++++-------- 7 files changed, 36 insertions(+), 99 deletions(-) diff --git a/stackit/internal/services/dremio/dremio_acc_test.go b/stackit/internal/services/dremio/dremio_acc_test.go index 17965da57..339a51fb9 100644 --- a/stackit/internal/services/dremio/dremio_acc_test.go +++ b/stackit/internal/services/dremio/dremio_acc_test.go @@ -93,7 +93,7 @@ func testDremioInstanceConfigVarsMaxUpdated() config.Variables { } func TestAccDremioInstanceMin(t *testing.T) { - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, CheckDestroy: testAccDremioInstanceDestroy, Steps: []resource.TestStep{ @@ -111,7 +111,6 @@ func TestAccDremioInstanceMin(t *testing.T) { resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioConfigVarsMin["display_name"])), resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioConfigVarsMin["authentication_type"])), - resource.TestCheckResourceAttrSet(dremioInstanceResource, "state"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), @@ -128,8 +127,6 @@ func TestAccDremioInstanceMin(t *testing.T) { resource.TestCheckResourceAttr(dremioUserResource, "last_name", testutil.ConvertConfigVariable(testDremioConfigVarsMin["last_name"])), resource.TestCheckResourceAttr(dremioUserResource, "name", testutil.ConvertConfigVariable(testDremioConfigVarsMin["name"])), resource.TestCheckResourceAttr(dremioUserResource, "password", testutil.ConvertConfigVariable(testDremioConfigVarsMin["password"])), - - resource.TestCheckResourceAttrSet(dremioUserResource, "state"), ), }, // 2) Data Source @@ -259,7 +256,6 @@ func TestAccDremioInstanceMin(t *testing.T) { resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), - resource.TestCheckResourceAttrSet(dremioInstanceResource, "state"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), @@ -270,7 +266,7 @@ func TestAccDremioInstanceMin(t *testing.T) { } func TestAccDremioInstanceMax(t *testing.T) { - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, CheckDestroy: testAccDremioInstanceDestroy, Steps: []resource.TestStep{ @@ -298,7 +294,6 @@ func TestAccDremioInstanceMax(t *testing.T) { resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), - resource.TestCheckResourceAttrSet(dremioInstanceResource, "state"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), @@ -316,8 +311,6 @@ func TestAccDremioInstanceMax(t *testing.T) { resource.TestCheckResourceAttr(dremioUserResource, "last_name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["last_name"])), resource.TestCheckResourceAttr(dremioUserResource, "name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["name"])), resource.TestCheckResourceAttr(dremioUserResource, "password", testutil.ConvertConfigVariable(testDremioConfigVarsMax["password"])), - - resource.TestCheckResourceAttrSet(dremioUserResource, "state"), ), }, // 2) Data Source @@ -482,7 +475,6 @@ func TestAccDremioInstanceMax(t *testing.T) { resource.TestCheckResourceAttrSet(dremioInstanceResource, "instance_id"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "id"), - resource.TestCheckResourceAttrSet(dremioInstanceResource, "state"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.ui"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.arrow_flight"), resource.TestCheckResourceAttrSet(dremioInstanceResource, "endpoints.catalog"), diff --git a/stackit/internal/services/dremio/instance/datasource.go b/stackit/internal/services/dremio/instance/datasource.go index 266501a7b..cd0d2c18c 100644 --- a/stackit/internal/services/dremio/instance/datasource.go +++ b/stackit/internal/services/dremio/instance/datasource.go @@ -94,15 +94,6 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques Optional: true, Computed: true, }, - "state": schema.StringAttribute{ - Description: descriptions["state"], - Computed: true, - }, - "error_message": schema.StringAttribute{ - Description: descriptions["error_message"], - Optional: true, - Computed: true, - }, "endpoints": schema.SingleNestedAttribute{ Description: descriptions["endpoints"], Computed: true, diff --git a/stackit/internal/services/dremio/instance/resource.go b/stackit/internal/services/dremio/instance/resource.go index 8ea558ac4..aa51dc4a7 100644 --- a/stackit/internal/services/dremio/instance/resource.go +++ b/stackit/internal/services/dremio/instance/resource.go @@ -54,9 +54,7 @@ type Model struct { Description types.String `tfsdk:"description"` // Read-only Fields - State types.String `tfsdk:"state"` - ErrorMessage types.String `tfsdk:"error_message"` - Endpoints types.Object `tfsdk:"endpoints"` // see EndpointsModel + Endpoints types.Object `tfsdk:"endpoints"` // see EndpointsModel } // InstanceModel maps the resource schema data. @@ -131,8 +129,6 @@ var descriptions = map[string]string{ //nolint:gosec // no hardcoded credentials "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", "display_name": "The display name is a short name chosen by the user to identify the resource.", "description": "The description is a longer text chosen by the user to provide more context for the resource.", - "state": "The current state of the resource.", - "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", "endpoints": "The available endpoints of the Dremio instance.", "endpoints_arrow_flight": "The arrow flight endpoint of the Dremio instance.", "endpoints_catalog": "The Apache Iceberg endpoint of the Dremio instance.", @@ -247,7 +243,9 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, }, }, "region": schema.StringAttribute{ - Optional: true, + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, Description: descriptions["region"], PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), @@ -356,15 +354,6 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, stringplanmodifier.UseStateForUnknown(), }, }, - "error_message": schema.StringAttribute{ - Description: descriptions["error_message"], - Optional: true, - Computed: true, - }, - "state": schema.StringAttribute{ - Description: descriptions["state"], - Computed: true, - }, "endpoints": schema.SingleNestedAttribute{ Description: descriptions["endpoints"], Computed: true, @@ -698,10 +687,7 @@ func mapModelFields(instanceResp *dremioSdk.DremioResponse, model *Model, region ) model.DisplayName = types.StringValue(instanceResp.DisplayName) - model.State = types.StringValue(string(instanceResp.State)) - model.Description = types.StringPointerValue(instanceResp.Description) - model.ErrorMessage = types.StringPointerValue(instanceResp.ErrorMessage) endpoints := &EndpointsModel{ ArrowFlight: types.StringValue(instanceResp.Endpoints.ArrowFlight), diff --git a/stackit/internal/services/dremio/instance/resource_test.go b/stackit/internal/services/dremio/instance/resource_test.go index c2cb550c9..a295afcc8 100644 --- a/stackit/internal/services/dremio/instance/resource_test.go +++ b/stackit/internal/services/dremio/instance/resource_test.go @@ -62,7 +62,6 @@ func TestMapFields(t *testing.T) { Catalog: "catalog", Ui: "ui", }, - State: "active", }, &Model{ Id: types.StringValue("pid,rid," + instanceId), @@ -100,8 +99,6 @@ func TestMapFields(t *testing.T) { Type: types.StringValue(string(dremioSdk.AUTHENTICATIONTYPE_LOCAL_ONLY)), }, - State: types.StringValue("active"), - ErrorMessage: types.StringNull(), Endpoints: types.ObjectValueMust( map[string]attr.Type{ "arrow_flight": types.StringType, diff --git a/stackit/internal/services/dremio/user/datasource.go b/stackit/internal/services/dremio/user/datasource.go index cb2ed96f0..c220e8a8d 100644 --- a/stackit/internal/services/dremio/user/datasource.go +++ b/stackit/internal/services/dremio/user/datasource.go @@ -93,11 +93,6 @@ func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r Optional: true, Computed: true, }, - "error_message": schema.StringAttribute{ - Description: descriptions["error_message"], - Optional: true, - Computed: true, - }, "email": schema.StringAttribute{ Description: descriptions["email"], Computed: true, @@ -114,10 +109,6 @@ func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r Description: descriptions["name"], Computed: true, }, - "state": schema.StringAttribute{ - Description: descriptions["state"], - Computed: true, - }, }, } } diff --git a/stackit/internal/services/dremio/user/resource.go b/stackit/internal/services/dremio/user/resource.go index 4217f934b..8a432dd20 100644 --- a/stackit/internal/services/dremio/user/resource.go +++ b/stackit/internal/services/dremio/user/resource.go @@ -54,10 +54,6 @@ type Model struct { // Optional Fields Description types.String `tfsdk:"description"` - - // Read-only Fields - State types.String `tfsdk:"state"` - ErrorMessage types.String `tfsdk:"error_message"` } type UserModel struct { @@ -70,20 +66,18 @@ type UserModel struct { } var descriptions = map[string]string{ - "main": "Manages a STACKIT Dremio instances user.", - "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".", - "project_id": "STACKIT Project ID to which the resource is associated.", - "instance_id": "The Dremio instance ID.", - "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", - "user_id": "The Dremio user ID.", - "email": "The email address of the user.", - "first_name": "The first name of the user.", - "last_name": "The last name of the user.", - "name": "The username of the user.", - "password": "The password of the user. Only used for creation and updates. Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character.", - "description": "The description of the user.", - "state": "The current state of the resource.", - "error_message": "A message describing an actionable error the user can resolve. This field is empty if no such error exists.", + "main": "Manages a STACKIT Dremio instances user.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".", + "project_id": "STACKIT Project ID to which the resource is associated.", + "instance_id": "The Dremio instance ID.", + "region": "The STACKIT region name the resource is located in. If not defined, the provider region is used.", + "user_id": "The Dremio user ID.", + "email": "The email address of the user.", + "first_name": "The first name of the user.", + "last_name": "The last name of the user.", + "name": "The username of the user.", + "password": "The password of the user. Only used for creation and updates. Must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character.", + "description": "The description of the user.", } type userResource struct { @@ -215,6 +209,8 @@ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, res "region": schema.StringAttribute{ Description: descriptions["region"], Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, @@ -228,15 +224,6 @@ func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, res stringplanmodifier.UseStateForUnknown(), }, }, - "error_message": schema.StringAttribute{ - Description: descriptions["error_message"], - Optional: true, - Computed: true, - }, - "state": schema.StringAttribute{ - Description: descriptions["state"], - Computed: true, - }, "user_id": schema.StringAttribute{ Description: descriptions["user_id"], Computed: true, @@ -472,9 +459,6 @@ func mapFields(userResp *dremioSdk.DremioUserResponse, model *Model, region stri model.LastName = types.StringValue(userResp.LastName) model.Name = types.StringValue(userResp.Name) - model.State = types.StringValue(string(userResp.State)) - model.ErrorMessage = types.StringPointerValue(userResp.ErrorMessage) - return nil } diff --git a/stackit/internal/services/dremio/user/resource_test.go b/stackit/internal/services/dremio/user/resource_test.go index 25cb225d9..718350d9e 100644 --- a/stackit/internal/services/dremio/user/resource_test.go +++ b/stackit/internal/services/dremio/user/resource_test.go @@ -29,28 +29,24 @@ func TestMapFields(t *testing.T) { InstanceId: types.StringValue(instanceId), }, &dremioSdk.DremioUserResponse{ - Id: userId, - Description: utils.Ptr("test description"), - Email: "test-user@example.com", - FirstName: "Test", - LastName: "User", - Name: "testUser", - State: "active", - ErrorMessage: utils.Ptr("test error message"), + Id: userId, + Description: utils.Ptr("test description"), + Email: "test-user@example.com", + FirstName: "Test", + LastName: "User", + Name: "testUser", }, &Model{ - Id: types.StringValue(fmt.Sprintf("pid,rid,%s,%s", instanceId, userId)), - ProjectId: types.StringValue("pid"), - Region: types.StringValue("rid"), - InstanceId: types.StringValue(instanceId), - UserId: types.StringValue(userId), - Description: types.StringPointerValue(utils.Ptr("test description")), - Email: types.StringValue("test-user@example.com"), - FirstName: types.StringValue("Test"), - LastName: types.StringValue("User"), - Name: types.StringValue("testUser"), - State: types.StringValue("active"), - ErrorMessage: types.StringPointerValue(utils.Ptr("test error message")), + Id: types.StringValue(fmt.Sprintf("pid,rid,%s,%s", instanceId, userId)), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("rid"), + InstanceId: types.StringValue(instanceId), + UserId: types.StringValue(userId), + Description: types.StringPointerValue(utils.Ptr("test description")), + Email: types.StringValue("test-user@example.com"), + FirstName: types.StringValue("Test"), + LastName: types.StringValue("User"), + Name: types.StringValue("testUser"), }, false, }, From 04e7e46bdca0f3b990c241afe813bdfd7d005b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bjarne=20Schr=C3=B6der?= Date: Thu, 11 Jun 2026 18:28:31 +0200 Subject: [PATCH 33/33] chore(dremio): Regenerating docs --- docs/data-sources/dremio_instance.md | 2 -- docs/data-sources/dremio_user.md | 2 -- docs/resources/dremio_instance.md | 2 -- docs/resources/dremio_user.md | 2 -- 4 files changed, 8 deletions(-) diff --git a/docs/data-sources/dremio_instance.md b/docs/data-sources/dremio_instance.md index 67d6afbff..afa80edf6 100644 --- a/docs/data-sources/dremio_instance.md +++ b/docs/data-sources/dremio_instance.md @@ -34,7 +34,6 @@ data "stackit_dremio_instance" "example" { ### Optional - `description` (String) The description is a longer text chosen by the user to provide more context for the resource. -- `error_message` (String) A message describing an actionable error the user can resolve. This field is empty if no such error exists. - `region` (String) The STACKIT region name the resource is located in. If not defined, the provider region is used. ### Read-Only @@ -43,7 +42,6 @@ data "stackit_dremio_instance" "example" { - `display_name` (String) The display name is a short name chosen by the user to identify the resource. - `endpoints` (Attributes) The available endpoints of the Dremio instance. (see [below for nested schema](#nestedatt--endpoints)) - `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`". -- `state` (String) The current state of the resource. ### Nested Schema for `authentication` diff --git a/docs/data-sources/dremio_user.md b/docs/data-sources/dremio_user.md index 2965c4ad3..4151e77ce 100644 --- a/docs/data-sources/dremio_user.md +++ b/docs/data-sources/dremio_user.md @@ -36,7 +36,6 @@ data "stackit_dremio_user" "example" { ### Optional - `description` (String) The description of the user. -- `error_message` (String) A message describing an actionable error the user can resolve. This field is empty if no such error exists. - `region` (String) The STACKIT region name the resource is located in. If not defined, the provider region is used. ### Read-Only @@ -46,4 +45,3 @@ data "stackit_dremio_user" "example" { - `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`,`user_id`". - `last_name` (String) The last name of the user. - `name` (String) The username of the user. -- `state` (String) The current state of the resource. diff --git a/docs/resources/dremio_instance.md b/docs/resources/dremio_instance.md index 11f1cfce5..151822405 100644 --- a/docs/resources/dremio_instance.md +++ b/docs/resources/dremio_instance.md @@ -63,7 +63,6 @@ import { ### Optional - `description` (String) The description is a longer text chosen by the user to provide more context for the resource. -- `error_message` (String) A message describing an actionable error the user can resolve. This field is empty if no such error exists. - `region` (String) The STACKIT region name the resource is located in. If not defined, the provider region is used. - `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) @@ -72,7 +71,6 @@ import { - `endpoints` (Attributes) The available endpoints of the Dremio instance. (see [below for nested schema](#nestedatt--endpoints)) - `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`". - `instance_id` (String) The Dremio instance ID. -- `state` (String) The current state of the resource. ### Nested Schema for `authentication` diff --git a/docs/resources/dremio_user.md b/docs/resources/dremio_user.md index c891c6d09..1405e2b69 100644 --- a/docs/resources/dremio_user.md +++ b/docs/resources/dremio_user.md @@ -50,14 +50,12 @@ import { ### Optional - `description` (String) The description of the user. -- `error_message` (String) A message describing an actionable error the user can resolve. This field is empty if no such error exists. - `region` (String) The STACKIT region name the resource is located in. If not defined, the provider region is used. - `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) ### Read-Only - `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`,`user_id`". -- `state` (String) The current state of the resource. - `user_id` (String) The Dremio user ID.