Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/data-sources/dremio_instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ data "stackit_dremio_instance" "example" {

### Read-Only

- `acl` (List of String) The Access Control List (ACL) defining the IPv4 or IPv6 CIDR ranges allowed to connect to the Dremio instance. An empty list allows access from any address.
- `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))
Expand Down
4 changes: 4 additions & 0 deletions docs/resources/dremio_instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ resource "stackit_dremio_instance" "example" {
region = "eu01"
display_name = "exampleName"
description = "Example description"
acl = [
"0.0.0.0/0"
]
authentication = {
type = "local-only" // "oauth" or "azuread" for IDP config

Expand Down Expand Up @@ -57,6 +60,7 @@ resource "stackit_dremio_instance" "example" {

### Optional

- `acl` (List of String) The Access Control List (ACL) defining the IPv4 or IPv6 CIDR ranges allowed to connect to the Dremio instance. An empty list allows access from any address.
- `description` (String) The description is a longer text chosen by the user to provide more context for the resource.
- `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))
Expand Down
3 changes: 3 additions & 0 deletions examples/resources/stackit_dremio_instance/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ resource "stackit_dremio_instance" "example" {
region = "eu01"
display_name = "exampleName"
description = "Example description"
acl = [
"0.0.0.0/0"
]
authentication = {
Comment thread
dtext marked this conversation as resolved.
type = "local-only" // "oauth" or "azuread" for IDP config

Expand Down
18 changes: 12 additions & 6 deletions stackit/internal/services/dremio/dremio_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ var testDremioConfigVarsMax = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId),
"region": config.StringVariable("eu01"),
// Instance
"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"),
"authentication_oauth_client_id": config.StringVariable("oauth-client-id"),
"authentication_oauth_client_secret": config.StringVariable("oauth-client-secret"),
"display_name": config.StringVariable("tfAccDremioMaxInstance"),
"description": config.StringVariable("description"),
"acl": config.ListVariable(config.StringVariable("0.0.0.0/0")),
"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"),
"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"),
Expand Down Expand Up @@ -284,6 +285,7 @@ func TestAccDremioInstanceMax(t *testing.T) {
resource.TestCheckResourceAttr(dremioInstanceResource, "region", testutil.Region),
resource.TestCheckResourceAttr(dremioInstanceResource, "display_name", testutil.ConvertConfigVariable(testDremioConfigVarsMax["display_name"])),
resource.TestCheckResourceAttr(dremioInstanceResource, "description", testutil.ConvertConfigVariable(testDremioConfigVarsMax["description"])),
testutil.CheckListAttr(dremioInstanceResource, "acl", testDremioConfigVarsMax["acl"]),

resource.TestCheckResourceAttr(dremioInstanceResource, "authentication.type", testutil.ConvertConfigVariable(testDremioConfigVarsMax["authentication_type"])),

Expand Down Expand Up @@ -343,6 +345,10 @@ func TestAccDremioInstanceMax(t *testing.T) {
dremioInstanceResource, "description",
dremioInstanceDataResource, "description",
),
resource.TestCheckResourceAttrPair(
dremioInstanceResource, "acl",
dremioInstanceDataResource, "acl",
),
resource.TestCheckResourceAttrPair(
dremioInstanceResource, "authentication.type",
dremioInstanceDataResource, "authentication.type",
Expand Down
9 changes: 7 additions & 2 deletions stackit/internal/services/dremio/instance/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"

dremioSdk "github.com/stackitcloud/stackit-sdk-go/services/dremio/v1betaapi"

"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/v1betaapi"
)

var (
Expand Down Expand Up @@ -220,6 +220,11 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
},
},
},
"acl": schema.ListAttribute{
ElementType: types.StringType,
Computed: true,
Description: descriptions["acl"],
},
"authentication": schema.SingleNestedAttribute{
Description: descriptions["authentication"],
Computed: true,
Expand Down
80 changes: 76 additions & 4 deletions stackit/internal/services/dremio/instance/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import (
"strings"

"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault"
"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"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"

Expand Down Expand Up @@ -48,6 +52,7 @@ type Model struct {

// Optional Fields
Description types.String `tfsdk:"description"`
Acl types.List `tfsdk:"acl"`

// Read-only Fields
Endpoints types.Object `tfsdk:"endpoints"` // see EndpointsModel
Expand Down Expand Up @@ -130,6 +135,7 @@ var descriptions = map[string]string{ //nolint:gosec // no hardcoded credentials
"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.",
"acl": "The Access Control List (ACL) defining the IPv4 or IPv6 CIDR ranges allowed to connect to the Dremio instance. An empty list allows access from any address.",
"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.",
Expand Down Expand Up @@ -253,6 +259,24 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest,
Description: descriptions["display_name"],
Required: true,
},
"acl": schema.ListAttribute{
Optional: true,
Computed: true, // Required when using Default
ElementType: types.StringType,
// The API returns an empty list for acl even if the field is omitted (null) in the request.
// This causes an "inconsistent result" error in Terraform, because the config is null
// but the state is [].
//
// By setting a Default value of an empty list, we tell Terraform to treat a missing
// acl block in the HCL as if the user explicitly defined acl = [].
// This ensures the config (empty list) matches the API response (empty list).
Default: listdefault.StaticValue(types.ListValueMust(types.StringType, []attr.Value{})),
Description: descriptions["acl"],
Validators: []validator.List{
listvalidator.NoNullValues(),
listvalidator.ValueStringsAre(validate.CIDR()),
},
},
"authentication": schema.SingleNestedAttribute{
Description: descriptions["authentication"],
Required: true,
Expand Down Expand Up @@ -411,7 +435,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(ctx, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err))
return
Expand Down Expand Up @@ -535,7 +559,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(ctx, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err))
return
Expand Down Expand Up @@ -702,9 +726,41 @@ func mapModelFields(ctx context.Context, instanceResp *dremioSdk.DremioResponse,
}
model.Endpoints = endpointsObj

acl, err := mapAcl(ctx, instanceResp.Acl, model.Acl)
if err != nil {
return err
}
model.Acl = acl

return nil
}

func mapAcl(ctx context.Context, apiAcl []string, modelAcl types.List) (basetypes.ListValue, error) {
var acl basetypes.ListValue
var diags diag.Diagnostics

if apiAcl == nil {
acl = types.ListNull(types.StringType)
} else {
var modelAclSlice []string

if !modelAcl.IsNull() && !modelAcl.IsUnknown() {
diags = modelAcl.ElementsAs(ctx, &modelAclSlice, false)
if diags.HasError() {
return basetypes.ListValue{}, fmt.Errorf("failed to parse acl: %w", core.DiagsToError(diags))
}
}
aclSlice := utils.ReconcileStringSlices(modelAclSlice, apiAcl)

acl, diags = types.ListValueFrom(ctx, types.StringType, aclSlice)
if diags.HasError() {
return basetypes.ListValue{}, fmt.Errorf("mapping ACL: %w", core.DiagsToError(diags))
}
}

Comment thread
Fyusel marked this conversation as resolved.
return acl, nil
}

func mapAuthentication(instanceResp *dremioSdk.DremioResponse, auth *AuthenticationModel) error {
if instanceResp == nil {
return fmt.Errorf("response input is nil")
Expand Down Expand Up @@ -757,7 +813,7 @@ func mapAuthentication(instanceResp *dremioSdk.DremioResponse, auth *Authenticat
}

// Build UpdateDremioInstancePayload from provider's model
func toUpdatePayload(model *InstanceModel) (*dremioSdk.UpdateDremioInstancePayload, error) {
func toUpdatePayload(ctx context.Context, model *InstanceModel) (*dremioSdk.UpdateDremioInstancePayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
Expand All @@ -767,15 +823,22 @@ func toUpdatePayload(model *InstanceModel) (*dremioSdk.UpdateDremioInstancePaylo
return nil, fmt.Errorf("failed to parse authentication: %w", err)
}

var acl []string
diags := model.Acl.ElementsAs(ctx, &acl, false)
if diags.HasError() {
return nil, fmt.Errorf("failed to parse acl: %v", diags)
}

return &dremioSdk.UpdateDremioInstancePayload{
Acl: acl,
Authentication: authentication,
Description: model.Description.ValueStringPointer(),
DisplayName: model.DisplayName.ValueStringPointer(),
}, nil
}

// Build CreateDremioInstancePayload from provider's model
func toCreatePayload(model *InstanceModel) (*dremioSdk.CreateDremioInstancePayload, error) {
func toCreatePayload(ctx context.Context, model *InstanceModel) (*dremioSdk.CreateDremioInstancePayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
Expand All @@ -785,7 +848,16 @@ func toCreatePayload(model *InstanceModel) (*dremioSdk.CreateDremioInstancePaylo
return nil, fmt.Errorf("failed to parse authentication: %w", err)
}

var acl []string
if !model.Acl.IsNull() && !model.Acl.IsUnknown() {
diags := model.Acl.ElementsAs(ctx, &acl, false)
if diags.HasError() {
return nil, fmt.Errorf("failed to parse acl: %v", diags)
}
}

return &dremioSdk.CreateDremioInstancePayload{
Acl: acl,
Authentication: authentication,
Description: model.Description.ValueStringPointer(),
DisplayName: model.DisplayName.ValueString(),
Expand Down
7 changes: 5 additions & 2 deletions stackit/internal/services/dremio/instance/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func TestMapFields(t *testing.T) {
CreateTime: time.Now(),
Description: utils.Ptr("minimal-required-values"),
DisplayName: "greatName",
Acl: []string{"0.0.0.0/0"},
Authentication: dremioSdk.Authentication{
Azuread: &dremioSdk.Azuread{
AuthorityUrl: "azure-authority",
Expand Down Expand Up @@ -75,6 +76,8 @@ func TestMapFields(t *testing.T) {
DisplayName: types.StringValue("greatName"),
Description: types.StringValue("minimal-required-values"),

Acl: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("0.0.0.0/0")}),

Comment on lines +79 to +80

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we claim to support ipv6, should we also add a test for that?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue probably not here because they're just strings as well. The only thing preventing this from being compatible with ipv6 would be if validate.CIDR() didn't pass for IPv6 CIDRs, but they're just using net.ParseCIDR() which documents IPv6 compatibility.

Endpoints: types.ObjectValueMust(
map[string]attr.Type{
"arrow_flight": types.StringType,
Expand Down Expand Up @@ -378,7 +381,7 @@ func TestToCreatePayload(t *testing.T) {

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
payload, err := toCreatePayload(tt.state)
payload, err := toCreatePayload(t.Context(), tt.state)
if (err != nil) != tt.wantErr {
t.Errorf("toCreatePayload error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down Expand Up @@ -609,7 +612,7 @@ func TestToUpdatePayload(t *testing.T) {

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
payload, err := toUpdatePayload(tt.state)
payload, err := toUpdatePayload(t.Context(), tt.state)
if (err != nil) != tt.wantErr {
t.Errorf("toUpdatePayload error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
2 changes: 2 additions & 0 deletions stackit/internal/services/dremio/testdata/resource-max.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ variable "region" {}
// Instance Variables
variable "display_name" {}
variable "description" {}
variable "acl" {}

variable "authentication_type" {}

Expand Down Expand Up @@ -36,6 +37,7 @@ resource "stackit_dremio_instance" "example" {
region = var.region
display_name = var.display_name
description = var.description
acl = var.acl
authentication = {
type = var.authentication_type

Expand Down