From f237ee4717f06050d1335555c70aba63088f6055 Mon Sep 17 00:00:00 2001 From: Or Bauberg Date: Thu, 23 Jan 2025 16:31:19 +0000 Subject: [PATCH 1/7] initial commit --- internal/provider/provider.go | 3 + internal/services/gateway/base.go | 20 ++ internal/services/gateway/base_test.go | 16 ++ .../gateway/data_on_permises_gateway.go | 227 ++++++++++++++++++ .../data_on_premises_gateway_personal.go | 133 ++++++++++ .../data_on_premises_gateway_personal_test.go | 118 +++++++++ .../gateway/data_on_premises_gateway_test.go | 166 +++++++++++++ .../gateway/data_virtual_network_gateway.go | 225 +++++++++++++++++ .../data_virtual_network_gateway_test.go | 166 +++++++++++++ .../gateway/data_virtual_network_gateways.go | 225 +++++++++++++++++ internal/services/gateway/models_gateway.go | 168 +++++++++++++ internal/testhelp/fakes/fabric_gateways.go | 189 +++++++++++++++ internal/testhelp/fakes/fake_server.go | 1 + internal/testhelp/utils.go | 5 + 14 files changed, 1662 insertions(+) create mode 100644 internal/services/gateway/base.go create mode 100644 internal/services/gateway/base_test.go create mode 100644 internal/services/gateway/data_on_permises_gateway.go create mode 100644 internal/services/gateway/data_on_premises_gateway_personal.go create mode 100644 internal/services/gateway/data_on_premises_gateway_personal_test.go create mode 100644 internal/services/gateway/data_on_premises_gateway_test.go create mode 100644 internal/services/gateway/data_virtual_network_gateway.go create mode 100644 internal/services/gateway/data_virtual_network_gateway_test.go create mode 100644 internal/services/gateway/data_virtual_network_gateways.go create mode 100644 internal/services/gateway/models_gateway.go create mode 100644 internal/testhelp/fakes/fabric_gateways.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 12b191b4..456d0728 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -41,6 +41,7 @@ import ( "github.com/microsoft/terraform-provider-fabric/internal/services/environment" "github.com/microsoft/terraform-provider-fabric/internal/services/eventhouse" "github.com/microsoft/terraform-provider-fabric/internal/services/eventstream" + "github.com/microsoft/terraform-provider-fabric/internal/services/gateway" "github.com/microsoft/terraform-provider-fabric/internal/services/kqldatabase" "github.com/microsoft/terraform-provider-fabric/internal/services/kqlqueryset" "github.com/microsoft/terraform-provider-fabric/internal/services/lakehouse" @@ -396,6 +397,8 @@ func (p *FabricProvider) DataSources(ctx context.Context) []func() datasource.Da func() datasource.DataSource { return eventhouse.NewDataSourceEventhouses(ctx) }, eventstream.NewDataSourceEventstream, eventstream.NewDataSourceEventstreams, + gateway.NewDataSourceVirtualNetworkGateway, + gateway.NewDataSourceOnPremisesGateway, kqldatabase.NewDataSourceKQLDatabase, kqldatabase.NewDataSourceKQLDatabases, kqlqueryset.NewDataSourceKQLQueryset, diff --git a/internal/services/gateway/base.go b/internal/services/gateway/base.go new file mode 100644 index 00000000..34465d1b --- /dev/null +++ b/internal/services/gateway/base.go @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "github.com/microsoft/terraform-provider-fabric/internal/common" +) + +const ( + OnPremisesItemTFType = "OnPremises" + OnPremisesPersonalItemType = "OnPremisesPersonal" + VirtualNetworkItemTFType = "VirtualNetwork" + VirtualNetworkItemsTFType = "VirtualNetworks" + ItemName = "Gateway" + ItemsName = "Gateways" + ItemsTFName = "gateways" + ItemDocsSPNSupport = common.DocsSPNSupported + ItemDocsURL = "https://learn.microsoft.com/en-us/fabric/data-factory/how-to-access-on-premises-data" +) diff --git a/internal/services/gateway/base_test.go b/internal/services/gateway/base_test.go new file mode 100644 index 00000000..a2f7c41d --- /dev/null +++ b/internal/services/gateway/base_test.go @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway_test + +import ( + "github.com/microsoft/terraform-provider-fabric/internal/services/gateway" +) + +const ( + virtualNetworkItemTFName = gateway.VirtualNetworkItemTFType + OnPremisesItemTFName = gateway.OnPremisesItemTFType + OnPremisesPersonalItemTFName = gateway.OnPremisesPersonalItemType + itemsTFName = gateway.ItemsTFName + itemType = gateway.ItemName +) diff --git a/internal/services/gateway/data_on_permises_gateway.go b/internal/services/gateway/data_on_permises_gateway.go new file mode 100644 index 00000000..6075c9d5 --- /dev/null +++ b/internal/services/gateway/data_on_permises_gateway.go @@ -0,0 +1,227 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-log/tflog" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" + pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" +) + +var ( + _ datasource.DataSourceWithConfigValidators = (*dataSourceOnPremisesGateway)(nil) + _ datasource.DataSourceWithConfigure = (*dataSourceOnPremisesGateway)(nil) +) + +type dataSourceOnPremisesGateway struct { + pConfigData *pconfig.ProviderData + client *fabcore.GatewaysClient +} + +func NewDataSourceOnPremisesGateway() datasource.DataSource { + return &dataSourceOnPremisesGateway{} +} + +func (d *dataSourceOnPremisesGateway) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + OnPremisesItemTFType +} + +func (d *dataSourceOnPremisesGateway) Schema( + ctx context.Context, + _ datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Get a Fabric " + ItemName + ".\n\n" + + "Use this data source to fetch [" + ItemName + "](" + ItemDocsURL + ").\n\n" + + ItemDocsSPNSupport, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s ID.", ItemName), + Optional: true, + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "display_name": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s display name.", ItemName), + Optional: true, + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s type.", ItemName), + Computed: true, + }, + "allow_cloud_connection_refresh": schema.BoolAttribute{ + MarkdownDescription: "Defines if cloud connection refresh is allowed.", + Computed: true, + }, + "allow_custom_connectors": schema.BoolAttribute{ + MarkdownDescription: "Defines if custom connectors are allowed.", + Computed: true, + }, + "load_balancing_setting": schema.StringAttribute{ + MarkdownDescription: "Gateway load balancing setting.", + Computed: true, + }, + "number_of_member_gateways": schema.NumberAttribute{ + MarkdownDescription: "The number of member gateways.", + Computed: true, + }, + "public_key": schema.SingleNestedAttribute{ + MarkdownDescription: "The public key settings.", + Computed: true, + CustomType: supertypes.NewSingleNestedObjectTypeOf[PublicKeyModel](ctx), + Attributes: map[string]schema.Attribute{ + "exponent": schema.StringAttribute{ + MarkdownDescription: "The RSA exponent.", + Computed: true, + }, + "modulus": schema.StringAttribute{ + MarkdownDescription: "The RSA modulus.", + Computed: true, + }, + }, + }, + "version": schema.StringAttribute{ + MarkdownDescription: "The gateway version.", + Computed: true, + }, + "timeouts": timeouts.Attributes(ctx), + }, + } +} + +func (d *dataSourceOnPremisesGateway) ConfigValidators(_ context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.Conflicting( + path.MatchRoot("id"), + path.MatchRoot("display_name"), + ), + datasourcevalidator.ExactlyOneOf( + path.MatchRoot("id"), + path.MatchRoot("display_name"), + ), + } +} + +// Configure adds the provider configured client to the data source. +func (d *dataSourceOnPremisesGateway) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { + resp.Diagnostics.AddError( + common.ErrorDataSourceConfigType, + fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), + ) + + return + } + + d.pConfigData = pConfigData + d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) +} + +// Read refreshes the Terraform state with the latest data. +func (d *dataSourceOnPremisesGateway) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "READ", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "READ", map[string]any{ + "config": req.Config, + }) + + var data OnPremisesGatewayModel + + if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := data.Timeouts.Read(ctx, d.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + if data.ID.ValueString() != "" { + diags = d.getByID(ctx, &data) + } else { + diags = d.getByDisplayName(ctx, &data) + } + + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) + + tflog.Debug(ctx, "READ", map[string]any{ + "action": "end", + }) + + if resp.Diagnostics.HasError() { + return + } +} + +func (d *dataSourceOnPremisesGateway) getByID(ctx context.Context, model *OnPremisesGatewayModel) diag.Diagnostics { + tflog.Trace(ctx, "GET BY ID", map[string]any{ + "id": model.ID.ValueString(), + }) + + respGet, err := d.client.GetGateway(ctx, model.ID.ValueString(), nil) + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, nil); diags.HasError() { + return diags + } + + if gw, ok := respGet.GatewayClassification.(*fabcore.OnPremisesGateway); ok { + model.set(ctx, *gw) + return nil + } else { + var diags diag.Diagnostics + diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") + return diags + } +} + +func (d *dataSourceOnPremisesGateway) getByDisplayName(ctx context.Context, model *OnPremisesGatewayModel) diag.Diagnostics { + tflog.Trace(ctx, fmt.Sprintf("getting %s by 'display_name'", ItemName)) + + gateways, err := d.client.ListGateways(ctx, nil) + + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, nil); diags.HasError() { + return diags + } + + for _, gw := range gateways { + if OnPremisesGateway, ok := gw.(*fabcore.OnPremisesGateway); ok { + if *OnPremisesGateway.DisplayName == model.DisplayName.ValueString() { + model.set(ctx, *OnPremisesGateway) + return nil + } + } + } + + var diags diag.Diagnostics + diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") + return diags +} diff --git a/internal/services/gateway/data_on_premises_gateway_personal.go b/internal/services/gateway/data_on_premises_gateway_personal.go new file mode 100644 index 00000000..558f85c4 --- /dev/null +++ b/internal/services/gateway/data_on_premises_gateway_personal.go @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" +) + +// dataSourceOnPremisesGatewayPersonal fetches the simplified on-prem gateway: OnPremisesGatewayPersonal. +type dataSourceOnPremisesGatewayPersonal struct { + pConfigData *pconfig.ProviderData + client *fabcore.GatewaysClient +} + +func NewDataSourceOnPremisesGatewayPersonal() datasource.DataSource { + return &dataSourceOnPremisesGatewayPersonal{} +} + +// Metadata sets the data source type name. +func (d *dataSourceOnPremisesGatewayPersonal) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_on_premises_gateway_personal" +} + +// Schema defines the attributes for OnPremisesGatewayPersonal. +func (d *dataSourceOnPremisesGatewayPersonal) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Retrieve an on-premises gateway in its 'personal' form (ID, public key, type, version).", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The gateway ID (UUID).", + Optional: true, + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "The type of the gateway.", + Computed: true, + }, + "version": schema.StringAttribute{ + MarkdownDescription: "The gateway version.", + Computed: true, + }, + "public_key": schema.SingleNestedAttribute{ + MarkdownDescription: "The public key settings of the gateway.", + Computed: true, + CustomType: supertypes.NewSingleNestedObjectTypeOf[PublicKeyModel](ctx), + Attributes: map[string]schema.Attribute{ + "exponent": schema.StringAttribute{ + MarkdownDescription: "The RSA exponent.", + Computed: true, + }, + "modulus": schema.StringAttribute{ + MarkdownDescription: "The RSA modulus.", + Computed: true, + }, + }, + }, + }, + } +} + +// Configure stores provider data and creates a new GatewaysClient. +func (d *dataSourceOnPremisesGatewayPersonal) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { + resp.Diagnostics.AddError( + common.ErrorDataSourceConfigType, + fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), + ) + return + } + d.pConfigData = pConfigData + d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) +} + +// Read fetches the OnPremisesGateway from the Fabric service, then maps to the personal model. +func (d *dataSourceOnPremisesGatewayPersonal) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data OnPremisesGatewayPersonalModel + + // Parse config into data model + if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + // If no ID was set, we can return early or handle lookups by name, etc. Here, handle if ID is blank + if data.ID.ValueString() == "" { + resp.Diagnostics.AddError( + "Missing ID", + "An ID is required to look up a personal on-premises gateway.", + ) + return + } + + // Do the actual GET call + gatewayResp, errResp := d.client.GetGateway(ctx, data.ID.ValueString(), nil) + if errResp != nil { + resp.Diagnostics.AddError("GetGateway failed", errResp.Error()) + return + } + + // Type-assert to OnPremisesGateway + realGw, ok := gatewayResp.GatewayClassification.(*fabcore.OnPremisesGateway) + if !ok { + resp.Diagnostics.AddError("Unexpected Gateway Type", "Result is not an OnPremisesGateway.") + return + } + + // Map the returned gateway to the personal model + gateway := OnPremisesGatewayPersonalModel{} + diags := gateway.set(ctx, *realGw) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + // Save data back into state + if diags := resp.State.Set(ctx, gateway); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } +} diff --git a/internal/services/gateway/data_on_premises_gateway_personal_test.go b/internal/services/gateway/data_on_premises_gateway_personal_test.go new file mode 100644 index 00000000..de03e7db --- /dev/null +++ b/internal/services/gateway/data_on_premises_gateway_personal_test.go @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway_test + +import ( + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testDataSourceOnPremisesPresonalItemFabricFQN = testhelp.DataSourceFQN("fabric", OnPremisesItemTFName, "test") + testDataSourceOnPremisesPersonalItemHeader = at.DataSourceHeader(testhelp.TypeName("fabric", OnPremisesItemTFName), "test") +) + +func TestUnit_OnPremisesGatewayPersonalDataSource(t *testing.T) { + entity := fakes.NewRandomOnPermisesGatewayPersonal() + + fakes.FakeServer.Upsert(fakes.NewRandomOnPermisesGatewayPersonal()) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomOnPermisesGatewayPersonal()) + + resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + // error - no attributes + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalItemHeader, + map[string]any{}, + ), + ExpectError: regexp.MustCompile(`Exactly one of these attributes must be configured: \[id,display_name\]`), + }, + // error - id - invalid UUID + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalItemHeader, + map[string]any{ + "id": "invalid uuid", + }, + ), + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + // error - unexpected attribute + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalItemHeader, + map[string]any{ + "id": *entity.ID, + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // read by id - not found + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalItemHeader, + map[string]any{ + "id": testhelp.RandomUUID(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // read by id + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalItemHeader, + map[string]any{ + "id": *entity.ID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceOnPremisesPresonalItemFabricFQN, "id", *entity.ID), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesPresonalItemFabricFQN, "public_key.exponent"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesPresonalItemFabricFQN, "public_key.modulus"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesPresonalItemFabricFQN, "version"), + ), + }, + })) +} + +func TestAcc_OnPremisesGatewayPersonalDataSource(t *testing.T) { + entity := testhelp.WellKnown()["OnPermisesGatewayPersonal"].(map[string]any) + entityID := entity["id"].(string) + entityDescription := entity["description"].(string) + + resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalItemHeader, + map[string]any{ + "id": entityID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceOnPremisesPresonalItemFabricFQN, "id", entityID), + resource.TestCheckResourceAttr(testDataSourceOnPremisesPresonalItemFabricFQN, "description", entityDescription), + ), + }, + // read by id - not found + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalItemHeader, + map[string]any{ + "id": testhelp.RandomUUID(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + })) +} diff --git a/internal/services/gateway/data_on_premises_gateway_test.go b/internal/services/gateway/data_on_premises_gateway_test.go new file mode 100644 index 00000000..dd70291a --- /dev/null +++ b/internal/services/gateway/data_on_premises_gateway_test.go @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway_test + +import ( + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testDataSourceOnPremisesItemFabricFQN = testhelp.DataSourceFQN("fabric", OnPremisesItemTFName, "test") + testDataSourceOnPremisesItemHeader = at.DataSourceHeader(testhelp.TypeName("fabric", OnPremisesItemTFName), "test") +) + +func TestUnit_OnPremisesGatewayDataSource(t *testing.T) { + entity := fakes.NewRandomOnPremisesGateway() + + fakes.FakeServer.Upsert(fakes.NewRandomOnPremisesGateway()) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomOnPremisesGateway()) + + resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + // error - no attributes + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{}, + ), + ExpectError: regexp.MustCompile(`Exactly one of these attributes must be configured: \[id,display_name\]`), + }, + // error - id - invalid UUID + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "id": "invalid uuid", + }, + ), + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + // error - unexpected attribute + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "id": *entity.ID, + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // error - conflicting attributes + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "id": *entity.ID, + "display_name": *entity.DisplayName, + }, + ), + ExpectError: regexp.MustCompile(`These attributes cannot be configured together: \[id,display_name\]`), + }, + // read by id - not found + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "id": testhelp.RandomUUID(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // read by id + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "id": *entity.ID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "id", *entity.ID), + resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "display_name", *entity.DisplayName), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "allow_cloud_connection_refresh"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "allow_custom_connectors"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "load_balancing_setting"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "number_of_member_gateways"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "public_key.exponent"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "public_key.modulus"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "version"), + ), + }, + // read by name - not found + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "display_name": testhelp.RandomName(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // read by name + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "display_name": *entity.DisplayName, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "id", *entity.ID), + resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "display_name", *entity.DisplayName), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "allow_cloud_connection_refresh"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "allow_custom_connectors"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "load_balancing_setting"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "number_of_member_gateways"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "public_key.exponent"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "public_key0.modulus"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "version"), + ), + }, + })) +} + +func TestAcc_OnPremisesGatewayDataSource(t *testing.T) { + entity := testhelp.WellKnown()["OnPermisesGateway"].(map[string]any) + entityID := entity["id"].(string) + entityDisplayName := entity["displayName"].(string) + entityDescription := entity["description"].(string) + + resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "id": entityID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "id", entityID), + resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "display_name", entityDisplayName), + resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "description", entityDescription), + ), + }, + // read by id - not found + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "id": testhelp.RandomUUID(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + })) +} diff --git a/internal/services/gateway/data_virtual_network_gateway.go b/internal/services/gateway/data_virtual_network_gateway.go new file mode 100644 index 00000000..ff1793f2 --- /dev/null +++ b/internal/services/gateway/data_virtual_network_gateway.go @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-log/tflog" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" + pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" +) + +var ( + _ datasource.DataSourceWithConfigValidators = (*dataSourceVirtualNetworkGateway)(nil) + _ datasource.DataSourceWithConfigure = (*dataSourceVirtualNetworkGateway)(nil) +) + +type dataSourceVirtualNetworkGateway struct { + pConfigData *pconfig.ProviderData + client *fabcore.GatewaysClient +} + +func NewDataSourceVirtualNetworkGateway() datasource.DataSource { + return &dataSourceVirtualNetworkGateway{} +} + +func (d *dataSourceVirtualNetworkGateway) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + VirtualNetworkItemTFType +} + +func (d *dataSourceVirtualNetworkGateway) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Get a Fabric " + ItemName + ".\n\n" + + "Use this data source to fetch [" + ItemName + "](" + ItemDocsURL + ").\n\n" + + ItemDocsSPNSupport, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s ID.", ItemName), + Optional: true, + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "display_name": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s display name.", ItemName), + Optional: true, + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s.", ItemName), + Computed: true, + }, + "capacity_id": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s capacity Id.", ItemName), + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "inactivity_minutes_before_sleep": schema.NumberAttribute{ + MarkdownDescription: "The number of minutes of inactivity before the gateway goes to sleep.", + Computed: true, + }, + "number_of_member_gateways": schema.NumberAttribute{ + MarkdownDescription: "The number of member gateways.", + Computed: true, + }, + "virtual_network_azure_resource": schema.SingleNestedAttribute{ + MarkdownDescription: "The Azure resource of the virtual network.", + Computed: true, + CustomType: supertypes.NewSingleNestedObjectTypeOf[VirtualNetworkAzureResourceModel](ctx), + Attributes: map[string]schema.Attribute{ + "subscription_id": schema.StringAttribute{ + MarkdownDescription: "The subscription ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "resource_group_name": schema.StringAttribute{ + MarkdownDescription: "The name of the resource group.", + Computed: true, + }, + "virtual_network_name": schema.StringAttribute{ + MarkdownDescription: "The name of the virtual network.", + Computed: true, + }, + "subnet_name": schema.StringAttribute{ + MarkdownDescription: "The name of the subnet.", + Computed: true, + }, + }, + }, + "timeouts": timeouts.Attributes(ctx), + }, + } +} + +func (d *dataSourceVirtualNetworkGateway) ConfigValidators(_ context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.Conflicting( + path.MatchRoot("id"), + path.MatchRoot("display_name"), + ), + datasourcevalidator.ExactlyOneOf( + path.MatchRoot("id"), + path.MatchRoot("display_name"), + ), + } +} + +// Configure adds the provider configured client to the data source. +func (d *dataSourceVirtualNetworkGateway) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { + resp.Diagnostics.AddError( + common.ErrorDataSourceConfigType, + fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), + ) + + return + } + + d.pConfigData = pConfigData + d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) +} + +// Read refreshes the Terraform state with the latest data. +func (d *dataSourceVirtualNetworkGateway) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "READ", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "READ", map[string]any{ + "config": req.Config, + }) + + var data VirtualNetworkGatewayModel + + if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := data.Timeouts.Read(ctx, d.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + if data.ID.ValueString() != "" { + diags = d.getByID(ctx, &data) + } else { + diags = d.getByDisplayName(ctx, &data) + } + + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) + + tflog.Debug(ctx, "READ", map[string]any{ + "action": "end", + }) + + if resp.Diagnostics.HasError() { + return + } +} + +func (d *dataSourceVirtualNetworkGateway) getByID(ctx context.Context, model *VirtualNetworkGatewayModel) diag.Diagnostics { + tflog.Trace(ctx, "GET BY ID", map[string]any{ + "id": model.ID.ValueString(), + }) + + respGet, err := d.client.GetGateway(ctx, model.ID.ValueString(), nil) + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, nil); diags.HasError() { + return diags + } + + if gw, ok := respGet.GatewayClassification.(*fabcore.VirtualNetworkGateway); ok { + model.set(ctx, *gw) + return nil + } else { + var diags diag.Diagnostics + diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") + return diags + } +} + +func (d *dataSourceVirtualNetworkGateway) getByDisplayName(ctx context.Context, model *VirtualNetworkGatewayModel) diag.Diagnostics { + tflog.Trace(ctx, fmt.Sprintf("getting %s by 'display_name'", ItemName)) + + gateways, err := d.client.ListGateways(ctx, nil) + + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, nil); diags.HasError() { + return diags + } + + for _, gw := range gateways { + if virtualNetworkGateway, ok := gw.(*fabcore.VirtualNetworkGateway); ok { + if *virtualNetworkGateway.DisplayName == model.DisplayName.ValueString() { + model.set(ctx, *virtualNetworkGateway) + return nil + } + } + } + + var diags diag.Diagnostics + diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") + return diags +} diff --git a/internal/services/gateway/data_virtual_network_gateway_test.go b/internal/services/gateway/data_virtual_network_gateway_test.go new file mode 100644 index 00000000..8f53f5f4 --- /dev/null +++ b/internal/services/gateway/data_virtual_network_gateway_test.go @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway_test + +import ( + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testDataSourceVirtualNetworkFQN = testhelp.DataSourceFQN("fabric", virtualNetworkItemTFName, "test") + testVirtualNetworkDataSourceHeader = at.DataSourceHeader(testhelp.TypeName("fabric", virtualNetworkItemTFName), "test") +) + +func TestUnit_VirtualNetworkGatewayDataSource(t *testing.T) { + entity := fakes.NewRandomVirtualNetworkGateway() + + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + + resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + // error - no attributes + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{}, + ), + ExpectError: regexp.MustCompile(`Exactly one of these attributes must be configured: \[id,display_name\]`), + }, + // error - id - invalid UUID + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{ + "id": "invalid uuid", + }, + ), + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + // error - unexpected attribute + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{ + "id": *entity.ID, + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // error - conflicting attributes + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{ + "id": *entity.ID, + "display_name": *entity.DisplayName, + }, + ), + ExpectError: regexp.MustCompile(`These attributes cannot be configured together: \[id,display_name\]`), + }, + // read by id - not found + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{ + "id": testhelp.RandomUUID(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // read by id + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{ + "id": *entity.ID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "id", *entity.ID), + resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "display_name", *entity.DisplayName), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "inactivity_minutes_before_sleep"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "capacity_id"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "number_of_member_gateways"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.subscription_id"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.resource_group_name"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.virtual_network_name"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.subnet_name"), + ), + }, + // read by name - not found + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{ + "display_name": testhelp.RandomName(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // read by name + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{ + "display_name": *entity.DisplayName, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "id", *entity.ID), + resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "display_name", *entity.DisplayName), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "inactivity_minutes_before_sleep"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "capacity_id"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "number_of_member_gateways"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.subscription_id"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.resource_group_name"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.virtual_network_name"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.subnet_name"), + ), + }, + })) +} + +func TestAcc_VirtualNetworkGatewayDataSource(t *testing.T) { + entity := testhelp.WellKnown()["VirtualNetworkGateway"].(map[string]any) + entityID := entity["id"].(string) + entityDisplayName := entity["displayName"].(string) + entityDescription := entity["description"].(string) + + resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{ + "id": entityID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "id", entityID), + resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "display_name", entityDisplayName), + resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "description", entityDescription), + ), + }, + // read by id - not found + { + Config: at.CompileConfig( + testVirtualNetworkDataSourceHeader, + map[string]any{ + "id": testhelp.RandomUUID(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + })) +} diff --git a/internal/services/gateway/data_virtual_network_gateways.go b/internal/services/gateway/data_virtual_network_gateways.go new file mode 100644 index 00000000..ec228030 --- /dev/null +++ b/internal/services/gateway/data_virtual_network_gateways.go @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-log/tflog" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" + pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" +) + +var ( + _ datasource.DataSourceWithConfigValidators = (*dataSourceVirtualNetworkGateways)(nil) + _ datasource.DataSourceWithConfigure = (*dataSourceVirtualNetworkGateways)(nil) +) + +type dataSourceVirtualNetworkGateways struct { + pConfigData *pconfig.ProviderData + client *fabcore.GatewaysClient +} + +func NewDataSourceVirtualNetworkGateways() datasource.DataSource { + return &dataSourceVirtualNetworkGateways{} +} + +func (d *dataSourceVirtualNetworkGateways) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + VirtualNetworkItemTFType +} + +func (d *dataSourceVirtualNetworkGateways) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Get a Fabric " + ItemName + ".\n\n" + + "Use this data source to fetch [" + ItemName + "](" + ItemDocsURL + ").\n\n" + + ItemDocsSPNSupport, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s ID.", ItemName), + Optional: true, + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "display_name": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s display name.", ItemName), + Optional: true, + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The type of the %s.", ItemName), + Computed: true, + }, + "capacity_id": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("The %s capacity Id.", ItemName), + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "inactivity_minutes_before_sleep": schema.NumberAttribute{ + MarkdownDescription: "The number of minutes of inactivity before the gateway goes to sleep.", + Computed: true, + }, + "number_of_member_gateways": schema.NumberAttribute{ + MarkdownDescription: "The number of member gateways.", + Computed: true, + }, + "virtual_network_azure_resource": schema.SingleNestedAttribute{ + MarkdownDescription: "The Azure resource of the virtual network.", + Computed: true, + CustomType: supertypes.NewSingleNestedObjectTypeOf[VirtualNetworkAzureResourceModel](ctx), + Attributes: map[string]schema.Attribute{ + "subscription_id": schema.StringAttribute{ + MarkdownDescription: "The subscription ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "resource_group_name": schema.StringAttribute{ + MarkdownDescription: "The name of the resource group.", + Computed: true, + }, + "virtual_network_name": schema.StringAttribute{ + MarkdownDescription: "The name of the virtual network.", + Computed: true, + }, + "subnet_name": schema.StringAttribute{ + MarkdownDescription: "The name of the subnet.", + Computed: true, + }, + }, + }, + "timeouts": timeouts.Attributes(ctx), + }, + } +} + +func (d *dataSourceVirtualNetworkGateways) ConfigValidators(_ context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.Conflicting( + path.MatchRoot("id"), + path.MatchRoot("display_name"), + ), + datasourcevalidator.ExactlyOneOf( + path.MatchRoot("id"), + path.MatchRoot("display_name"), + ), + } +} + +// Configure adds the provider configured client to the data source. +func (d *dataSourceVirtualNetworkGateways) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { + resp.Diagnostics.AddError( + common.ErrorDataSourceConfigType, + fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), + ) + + return + } + + d.pConfigData = pConfigData + d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) +} + +// Read refreshes the Terraform state with the latest data. +func (d *dataSourceVirtualNetworkGateways) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "READ", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "READ", map[string]any{ + "config": req.Config, + }) + + var data VirtualNetworkGatewayModel + + if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := data.Timeouts.Read(ctx, d.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + if data.ID.ValueString() != "" { + diags = d.getByID(ctx, &data) + } else { + diags = d.getByDisplayName(ctx, &data) + } + + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) + + tflog.Debug(ctx, "READ", map[string]any{ + "action": "end", + }) + + if resp.Diagnostics.HasError() { + return + } +} + +func (d *dataSourceVirtualNetworkGateways) getByID(ctx context.Context, model *VirtualNetworkGatewayModel) diag.Diagnostics { + tflog.Trace(ctx, "GET BY ID", map[string]any{ + "id": model.ID.ValueString(), + }) + + respGet, err := d.client.GetGateway(ctx, model.ID.ValueString(), nil) + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, nil); diags.HasError() { + return diags + } + + if gw, ok := respGet.GatewayClassification.(*fabcore.VirtualNetworkGateway); ok { + model.set(ctx, *gw) + return nil + } else { + var diags diag.Diagnostics + diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") + return diags + } +} + +func (d *dataSourceVirtualNetworkGateways) getByDisplayName(ctx context.Context, model *VirtualNetworkGatewayModel) diag.Diagnostics { + tflog.Trace(ctx, fmt.Sprintf("getting %s by 'display_name'", ItemName)) + + gateways, err := d.client.ListGateways(ctx, nil) + + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, nil); diags.HasError() { + return diags + } + + for _, gw := range gateways { + if virtualNetworkGateway, ok := gw.(*fabcore.VirtualNetworkGateway); ok { + if *virtualNetworkGateway.DisplayName == model.DisplayName.ValueString() { + model.set(ctx, *virtualNetworkGateway) + return nil + } + } + } + + var diags diag.Diagnostics + diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") + return diags +} diff --git a/internal/services/gateway/models_gateway.go b/internal/services/gateway/models_gateway.go new file mode 100644 index 00000000..3536b301 --- /dev/null +++ b/internal/services/gateway/models_gateway.go @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type GatewayModelBase struct { + ID customtypes.UUID `tfsdk:"id"` + DisplayName types.String `tfsdk:"display_name"` + Type types.String `tfsdk:"type"` +} + +type OnPremisesGatewayModel struct { + GatewayModelBase + + AllowCloudConnectionRefresh *bool `tfsdk:"allow_cloud_connection_refresh"` + + AllowCustomConnectors *bool `tfsdk:"allow_custom_connectors"` + + LoadBalancingSetting types.String `tfsdk:"load_balancing_setting"` + + NumberOfMemberGateways *int32 `tfsdk:"number_of_member_gateways"` + + PublicKey supertypes.SingleNestedObjectValueOf[PublicKeyModel] `tfsdk:"public_key"` + + Version types.String `tfsdk:"version"` + + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type VirtualNetworkGatewayModel struct { + GatewayModelBase + + CapacityId customtypes.UUID `tfsdk:"capacity_id"` + + InactivityMinutesBeforeSleep *int32 `tfsdk:"inactivity_minutes_before_sleep"` + + NumberOfMemberGateways *int32 `tfsdk:"number_of_member_gateways"` + + VirtualNetworkAzureResource supertypes.SingleNestedObjectValueOf[VirtualNetworkAzureResourceModel] `tfsdk:"virtual_network_azure_resource"` + + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type OnPremisesGatewayPersonalModel struct { + ID customtypes.UUID `tfsdk:"id"` + PublicKey supertypes.SingleNestedObjectValueOf[PublicKeyModel] `tfsdk:"public_key"` + Type types.String `tfsdk:"type"` + Version types.String `tfsdk:"version"` +} + +type VirtualNetworkAzureResourceModel struct { + SubscriptionID customtypes.UUID `tfsdk:"subscription_id"` + + ResourceGroupName types.String `tfsdk:"resource_group_name"` + + VirtualNetworkName types.String `tfsdk:"virtual_network_name"` + + SubnetName types.String `tfsdk:"subnet_name"` +} + +type PublicKeyModel struct { + Exponent types.String `tfsdk:"exponent"` + + Modulus types.String `tfsdk:"modulus"` +} + +func (to *OnPremisesGatewayPersonalModel) set(ctx context.Context, from fabcore.OnPremisesGateway) diag.Diagnostics { + var diags diag.Diagnostics + to.ID = customtypes.NewUUIDPointerValue(from.ID) + + publicKey := supertypes.NewSingleNestedObjectValueOfNull[PublicKeyModel](ctx) + if from.PublicKey != nil { + publicKeyModel := &PublicKeyModel{} + publicKeyModel.set(*from.PublicKey) + + if pkDiags := publicKey.Set(ctx, publicKeyModel); pkDiags.HasError() { + diags.Append(pkDiags...) + return diags + } + } + to.PublicKey = publicKey + + to.Type = types.StringPointerValue((*string)(from.Type)) + to.Version = types.StringPointerValue(from.Version) + + return diags +} + +func (to *OnPremisesGatewayModel) set(ctx context.Context, from fabcore.OnPremisesGateway) diag.Diagnostics { + to.ID = customtypes.NewUUIDPointerValue(from.ID) + to.DisplayName = types.StringPointerValue(from.DisplayName) + to.Type = types.StringPointerValue((*string)(from.Type)) + to.AllowCloudConnectionRefresh = from.AllowCloudConnectionRefresh + to.AllowCustomConnectors = from.AllowCustomConnectors + to.LoadBalancingSetting = types.StringPointerValue((*string)(from.LoadBalancingSetting)) + to.NumberOfMemberGateways = from.NumberOfMemberGateways + to.Version = types.StringPointerValue(from.Version) + + publicKey := supertypes.NewSingleNestedObjectValueOfNull[PublicKeyModel](ctx) + + if from.PublicKey != nil { + publicKeyModel := &PublicKeyModel{} + publicKeyModel.set(*from.PublicKey) + + if diags := publicKey.Set(ctx, publicKeyModel); diags.HasError() { + return diags + } + } + + to.PublicKey = publicKey + + return nil +} + +func (to *VirtualNetworkGatewayModel) set(ctx context.Context, from fabcore.VirtualNetworkGateway) diag.Diagnostics { + to.ID = customtypes.NewUUIDPointerValue(from.ID) + to.DisplayName = types.StringPointerValue(from.DisplayName) + to.Type = types.StringPointerValue((*string)(from.Type)) + to.CapacityId = customtypes.NewUUIDPointerValue(from.CapacityID) + to.InactivityMinutesBeforeSleep = from.InactivityMinutesBeforeSleep + to.NumberOfMemberGateways = from.NumberOfMemberGateways + + virtualNetworkAzureResource := supertypes.NewSingleNestedObjectValueOfNull[VirtualNetworkAzureResourceModel](ctx) + + if from.VirtualNetworkAzureResource != nil { + virtualNetworkAzureResourceModel := &VirtualNetworkAzureResourceModel{} + virtualNetworkAzureResourceModel.set(*from.VirtualNetworkAzureResource) + + if diags := virtualNetworkAzureResource.Set(ctx, virtualNetworkAzureResourceModel); diags.HasError() { + return diags + } + } + + to.VirtualNetworkAzureResource = virtualNetworkAzureResource + + return nil +} + +func (to *VirtualNetworkAzureResourceModel) set(from fabcore.VirtualNetworkAzureResource) { + to.SubscriptionID = customtypes.NewUUIDPointerValue(from.SubscriptionID) + to.ResourceGroupName = types.StringPointerValue(from.ResourceGroupName) + to.VirtualNetworkName = types.StringPointerValue(from.VirtualNetworkName) + to.SubnetName = types.StringPointerValue(from.SubnetName) +} + +// should I change my blablabla? +// func (to *GatewayModelBase) set(from fabcore.Gateway) { +// to.ID = customtypes.NewUUIDPointerValue(from.ID) +// to.Type = types.StringPointerValue((*string)(from.Type)) +// } + +func (to *PublicKeyModel) set(from fabcore.PublicKey) { + to.Exponent = types.StringPointerValue(from.Exponent) + to.Modulus = types.StringPointerValue(from.Modulus) +} diff --git a/internal/testhelp/fakes/fabric_gateways.go b/internal/testhelp/fakes/fabric_gateways.go new file mode 100644 index 00000000..88317d99 --- /dev/null +++ b/internal/testhelp/fakes/fabric_gateways.go @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package fakes + +import ( + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + fabfake "github.com/microsoft/fabric-sdk-go/fabric/fake" + + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" +) + +// operationsGateway implements SimpleIDOperations. +type operationsGateway struct{} + +// Create implements concreteEntityOperations. +func (o *operationsGateway) Create(data fabcore.CreateGatewayRequestClassification) fabcore.GatewayClassification { + switch gateway := data.(type) { + case *fabcore.CreateVirtualNetworkGatewayRequest: + returnGateway := NewRandomVirtualNetworkGateway() + returnGateway.DisplayName = gateway.DisplayName + returnGateway.CapacityID = gateway.CapacityID + returnGateway.InactivityMinutesBeforeSleep = gateway.InactivityMinutesBeforeSleep + returnGateway.NumberOfMemberGateways = gateway.NumberOfMemberGateways + returnGateway.VirtualNetworkAzureResource = gateway.VirtualNetworkAzureResource + return &returnGateway + default: + panic("unimplemented") + } +} + +// GetID implements concreteEntityOperations. +func (o *operationsGateway) GetID(entity fabcore.GatewayClassification) string { + return *entity.GetGateway().ID +} + +// TransformGet implements concreteEntityOperations. +func (o *operationsGateway) TransformGet(entity fabcore.GatewayClassification) fabcore.GatewaysClientGetGatewayResponse { + return fabcore.GatewaysClientGetGatewayResponse{ + GatewayClassification: entity, + } +} + +// TransformList implements concreteEntityOperations. +func (o *operationsGateway) TransformList(list []fabcore.GatewayClassification) fabcore.GatewaysClientListGatewaysResponse { + return fabcore.GatewaysClientListGatewaysResponse{ + ListGatewaysResponse: fabcore.ListGatewaysResponse{ + Value: list, + }, + } +} + +// TransformUpdate implements concreteEntityOperations. +func (o *operationsGateway) TransformUpdate(entity fabcore.GatewayClassification) fabcore.GatewaysClientUpdateGatewayResponse { + return fabcore.GatewaysClientUpdateGatewayResponse{ + GatewayClassification: entity, + } +} + +// Update implements concreteEntityOperations. +func (o *operationsGateway) Update(base fabcore.GatewayClassification, data fabcore.UpdateGatewayRequestClassification) fabcore.GatewayClassification { + switch request := data.(type) { + case *fabcore.UpdateVirtualNetworkGatewayRequest: + gateway, _ := base.(*fabcore.VirtualNetworkGateway) + gateway.CapacityID = request.CapacityID + gateway.DisplayName = request.DisplayName + gateway.InactivityMinutesBeforeSleep = request.InactivityMinutesBeforeSleep + gateway.NumberOfMemberGateways = request.NumberOfMemberGateways + return gateway + case *fabcore.UpdateOnPremisesGatewayRequest: + gateway, _ := base.(*fabcore.OnPremisesGateway) + gateway.AllowCloudConnectionRefresh = request.AllowCloudConnectionRefresh + gateway.AllowCustomConnectors = request.AllowCustomConnectors + gateway.LoadBalancingSetting = request.LoadBalancingSetting + gateway.DisplayName = request.DisplayName + return gateway + default: + panic("unimplemented") + } +} + +// Validate implements concreteEntityOperations. +func (o *operationsGateway) Validate(newEntity fabcore.GatewayClassification, existing []fabcore.GatewayClassification) (statusCode int, err error) { + for _, existingGateway := range existing { + if existingGateway.GetGateway().Type != newEntity.GetGateway().Type { + continue + } + switch gateway := newEntity.(type) { + case *fabcore.VirtualNetworkGateway: + vng := existingGateway.(*fabcore.VirtualNetworkGateway) + if *vng.DisplayName == *gateway.DisplayName { + return http.StatusConflict, fabfake.SetResponseError(http.StatusConflict, fabcore.ErrWorkspace.WorkspaceNameAlreadyExists.Error(), fabcore.ErrWorkspace.WorkspaceNameAlreadyExists.Error()) + } + case *fabcore.OnPremisesGateway: + opg := existingGateway.(*fabcore.OnPremisesGateway) + if *opg.DisplayName == *gateway.DisplayName { + return http.StatusConflict, fabfake.SetResponseError(http.StatusConflict, fabcore.ErrWorkspace.WorkspaceNameAlreadyExists.Error(), fabcore.ErrWorkspace.WorkspaceNameAlreadyExists.Error()) + } + } + } + + return http.StatusCreated, nil +} + +// TransformCreate implements concreteEntityOperations. +func (o *operationsGateway) TransformCreate(entity fabcore.GatewayClassification) fabcore.GatewaysClientCreateGatewayResponse { + return fabcore.GatewaysClientCreateGatewayResponse{ + GatewayClassification: entity, + } +} + +func configureGateway(server *fakeServer) fabcore.VirtualNetworkGateway { + type concreteEntityOperations interface { + simpleIDOperations[ + fabcore.GatewayClassification, + fabcore.GatewaysClientGetGatewayResponse, + fabcore.GatewaysClientUpdateGatewayResponse, + fabcore.GatewaysClientCreateGatewayResponse, + fabcore.GatewaysClientListGatewaysResponse, + fabcore.CreateGatewayRequestClassification, + fabcore.UpdateGatewayRequestClassification, + ] + } + + var entityOperations concreteEntityOperations = &operationsGateway{} + + handler := newTypedHandler(server, entityOperations) + + handleGetWithSimpleID(handler, entityOperations, &handler.ServerFactory.Core.GatewaysServer.GetGateway) + handleUpdateWithSimpleID(handler, entityOperations, entityOperations, &handler.ServerFactory.Core.GatewaysServer.UpdateGateway) + handleCreate(handler, entityOperations, entityOperations, entityOperations, &handler.ServerFactory.Core.GatewaysServer.CreateGateway) + handleDeleteWithSimpleID(handler, &handler.ServerFactory.Core.GatewaysServer.DeleteGateway) + + handleListPager( + handler, + entityOperations, + &handler.ServerFactory.Core.GatewaysServer.NewListGatewaysPager) + + return fabcore.VirtualNetworkGateway{} +} + +func NewRandomVirtualNetworkGateway() fabcore.VirtualNetworkGateway { + return fabcore.VirtualNetworkGateway{ + ID: to.Ptr(testhelp.RandomUUID()), + DisplayName: to.Ptr(testhelp.RandomName()), + InactivityMinutesBeforeSleep: to.Ptr(testhelp.RandomInt32(100)), + NumberOfMemberGateways: to.Ptr(testhelp.RandomInt32(100)), + CapacityID: to.Ptr(testhelp.RandomUUID()), + Type: to.Ptr(fabcore.GatewayTypeVirtualNetwork), + VirtualNetworkAzureResource: &fabcore.VirtualNetworkAzureResource{ + SubscriptionID: to.Ptr(testhelp.RandomUUID()), + ResourceGroupName: to.Ptr(testhelp.RandomName()), + VirtualNetworkName: to.Ptr(testhelp.RandomName()), + SubnetName: to.Ptr(testhelp.RandomName()), + }, + } +} + +func NewRandomOnPremisesGateway() fabcore.OnPremisesGateway { + return fabcore.OnPremisesGateway{ + ID: to.Ptr(testhelp.RandomUUID()), + DisplayName: to.Ptr(testhelp.RandomName()), + NumberOfMemberGateways: to.Ptr(testhelp.RandomInt32(100)), + Type: to.Ptr(fabcore.GatewayTypeOnPremises), + AllowCloudConnectionRefresh: to.Ptr(true), + AllowCustomConnectors: to.Ptr(false), + LoadBalancingSetting: to.Ptr(fabcore.LoadBalancingSettingDistributeEvenly), + PublicKey: &fabcore.PublicKey{ + Exponent: to.Ptr(testhelp.RandomName()), + Modulus: to.Ptr(testhelp.RandomName()), + }, + Version: to.Ptr("1.0"), + } +} + +func NewRandomOnPermisesGatewayPersonal() fabcore.OnPremisesGatewayPersonal { + return fabcore.OnPremisesGatewayPersonal{ + ID: to.Ptr(testhelp.RandomUUID()), + Type: to.Ptr(fabcore.GatewayTypeOnPremisesPersonal), + Version: to.Ptr("1.0"), + PublicKey: &fabcore.PublicKey{ + Exponent: to.Ptr(testhelp.RandomName()), + Modulus: to.Ptr(testhelp.RandomName()), + }, + } +} diff --git a/internal/testhelp/fakes/fake_server.go b/internal/testhelp/fakes/fake_server.go index b6f671e9..f58d5a1a 100644 --- a/internal/testhelp/fakes/fake_server.go +++ b/internal/testhelp/fakes/fake_server.go @@ -35,6 +35,7 @@ func newFakeServer() *fakeServer { handleEntity(server, configureDomain) handleEntity(server, configureEventhouse) handleEntity(server, configureEnvironment) + handleEntity(server, configureGateway) handleEntity(server, configureKQLDatabase) handleEntity(server, configureLakehouse) handleEntity(server, configureNotebook) diff --git a/internal/testhelp/utils.go b/internal/testhelp/utils.go index 167b1d45..e2ce3265 100644 --- a/internal/testhelp/utils.go +++ b/internal/testhelp/utils.go @@ -9,6 +9,7 @@ import ( "encoding/pem" "errors" "fmt" + "math/rand" "strings" "github.com/hashicorp/go-uuid" @@ -50,6 +51,10 @@ func RandomP12Cert(password string) []byte { return p12 } +func RandomInt32(max int32) int32 { + return rand.Int31n(max) +} + func createP12Bundle(certPEMStr, privateKeyPEMStr, password string) ([]byte, error) { // Decode the private key PEM block block, _ := pem.Decode([]byte(privateKeyPEMStr)) From 02f523957c3603ceb2838b0c1a00e9005affd3fd Mon Sep 17 00:00:00 2001 From: Or Bauberg Date: Wed, 29 Jan 2025 15:52:29 +0000 Subject: [PATCH 2/7] push for pablo --- internal/services/gateway/base.go | 20 +- internal/services/gateway/base_test.go | 5 +- ...gateway.go => data_on_premises_gateway.go} | 8 +- .../data_on_premises_gateway_personal.go | 20 +- .../data_on_premises_gateway_personal_test.go | 8 +- .../gateway/data_on_premises_gateway_test.go | 2 +- .../gateway/data_on_premises_gateways.go | 131 +++++++++++ .../data_on_premises_gateways_personal.go | 137 ++++++++++++ .../gateway/data_on_premises_gateways_test.go | 74 +++++++ .../gateway/data_virtual_network_gateway.go | 8 +- .../data_virtual_network_gateway_test.go | 4 +- .../gateway/data_virtual_network_gateways.go | 205 ++++++------------ .../data_virtual_network_gateways_test.go | 74 +++++++ ...gateway.go => models_base_data_gateway.go} | 51 +++-- .../services/gateway/models_data_gateway.go | 26 +++ .../services/gateway/models_data_gateways.go | 101 +++++++++ .../gateway/models_resource_gateway.go | 12 + .../resource_virtual_network_gateway.go | 1 + internal/testhelp/fakes/fabric_gateways.go | 2 +- 19 files changed, 690 insertions(+), 199 deletions(-) rename internal/services/gateway/{data_on_permises_gateway.go => data_on_premises_gateway.go} (97%) create mode 100644 internal/services/gateway/data_on_premises_gateways.go create mode 100644 internal/services/gateway/data_on_premises_gateways_personal.go create mode 100644 internal/services/gateway/data_on_premises_gateways_test.go create mode 100644 internal/services/gateway/data_virtual_network_gateways_test.go rename internal/services/gateway/{models_gateway.go => models_base_data_gateway.go} (75%) create mode 100644 internal/services/gateway/models_data_gateway.go create mode 100644 internal/services/gateway/models_data_gateways.go create mode 100644 internal/services/gateway/models_resource_gateway.go create mode 100644 internal/services/gateway/resource_virtual_network_gateway.go diff --git a/internal/services/gateway/base.go b/internal/services/gateway/base.go index 34465d1b..8e2cd3d1 100644 --- a/internal/services/gateway/base.go +++ b/internal/services/gateway/base.go @@ -8,13 +8,15 @@ import ( ) const ( - OnPremisesItemTFType = "OnPremises" - OnPremisesPersonalItemType = "OnPremisesPersonal" - VirtualNetworkItemTFType = "VirtualNetwork" - VirtualNetworkItemsTFType = "VirtualNetworks" - ItemName = "Gateway" - ItemsName = "Gateways" - ItemsTFName = "gateways" - ItemDocsSPNSupport = common.DocsSPNSupported - ItemDocsURL = "https://learn.microsoft.com/en-us/fabric/data-factory/how-to-access-on-premises-data" + OnPremisesItemTFType = "OnPremisesGateway" + OnPremisesItemsTFType = "OnPremisesGateways" + OnPremisesPersonalItemType = "OnPremisesPersonalGateway" + OnPremisesPersonalItemsType = "OnPremisesPersonalGateways" + VirtualNetworkItemTFType = "VirtualNetworkGateway" + VirtualNetworkItemsTFType = "VirtualNetworkGateways" + ItemName = "Gateway" + ItemsName = "Gateways" + ItemsTFName = "gateways" + ItemDocsSPNSupport = common.DocsSPNSupported + ItemDocsURL = "https://learn.microsoft.com/en-us/fabric/data-factory/how-to-access-on-premises-data" ) diff --git a/internal/services/gateway/base_test.go b/internal/services/gateway/base_test.go index a2f7c41d..bbf45249 100644 --- a/internal/services/gateway/base_test.go +++ b/internal/services/gateway/base_test.go @@ -8,9 +8,12 @@ import ( ) const ( - virtualNetworkItemTFName = gateway.VirtualNetworkItemTFType + VirtualNetworkItemTFName = gateway.VirtualNetworkItemTFType + VirtualNetworkItemsTFName = gateway.VirtualNetworkItemsTFType OnPremisesItemTFName = gateway.OnPremisesItemTFType + OnPremisesItemsTFName = gateway.OnPremisesItemsTFType OnPremisesPersonalItemTFName = gateway.OnPremisesPersonalItemType + OnPremisesPersonalItemsType = gateway.OnPremisesPersonalItemsType itemsTFName = gateway.ItemsTFName itemType = gateway.ItemName ) diff --git a/internal/services/gateway/data_on_permises_gateway.go b/internal/services/gateway/data_on_premises_gateway.go similarity index 97% rename from internal/services/gateway/data_on_permises_gateway.go rename to internal/services/gateway/data_on_premises_gateway.go index 6075c9d5..ef7cfe95 100644 --- a/internal/services/gateway/data_on_permises_gateway.go +++ b/internal/services/gateway/data_on_premises_gateway.go @@ -85,7 +85,7 @@ func (d *dataSourceOnPremisesGateway) Schema( "public_key": schema.SingleNestedAttribute{ MarkdownDescription: "The public key settings.", Computed: true, - CustomType: supertypes.NewSingleNestedObjectTypeOf[PublicKeyModel](ctx), + CustomType: supertypes.NewSingleNestedObjectTypeOf[publicKeyModel](ctx), Attributes: map[string]schema.Attribute{ "exponent": schema.StringAttribute{ MarkdownDescription: "The RSA exponent.", @@ -148,7 +148,7 @@ func (d *dataSourceOnPremisesGateway) Read(ctx context.Context, req datasource.R "config": req.Config, }) - var data OnPremisesGatewayModel + var data datasourceOnPremisesGatewayModel if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { return @@ -183,7 +183,7 @@ func (d *dataSourceOnPremisesGateway) Read(ctx context.Context, req datasource.R } } -func (d *dataSourceOnPremisesGateway) getByID(ctx context.Context, model *OnPremisesGatewayModel) diag.Diagnostics { +func (d *dataSourceOnPremisesGateway) getByID(ctx context.Context, model *datasourceOnPremisesGatewayModel) diag.Diagnostics { tflog.Trace(ctx, "GET BY ID", map[string]any{ "id": model.ID.ValueString(), }) @@ -203,7 +203,7 @@ func (d *dataSourceOnPremisesGateway) getByID(ctx context.Context, model *OnPrem } } -func (d *dataSourceOnPremisesGateway) getByDisplayName(ctx context.Context, model *OnPremisesGatewayModel) diag.Diagnostics { +func (d *dataSourceOnPremisesGateway) getByDisplayName(ctx context.Context, model *datasourceOnPremisesGatewayModel) diag.Diagnostics { tflog.Trace(ctx, fmt.Sprintf("getting %s by 'display_name'", ItemName)) gateways, err := d.client.ListGateways(ctx, nil) diff --git a/internal/services/gateway/data_on_premises_gateway_personal.go b/internal/services/gateway/data_on_premises_gateway_personal.go index 558f85c4..86ad2644 100644 --- a/internal/services/gateway/data_on_premises_gateway_personal.go +++ b/internal/services/gateway/data_on_premises_gateway_personal.go @@ -19,7 +19,6 @@ import ( pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" ) -// dataSourceOnPremisesGatewayPersonal fetches the simplified on-prem gateway: OnPremisesGatewayPersonal. type dataSourceOnPremisesGatewayPersonal struct { pConfigData *pconfig.ProviderData client *fabcore.GatewaysClient @@ -29,12 +28,10 @@ func NewDataSourceOnPremisesGatewayPersonal() datasource.DataSource { return &dataSourceOnPremisesGatewayPersonal{} } -// Metadata sets the data source type name. func (d *dataSourceOnPremisesGatewayPersonal) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_on_premises_gateway_personal" } -// Schema defines the attributes for OnPremisesGatewayPersonal. func (d *dataSourceOnPremisesGatewayPersonal) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: "Retrieve an on-premises gateway in its 'personal' form (ID, public key, type, version).", @@ -56,7 +53,7 @@ func (d *dataSourceOnPremisesGatewayPersonal) Schema(ctx context.Context, _ data "public_key": schema.SingleNestedAttribute{ MarkdownDescription: "The public key settings of the gateway.", Computed: true, - CustomType: supertypes.NewSingleNestedObjectTypeOf[PublicKeyModel](ctx), + CustomType: supertypes.NewSingleNestedObjectTypeOf[publicKeyModel](ctx), Attributes: map[string]schema.Attribute{ "exponent": schema.StringAttribute{ MarkdownDescription: "The RSA exponent.", @@ -72,7 +69,6 @@ func (d *dataSourceOnPremisesGatewayPersonal) Schema(ctx context.Context, _ data } } -// Configure stores provider data and creates a new GatewaysClient. func (d *dataSourceOnPremisesGatewayPersonal) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) if !ok { @@ -86,15 +82,13 @@ func (d *dataSourceOnPremisesGatewayPersonal) Configure(ctx context.Context, req d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) } -// Read fetches the OnPremisesGateway from the Fabric service, then maps to the personal model. func (d *dataSourceOnPremisesGatewayPersonal) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data OnPremisesGatewayPersonalModel + var data datasourceOnPremisesGatewayPersonalModel - // Parse config into data model if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { return } - // If no ID was set, we can return early or handle lookups by name, etc. Here, handle if ID is blank + if data.ID.ValueString() == "" { resp.Diagnostics.AddError( "Missing ID", @@ -110,15 +104,13 @@ func (d *dataSourceOnPremisesGatewayPersonal) Read(ctx context.Context, req data return } - // Type-assert to OnPremisesGateway - realGw, ok := gatewayResp.GatewayClassification.(*fabcore.OnPremisesGateway) + realGw, ok := gatewayResp.GatewayClassification.(*fabcore.OnPremisesGatewayPersonal) if !ok { - resp.Diagnostics.AddError("Unexpected Gateway Type", "Result is not an OnPremisesGateway.") + resp.Diagnostics.AddError("Unexpected Gateway Type", "Result is not an OnPremisesGatewayPersonal.") return } - // Map the returned gateway to the personal model - gateway := OnPremisesGatewayPersonalModel{} + gateway := datasourceOnPremisesGatewayPersonalModel{} diags := gateway.set(ctx, *realGw) if diags.HasError() { resp.Diagnostics.Append(diags...) diff --git a/internal/services/gateway/data_on_premises_gateway_personal_test.go b/internal/services/gateway/data_on_premises_gateway_personal_test.go index de03e7db..195a32cc 100644 --- a/internal/services/gateway/data_on_premises_gateway_personal_test.go +++ b/internal/services/gateway/data_on_premises_gateway_personal_test.go @@ -22,11 +22,11 @@ var ( ) func TestUnit_OnPremisesGatewayPersonalDataSource(t *testing.T) { - entity := fakes.NewRandomOnPermisesGatewayPersonal() + entity := fakes.NewRandomOnPremisesGatewayPersonal() - fakes.FakeServer.Upsert(fakes.NewRandomOnPermisesGatewayPersonal()) + fakes.FakeServer.Upsert(fakes.NewRandomOnPremisesGatewayPersonal()) fakes.FakeServer.Upsert(entity) - fakes.FakeServer.Upsert(fakes.NewRandomOnPermisesGatewayPersonal()) + fakes.FakeServer.Upsert(fakes.NewRandomOnPremisesGatewayPersonal()) resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ // error - no attributes @@ -87,7 +87,7 @@ func TestUnit_OnPremisesGatewayPersonalDataSource(t *testing.T) { } func TestAcc_OnPremisesGatewayPersonalDataSource(t *testing.T) { - entity := testhelp.WellKnown()["OnPermisesGatewayPersonal"].(map[string]any) + entity := testhelp.WellKnown()["OnPremisesGatewayPersonal"].(map[string]any) entityID := entity["id"].(string) entityDescription := entity["description"].(string) diff --git a/internal/services/gateway/data_on_premises_gateway_test.go b/internal/services/gateway/data_on_premises_gateway_test.go index dd70291a..c289790a 100644 --- a/internal/services/gateway/data_on_premises_gateway_test.go +++ b/internal/services/gateway/data_on_premises_gateway_test.go @@ -133,7 +133,7 @@ func TestUnit_OnPremisesGatewayDataSource(t *testing.T) { } func TestAcc_OnPremisesGatewayDataSource(t *testing.T) { - entity := testhelp.WellKnown()["OnPermisesGateway"].(map[string]any) + entity := testhelp.WellKnown()["OnPremisesGateway"].(map[string]any) entityID := entity["id"].(string) entityDisplayName := entity["displayName"].(string) entityDescription := entity["description"].(string) diff --git a/internal/services/gateway/data_on_premises_gateways.go b/internal/services/gateway/data_on_premises_gateways.go new file mode 100644 index 00000000..7b3fa217 --- /dev/null +++ b/internal/services/gateway/data_on_premises_gateways.go @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-log/tflog" + + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" + pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" +) + +// Ensure it implements the interface. +var _ datasource.DataSourceWithConfigure = (*dataSourceOnPremisesGateways)(nil) + +// dataSourceOnPremisesGateways is analogous to data_virtual_network_gateways.go, but for on-premises gateways (plural). +type dataSourceOnPremisesGateways struct { + pConfigData *pconfig.ProviderData + client *fabcore.GatewaysClient +} + +func NewDataSourceOnPremisesGateways() datasource.DataSource { + return &dataSourceOnPremisesGateways{} +} + +func (d *dataSourceOnPremisesGateways) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_on_premises_gateways" +} + +func (d *dataSourceOnPremisesGateways) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "List all Fabric on-premises gateways.", + Attributes: map[string]schema.Attribute{ + "values": schema.ListNestedAttribute{ + Computed: true, + MarkdownDescription: "A list of on-premises gateways.", + CustomType: supertypes.NewListNestedObjectTypeOf[onPremisesGatewayModelBase](ctx), + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The on-premises gateway ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "display_name": schema.StringAttribute{ + MarkdownDescription: "The display name of the on-premises gateway.", + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "The on-premises gateway type.", + Computed: true, + }, + "allow_cloud_connection_refresh": schema.BoolAttribute{ + MarkdownDescription: "Allow cloud connection refresh.", + Computed: true, + }, + // Add any other on-premises gateway fields as needed, + // staying consistent with onPremisesGatewayModelBase. + }, + }, + }, + "timeouts": timeouts.Attributes(ctx), + }, + } +} + +func (d *dataSourceOnPremisesGateways) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { + resp.Diagnostics.AddError( + common.ErrorDataSourceConfigType, + fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), + ) + return + } + + d.pConfigData = pConfigData + d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) +} + +func (d *dataSourceOnPremisesGateways) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "READ-ALL-On-Premises-Gateways", map[string]any{"action": "start"}) + + var data dataSourceOnPremisesGatewaysModel + if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := data.Timeouts.Read(ctx, d.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + if resp.Diagnostics.Append(d.list(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) + tflog.Debug(ctx, "READ-ALL-On-Premises-Gateways", map[string]any{"action": "end"}) +} + +// list retrieves all gateways from the Fabric SDK and filters only the on-premises gateway ones. +func (d *dataSourceOnPremisesGateways) list(ctx context.Context, model *dataSourceOnPremisesGatewaysModel) diag.Diagnostics { + tflog.Trace(ctx, "Listing all on-premises gateways") + + gatewaysResp, err := d.client.ListGateways(ctx, nil) + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationList, nil); diags.HasError() { + return diags + } + + return model.setValues(ctx, gatewaysResp) +} diff --git a/internal/services/gateway/data_on_premises_gateways_personal.go b/internal/services/gateway/data_on_premises_gateways_personal.go new file mode 100644 index 00000000..d8144ef5 --- /dev/null +++ b/internal/services/gateway/data_on_premises_gateways_personal.go @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-log/tflog" + + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" + pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" +) + +// dataSourceOnPremisesGatewayPersonals is the "plural" version of data_on_premises_gateway_personal.go. +type dataSourceOnPremisesGatewayPersonals struct { + pConfigData *pconfig.ProviderData + client *fabcore.GatewaysClient +} + +func NewDataSourceOnPremisesGatewayPersonals() datasource.DataSource { + return &dataSourceOnPremisesGatewayPersonals{} +} + +func (d *dataSourceOnPremisesGatewayPersonals) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + // e.g.: fabric_on_premises_gateway_personals + resp.TypeName = req.ProviderTypeName + "_on_premises_gateway_personals" +} + +func (d *dataSourceOnPremisesGatewayPersonals) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "List all on-premises personal gateways.", + Attributes: map[string]schema.Attribute{ + "values": schema.ListNestedAttribute{ + Computed: true, + MarkdownDescription: "A list of on-premises personal gateways.", + CustomType: supertypes.NewListNestedObjectTypeOf[onPremisesGatewayPersonalModelBase](ctx), + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The on-premises personal gateway ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "public_key": schema.SingleNestedAttribute{ + MarkdownDescription: "The public key settings.", + Computed: true, + CustomType: supertypes.NewSingleNestedObjectTypeOf[publicKeyModel](ctx), + Attributes: map[string]schema.Attribute{ + "exponent": schema.StringAttribute{ + MarkdownDescription: "RSA exponent.", + Computed: true, + }, + "modulus": schema.StringAttribute{ + MarkdownDescription: "RSA modulus.", + Computed: true, + }, + }, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "The on-premises personal gateway type.", + Computed: true, + }, + "version": schema.StringAttribute{ + MarkdownDescription: "The personal gateway version.", + Computed: true, + }, + }, + }, + }, + "timeouts": timeouts.Attributes(ctx), + }, + } +} + +func (d *dataSourceOnPremisesGatewayPersonals) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { + resp.Diagnostics.AddError( + common.ErrorDataSourceConfigType, + fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), + ) + return + } + + d.pConfigData = pConfigData + d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) +} + +func (d *dataSourceOnPremisesGatewayPersonals) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "READ-ALL-On-Premises-Gateway-Personals", map[string]any{"action": "start"}) + + var data dataSourceOnPremisesGatewayPersonalsModel + if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := data.Timeouts.Read(ctx, d.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + if resp.Diagnostics.Append(d.list(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) + tflog.Debug(ctx, "READ-ALL-On-Premises-Gateway-Personals", map[string]any{"action": "end"}) +} + +func (d *dataSourceOnPremisesGatewayPersonals) list(ctx context.Context, model *dataSourceOnPremisesGatewayPersonalsModel) diag.Diagnostics { + tflog.Trace(ctx, "Listing all on-premises personal gateways") + + allItems, err := d.client.ListGateways(ctx, nil) + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationList, nil); diags.HasError() { + return diags + } + + return model.setValues(ctx, allItems) +} diff --git a/internal/services/gateway/data_on_premises_gateways_test.go b/internal/services/gateway/data_on_premises_gateways_test.go new file mode 100644 index 00000000..91ed3699 --- /dev/null +++ b/internal/services/gateway/data_on_premises_gateways_test.go @@ -0,0 +1,74 @@ +package gateway_test + +import ( + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testDataSourceOnPremisesGatewaysFQN = testhelp.DataSourceFQN("fabric", OnPremisesItemsTFName, "test") + testDataSourceOnPremisesGatewaysHeader = at.DataSourceHeader(testhelp.TypeName("fabric", OnPremisesItemsTFName), "test") +) + +func TestUnit_OnPremisesGatewaysDataSource(t *testing.T) { + entity := fakes.NewRandomOnPremisesGateway() + + fakes.FakeServer.Upsert(fakes.NewRandomOnPremisesGateway()) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomOnPremisesGateway()) + + resource.ParallelTest(t, testhelp.NewTestUnitCase( + t, + nil, + fakes.FakeServer.ServerFactory, + nil, + []resource.TestStep{ + // Step to ensure an unexpected attribute triggers an error. + { + Config: at.CompileConfig( + testDataSourceOnPremisesGatewaysHeader, + map[string]any{ + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // A normal read test with an empty config. + { + Config: at.CompileConfig( + testDataSourceOnPremisesGatewaysHeader, + map[string]any{}, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesGatewaysFQN, "values.0.id"), + ), + }, + }, + )) +} + +func TestAcc_OnPremisesGatewaysDataSource(t *testing.T) { + resource.ParallelTest(t, testhelp.NewTestAccCase( + t, + nil, + nil, + []resource.TestStep{ + // read + { + Config: at.CompileConfig( + testDataSourceOnPremisesGatewaysHeader, + map[string]any{}, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesGatewaysFQN, "values.0.id"), + ), + }, + }, + )) +} diff --git a/internal/services/gateway/data_virtual_network_gateway.go b/internal/services/gateway/data_virtual_network_gateway.go index ff1793f2..ee096a79 100644 --- a/internal/services/gateway/data_virtual_network_gateway.go +++ b/internal/services/gateway/data_virtual_network_gateway.go @@ -78,7 +78,7 @@ func (d *dataSourceVirtualNetworkGateway) Schema(ctx context.Context, _ datasour "virtual_network_azure_resource": schema.SingleNestedAttribute{ MarkdownDescription: "The Azure resource of the virtual network.", Computed: true, - CustomType: supertypes.NewSingleNestedObjectTypeOf[VirtualNetworkAzureResourceModel](ctx), + CustomType: supertypes.NewSingleNestedObjectTypeOf[virtualNetworkAzureResourceModel](ctx), Attributes: map[string]schema.Attribute{ "subscription_id": schema.StringAttribute{ MarkdownDescription: "The subscription ID.", @@ -146,7 +146,7 @@ func (d *dataSourceVirtualNetworkGateway) Read(ctx context.Context, req datasour "config": req.Config, }) - var data VirtualNetworkGatewayModel + var data datasourceVirtualNetworkGatewayModel if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { return @@ -181,7 +181,7 @@ func (d *dataSourceVirtualNetworkGateway) Read(ctx context.Context, req datasour } } -func (d *dataSourceVirtualNetworkGateway) getByID(ctx context.Context, model *VirtualNetworkGatewayModel) diag.Diagnostics { +func (d *dataSourceVirtualNetworkGateway) getByID(ctx context.Context, model *datasourceVirtualNetworkGatewayModel) diag.Diagnostics { tflog.Trace(ctx, "GET BY ID", map[string]any{ "id": model.ID.ValueString(), }) @@ -201,7 +201,7 @@ func (d *dataSourceVirtualNetworkGateway) getByID(ctx context.Context, model *Vi } } -func (d *dataSourceVirtualNetworkGateway) getByDisplayName(ctx context.Context, model *VirtualNetworkGatewayModel) diag.Diagnostics { +func (d *dataSourceVirtualNetworkGateway) getByDisplayName(ctx context.Context, model *datasourceVirtualNetworkGatewayModel) diag.Diagnostics { tflog.Trace(ctx, fmt.Sprintf("getting %s by 'display_name'", ItemName)) gateways, err := d.client.ListGateways(ctx, nil) diff --git a/internal/services/gateway/data_virtual_network_gateway_test.go b/internal/services/gateway/data_virtual_network_gateway_test.go index 8f53f5f4..3e331f93 100644 --- a/internal/services/gateway/data_virtual_network_gateway_test.go +++ b/internal/services/gateway/data_virtual_network_gateway_test.go @@ -17,8 +17,8 @@ import ( ) var ( - testDataSourceVirtualNetworkFQN = testhelp.DataSourceFQN("fabric", virtualNetworkItemTFName, "test") - testVirtualNetworkDataSourceHeader = at.DataSourceHeader(testhelp.TypeName("fabric", virtualNetworkItemTFName), "test") + testDataSourceVirtualNetworkFQN = testhelp.DataSourceFQN("fabric", VirtualNetworkItemTFName, "test") + testVirtualNetworkDataSourceHeader = at.DataSourceHeader(testhelp.TypeName("fabric", VirtualNetworkItemTFName), "test") ) func TestUnit_VirtualNetworkGatewayDataSource(t *testing.T) { diff --git a/internal/services/gateway/data_virtual_network_gateways.go b/internal/services/gateway/data_virtual_network_gateways.go index ec228030..94fb7b43 100644 --- a/internal/services/gateway/data_virtual_network_gateways.go +++ b/internal/services/gateway/data_virtual_network_gateways.go @@ -1,6 +1,3 @@ -// Copyright (c) Microsoft Corporation -// SPDX-License-Identifier: MPL-2.0 - package gateway import ( @@ -8,12 +5,11 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" - "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-log/tflog" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" @@ -23,10 +19,7 @@ import ( pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" ) -var ( - _ datasource.DataSourceWithConfigValidators = (*dataSourceVirtualNetworkGateways)(nil) - _ datasource.DataSourceWithConfigure = (*dataSourceVirtualNetworkGateways)(nil) -) +var _ datasource.DataSourceWithConfigure = (*dataSourceVirtualNetworkGateways)(nil) type dataSourceVirtualNetworkGateways struct { pConfigData *pconfig.ProviderData @@ -38,64 +31,69 @@ func NewDataSourceVirtualNetworkGateways() datasource.DataSource { } func (d *dataSourceVirtualNetworkGateways) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_" + VirtualNetworkItemTFType + resp.TypeName = req.ProviderTypeName + "_virtual_network_gateways" } func (d *dataSourceVirtualNetworkGateways) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Get a Fabric " + ItemName + ".\n\n" + - "Use this data source to fetch [" + ItemName + "](" + ItemDocsURL + ").\n\n" + - ItemDocsSPNSupport, + MarkdownDescription: "List all Fabric Virtual Network Gateways.", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - MarkdownDescription: fmt.Sprintf("The %s ID.", ItemName), - Optional: true, - Computed: true, - CustomType: customtypes.UUIDType{}, - }, - "display_name": schema.StringAttribute{ - MarkdownDescription: fmt.Sprintf("The %s display name.", ItemName), - Optional: true, - Computed: true, - }, - "type": schema.StringAttribute{ - MarkdownDescription: fmt.Sprintf("The type of the %s.", ItemName), - Computed: true, - }, - "capacity_id": schema.StringAttribute{ - MarkdownDescription: fmt.Sprintf("The %s capacity Id.", ItemName), - Computed: true, - CustomType: customtypes.UUIDType{}, - }, - "inactivity_minutes_before_sleep": schema.NumberAttribute{ - MarkdownDescription: "The number of minutes of inactivity before the gateway goes to sleep.", + "values": schema.ListNestedAttribute{ Computed: true, - }, - "number_of_member_gateways": schema.NumberAttribute{ - MarkdownDescription: "The number of member gateways.", - Computed: true, - }, - "virtual_network_azure_resource": schema.SingleNestedAttribute{ - MarkdownDescription: "The Azure resource of the virtual network.", - Computed: true, - CustomType: supertypes.NewSingleNestedObjectTypeOf[VirtualNetworkAzureResourceModel](ctx), - Attributes: map[string]schema.Attribute{ - "subscription_id": schema.StringAttribute{ - MarkdownDescription: "The subscription ID.", - Computed: true, - CustomType: customtypes.UUIDType{}, - }, - "resource_group_name": schema.StringAttribute{ - MarkdownDescription: "The name of the resource group.", - Computed: true, - }, - "virtual_network_name": schema.StringAttribute{ - MarkdownDescription: "The name of the virtual network.", - Computed: true, - }, - "subnet_name": schema.StringAttribute{ - MarkdownDescription: "The name of the subnet.", - Computed: true, + MarkdownDescription: "A list of Virtual Network Gateways.", + CustomType: supertypes.NewListNestedObjectTypeOf[virtualNetworkGatewayModelBase](ctx), + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The gateway ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "display_name": schema.StringAttribute{ + MarkdownDescription: "The display name of the gateway.", + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "The gateway type.", + Computed: true, + }, + "capacity_id": schema.StringAttribute{ + MarkdownDescription: "The Fabric license capacity ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "inactivity_minutes_before_sleep": schema.Int64Attribute{ + MarkdownDescription: "Minutes of inactivity before auto-sleep.", + Computed: true, + }, + "number_of_member_gateways": schema.Int64Attribute{ + MarkdownDescription: "The number of member gateways.", + Computed: true, + }, + "virtual_network_azure_resource": schema.SingleNestedAttribute{ + MarkdownDescription: "The Azure resource details for this gateway's Virtual Network.", + Computed: true, + CustomType: supertypes.NewSingleNestedObjectTypeOf[virtualNetworkAzureResourceModel](ctx), + Attributes: map[string]schema.Attribute{ + "subscription_id": schema.StringAttribute{ + MarkdownDescription: "The subscription ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "resource_group_name": schema.StringAttribute{ + MarkdownDescription: "The name of the resource group.", + Computed: true, + }, + "virtual_network_name": schema.StringAttribute{ + MarkdownDescription: "The name of the virtual network.", + Computed: true, + }, + "subnet_name": schema.StringAttribute{ + MarkdownDescription: "The name of the subnet.", + Computed: true, + }, + }, + }, }, }, }, @@ -104,20 +102,6 @@ func (d *dataSourceVirtualNetworkGateways) Schema(ctx context.Context, _ datasou } } -func (d *dataSourceVirtualNetworkGateways) ConfigValidators(_ context.Context) []datasource.ConfigValidator { - return []datasource.ConfigValidator{ - datasourcevalidator.Conflicting( - path.MatchRoot("id"), - path.MatchRoot("display_name"), - ), - datasourcevalidator.ExactlyOneOf( - path.MatchRoot("id"), - path.MatchRoot("display_name"), - ), - } -} - -// Configure adds the provider configured client to the data source. func (d *dataSourceVirtualNetworkGateways) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { if req.ProviderData == nil { return @@ -129,7 +113,6 @@ func (d *dataSourceVirtualNetworkGateways) Configure(_ context.Context, req data common.ErrorDataSourceConfigType, fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), ) - return } @@ -137,17 +120,10 @@ func (d *dataSourceVirtualNetworkGateways) Configure(_ context.Context, req data d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) } -// Read refreshes the Terraform state with the latest data. func (d *dataSourceVirtualNetworkGateways) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - tflog.Debug(ctx, "READ", map[string]any{ - "action": "start", - }) - tflog.Trace(ctx, "READ", map[string]any{ - "config": req.Config, - }) - - var data VirtualNetworkGatewayModel + tflog.Debug(ctx, "READ-ALL-Virtual-Network-Gateways", map[string]any{"action": "start"}) + var data dataSourceVirtualNetworkGatewaysModel if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { return } @@ -160,66 +136,21 @@ func (d *dataSourceVirtualNetworkGateways) Read(ctx context.Context, req datasou ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - if data.ID.ValueString() != "" { - diags = d.getByID(ctx, &data) - } else { - diags = d.getByDisplayName(ctx, &data) - } - - if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + if resp.Diagnostics.Append(d.list(ctx, &data)...); resp.Diagnostics.HasError() { return } resp.Diagnostics.Append(resp.State.Set(ctx, data)...) - - tflog.Debug(ctx, "READ", map[string]any{ - "action": "end", - }) - - if resp.Diagnostics.HasError() { - return - } -} - -func (d *dataSourceVirtualNetworkGateways) getByID(ctx context.Context, model *VirtualNetworkGatewayModel) diag.Diagnostics { - tflog.Trace(ctx, "GET BY ID", map[string]any{ - "id": model.ID.ValueString(), - }) - - respGet, err := d.client.GetGateway(ctx, model.ID.ValueString(), nil) - if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, nil); diags.HasError() { - return diags - } - - if gw, ok := respGet.GatewayClassification.(*fabcore.VirtualNetworkGateway); ok { - model.set(ctx, *gw) - return nil - } else { - var diags diag.Diagnostics - diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") - return diags - } + tflog.Debug(ctx, "READ-ALL-Virtual-Network-Gateways", map[string]any{"action": "end"}) } -func (d *dataSourceVirtualNetworkGateways) getByDisplayName(ctx context.Context, model *VirtualNetworkGatewayModel) diag.Diagnostics { - tflog.Trace(ctx, fmt.Sprintf("getting %s by 'display_name'", ItemName)) - - gateways, err := d.client.ListGateways(ctx, nil) +func (d *dataSourceVirtualNetworkGateways) list(ctx context.Context, model *dataSourceVirtualNetworkGatewaysModel) diag.Diagnostics { + tflog.Trace(ctx, "Listing all virtual network gateways") - if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, nil); diags.HasError() { + gatewaysResp, err := d.client.ListGateways(ctx, nil) + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationList, nil); diags.HasError() { return diags } - for _, gw := range gateways { - if virtualNetworkGateway, ok := gw.(*fabcore.VirtualNetworkGateway); ok { - if *virtualNetworkGateway.DisplayName == model.DisplayName.ValueString() { - model.set(ctx, *virtualNetworkGateway) - return nil - } - } - } - - var diags diag.Diagnostics - diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") - return diags + return model.setValues(ctx, gatewaysResp) } diff --git a/internal/services/gateway/data_virtual_network_gateways_test.go b/internal/services/gateway/data_virtual_network_gateways_test.go new file mode 100644 index 00000000..84a64d63 --- /dev/null +++ b/internal/services/gateway/data_virtual_network_gateways_test.go @@ -0,0 +1,74 @@ +package gateway_test + +import ( + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testDataSourceVirtualNetworkGatewaysFQN = testhelp.DataSourceFQN("fabric", "virtual_network_gateways", "test") + testDataSourceVirtualNetworkGatewaysHeader = at.DataSourceHeader(testhelp.TypeName("fabric", "virtual_network_gateways"), "test") +) + +func TestUnit_VirtualNetworkGatewaysDataSource(t *testing.T) { + entity := fakes.NewRandomVirtualNetworkGateway() + + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + + resource.ParallelTest(t, testhelp.NewTestUnitCase( + t, + nil, + fakes.FakeServer.ServerFactory, + nil, + []resource.TestStep{ + // Check that using an unexpected attribute fails. + { + Config: at.CompileConfig( + testDataSourceVirtualNetworkGatewaysHeader, + map[string]any{ + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // A normal read test with an empty config (no filter). + { + Config: at.CompileConfig( + testDataSourceVirtualNetworkGatewaysHeader, + map[string]any{}, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkGatewaysFQN, "values.0.id"), + ), + }, + }, + )) +} + +func TestAcc_VirtualNetworkGatewaysDataSource(t *testing.T) { + resource.ParallelTest(t, testhelp.NewTestAccCase( + t, + nil, + nil, + []resource.TestStep{ + // read test. + { + Config: at.CompileConfig( + testDataSourceVirtualNetworkGatewaysHeader, + map[string]any{}, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkGatewaysFQN, "values.0.id"), + ), + }, + }, + )) +} diff --git a/internal/services/gateway/models_gateway.go b/internal/services/gateway/models_base_data_gateway.go similarity index 75% rename from internal/services/gateway/models_gateway.go rename to internal/services/gateway/models_base_data_gateway.go index 3536b301..97da165b 100644 --- a/internal/services/gateway/models_gateway.go +++ b/internal/services/gateway/models_base_data_gateway.go @@ -22,7 +22,7 @@ type GatewayModelBase struct { Type types.String `tfsdk:"type"` } -type OnPremisesGatewayModel struct { +type onPremisesGatewayModelBase struct { GatewayModelBase AllowCloudConnectionRefresh *bool `tfsdk:"allow_cloud_connection_refresh"` @@ -33,14 +33,14 @@ type OnPremisesGatewayModel struct { NumberOfMemberGateways *int32 `tfsdk:"number_of_member_gateways"` - PublicKey supertypes.SingleNestedObjectValueOf[PublicKeyModel] `tfsdk:"public_key"` + PublicKey supertypes.SingleNestedObjectValueOf[publicKeyModel] `tfsdk:"public_key"` Version types.String `tfsdk:"version"` Timeouts timeouts.Value `tfsdk:"timeouts"` } -type VirtualNetworkGatewayModel struct { +type virtualNetworkGatewayModelBase struct { GatewayModelBase CapacityId customtypes.UUID `tfsdk:"capacity_id"` @@ -49,19 +49,24 @@ type VirtualNetworkGatewayModel struct { NumberOfMemberGateways *int32 `tfsdk:"number_of_member_gateways"` - VirtualNetworkAzureResource supertypes.SingleNestedObjectValueOf[VirtualNetworkAzureResourceModel] `tfsdk:"virtual_network_azure_resource"` + VirtualNetworkAzureResource supertypes.SingleNestedObjectValueOf[virtualNetworkAzureResourceModel] `tfsdk:"virtual_network_azure_resource"` Timeouts timeouts.Value `tfsdk:"timeouts"` } -type OnPremisesGatewayPersonalModel struct { - ID customtypes.UUID `tfsdk:"id"` - PublicKey supertypes.SingleNestedObjectValueOf[PublicKeyModel] `tfsdk:"public_key"` - Type types.String `tfsdk:"type"` - Version types.String `tfsdk:"version"` +type onPremisesGatewayPersonalModelBase struct { + ID customtypes.UUID `tfsdk:"id"` + + PublicKey supertypes.SingleNestedObjectValueOf[publicKeyModel] `tfsdk:"public_key"` + + Type types.String `tfsdk:"type"` + + Version types.String `tfsdk:"version"` + + Timeouts timeouts.Value `tfsdk:"timeouts"` } -type VirtualNetworkAzureResourceModel struct { +type virtualNetworkAzureResourceModel struct { SubscriptionID customtypes.UUID `tfsdk:"subscription_id"` ResourceGroupName types.String `tfsdk:"resource_group_name"` @@ -69,21 +74,23 @@ type VirtualNetworkAzureResourceModel struct { VirtualNetworkName types.String `tfsdk:"virtual_network_name"` SubnetName types.String `tfsdk:"subnet_name"` + + Timeouts timeouts.Value `tfsdk:"timeouts"` } -type PublicKeyModel struct { +type publicKeyModel struct { Exponent types.String `tfsdk:"exponent"` Modulus types.String `tfsdk:"modulus"` } -func (to *OnPremisesGatewayPersonalModel) set(ctx context.Context, from fabcore.OnPremisesGateway) diag.Diagnostics { +func (to *onPremisesGatewayPersonalModelBase) set(ctx context.Context, from fabcore.OnPremisesGatewayPersonal) diag.Diagnostics { var diags diag.Diagnostics to.ID = customtypes.NewUUIDPointerValue(from.ID) - publicKey := supertypes.NewSingleNestedObjectValueOfNull[PublicKeyModel](ctx) + publicKey := supertypes.NewSingleNestedObjectValueOfNull[publicKeyModel](ctx) if from.PublicKey != nil { - publicKeyModel := &PublicKeyModel{} + publicKeyModel := &publicKeyModel{} publicKeyModel.set(*from.PublicKey) if pkDiags := publicKey.Set(ctx, publicKeyModel); pkDiags.HasError() { @@ -99,7 +106,7 @@ func (to *OnPremisesGatewayPersonalModel) set(ctx context.Context, from fabcore. return diags } -func (to *OnPremisesGatewayModel) set(ctx context.Context, from fabcore.OnPremisesGateway) diag.Diagnostics { +func (to *onPremisesGatewayModelBase) set(ctx context.Context, from fabcore.OnPremisesGateway) diag.Diagnostics { to.ID = customtypes.NewUUIDPointerValue(from.ID) to.DisplayName = types.StringPointerValue(from.DisplayName) to.Type = types.StringPointerValue((*string)(from.Type)) @@ -109,10 +116,10 @@ func (to *OnPremisesGatewayModel) set(ctx context.Context, from fabcore.OnPremis to.NumberOfMemberGateways = from.NumberOfMemberGateways to.Version = types.StringPointerValue(from.Version) - publicKey := supertypes.NewSingleNestedObjectValueOfNull[PublicKeyModel](ctx) + publicKey := supertypes.NewSingleNestedObjectValueOfNull[publicKeyModel](ctx) if from.PublicKey != nil { - publicKeyModel := &PublicKeyModel{} + publicKeyModel := &publicKeyModel{} publicKeyModel.set(*from.PublicKey) if diags := publicKey.Set(ctx, publicKeyModel); diags.HasError() { @@ -125,7 +132,7 @@ func (to *OnPremisesGatewayModel) set(ctx context.Context, from fabcore.OnPremis return nil } -func (to *VirtualNetworkGatewayModel) set(ctx context.Context, from fabcore.VirtualNetworkGateway) diag.Diagnostics { +func (to *virtualNetworkGatewayModelBase) set(ctx context.Context, from fabcore.VirtualNetworkGateway) diag.Diagnostics { to.ID = customtypes.NewUUIDPointerValue(from.ID) to.DisplayName = types.StringPointerValue(from.DisplayName) to.Type = types.StringPointerValue((*string)(from.Type)) @@ -133,10 +140,10 @@ func (to *VirtualNetworkGatewayModel) set(ctx context.Context, from fabcore.Virt to.InactivityMinutesBeforeSleep = from.InactivityMinutesBeforeSleep to.NumberOfMemberGateways = from.NumberOfMemberGateways - virtualNetworkAzureResource := supertypes.NewSingleNestedObjectValueOfNull[VirtualNetworkAzureResourceModel](ctx) + virtualNetworkAzureResource := supertypes.NewSingleNestedObjectValueOfNull[virtualNetworkAzureResourceModel](ctx) if from.VirtualNetworkAzureResource != nil { - virtualNetworkAzureResourceModel := &VirtualNetworkAzureResourceModel{} + virtualNetworkAzureResourceModel := &virtualNetworkAzureResourceModel{} virtualNetworkAzureResourceModel.set(*from.VirtualNetworkAzureResource) if diags := virtualNetworkAzureResource.Set(ctx, virtualNetworkAzureResourceModel); diags.HasError() { @@ -149,7 +156,7 @@ func (to *VirtualNetworkGatewayModel) set(ctx context.Context, from fabcore.Virt return nil } -func (to *VirtualNetworkAzureResourceModel) set(from fabcore.VirtualNetworkAzureResource) { +func (to *virtualNetworkAzureResourceModel) set(from fabcore.VirtualNetworkAzureResource) { to.SubscriptionID = customtypes.NewUUIDPointerValue(from.SubscriptionID) to.ResourceGroupName = types.StringPointerValue(from.ResourceGroupName) to.VirtualNetworkName = types.StringPointerValue(from.VirtualNetworkName) @@ -162,7 +169,7 @@ func (to *VirtualNetworkAzureResourceModel) set(from fabcore.VirtualNetworkAzure // to.Type = types.StringPointerValue((*string)(from.Type)) // } -func (to *PublicKeyModel) set(from fabcore.PublicKey) { +func (to *publicKeyModel) set(from fabcore.PublicKey) { to.Exponent = types.StringPointerValue(from.Exponent) to.Modulus = types.StringPointerValue(from.Modulus) } diff --git a/internal/services/gateway/models_data_gateway.go b/internal/services/gateway/models_data_gateway.go new file mode 100644 index 00000000..c9c5c586 --- /dev/null +++ b/internal/services/gateway/models_data_gateway.go @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" +) + +type datasourceOnPremisesGatewayModel struct { + onPremisesGatewayModelBase + + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type datasourceVirtualNetworkGatewayModel struct { + virtualNetworkGatewayModelBase + + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type datasourceOnPremisesGatewayPersonalModel struct { + onPremisesGatewayPersonalModelBase + + Timeouts timeouts.Value `tfsdk:"timeouts"` +} diff --git a/internal/services/gateway/models_data_gateways.go b/internal/services/gateway/models_data_gateways.go new file mode 100644 index 00000000..36c31ffe --- /dev/null +++ b/internal/services/gateway/models_data_gateways.go @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/diag" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" +) + +type dataSourceOnPremisesGatewaysModel struct { + Values supertypes.ListNestedObjectValueOf[onPremisesGatewayModelBase] `tfsdk:"values"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +func (m *dataSourceOnPremisesGatewaysModel) setValues(ctx context.Context, from []fabcore.GatewayClassification) diag.Diagnostics { + var diags diag.Diagnostics + slice := make([]*onPremisesGatewayModelBase, 0, len(from)) + + for _, classification := range from { + gw, ok := classification.(*fabcore.OnPremisesGateway) + if !ok { + continue // skip non-OnPremisesGateway types + } + + var entityModel onPremisesGatewayModelBase + if setDiags := entityModel.set(ctx, *gw); setDiags.HasError() { + diags.Append(setDiags...) + continue + } + slice = append(slice, &entityModel) + } + + if listDiags := m.Values.Set(ctx, slice); listDiags.HasError() { + diags.Append(listDiags...) + } + return diags +} + +type dataSourceVirtualNetworkGatewaysModel struct { + Values supertypes.ListNestedObjectValueOf[virtualNetworkGatewayModelBase] `tfsdk:"values"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +func (m *dataSourceVirtualNetworkGatewaysModel) setValues(ctx context.Context, from []fabcore.GatewayClassification) diag.Diagnostics { + var diags diag.Diagnostics + slice := make([]*virtualNetworkGatewayModelBase, 0, len(from)) + + for _, entity := range from { + gw, ok := entity.(*fabcore.VirtualNetworkGateway) + + if !ok { + continue // skip non-VirtualNetworkGateway types + } + + var entityModel virtualNetworkGatewayModelBase + if setDiags := entityModel.set(ctx, *gw); setDiags.HasError() { + diags.Append(setDiags...) + continue + } + slice = append(slice, &entityModel) + } + + if listDiags := m.Values.Set(ctx, slice); listDiags.HasError() { + diags.Append(listDiags...) + } + return diags +} + +type dataSourceOnPremisesGatewayPersonalsModel struct { + Values supertypes.ListNestedObjectValueOf[onPremisesGatewayPersonalModelBase] `tfsdk:"values"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +func (m *dataSourceOnPremisesGatewayPersonalsModel) setValues(ctx context.Context, from []fabcore.GatewayClassification) diag.Diagnostics { + var diags diag.Diagnostics + slice := make([]*onPremisesGatewayPersonalModelBase, 0, len(from)) + + for _, classification := range from { + gw, ok := classification.(*fabcore.OnPremisesGatewayPersonal) + if !ok { + continue // skip non-OnPremisesGatewayPersonal types + } + + var entityModel onPremisesGatewayPersonalModelBase + if setDiags := entityModel.set(ctx, *gw); setDiags.HasError() { + diags.Append(setDiags...) + continue + } + slice = append(slice, &entityModel) + } + + if listDiags := m.Values.Set(ctx, slice); listDiags.HasError() { + diags.Append(listDiags...) + } + return diags +} diff --git a/internal/services/gateway/models_resource_gateway.go b/internal/services/gateway/models_resource_gateway.go new file mode 100644 index 00000000..e11a8f78 --- /dev/null +++ b/internal/services/gateway/models_resource_gateway.go @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + +type ResourceVirtualNetworkGatewayModel struct { + virtualNetworkGatewayModelBase + + Timeouts timeouts.Value `tfsdk:"timeouts"` +} diff --git a/internal/services/gateway/resource_virtual_network_gateway.go b/internal/services/gateway/resource_virtual_network_gateway.go new file mode 100644 index 00000000..fa0ed3c2 --- /dev/null +++ b/internal/services/gateway/resource_virtual_network_gateway.go @@ -0,0 +1 @@ +package gateway diff --git a/internal/testhelp/fakes/fabric_gateways.go b/internal/testhelp/fakes/fabric_gateways.go index 88317d99..bef93618 100644 --- a/internal/testhelp/fakes/fabric_gateways.go +++ b/internal/testhelp/fakes/fabric_gateways.go @@ -176,7 +176,7 @@ func NewRandomOnPremisesGateway() fabcore.OnPremisesGateway { } } -func NewRandomOnPermisesGatewayPersonal() fabcore.OnPremisesGatewayPersonal { +func NewRandomOnPremisesGatewayPersonal() fabcore.OnPremisesGatewayPersonal { return fabcore.OnPremisesGatewayPersonal{ ID: to.Ptr(testhelp.RandomUUID()), Type: to.Ptr(fabcore.GatewayTypeOnPremisesPersonal), From c1b5b21ab7c58cfe55e76183ceb74254d0f8fb51 Mon Sep 17 00:00:00 2001 From: Pablo Zaidenvoren Date: Wed, 29 Jan 2025 17:35:27 +0000 Subject: [PATCH 3/7] fake server improvements to handle interfaces --- .../data_virtual_network_gateway_test.go | 6 +++-- .../gateway/models_base_data_gateway.go | 9 ------- internal/testhelp/fakes/fabric_gateways.go | 26 +++++++++++++------ internal/testhelp/fakes/fake_server.go | 17 +++++++++--- internal/testhelp/fakes/fake_typedhandler.go | 6 +++++ 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/internal/services/gateway/data_virtual_network_gateway_test.go b/internal/services/gateway/data_virtual_network_gateway_test.go index 3e331f93..7e585140 100644 --- a/internal/services/gateway/data_virtual_network_gateway_test.go +++ b/internal/services/gateway/data_virtual_network_gateway_test.go @@ -24,9 +24,11 @@ var ( func TestUnit_VirtualNetworkGatewayDataSource(t *testing.T) { entity := fakes.NewRandomVirtualNetworkGateway() - fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + vng1 := fakes.NewRandomVirtualNetworkGateway() + vng3 := fakes.NewRandomVirtualNetworkGateway() + fakes.FakeServer.Upsert(vng1) fakes.FakeServer.Upsert(entity) - fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + fakes.FakeServer.Upsert(vng3) resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ // error - no attributes diff --git a/internal/services/gateway/models_base_data_gateway.go b/internal/services/gateway/models_base_data_gateway.go index 97da165b..baf9554c 100644 --- a/internal/services/gateway/models_base_data_gateway.go +++ b/internal/services/gateway/models_base_data_gateway.go @@ -6,7 +6,6 @@ package gateway import ( "context" - "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" @@ -36,8 +35,6 @@ type onPremisesGatewayModelBase struct { PublicKey supertypes.SingleNestedObjectValueOf[publicKeyModel] `tfsdk:"public_key"` Version types.String `tfsdk:"version"` - - Timeouts timeouts.Value `tfsdk:"timeouts"` } type virtualNetworkGatewayModelBase struct { @@ -50,8 +47,6 @@ type virtualNetworkGatewayModelBase struct { NumberOfMemberGateways *int32 `tfsdk:"number_of_member_gateways"` VirtualNetworkAzureResource supertypes.SingleNestedObjectValueOf[virtualNetworkAzureResourceModel] `tfsdk:"virtual_network_azure_resource"` - - Timeouts timeouts.Value `tfsdk:"timeouts"` } type onPremisesGatewayPersonalModelBase struct { @@ -62,8 +57,6 @@ type onPremisesGatewayPersonalModelBase struct { Type types.String `tfsdk:"type"` Version types.String `tfsdk:"version"` - - Timeouts timeouts.Value `tfsdk:"timeouts"` } type virtualNetworkAzureResourceModel struct { @@ -74,8 +67,6 @@ type virtualNetworkAzureResourceModel struct { VirtualNetworkName types.String `tfsdk:"virtual_network_name"` SubnetName types.String `tfsdk:"subnet_name"` - - Timeouts timeouts.Value `tfsdk:"timeouts"` } type publicKeyModel struct { diff --git a/internal/testhelp/fakes/fabric_gateways.go b/internal/testhelp/fakes/fabric_gateways.go index bef93618..903da93c 100644 --- a/internal/testhelp/fakes/fabric_gateways.go +++ b/internal/testhelp/fakes/fabric_gateways.go @@ -26,7 +26,7 @@ func (o *operationsGateway) Create(data fabcore.CreateGatewayRequestClassificati returnGateway.InactivityMinutesBeforeSleep = gateway.InactivityMinutesBeforeSleep returnGateway.NumberOfMemberGateways = gateway.NumberOfMemberGateways returnGateway.VirtualNetworkAzureResource = gateway.VirtualNetworkAzureResource - return &returnGateway + return returnGateway default: panic("unimplemented") } @@ -112,7 +112,19 @@ func (o *operationsGateway) TransformCreate(entity fabcore.GatewayClassification } } -func configureGateway(server *fakeServer) fabcore.VirtualNetworkGateway { +func configureVirtualNetworkGateway(server *fakeServer) fabcore.VirtualNetworkGateway { + configureGatewayClassification(server) + + return fabcore.VirtualNetworkGateway{} +} + +func configureOnPremisesGatewayPersonal(server *fakeServer) fabcore.OnPremisesGatewayPersonal { + configureGatewayClassification(server) + + return fabcore.OnPremisesGatewayPersonal{} +} + +func configureGatewayClassification(server *fakeServer) { type concreteEntityOperations interface { simpleIDOperations[ fabcore.GatewayClassification, @@ -138,12 +150,10 @@ func configureGateway(server *fakeServer) fabcore.VirtualNetworkGateway { handler, entityOperations, &handler.ServerFactory.Core.GatewaysServer.NewListGatewaysPager) - - return fabcore.VirtualNetworkGateway{} } -func NewRandomVirtualNetworkGateway() fabcore.VirtualNetworkGateway { - return fabcore.VirtualNetworkGateway{ +func NewRandomVirtualNetworkGateway() *fabcore.VirtualNetworkGateway { + return &fabcore.VirtualNetworkGateway{ ID: to.Ptr(testhelp.RandomUUID()), DisplayName: to.Ptr(testhelp.RandomName()), InactivityMinutesBeforeSleep: to.Ptr(testhelp.RandomInt32(100)), @@ -159,8 +169,8 @@ func NewRandomVirtualNetworkGateway() fabcore.VirtualNetworkGateway { } } -func NewRandomOnPremisesGateway() fabcore.OnPremisesGateway { - return fabcore.OnPremisesGateway{ +func NewRandomOnPremisesGateway() *fabcore.OnPremisesGateway { + return &fabcore.OnPremisesGateway{ ID: to.Ptr(testhelp.RandomUUID()), DisplayName: to.Ptr(testhelp.RandomName()), NumberOfMemberGateways: to.Ptr(testhelp.RandomInt32(100)), diff --git a/internal/testhelp/fakes/fake_server.go b/internal/testhelp/fakes/fake_server.go index f58d5a1a..7cd4a57b 100644 --- a/internal/testhelp/fakes/fake_server.go +++ b/internal/testhelp/fakes/fake_server.go @@ -35,7 +35,8 @@ func newFakeServer() *fakeServer { handleEntity(server, configureDomain) handleEntity(server, configureEventhouse) handleEntity(server, configureEnvironment) - handleEntity(server, configureGateway) + handleEntity(server, configureOnPremisesGatewayPersonal) + handleEntity(server, configureVirtualNetworkGateway) handleEntity(server, configureKQLDatabase) handleEntity(server, configureLakehouse) handleEntity(server, configureNotebook) @@ -58,7 +59,12 @@ func handleEntity[TEntity any](server *fakeServer, configureFunction func(server // SupportsType returns true if the server supports the given type. func (s *fakeServer) isSupportedType(t reflect.Type) bool { for _, supportedType := range s.types { - if supportedType == t { + // if supportedType is an interface, check if t implements it + if supportedType.Kind() == reflect.Interface { + if t.Implements(supportedType) { + return true + } + } else if supportedType == t { return true } } @@ -69,7 +75,12 @@ func (s *fakeServer) isSupportedType(t reflect.Type) bool { // Upsert inserts or updates an element in the server. // It panics if the element type is not supported. func (s *fakeServer) Upsert(element any) { - if !s.isSupportedType(reflect.TypeOf(element)) { + elementType := reflect.TypeOf(element) + // if elementType is a pointer, get the underlying type + if elementType.Kind() == reflect.Ptr { + elementType = elementType.Elem() + } + if !s.isSupportedType(elementType) { panic("Unsupported type: " + reflect.TypeOf(element).String() + ". Did you forget to call HandleEntity in NewFakeServer?") // lintignore:R009 } diff --git a/internal/testhelp/fakes/fake_typedhandler.go b/internal/testhelp/fakes/fake_typedhandler.go index 02edc146..7b47d5e0 100644 --- a/internal/testhelp/fakes/fake_typedhandler.go +++ b/internal/testhelp/fakes/fake_typedhandler.go @@ -287,6 +287,12 @@ func (h *typedHandler[TEntity]) entityTypeIsFabricItem() bool { func (h *typedHandler[TEntity]) entityTypeCanBeConvertedToFabricItem() bool { var entity TEntity + // if entity is an interface, return false + entityAsAny := (any)(entity) + if entityAsAny == nil { + return false + } + requiredPropertyNames := []string{"ID", "WorkspaceID", "DisplayName", "Description", "Type"} for _, propertyName := range requiredPropertyNames { From 6a237ef3ae4d338b87b80c7ba556b91267b92699 Mon Sep 17 00:00:00 2001 From: Pablo Zaidenvoren Date: Wed, 29 Jan 2025 18:27:05 +0000 Subject: [PATCH 4/7] test: simplify test setup for VirtualNetworkGatewayDataSource --- .../services/gateway/data_virtual_network_gateway_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/services/gateway/data_virtual_network_gateway_test.go b/internal/services/gateway/data_virtual_network_gateway_test.go index 7e585140..3e331f93 100644 --- a/internal/services/gateway/data_virtual_network_gateway_test.go +++ b/internal/services/gateway/data_virtual_network_gateway_test.go @@ -24,11 +24,9 @@ var ( func TestUnit_VirtualNetworkGatewayDataSource(t *testing.T) { entity := fakes.NewRandomVirtualNetworkGateway() - vng1 := fakes.NewRandomVirtualNetworkGateway() - vng3 := fakes.NewRandomVirtualNetworkGateway() - fakes.FakeServer.Upsert(vng1) + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) fakes.FakeServer.Upsert(entity) - fakes.FakeServer.Upsert(vng3) + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ // error - no attributes From c3881a330591d27a302517c2262ec09de1deff42 Mon Sep 17 00:00:00 2001 From: Or Bauberg Date: Tue, 4 Feb 2025 10:52:13 +0000 Subject: [PATCH 5/7] all changes but acc_tests --- internal/provider/provider.go | 9 +- internal/services/gateway/base.go | 20 +- internal/services/gateway/base_test.go | 16 +- .../gateway/data_gateway_role_assignments.go | 161 ++++++++ .../data_gateway_role_assignments_test.go | 81 ++++ .../gateway/data_on_premises_gateway.go | 8 +- .../data_on_premises_gateway_personal.go | 10 +- .../data_on_premises_gateway_personal_test.go | 119 +++--- .../gateway/data_on_premises_gateway_test.go | 2 +- .../gateway/data_on_premises_gateways.go | 37 +- .../data_on_premises_gateways_personal.go | 7 +- ...data_on_premises_gateways_personal_test.go | 54 +++ .../gateway/data_virtual_network_gateway.go | 8 +- .../gateway/data_virtual_network_gateways.go | 6 +- internal/services/gateway/fake_test.go | 63 ++++ .../gateway/models_base_data_gateway.go | 45 +-- .../models_gateway_data_role_assignment.go | 94 +++++ .../gateway/models_resource_gateway.go | 67 +++- ...models_resource_gateway_role_assignment.go | 49 +++ .../resource_gateway_role_assignments.go | 301 +++++++++++++++ .../resource_gateway_role_assignments_test.go | 197 ++++++++++ .../resource_virtual_network_gateway.go | 353 ++++++++++++++++++ .../resource_virtual_network_gateway_test.go | 282 ++++++++++++++ internal/testhelp/fakes/fabric_gateways.go | 10 +- internal/testhelp/fakes/fake_server.go | 1 + 25 files changed, 1853 insertions(+), 147 deletions(-) create mode 100644 internal/services/gateway/data_gateway_role_assignments.go create mode 100644 internal/services/gateway/data_gateway_role_assignments_test.go create mode 100644 internal/services/gateway/data_on_premises_gateways_personal_test.go create mode 100644 internal/services/gateway/fake_test.go create mode 100644 internal/services/gateway/models_gateway_data_role_assignment.go create mode 100644 internal/services/gateway/models_resource_gateway_role_assignment.go create mode 100644 internal/services/gateway/resource_gateway_role_assignments.go create mode 100644 internal/services/gateway/resource_gateway_role_assignments_test.go create mode 100644 internal/services/gateway/resource_virtual_network_gateway_test.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 456d0728..f05a9648 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -362,6 +362,8 @@ func (p *FabricProvider) Resources(ctx context.Context) []func() resource.Resour func() resource.Resource { return environment.NewResourceEnvironment(ctx) }, func() resource.Resource { return eventhouse.NewResourceEventhouse(ctx) }, eventstream.NewResourceEventstream, + gateway.NewResourceGatewayRoleAssignment, + gateway.NewResourceVirtualNetworkGateway, kqldatabase.NewResourceKQLDatabase, func() resource.Resource { return lakehouse.NewResourceLakehouse(ctx) }, mlexperiment.NewResourceMLExperiment, @@ -397,8 +399,13 @@ func (p *FabricProvider) DataSources(ctx context.Context) []func() datasource.Da func() datasource.DataSource { return eventhouse.NewDataSourceEventhouses(ctx) }, eventstream.NewDataSourceEventstream, eventstream.NewDataSourceEventstreams, - gateway.NewDataSourceVirtualNetworkGateway, + gateway.NewDataSourceGatewayRoleAssignments, gateway.NewDataSourceOnPremisesGateway, + gateway.NewDataSourceOnPremisesGateways, + gateway.NewDataSourceOnPremisesGatewayPersonal, + gateway.NewDataSourceOnPremisesGatewayPersonals, + gateway.NewDataSourceVirtualNetworkGateway, + gateway.NewDataSourceVirtualNetworkGateways, kqldatabase.NewDataSourceKQLDatabase, kqldatabase.NewDataSourceKQLDatabases, kqlqueryset.NewDataSourceKQLQueryset, diff --git a/internal/services/gateway/base.go b/internal/services/gateway/base.go index 8e2cd3d1..62b9ce8a 100644 --- a/internal/services/gateway/base.go +++ b/internal/services/gateway/base.go @@ -8,15 +8,23 @@ import ( ) const ( - OnPremisesItemTFType = "OnPremisesGateway" - OnPremisesItemsTFType = "OnPremisesGateways" - OnPremisesPersonalItemType = "OnPremisesPersonalGateway" - OnPremisesPersonalItemsType = "OnPremisesPersonalGateways" - VirtualNetworkItemTFType = "VirtualNetworkGateway" - VirtualNetworkItemsTFType = "VirtualNetworkGateways" + OnPremisesItemTFType = "on_premises_gateway" + OnPremisesItemsTFType = "on_premises_gateways" + OnPremisesPersonalItemType = "on_premises_personal_gateway" + OnPremisesPersonalItemsType = "on_premises_personal_gateways" + VirtualNetworkItemTFType = "virtual_network_gateway" + VirtualNetworkItemsTFType = "virtual_network_gateways" ItemName = "Gateway" ItemsName = "Gateways" ItemsTFName = "gateways" ItemDocsSPNSupport = common.DocsSPNSupported ItemDocsURL = "https://learn.microsoft.com/en-us/fabric/data-factory/how-to-access-on-premises-data" ) + +var ( + PossibleInactivityMinutesBeforeSleepValues = []int32{30, 60, 90, 120, 150, 240, 360, 480, 720, 1440} + + MinNumberOfMemberGatewaysValues = int32(1) + + MaxNumberOfMemberGatewaysValues = int32(7) +) diff --git a/internal/services/gateway/base_test.go b/internal/services/gateway/base_test.go index bbf45249..e8b0ae76 100644 --- a/internal/services/gateway/base_test.go +++ b/internal/services/gateway/base_test.go @@ -8,12 +8,12 @@ import ( ) const ( - VirtualNetworkItemTFName = gateway.VirtualNetworkItemTFType - VirtualNetworkItemsTFName = gateway.VirtualNetworkItemsTFType - OnPremisesItemTFName = gateway.OnPremisesItemTFType - OnPremisesItemsTFName = gateway.OnPremisesItemsTFType - OnPremisesPersonalItemTFName = gateway.OnPremisesPersonalItemType - OnPremisesPersonalItemsType = gateway.OnPremisesPersonalItemsType - itemsTFName = gateway.ItemsTFName - itemType = gateway.ItemName + VirtualNetworkItemTFName = gateway.VirtualNetworkItemTFType + VirtualNetworkItemsTFName = gateway.VirtualNetworkItemsTFType + OnPremisesItemTFName = gateway.OnPremisesItemTFType + OnPremisesItemsTFName = gateway.OnPremisesItemsTFType + OnPremisesPersonalItemTFName = gateway.OnPremisesPersonalItemType + OnPremisesPersonalItemsTFName = gateway.OnPremisesPersonalItemsType + itemsTFName = gateway.ItemsTFName + itemType = gateway.ItemName ) diff --git a/internal/services/gateway/data_gateway_role_assignments.go b/internal/services/gateway/data_gateway_role_assignments.go new file mode 100644 index 00000000..3edde4d8 --- /dev/null +++ b/internal/services/gateway/data_gateway_role_assignments.go @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-log/tflog" + + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" + pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" +) + +var _ datasource.DataSourceWithConfigure = (*dataSourceGatewayRoleAssignments)(nil) + +type dataSourceGatewayRoleAssignments struct { + pConfigData *pconfig.ProviderData + client *fabcore.GatewaysClient +} + +func NewDataSourceGatewayRoleAssignments() datasource.DataSource { + return &dataSourceGatewayRoleAssignments{} +} + +func (d *dataSourceGatewayRoleAssignments) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + "gateway_role_assignments" +} + +func (d *dataSourceGatewayRoleAssignments) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "List the Fabric gateway role assignments.\n\n" + + "Use this data source to list the role assignments for a gateway.\n\n" + + ItemDocsSPNSupport, + Attributes: map[string]schema.Attribute{ + "gateway_id": schema.StringAttribute{ + MarkdownDescription: "The Gateway ID.", + Required: true, + CustomType: customtypes.UUIDType{}, + }, + "values": schema.ListNestedAttribute{ + MarkdownDescription: "A list of gateway role assignments.", + Computed: true, + CustomType: supertypes.NewListNestedObjectTypeOf[gatewayRoleAssignmentModel](ctx), + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The role assignment ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "role": schema.StringAttribute{ + MarkdownDescription: "The gateway role of the principal.", + Computed: true, + }, + "display_name": schema.StringAttribute{ + MarkdownDescription: "The principal's display name.", + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "The type of the principal.", + Computed: true, + }, + "details": schema.SingleNestedAttribute{ + MarkdownDescription: "The principal details.", + Computed: true, + CustomType: supertypes.NewSingleNestedObjectTypeOf[principalDetailsModel](ctx), + Attributes: map[string]schema.Attribute{ + "user_principal_name": schema.StringAttribute{ + MarkdownDescription: "The user principal name.", + Computed: true, + }, + "group_type": schema.StringAttribute{ + MarkdownDescription: "The group type.", + Computed: true, + }, + "app_id": schema.StringAttribute{ + MarkdownDescription: "The Service Principal's Microsoft Entra App ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + "parent_principal_id": schema.StringAttribute{ + MarkdownDescription: "The parent principal ID of Service Principal Profile.", + Computed: true, + CustomType: customtypes.UUIDType{}, + }, + }, + }, + }, + }, + }, + "timeouts": timeouts.Attributes(ctx), + }, + } +} + +func (d *dataSourceGatewayRoleAssignments) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { + resp.Diagnostics.AddError( + common.ErrorDataSourceConfigType, + fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), + ) + return + } + d.pConfigData = pConfigData + // Create a gateways client via the provider's FabricClient. + d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) +} + +func (d *dataSourceGatewayRoleAssignments) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "READ", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "READ", map[string]any{ + "config": req.Config, + }) + + var data dataSourceGatewayRoleAssignmentsModel + if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := data.Timeouts.Read(ctx, d.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + respList, err := d.client.ListGatewayRoleAssignments(ctx, data.GatewayID.ValueString(), nil) + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationList, nil); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + if diags := data.setValues(ctx, respList); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) + + tflog.Debug(ctx, "READ", map[string]any{ + "action": "end", + }) +} diff --git a/internal/services/gateway/data_gateway_role_assignments_test.go b/internal/services/gateway/data_gateway_role_assignments_test.go new file mode 100644 index 00000000..633247ae --- /dev/null +++ b/internal/services/gateway/data_gateway_role_assignments_test.go @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway_test + +import ( + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testDataSourceGatewayRoleAssignments = testhelp.DataSourceFQN("fabric", "gateway_role_assignments", "test") + testDataSourceGatewayRoleAssignmentsHeader = at.DataSourceHeader(testhelp.TypeName("fabric", "gateway_role_assignments"), "test") +) + +func TestUnit_GatewayRoleAssignmentsDataSource(t *testing.T) { + gatewayID := testhelp.RandomUUID() + gatewayRoleAssignments := NewRandomGatewayRoleAssignments() + fakes.FakeServer.ServerFactory.Core.GatewaysServer.NewListGatewayRoleAssignmentsPager = fakeGatewayRoleAssignments(gatewayRoleAssignments) + + entity := gatewayRoleAssignments.Value[1] + + resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + // Step 1: Error on unexpected attribute. + { + Config: at.CompileConfig( + testDataSourceGatewayRoleAssignmentsHeader, + map[string]any{ + "gateway_id": gatewayID, + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // Step 2: Read the role assignments. + { + Config: at.CompileConfig( + testDataSourceGatewayRoleAssignmentsHeader, + map[string]any{ + "gateway_id": gatewayID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceGatewayRoleAssignments, "gateway_id", gatewayID), + resource.TestCheckResourceAttrPtr(testDataSourceGatewayRoleAssignments, "values.1.id", entity.ID), + resource.TestCheckResourceAttrPtr(testDataSourceGatewayRoleAssignments, "values.1.role", (*string)(entity.Role)), + resource.TestCheckResourceAttrPtr(testDataSourceGatewayRoleAssignments, "values.1.display_name", entity.Principal.DisplayName), + resource.TestCheckResourceAttrPtr(testDataSourceGatewayRoleAssignments, "values.1.type", (*string)(entity.Principal.Type)), + // Additional nested details checks can be added here. + ), + }, + })) +} + +func TestAcc_GatewayRoleAssignmentsDataSource(t *testing.T) { + // For acceptance testing, assume a well-known gateway is provided. + gateway := testhelp.WellKnown()["GatewayDS"].(map[string]any) + gatewayID := gateway["id"].(string) + + resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ + // Step: Read the gateway role assignments. + { + Config: at.CompileConfig( + testDataSourceGatewayRoleAssignmentsHeader, + map[string]any{ + "gateway_id": gatewayID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceGatewayRoleAssignments, "gateway_id", gatewayID), + resource.TestCheckResourceAttrSet(testDataSourceGatewayRoleAssignments, "values.0.id"), + ), + }, + })) +} diff --git a/internal/services/gateway/data_on_premises_gateway.go b/internal/services/gateway/data_on_premises_gateway.go index ef7cfe95..80113217 100644 --- a/internal/services/gateway/data_on_premises_gateway.go +++ b/internal/services/gateway/data_on_premises_gateway.go @@ -62,10 +62,6 @@ func (d *dataSourceOnPremisesGateway) Schema( Optional: true, Computed: true, }, - "type": schema.StringAttribute{ - MarkdownDescription: fmt.Sprintf("The %s type.", ItemName), - Computed: true, - }, "allow_cloud_connection_refresh": schema.BoolAttribute{ MarkdownDescription: "Defines if cloud connection refresh is allowed.", Computed: true, @@ -78,7 +74,7 @@ func (d *dataSourceOnPremisesGateway) Schema( MarkdownDescription: "Gateway load balancing setting.", Computed: true, }, - "number_of_member_gateways": schema.NumberAttribute{ + "number_of_member_gateways": schema.Int32Attribute{ MarkdownDescription: "The number of member gateways.", Computed: true, }, @@ -222,6 +218,6 @@ func (d *dataSourceOnPremisesGateway) getByDisplayName(ctx context.Context, mode } var diags diag.Diagnostics - diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") + diags.AddError(common.ErrorReadHeader, "no on-premises gateway with display name found") return diags } diff --git a/internal/services/gateway/data_on_premises_gateway_personal.go b/internal/services/gateway/data_on_premises_gateway_personal.go index 86ad2644..c109dbab 100644 --- a/internal/services/gateway/data_on_premises_gateway_personal.go +++ b/internal/services/gateway/data_on_premises_gateway_personal.go @@ -29,7 +29,7 @@ func NewDataSourceOnPremisesGatewayPersonal() datasource.DataSource { } func (d *dataSourceOnPremisesGatewayPersonal) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_on_premises_gateway_personal" + resp.TypeName = req.ProviderTypeName + "_" + OnPremisesPersonalItemType } func (d *dataSourceOnPremisesGatewayPersonal) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { @@ -37,15 +37,11 @@ func (d *dataSourceOnPremisesGatewayPersonal) Schema(ctx context.Context, _ data MarkdownDescription: "Retrieve an on-premises gateway in its 'personal' form (ID, public key, type, version).", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - MarkdownDescription: "The gateway ID (UUID).", + MarkdownDescription: "The gateway ID.", Optional: true, Computed: true, CustomType: customtypes.UUIDType{}, }, - "type": schema.StringAttribute{ - MarkdownDescription: "The type of the gateway.", - Computed: true, - }, "version": schema.StringAttribute{ MarkdownDescription: "The gateway version.", Computed: true, @@ -97,7 +93,6 @@ func (d *dataSourceOnPremisesGatewayPersonal) Read(ctx context.Context, req data return } - // Do the actual GET call gatewayResp, errResp := d.client.GetGateway(ctx, data.ID.ValueString(), nil) if errResp != nil { resp.Diagnostics.AddError("GetGateway failed", errResp.Error()) @@ -117,7 +112,6 @@ func (d *dataSourceOnPremisesGatewayPersonal) Read(ctx context.Context, req data return } - // Save data back into state if diags := resp.State.Set(ctx, gateway); diags.HasError() { resp.Diagnostics.Append(diags...) return diff --git a/internal/services/gateway/data_on_premises_gateway_personal_test.go b/internal/services/gateway/data_on_premises_gateway_personal_test.go index 195a32cc..8510eed6 100644 --- a/internal/services/gateway/data_on_premises_gateway_personal_test.go +++ b/internal/services/gateway/data_on_premises_gateway_personal_test.go @@ -11,14 +11,13 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/microsoft/terraform-provider-fabric/internal/common" - "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" "github.com/microsoft/terraform-provider-fabric/internal/testhelp" "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" ) var ( - testDataSourceOnPremisesPresonalItemFabricFQN = testhelp.DataSourceFQN("fabric", OnPremisesItemTFName, "test") - testDataSourceOnPremisesPersonalItemHeader = at.DataSourceHeader(testhelp.TypeName("fabric", OnPremisesItemTFName), "test") + testDataSourceOnPremisesPersonalFQN = testhelp.DataSourceFQN("fabric", OnPremisesPersonalItemTFName, "test") + testDataSourceOnPremisesPersonalHeader = at.DataSourceHeader(testhelp.TypeName("fabric", OnPremisesPersonalItemTFName), "test") ) func TestUnit_OnPremisesGatewayPersonalDataSource(t *testing.T) { @@ -28,62 +27,58 @@ func TestUnit_OnPremisesGatewayPersonalDataSource(t *testing.T) { fakes.FakeServer.Upsert(entity) fakes.FakeServer.Upsert(fakes.NewRandomOnPremisesGatewayPersonal()) - resource.ParallelTest(t, testhelp.NewTestUnitCase(t, nil, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ - // error - no attributes - { - Config: at.CompileConfig( - testDataSourceOnPremisesPersonalItemHeader, - map[string]any{}, - ), - ExpectError: regexp.MustCompile(`Exactly one of these attributes must be configured: \[id,display_name\]`), - }, - // error - id - invalid UUID - { - Config: at.CompileConfig( - testDataSourceOnPremisesPersonalItemHeader, - map[string]any{ - "id": "invalid uuid", - }, - ), - ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + resource.ParallelTest(t, testhelp.NewTestUnitCase( + t, + nil, + fakes.FakeServer.ServerFactory, + nil, + []resource.TestStep{ + // Step 1: Unexpected attribute should trigger an error. + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalHeader, + map[string]any{ + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // Step 2: Missing ID should trigger an error since ID is required for lookup. + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalHeader, + map[string]any{}, + ), + // "Missing ID" error is raised in Read when data.ID is empty. + ExpectError: regexp.MustCompile(`Missing ID`), + }, + // Step 3: Invalid UUID string should trigger an error. + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalHeader, + map[string]any{ + "id": "not-a-valid-uuid", + }, + ), + ExpectError: regexp.MustCompile(`invalid UUID`), + }, + // Step 4: Valid read test using the entity's ID. + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalHeader, + map[string]any{ + "id": *entity.ID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceOnPremisesPersonalFQN, "id", *entity.ID), + resource.TestCheckResourceAttr(testDataSourceOnPremisesPersonalFQN, "version", *entity.Version), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesPersonalFQN, "public_key.exponent"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesPersonalFQN, "public_key.modulus"), + ), + }, }, - // error - unexpected attribute - { - Config: at.CompileConfig( - testDataSourceOnPremisesPersonalItemHeader, - map[string]any{ - "id": *entity.ID, - "unexpected_attr": "test", - }, - ), - ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), - }, - // read by id - not found - { - Config: at.CompileConfig( - testDataSourceOnPremisesPersonalItemHeader, - map[string]any{ - "id": testhelp.RandomUUID(), - }, - ), - ExpectError: regexp.MustCompile(common.ErrorReadHeader), - }, - // read by id - { - Config: at.CompileConfig( - testDataSourceOnPremisesPersonalItemHeader, - map[string]any{ - "id": *entity.ID, - }, - ), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(testDataSourceOnPremisesPresonalItemFabricFQN, "id", *entity.ID), - resource.TestCheckResourceAttrSet(testDataSourceOnPremisesPresonalItemFabricFQN, "public_key.exponent"), - resource.TestCheckResourceAttrSet(testDataSourceOnPremisesPresonalItemFabricFQN, "public_key.modulus"), - resource.TestCheckResourceAttrSet(testDataSourceOnPremisesPresonalItemFabricFQN, "version"), - ), - }, - })) + )) } func TestAcc_OnPremisesGatewayPersonalDataSource(t *testing.T) { @@ -94,20 +89,20 @@ func TestAcc_OnPremisesGatewayPersonalDataSource(t *testing.T) { resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ { Config: at.CompileConfig( - testDataSourceOnPremisesPersonalItemHeader, + testDataSourceOnPremisesPersonalHeader, map[string]any{ "id": entityID, }, ), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(testDataSourceOnPremisesPresonalItemFabricFQN, "id", entityID), - resource.TestCheckResourceAttr(testDataSourceOnPremisesPresonalItemFabricFQN, "description", entityDescription), + resource.TestCheckResourceAttr(testDataSourceOnPremisesPersonalFQN, "id", entityID), + resource.TestCheckResourceAttr(testDataSourceOnPremisesPersonalFQN, "description", entityDescription), ), }, // read by id - not found { Config: at.CompileConfig( - testDataSourceOnPremisesPersonalItemHeader, + testDataSourceOnPremisesPersonalHeader, map[string]any{ "id": testhelp.RandomUUID(), }, diff --git a/internal/services/gateway/data_on_premises_gateway_test.go b/internal/services/gateway/data_on_premises_gateway_test.go index c289790a..b8f78570 100644 --- a/internal/services/gateway/data_on_premises_gateway_test.go +++ b/internal/services/gateway/data_on_premises_gateway_test.go @@ -125,7 +125,7 @@ func TestUnit_OnPremisesGatewayDataSource(t *testing.T) { resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "load_balancing_setting"), resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "number_of_member_gateways"), resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "public_key.exponent"), - resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "public_key0.modulus"), + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "public_key.modulus"), resource.TestCheckResourceAttrSet(testDataSourceOnPremisesItemFabricFQN, "version"), ), }, diff --git a/internal/services/gateway/data_on_premises_gateways.go b/internal/services/gateway/data_on_premises_gateways.go index 7b3fa217..1573bfad 100644 --- a/internal/services/gateway/data_on_premises_gateways.go +++ b/internal/services/gateway/data_on_premises_gateways.go @@ -36,7 +36,7 @@ func NewDataSourceOnPremisesGateways() datasource.DataSource { } func (d *dataSourceOnPremisesGateways) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_on_premises_gateways" + resp.TypeName = req.ProviderTypeName + "_" + OnPremisesItemsTFType } func (d *dataSourceOnPremisesGateways) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { @@ -58,16 +58,41 @@ func (d *dataSourceOnPremisesGateways) Schema(ctx context.Context, _ datasource. MarkdownDescription: "The display name of the on-premises gateway.", Computed: true, }, - "type": schema.StringAttribute{ - MarkdownDescription: "The on-premises gateway type.", + "allow_custom_connectors": schema.BoolAttribute{ + MarkdownDescription: "Allow custom connectors.", Computed: true, }, "allow_cloud_connection_refresh": schema.BoolAttribute{ - MarkdownDescription: "Allow cloud connection refresh.", + MarkdownDescription: "Allow custom connectors refresh.", + Computed: true, + }, + "number_of_member_gateways": schema.Int64Attribute{ + MarkdownDescription: "The number of member gateways.", + Computed: true, + }, + "load_balancing_setting": schema.StringAttribute{ + MarkdownDescription: "The load balancing setting.", + Computed: true, + }, + "public_key": schema.SingleNestedAttribute{ + MarkdownDescription: "The public key settings.", + Computed: true, + CustomType: supertypes.NewSingleNestedObjectTypeOf[publicKeyModel](ctx), + Attributes: map[string]schema.Attribute{ + "exponent": schema.StringAttribute{ + MarkdownDescription: "The RSA exponent.", + Computed: true, + }, + "modulus": schema.StringAttribute{ + MarkdownDescription: "The RSA modulus.", + Computed: true, + }, + }, + }, + "version": schema.StringAttribute{ + MarkdownDescription: "The gateway version.", Computed: true, }, - // Add any other on-premises gateway fields as needed, - // staying consistent with onPremisesGatewayModelBase. }, }, }, diff --git a/internal/services/gateway/data_on_premises_gateways_personal.go b/internal/services/gateway/data_on_premises_gateways_personal.go index d8144ef5..55555673 100644 --- a/internal/services/gateway/data_on_premises_gateways_personal.go +++ b/internal/services/gateway/data_on_premises_gateways_personal.go @@ -33,8 +33,7 @@ func NewDataSourceOnPremisesGatewayPersonals() datasource.DataSource { } func (d *dataSourceOnPremisesGatewayPersonals) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - // e.g.: fabric_on_premises_gateway_personals - resp.TypeName = req.ProviderTypeName + "_on_premises_gateway_personals" + resp.TypeName = req.ProviderTypeName + "_" + OnPremisesPersonalItemsType } func (d *dataSourceOnPremisesGatewayPersonals) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { @@ -67,10 +66,6 @@ func (d *dataSourceOnPremisesGatewayPersonals) Schema(ctx context.Context, _ dat }, }, }, - "type": schema.StringAttribute{ - MarkdownDescription: "The on-premises personal gateway type.", - Computed: true, - }, "version": schema.StringAttribute{ MarkdownDescription: "The personal gateway version.", Computed: true, diff --git a/internal/services/gateway/data_on_premises_gateways_personal_test.go b/internal/services/gateway/data_on_premises_gateways_personal_test.go new file mode 100644 index 00000000..8419e2b9 --- /dev/null +++ b/internal/services/gateway/data_on_premises_gateways_personal_test.go @@ -0,0 +1,54 @@ +package gateway_test + +import ( + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testDataSourceOnPremisesPersonalsFQN = testhelp.DataSourceFQN("fabric", OnPremisesPersonalItemsTFName, "test") + testDataSourceOnPremisesPersonalsHeader = at.DataSourceHeader(testhelp.TypeName("fabric", OnPremisesPersonalItemsTFName), "test") +) + +func TestUnit_OnPremisesGatewaysPersonalDataSource(t *testing.T) { + entity := fakes.NewRandomOnPremisesGatewayPersonal() + + fakes.FakeServer.Upsert(fakes.NewRandomOnPremisesGatewayPersonal()) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomOnPremisesGatewayPersonal()) + + resource.ParallelTest(t, testhelp.NewTestUnitCase( + t, + nil, + fakes.FakeServer.ServerFactory, + nil, + []resource.TestStep{ + // Step 1: Use an unexpected attribute to trigger an error. + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalsHeader, + map[string]any{ + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // Step 2: Normal read test with empty configuration. + { + Config: at.CompileConfig( + testDataSourceOnPremisesPersonalsHeader, + map[string]any{}, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(testDataSourceOnPremisesPersonalsFQN, "values.0.id"), + ), + }, + }, + )) +} diff --git a/internal/services/gateway/data_virtual_network_gateway.go b/internal/services/gateway/data_virtual_network_gateway.go index ee096a79..f7ceed07 100644 --- a/internal/services/gateway/data_virtual_network_gateway.go +++ b/internal/services/gateway/data_virtual_network_gateway.go @@ -58,20 +58,16 @@ func (d *dataSourceVirtualNetworkGateway) Schema(ctx context.Context, _ datasour Optional: true, Computed: true, }, - "type": schema.StringAttribute{ - MarkdownDescription: fmt.Sprintf("The %s.", ItemName), - Computed: true, - }, "capacity_id": schema.StringAttribute{ MarkdownDescription: fmt.Sprintf("The %s capacity Id.", ItemName), Computed: true, CustomType: customtypes.UUIDType{}, }, - "inactivity_minutes_before_sleep": schema.NumberAttribute{ + "inactivity_minutes_before_sleep": schema.Int32Attribute{ MarkdownDescription: "The number of minutes of inactivity before the gateway goes to sleep.", Computed: true, }, - "number_of_member_gateways": schema.NumberAttribute{ + "number_of_member_gateways": schema.Int32Attribute{ MarkdownDescription: "The number of member gateways.", Computed: true, }, diff --git a/internal/services/gateway/data_virtual_network_gateways.go b/internal/services/gateway/data_virtual_network_gateways.go index 94fb7b43..39ce3dfe 100644 --- a/internal/services/gateway/data_virtual_network_gateways.go +++ b/internal/services/gateway/data_virtual_network_gateways.go @@ -31,7 +31,7 @@ func NewDataSourceVirtualNetworkGateways() datasource.DataSource { } func (d *dataSourceVirtualNetworkGateways) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_virtual_network_gateways" + resp.TypeName = req.ProviderTypeName + "_" + VirtualNetworkItemsTFType } func (d *dataSourceVirtualNetworkGateways) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { @@ -53,10 +53,6 @@ func (d *dataSourceVirtualNetworkGateways) Schema(ctx context.Context, _ datasou MarkdownDescription: "The display name of the gateway.", Computed: true, }, - "type": schema.StringAttribute{ - MarkdownDescription: "The gateway type.", - Computed: true, - }, "capacity_id": schema.StringAttribute{ MarkdownDescription: "The Fabric license capacity ID.", Computed: true, diff --git a/internal/services/gateway/fake_test.go b/internal/services/gateway/fake_test.go new file mode 100644 index 00000000..ea701fb8 --- /dev/null +++ b/internal/services/gateway/fake_test.go @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway_test + +import ( + "net/http" + + azfake "github.com/Azure/azure-sdk-for-go/sdk/azcore/fake" + azto "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" +) + +// Replace "Workspace" with "Gateway" in types and function names. +func fakeGatewayRoleAssignments(exampleResp fabcore.GatewayRoleAssignments) func(gatewayID string, options *fabcore.GatewaysClientListGatewayRoleAssignmentsOptions) (resp azfake.PagerResponder[fabcore.GatewaysClientListGatewayRoleAssignmentsResponse]) { + return func(_ string, _ *fabcore.GatewaysClientListGatewayRoleAssignmentsOptions) (resp azfake.PagerResponder[fabcore.GatewaysClientListGatewayRoleAssignmentsResponse]) { + resp = azfake.PagerResponder[fabcore.GatewaysClientListGatewayRoleAssignmentsResponse]{} + resp.AddPage(http.StatusOK, fabcore.GatewaysClientListGatewayRoleAssignmentsResponse{GatewayRoleAssignments: exampleResp}, nil) + return + } +} + +// NewRandomGatewayRoleAssignments creates a random GatewayRoleAssignments object for testing. +func NewRandomGatewayRoleAssignments() fabcore.GatewayRoleAssignments { + // Generate random IDs for two role assignments. + assignmentID0 := testhelp.RandomUUID() + assignmentID1 := testhelp.RandomUUID() + principalID0 := testhelp.RandomUUID() + principalID1 := testhelp.RandomUUID() + + return fabcore.GatewayRoleAssignments{ + Value: []fabcore.GatewayRoleAssignment{ + { + ID: azto.Ptr(assignmentID0), + Role: azto.Ptr(fabcore.GatewayRoleAdmin), // assuming GatewayRoleAdmin exists + Principal: &fabcore.Principal{ + ID: azto.Ptr(principalID0), + Type: azto.Ptr(fabcore.PrincipalTypeGroup), + DisplayName: azto.Ptr(testhelp.RandomName()), + GroupDetails: &fabcore.PrincipalGroupDetails{ + GroupType: azto.Ptr(fabcore.GroupTypeSecurityGroup), + }, + }, + }, + { + ID: azto.Ptr(assignmentID1), + Role: azto.Ptr(fabcore.GatewayRoleAdmin), + Principal: &fabcore.Principal{ + ID: azto.Ptr(principalID1), + Type: azto.Ptr(fabcore.PrincipalTypeUser), + DisplayName: azto.Ptr(testhelp.RandomName()), + UserDetails: &fabcore.PrincipalUserDetails{ + UserPrincipalName: azto.Ptr(testhelp.RandomName()), + }, + }, + }, + }, + ContinuationToken: nil, + ContinuationURI: nil, + } +} diff --git a/internal/services/gateway/models_base_data_gateway.go b/internal/services/gateway/models_base_data_gateway.go index baf9554c..d706454a 100644 --- a/internal/services/gateway/models_base_data_gateway.go +++ b/internal/services/gateway/models_base_data_gateway.go @@ -15,22 +15,18 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type GatewayModelBase struct { - ID customtypes.UUID `tfsdk:"id"` - DisplayName types.String `tfsdk:"display_name"` - Type types.String `tfsdk:"type"` -} - type onPremisesGatewayModelBase struct { - GatewayModelBase + ID customtypes.UUID `tfsdk:"id"` + + DisplayName types.String `tfsdk:"display_name"` - AllowCloudConnectionRefresh *bool `tfsdk:"allow_cloud_connection_refresh"` + AllowCloudConnectionRefresh types.Bool `tfsdk:"allow_cloud_connection_refresh"` - AllowCustomConnectors *bool `tfsdk:"allow_custom_connectors"` + AllowCustomConnectors types.Bool `tfsdk:"allow_custom_connectors"` LoadBalancingSetting types.String `tfsdk:"load_balancing_setting"` - NumberOfMemberGateways *int32 `tfsdk:"number_of_member_gateways"` + NumberOfMemberGateways types.Int32 `tfsdk:"number_of_member_gateways"` PublicKey supertypes.SingleNestedObjectValueOf[publicKeyModel] `tfsdk:"public_key"` @@ -38,13 +34,15 @@ type onPremisesGatewayModelBase struct { } type virtualNetworkGatewayModelBase struct { - GatewayModelBase + ID customtypes.UUID `tfsdk:"id"` + + DisplayName types.String `tfsdk:"display_name"` CapacityId customtypes.UUID `tfsdk:"capacity_id"` - InactivityMinutesBeforeSleep *int32 `tfsdk:"inactivity_minutes_before_sleep"` + InactivityMinutesBeforeSleep types.Int32 `tfsdk:"inactivity_minutes_before_sleep"` - NumberOfMemberGateways *int32 `tfsdk:"number_of_member_gateways"` + NumberOfMemberGateways types.Int32 `tfsdk:"number_of_member_gateways"` VirtualNetworkAzureResource supertypes.SingleNestedObjectValueOf[virtualNetworkAzureResourceModel] `tfsdk:"virtual_network_azure_resource"` } @@ -54,8 +52,6 @@ type onPremisesGatewayPersonalModelBase struct { PublicKey supertypes.SingleNestedObjectValueOf[publicKeyModel] `tfsdk:"public_key"` - Type types.String `tfsdk:"type"` - Version types.String `tfsdk:"version"` } @@ -91,7 +87,6 @@ func (to *onPremisesGatewayPersonalModelBase) set(ctx context.Context, from fabc } to.PublicKey = publicKey - to.Type = types.StringPointerValue((*string)(from.Type)) to.Version = types.StringPointerValue(from.Version) return diags @@ -100,11 +95,10 @@ func (to *onPremisesGatewayPersonalModelBase) set(ctx context.Context, from fabc func (to *onPremisesGatewayModelBase) set(ctx context.Context, from fabcore.OnPremisesGateway) diag.Diagnostics { to.ID = customtypes.NewUUIDPointerValue(from.ID) to.DisplayName = types.StringPointerValue(from.DisplayName) - to.Type = types.StringPointerValue((*string)(from.Type)) - to.AllowCloudConnectionRefresh = from.AllowCloudConnectionRefresh - to.AllowCustomConnectors = from.AllowCustomConnectors + to.AllowCloudConnectionRefresh = types.BoolPointerValue(from.AllowCloudConnectionRefresh) + to.AllowCustomConnectors = types.BoolPointerValue(from.AllowCustomConnectors) to.LoadBalancingSetting = types.StringPointerValue((*string)(from.LoadBalancingSetting)) - to.NumberOfMemberGateways = from.NumberOfMemberGateways + to.NumberOfMemberGateways = types.Int32PointerValue(from.NumberOfMemberGateways) to.Version = types.StringPointerValue(from.Version) publicKey := supertypes.NewSingleNestedObjectValueOfNull[publicKeyModel](ctx) @@ -126,10 +120,9 @@ func (to *onPremisesGatewayModelBase) set(ctx context.Context, from fabcore.OnPr func (to *virtualNetworkGatewayModelBase) set(ctx context.Context, from fabcore.VirtualNetworkGateway) diag.Diagnostics { to.ID = customtypes.NewUUIDPointerValue(from.ID) to.DisplayName = types.StringPointerValue(from.DisplayName) - to.Type = types.StringPointerValue((*string)(from.Type)) to.CapacityId = customtypes.NewUUIDPointerValue(from.CapacityID) - to.InactivityMinutesBeforeSleep = from.InactivityMinutesBeforeSleep - to.NumberOfMemberGateways = from.NumberOfMemberGateways + to.InactivityMinutesBeforeSleep = types.Int32PointerValue(from.InactivityMinutesBeforeSleep) + to.NumberOfMemberGateways = types.Int32PointerValue(from.NumberOfMemberGateways) virtualNetworkAzureResource := supertypes.NewSingleNestedObjectValueOfNull[virtualNetworkAzureResourceModel](ctx) @@ -154,12 +147,6 @@ func (to *virtualNetworkAzureResourceModel) set(from fabcore.VirtualNetworkAzure to.SubnetName = types.StringPointerValue(from.SubnetName) } -// should I change my blablabla? -// func (to *GatewayModelBase) set(from fabcore.Gateway) { -// to.ID = customtypes.NewUUIDPointerValue(from.ID) -// to.Type = types.StringPointerValue((*string)(from.Type)) -// } - func (to *publicKeyModel) set(from fabcore.PublicKey) { to.Exponent = types.StringPointerValue(from.Exponent) to.Modulus = types.StringPointerValue(from.Modulus) diff --git a/internal/services/gateway/models_gateway_data_role_assignment.go b/internal/services/gateway/models_gateway_data_role_assignment.go new file mode 100644 index 00000000..b8354c15 --- /dev/null +++ b/internal/services/gateway/models_gateway_data_role_assignment.go @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" +) + +type dataSourceGatewayRoleAssignmentsModel struct { + GatewayID customtypes.UUID `tfsdk:"gateway_id"` + Values supertypes.ListNestedObjectValueOf[gatewayRoleAssignmentModel] `tfsdk:"values"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +func (to *dataSourceGatewayRoleAssignmentsModel) setValues(ctx context.Context, from []fabcore.GatewayRoleAssignment) diag.Diagnostics { + slice := make([]*gatewayRoleAssignmentModel, 0, len(from)) + + for _, entity := range from { + var entityModel gatewayRoleAssignmentModel + + if diags := entityModel.set(ctx, entity); diags.HasError() { + return diags + } + + slice = append(slice, &entityModel) + } + + return to.Values.Set(ctx, slice) +} + +// maybe use common infra from WS +type gatewayRoleAssignmentModel struct { + ID customtypes.UUID `tfsdk:"id"` + Role types.String `tfsdk:"role"` + DisplayName types.String `tfsdk:"display_name"` + Type types.String `tfsdk:"type"` + Details supertypes.SingleNestedObjectValueOf[principalDetailsModel] `tfsdk:"details"` +} + +func (to *gatewayRoleAssignmentModel) set(ctx context.Context, from fabcore.GatewayRoleAssignment) diag.Diagnostics { + to.ID = customtypes.NewUUIDPointerValue(from.ID) + to.Role = types.StringPointerValue((*string)(from.Role)) + + // Initialize the details model and set its values. + detailsModel := &principalDetailsModel{} + detailsModel.set(from.Principal, to) + + if diags := to.Details.Set(ctx, detailsModel); diags.HasError() { + return diags + } + + // Set common attributes from the principal. + to.DisplayName = types.StringPointerValue(from.Principal.DisplayName) + to.Type = types.StringPointerValue((*string)(from.Principal.Type)) + return nil +} + +type principalDetailsModel struct { + UserPrincipalName types.String `tfsdk:"user_principal_name"` + GroupType types.String `tfsdk:"group_type"` + AppID customtypes.UUID `tfsdk:"app_id"` + ParentPrincipalID customtypes.UUID `tfsdk:"parent_principal_id"` +} + +func (to *principalDetailsModel) set(from *fabcore.Principal, roleAssignment *gatewayRoleAssignmentModel) { + to.UserPrincipalName = types.StringNull() + to.GroupType = types.StringNull() + to.AppID = customtypes.NewUUIDNull() + to.ParentPrincipalID = customtypes.NewUUIDNull() + + // Set the DisplayName and Type on the role assignment (if not already set). + roleAssignment.DisplayName = types.StringPointerValue(from.DisplayName) + roleAssignment.Type = types.StringPointerValue((*string)(from.Type)) + + switch *from.Type { + case fabcore.PrincipalTypeUser: + to.UserPrincipalName = types.StringPointerValue(from.UserDetails.UserPrincipalName) + case fabcore.PrincipalTypeGroup: + to.GroupType = types.StringPointerValue((*string)(from.GroupDetails.GroupType)) + case fabcore.PrincipalTypeServicePrincipal: + to.AppID = customtypes.NewUUIDPointerValue(from.ServicePrincipalDetails.AADAppID) + case fabcore.PrincipalTypeServicePrincipalProfile: + to.ParentPrincipalID = customtypes.NewUUIDPointerValue(from.ServicePrincipalProfileDetails.ParentPrincipal.ID) + } +} diff --git a/internal/services/gateway/models_resource_gateway.go b/internal/services/gateway/models_resource_gateway.go index e11a8f78..f760f9fc 100644 --- a/internal/services/gateway/models_resource_gateway.go +++ b/internal/services/gateway/models_resource_gateway.go @@ -3,10 +3,75 @@ package gateway -import "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + + "github.com/hashicorp/terraform-plugin-framework/diag" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" +) type ResourceVirtualNetworkGatewayModel struct { virtualNetworkGatewayModelBase Timeouts timeouts.Value `tfsdk:"timeouts"` } + +type requestCreateGateway struct { + fabcore.CreateGatewayRequestClassification +} + +type requestUpdateGateway struct { + fabcore.UpdateGatewayRequestClassification +} + +func (to *requestCreateGateway) set(ctx context.Context, from ResourceVirtualNetworkGatewayModel) diag.Diagnostics { + var diags diag.Diagnostics + + gatewayType := fabcore.GatewayTypeVirtualNetwork + + virtualNetworkAzureResource, diags := from.VirtualNetworkAzureResource.Get(ctx) + if diags.HasError() { + return diags + } + + to.CreateGatewayRequestClassification = &fabcore.CreateVirtualNetworkGatewayRequest{ + Type: &gatewayType, + CapacityID: from.CapacityId.ValueStringPointer(), + DisplayName: from.DisplayName.ValueStringPointer(), + InactivityMinutesBeforeSleep: from.InactivityMinutesBeforeSleep.ValueInt32Pointer(), + NumberOfMemberGateways: from.NumberOfMemberGateways.ValueInt32Pointer(), + VirtualNetworkAzureResource: &fabcore.VirtualNetworkAzureResource{ + SubscriptionID: virtualNetworkAzureResource.SubscriptionID.ValueStringPointer(), + ResourceGroupName: virtualNetworkAzureResource.ResourceGroupName.ValueStringPointer(), + VirtualNetworkName: virtualNetworkAzureResource.VirtualNetworkName.ValueStringPointer(), + SubnetName: virtualNetworkAzureResource.SubnetName.ValueStringPointer(), + }, + } + + return nil +} + +func (to *requestUpdateGateway) set(from ResourceVirtualNetworkGatewayModel) diag.Diagnostics { + var diags diag.Diagnostics + + gatewayType := fabcore.GatewayTypeVirtualNetwork + + switch gatewayType { + case fabcore.GatewayTypeVirtualNetwork: + to.UpdateGatewayRequestClassification = &fabcore.UpdateVirtualNetworkGatewayRequest{ + Type: &gatewayType, + DisplayName: from.DisplayName.ValueStringPointer(), + CapacityID: from.CapacityId.ValueStringPointer(), + InactivityMinutesBeforeSleep: from.InactivityMinutesBeforeSleep.ValueInt32Pointer(), + } + default: + diags.AddError("Unsupported Gateway type", fmt.Sprintf("The Gateway type '%T' is not supported.", gatewayType)) + + return diags + } + + return nil +} diff --git a/internal/services/gateway/models_resource_gateway_role_assignment.go b/internal/services/gateway/models_resource_gateway_role_assignment.go new file mode 100644 index 00000000..81520cf0 --- /dev/null +++ b/internal/services/gateway/models_resource_gateway_role_assignment.go @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + + "github.com/hashicorp/terraform-plugin-framework/types" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" +) + +type resourceGatewayRoleAssignmentModel struct { + ID customtypes.UUID `tfsdk:"id"` + PrincipalID customtypes.UUID `tfsdk:"principal_id"` + PrincipalType types.String `tfsdk:"principal_type"` + Role types.String `tfsdk:"role"` + GatewayID customtypes.UUID `tfsdk:"gateway_id"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +func (to *resourceGatewayRoleAssignmentModel) set(from fabcore.GatewayRoleAssignment) { + to.ID = customtypes.NewUUIDPointerValue(from.ID) + to.PrincipalID = customtypes.NewUUIDPointerValue(from.Principal.ID) + to.PrincipalType = types.StringPointerValue((*string)(from.Principal.Type)) + to.Role = types.StringPointerValue((*string)(from.Role)) +} + +type requestCreateGatewayRoleAssignment struct { + fabcore.AddGatewayRoleAssignmentRequest +} + +func (to *requestCreateGatewayRoleAssignment) set(from resourceGatewayRoleAssignmentModel) { + to.Principal = &fabcore.Principal{ + ID: from.PrincipalID.ValueStringPointer(), + Type: (*fabcore.PrincipalType)(from.PrincipalType.ValueStringPointer()), + } + to.Role = (*fabcore.GatewayRole)(from.Role.ValueStringPointer()) +} + +type requestUpdateGatewayRoleAssignment struct { + fabcore.UpdateGatewayRoleAssignmentRequest +} + +func (to *requestUpdateGatewayRoleAssignment) set(from resourceGatewayRoleAssignmentModel) { + to.Role = (*fabcore.GatewayRole)(from.Role.ValueStringPointer()) +} diff --git a/internal/services/gateway/resource_gateway_role_assignments.go b/internal/services/gateway/resource_gateway_role_assignments.go new file mode 100644 index 00000000..d0323032 --- /dev/null +++ b/internal/services/gateway/resource_gateway_role_assignments.go @@ -0,0 +1,301 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-log/tflog" + + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" + pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" +) + +var ( + _ resource.ResourceWithConfigure = (*resourceGatewayRoleAssignment)(nil) + _ resource.ResourceWithImportState = (*resourceGatewayRoleAssignment)(nil) +) + +const GatewayRoleAssignmentTFName = "gateway_role_assignment" + +type resourceGatewayRoleAssignment struct { + pConfigData *pconfig.ProviderData + client *fabcore.GatewaysClient +} + +func NewResourceGatewayRoleAssignment() resource.Resource { + return &resourceGatewayRoleAssignment{} +} + +func (r *resourceGatewayRoleAssignment) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + GatewayRoleAssignmentTFName +} + +func (r *resourceGatewayRoleAssignment) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Manage a Gateway Role Assignment.\n\n" + + "Assign a role to a principal for a specific gateway.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The Gateway Role Assignment ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "principal_id": schema.StringAttribute{ + MarkdownDescription: "The Principal ID.", + Required: true, + CustomType: customtypes.UUIDType{}, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "principal_type": schema.StringAttribute{ + MarkdownDescription: "The type of the principal. Accepted values: " + utils.ConvertStringSlicesToString(fabcore.PossiblePrincipalTypeValues(), true, true) + ".", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf(utils.ConvertEnumsToStringSlices(fabcore.PossiblePrincipalTypeValues(), false)...), + }, + }, + "role": schema.StringAttribute{ + MarkdownDescription: "The Gateway Role assigned to the principal. Accepted values: " + utils.ConvertStringSlicesToString(fabcore.PossibleGatewayRoleValues(), true, true) + ".", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(utils.ConvertEnumsToStringSlices(fabcore.PossibleGatewayRoleValues(), false)...), + }, + }, + "gateway_id": schema.StringAttribute{ + MarkdownDescription: "The Gateway ID.", + Required: true, + CustomType: customtypes.UUIDType{}, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "timeouts": timeouts.AttributesAll(ctx), + }, + } +} + +func (r *resourceGatewayRoleAssignment) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { + resp.Diagnostics.AddError( + common.ErrorResourceConfigType, + fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), + ) + return + } + + r.pConfigData = pConfigData + // Create a gateways client using the provider's FabricClient. + r.client = fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient() +} + +func (r *resourceGatewayRoleAssignment) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + tflog.Debug(ctx, "CREATE GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "CREATE GATEWAY ROLE ASSIGNMENT", map[string]any{ + "plan": req.Plan, + }) + + var plan resourceGatewayRoleAssignmentModel + if resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := plan.Timeouts.Create(ctx, r.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + var reqCreate requestCreateGatewayRoleAssignment + reqCreate.set(plan) + + respCreate, err := r.client.AddGatewayRoleAssignment(ctx, plan.GatewayID.ValueString(), reqCreate.AddGatewayRoleAssignmentRequest, nil) + if resp.Diagnostics.Append(utils.GetDiagsFromError(ctx, err, utils.OperationCreate, nil)...); resp.Diagnostics.HasError() { + return + } + + plan.set(respCreate.GatewayRoleAssignment) + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + + tflog.Debug(ctx, "CREATE GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "end", + }) +} + +func (r *resourceGatewayRoleAssignment) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + tflog.Debug(ctx, "READ GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "start", + }) + var state resourceGatewayRoleAssignmentModel + if resp.Diagnostics.Append(req.State.Get(ctx, &state)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := state.Timeouts.Read(ctx, r.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + err := r.get(ctx, &state) + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, fabcore.ErrCommon.EntityNotFound); diags.HasError() { + if utils.IsErrNotFound(state.ID.ValueString(), &diags, fabcore.ErrCommon.EntityNotFound) { + resp.State.RemoveResource(ctx) + } + resp.Diagnostics.Append(diags...) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) + tflog.Debug(ctx, "READ GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "end", + }) +} + +func (r *resourceGatewayRoleAssignment) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + tflog.Debug(ctx, "UPDATE GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "start", + }) + var plan resourceGatewayRoleAssignmentModel + if resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := plan.Timeouts.Update(ctx, r.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + var reqUpdate requestUpdateGatewayRoleAssignment + reqUpdate.set(plan) + + respUpdate, err := r.client.UpdateGatewayRoleAssignment(ctx, plan.GatewayID.ValueString(), plan.ID.ValueString(), reqUpdate.UpdateGatewayRoleAssignmentRequest, nil) + if resp.Diagnostics.Append(utils.GetDiagsFromError(ctx, err, utils.OperationUpdate, nil)...); resp.Diagnostics.HasError() { + return + } + + plan.set(respUpdate.GatewayRoleAssignment) + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + tflog.Debug(ctx, "UPDATE GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "end", + }) +} + +func (r *resourceGatewayRoleAssignment) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Debug(ctx, "DELETE GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "start", + }) + var state resourceGatewayRoleAssignmentModel + if resp.Diagnostics.Append(req.State.Get(ctx, &state)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := state.Timeouts.Delete(ctx, r.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + _, err := r.client.DeleteGatewayRoleAssignment(ctx, state.GatewayID.ValueString(), state.ID.ValueString(), nil) + if resp.Diagnostics.Append(utils.GetDiagsFromError(ctx, err, utils.OperationDelete, nil)...); resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "DELETE GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "end", + }) +} + +func (r *resourceGatewayRoleAssignment) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + tflog.Debug(ctx, "IMPORT GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "start", + }) + // Expected import ID format: gatewayID/gatewayRoleAssignmentID + gatewayID, gatewayRoleAssignmentID, found := strings.Cut(req.ID, "/") + if !found { + resp.Diagnostics.AddError( + common.ErrorImportIdentifierHeader, + fmt.Sprintf(common.ErrorImportIdentifierDetails, "GatewayID/GatewayRoleAssignmentID"), + ) + return + } + + uuidGatewayID, diags := customtypes.NewUUIDValueMust(gatewayID) + resp.Diagnostics.Append(diags...) + uuidGatewayRoleAssignmentID, diags := customtypes.NewUUIDValueMust(gatewayRoleAssignmentID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var timeout timeouts.Value + if resp.Diagnostics.Append(resp.State.GetAttribute(ctx, path.Root("timeouts"), &timeout)...); resp.Diagnostics.HasError() { + return + } + + state := resourceGatewayRoleAssignmentModel{ + ID: uuidGatewayRoleAssignmentID, + GatewayID: uuidGatewayID, + Timeouts: timeout, + } + + err := r.get(ctx, &state) + if resp.Diagnostics.Append(utils.GetDiagsFromError(ctx, err, utils.OperationImport, nil)...); resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) + tflog.Debug(ctx, "IMPORT GATEWAY ROLE ASSIGNMENT", map[string]any{ + "action": "end", + }) +} + +func (r *resourceGatewayRoleAssignment) get(ctx context.Context, model *resourceGatewayRoleAssignmentModel) error { + tflog.Trace(ctx, "getting Gateway Role Assignment") + respGet, err := r.client.GetGatewayRoleAssignment(ctx, model.GatewayID.ValueString(), model.ID.ValueString(), nil) + if err != nil { + return err + } + + model.set(respGet.GatewayRoleAssignment) + return nil +} diff --git a/internal/services/gateway/resource_gateway_role_assignments_test.go b/internal/services/gateway/resource_gateway_role_assignments_test.go new file mode 100644 index 00000000..67203452 --- /dev/null +++ b/internal/services/gateway/resource_gateway_role_assignments_test.go @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway_test + +import ( + "fmt" + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testResourceGatewayRoleAssignment = testhelp.ResourceFQN("fabric", "gateway_role_assignment", "test") + testResourceGatewayRoleAssignmentHeader = at.ResourceHeader(testhelp.TypeName("fabric", "gateway_role_assignment"), "test") +) + +func TestUnit_GatewayRoleAssignmentResource_Attributes(t *testing.T) { + resource.ParallelTest(t, testhelp.NewTestUnitCase(t, &testResourceGatewayRoleAssignment, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + // error - missing required attribute: gateway_id + { + ResourceName: testResourceGatewayRoleAssignment, + Config: at.CompileConfig( + testResourceGatewayRoleAssignmentHeader, + map[string]any{ + "principal_id": "00000000-0000-0000-0000-000000000000", + "principal_type": "User", + "role": "Member", + }, + ), + ExpectError: regexp.MustCompile(`The argument "gateway_id" is required, but no definition was found.`), + }, + // error - missing required attribute: principal_id + { + ResourceName: testResourceGatewayRoleAssignment, + Config: at.CompileConfig( + testResourceGatewayRoleAssignmentHeader, + map[string]any{ + "gateway_id": "00000000-0000-0000-0000-000000000000", + "principal_type": "User", + "role": "Member", + }, + ), + ExpectError: regexp.MustCompile(`The argument "principal_id" is required, but no definition was found.`), + }, + // error - missing required attribute: principal_type + { + ResourceName: testResourceGatewayRoleAssignment, + Config: at.CompileConfig( + testResourceGatewayRoleAssignmentHeader, + map[string]any{ + "gateway_id": "00000000-0000-0000-0000-000000000000", + "principal_id": "00000000-0000-0000-0000-000000000000", + "role": "Member", + }, + ), + ExpectError: regexp.MustCompile(`The argument "principal_type" is required, but no definition was found.`), + }, + // error - missing required attribute: role + { + ResourceName: testResourceGatewayRoleAssignment, + Config: at.CompileConfig( + testResourceGatewayRoleAssignmentHeader, + map[string]any{ + "gateway_id": "00000000-0000-0000-0000-000000000000", + "principal_id": "00000000-0000-0000-0000-000000000000", + "principal_type": "User", + }, + ), + ExpectError: regexp.MustCompile(`The argument "role" is required, but no definition was found.`), + }, + // error - invalid UUID for gateway_id + { + ResourceName: testResourceGatewayRoleAssignment, + Config: at.CompileConfig( + testResourceGatewayRoleAssignmentHeader, + map[string]any{ + "gateway_id": "invalid uuid", + "principal_id": "00000000-0000-0000-0000-000000000000", + "principal_type": "User", + "role": "Member", + }, + ), + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + // error - invalid UUID for principal_id + { + ResourceName: testResourceGatewayRoleAssignment, + Config: at.CompileConfig( + testResourceGatewayRoleAssignmentHeader, + map[string]any{ + "gateway_id": "00000000-0000-0000-0000-000000000000", + "principal_id": "invalid uuid", + "principal_type": "User", + "role": "Member", + }, + ), + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + })) +} + +func TestUnit_GatewayRoleAssignmentResource_ImportState(t *testing.T) { + testCase := at.CompileConfig( + testResourceGatewayRoleAssignmentHeader, + map[string]any{}, + ) + + resource.ParallelTest(t, testhelp.NewTestUnitCase(t, &testResourceGatewayRoleAssignment, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ + { + ResourceName: testResourceGatewayRoleAssignment, + Config: testCase, + ImportStateId: "not-valid", + ImportState: true, + ExpectError: regexp.MustCompile("GatewayID/GatewayRoleAssignmentID"), + }, + { + ResourceName: testResourceGatewayRoleAssignment, + Config: testCase, + ImportStateId: "test/id", + ImportState: true, + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + { + ResourceName: testResourceGatewayRoleAssignment, + Config: testCase, + ImportStateId: fmt.Sprintf("%s/%s", "test", "00000000-0000-0000-0000-000000000000"), + ImportState: true, + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + { + ResourceName: testResourceGatewayRoleAssignment, + Config: testCase, + ImportStateId: fmt.Sprintf("%s/%s", "00000000-0000-0000-0000-000000000000", "test"), + ImportState: true, + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + })) +} + +func TestAcc_GatewayRoleAssignmentResource_CRUD(t *testing.T) { + // Assume a well-known gateway is defined in the test environment. + gateway := testhelp.WellKnown()["Gateway"].(map[string]any) + gatewayID := gateway["id"].(string) + + // Assume a known principal is available. + principal := testhelp.WellKnown()["Principal"].(map[string]any) + principalID := principal["id"].(string) + principalType := principal["type"].(string) + + resource.Test(t, testhelp.NewTestAccCase(t, &testResourceGatewayRoleAssignment, nil, []resource.TestStep{ + // Create and Read + { + ResourceName: testResourceGatewayRoleAssignment, + Config: at.CompileConfig( + testResourceGatewayRoleAssignmentHeader, + map[string]any{ + "gateway_id": gatewayID, + "principal_id": principalID, + "principal_type": principalType, + "role": "Member", + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "gateway_id", gatewayID), + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "principal_id", principalID), + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "principal_type", principalType), + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "role", "Member"), + ), + }, + // Update and Read + { + ResourceName: testResourceGatewayRoleAssignment, + Config: at.CompileConfig( + testResourceGatewayRoleAssignmentHeader, + map[string]any{ + "gateway_id": gatewayID, + "principal_id": principalID, + "principal_type": principalType, + "role": "Viewer", + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "gateway_id", gatewayID), + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "principal_id", principalID), + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "principal_type", principalType), + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "role", "Viewer"), + ), + }, + })) +} diff --git a/internal/services/gateway/resource_virtual_network_gateway.go b/internal/services/gateway/resource_virtual_network_gateway.go index fa0ed3c2..0d4e8fbe 100644 --- a/internal/services/gateway/resource_virtual_network_gateway.go +++ b/internal/services/gateway/resource_virtual_network_gateway.go @@ -1 +1,354 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + package gateway + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" + "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-log/tflog" + fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" + supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" + pconfig "github.com/microsoft/terraform-provider-fabric/internal/provider/config" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = (*resourceVirtualNetworkGateway)(nil) + _ resource.ResourceWithConfigure = (*resourceVirtualNetworkGateway)(nil) + _ resource.ResourceWithImportState = (*resourceVirtualNetworkGateway)(nil) +) + +type resourceVirtualNetworkGateway struct { + pConfigData *pconfig.ProviderData + client *fabcore.GatewaysClient +} + +func NewResourceVirtualNetworkGateway() resource.Resource { + return &resourceVirtualNetworkGateway{} +} + +func (r *resourceVirtualNetworkGateway) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + VirtualNetworkItemTFType +} + +func (r *resourceVirtualNetworkGateway) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "This resource manages a Fabric " + ItemName + ".\n\n" + + "See [" + ItemName + "s](" + ItemDocsURL + ") for more information.\n\n" + + ItemDocsSPNSupport, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The " + ItemName + " ID.", + Computed: true, + CustomType: customtypes.UUIDType{}, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "display_name": schema.StringAttribute{ + MarkdownDescription: "The " + ItemName + " display name.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtMost(200), + }, + }, + "capacity_id": schema.StringAttribute{ + MarkdownDescription: "The " + ItemName + " capacity ID.", + Required: true, + CustomType: customtypes.UUIDType{}, + }, + "inactivity_minutes_before_sleep": schema.Int32Attribute{ + MarkdownDescription: "The " + ItemName + " inactivity minutes before sleep.", + Required: true, + Validators: []validator.Int32{ + int32validator.OneOf(PossibleInactivityMinutesBeforeSleepValues...), + }, + }, + "number_of_member_gateways": schema.Int32Attribute{ + MarkdownDescription: "The " + ItemName + " number of member gateways.", + Required: true, + Validators: []validator.Int32{ + int32validator.Between(MinNumberOfMemberGatewaysValues, MaxNumberOfMemberGatewaysValues), + }, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + "virtual_network_azure_resource": schema.SingleNestedAttribute{ + MarkdownDescription: "The Azure resource of the virtual network.", + Required: true, + CustomType: supertypes.NewSingleNestedObjectTypeOf[virtualNetworkAzureResourceModel](ctx), + Attributes: map[string]schema.Attribute{ + "subscription_id": schema.StringAttribute{ + MarkdownDescription: "The subscription ID.", + Required: true, + CustomType: customtypes.UUIDType{}, + }, + "resource_group_name": schema.StringAttribute{ + MarkdownDescription: "The name of the resource group.", + Required: true, + }, + "virtual_network_name": schema.StringAttribute{ + MarkdownDescription: "The name of the virtual network.", + Required: true, + }, + "subnet_name": schema.StringAttribute{ + MarkdownDescription: "The name of the subnet.", + Required: true, + }, + }, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplace(), + }, + }, + "timeouts": timeouts.AttributesAll(ctx), + }, + } +} + +func (r *resourceVirtualNetworkGateway) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { + resp.Diagnostics.AddError( + common.ErrorResourceConfigType, + fmt.Sprintf(common.ErrorFabricClientType, req.ProviderData), + ) + + return + } + + r.pConfigData = pConfigData + r.client = fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient() +} + +func (r *resourceVirtualNetworkGateway) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + tflog.Debug(ctx, "CREATE", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "CREATE", map[string]any{ + "config": req.Config, + "plan": req.Plan, + }) + + var plan, state ResourceVirtualNetworkGatewayModel + + if resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := plan.Timeouts.Create(ctx, r.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + state.Timeouts = plan.Timeouts + + var reqCreate requestCreateGateway + + reqCreate.set(ctx, plan) + + respCreate, err := r.client.CreateGateway(ctx, reqCreate.CreateGatewayRequestClassification, nil) + if resp.Diagnostics.Append(utils.GetDiagsFromError(ctx, err, utils.OperationCreate, nil)...); resp.Diagnostics.HasError() { + return + } + + // get the virtual gateway from the classifiaction + vng := respCreate.GatewayClassification.(*fabcore.VirtualNetworkGateway) + state.set(ctx, *vng) + + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) + + tflog.Debug(ctx, "CREATE", map[string]any{ + "action": "end", + }) + + if resp.Diagnostics.HasError() { + return + } +} + +func (r *resourceVirtualNetworkGateway) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + tflog.Debug(ctx, "READ", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "READ", map[string]any{ + "state": req.State, + }) + + var state ResourceVirtualNetworkGatewayModel + + if resp.Diagnostics.Append(req.State.Get(ctx, &state)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := state.Timeouts.Read(ctx, r.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + err := r.get(ctx, &state) + if diags := utils.GetDiagsFromError(ctx, err, utils.OperationRead, fabcore.ErrCommon.EntityNotFound); diags.HasError() { + if utils.IsErrNotFound(state.ID.ValueString(), &diags, fabcore.ErrCommon.EntityNotFound) { + resp.State.RemoveResource(ctx) + } + + resp.Diagnostics.Append(diags...) + + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) + + tflog.Debug(ctx, "READ", map[string]any{ + "action": "end", + }) + + if resp.Diagnostics.HasError() { + return + } +} + +func (r *resourceVirtualNetworkGateway) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + tflog.Debug(ctx, "UPDATE", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "UPDATE", map[string]any{ + "config": req.Config, + "plan": req.Plan, + "state": req.State, + }) + + var plan ResourceVirtualNetworkGatewayModel + + if resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := plan.Timeouts.Update(ctx, r.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + var reqUpdate requestUpdateGateway + + reqUpdate.set(plan) + + respUpdate, err := r.client.UpdateGateway(ctx, plan.ID.ValueString(), reqUpdate.UpdateGatewayRequestClassification, nil) + if resp.Diagnostics.Append(utils.GetDiagsFromError(ctx, err, utils.OperationUpdate, nil)...); resp.Diagnostics.HasError() { + return + } + + // get the virtual gateway from the classifiaction + vng := respUpdate.GatewayClassification.(*fabcore.VirtualNetworkGateway) + plan.set(ctx, *vng) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + + tflog.Debug(ctx, "UPDATE", map[string]any{ + "action": "end", + }) + + if resp.Diagnostics.HasError() { + return + } +} + +func (r *resourceVirtualNetworkGateway) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Debug(ctx, "DELETE", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "DELETE", map[string]any{ + "state": req.State, + }) + + var state ResourceVirtualNetworkGatewayModel + + if resp.Diagnostics.Append(req.State.Get(ctx, &state)...); resp.Diagnostics.HasError() { + return + } + + timeout, diags := state.Timeouts.Delete(ctx, r.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + _, err := r.client.DeleteGateway(ctx, state.ID.ValueString(), nil) + if resp.Diagnostics.Append(utils.GetDiagsFromError(ctx, err, utils.OperationDelete, nil)...); resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "DELETE", map[string]any{ + "action": "end", + }) +} + +func (r *resourceVirtualNetworkGateway) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + tflog.Debug(ctx, "IMPORT", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "IMPORT", map[string]any{ + "id": req.ID, + }) + + _, diags := customtypes.NewUUIDValueMust(req.ID) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + + tflog.Debug(ctx, "IMPORT", map[string]any{ + "action": "end", + }) + + if resp.Diagnostics.HasError() { + return + } +} + +func (r *resourceVirtualNetworkGateway) get(ctx context.Context, model *ResourceVirtualNetworkGatewayModel) error { + tflog.Trace(ctx, "getting "+ItemName) + + respGet, err := r.client.GetGateway(ctx, model.ID.ValueString(), nil) + if err != nil { + return err + } + + vng := respGet.GatewayClassification.(*fabcore.VirtualNetworkGateway) + model.set(ctx, *vng) + + return nil +} diff --git a/internal/services/gateway/resource_virtual_network_gateway_test.go b/internal/services/gateway/resource_virtual_network_gateway_test.go new file mode 100644 index 00000000..fd221142 --- /dev/null +++ b/internal/services/gateway/resource_virtual_network_gateway_test.go @@ -0,0 +1,282 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package gateway_test + +import ( + "errors" + "regexp" + "testing" + + at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp" + "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" +) + +var ( + testResourceVirtualNetworkGatewayFQN = testhelp.ResourceFQN("fabric", VirtualNetworkItemTFName, "test") + testResourceVirtualNetworkGatewayHeader = at.ResourceHeader(testhelp.TypeName("fabric", VirtualNetworkItemTFName), "test") +) + +func TestUnit_VirtualNetworkGatewayResource_Attributes(t *testing.T) { + resource.ParallelTest(t, testhelp.NewTestUnitCase( + t, + &testResourceVirtualNetworkGatewayFQN, + fakes.FakeServer.ServerFactory, + nil, + []resource.TestStep{ + // Error: Missing required attribute "display_name" + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "capacity_id": "123e4567-e89b-12d3-a456-426614174000", + "inactivity_minutes_before_sleep": 30, + "number_of_member_gateways": 3, + "virtual_network_azure_resource": map[string]any{ + "subscription_id": "123e4567-e89b-12d3-a456-426614174001", + "resource_group_name": "test-rg", + "virtual_network_name": "test-vnet", + "subnet_name": "test-subnet", + }, + }, + ), + ExpectError: regexp.MustCompile(`The argument "display_name" is required`), + }, + // Error: Unexpected attribute provided. + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": "test gateway", + "capacity_id": "123e4567-e89b-12d3-a456-426614174000", + "inactivity_minutes_before_sleep": 30, + "number_of_member_gateways": 3, + "virtual_network_azure_resource": map[string]any{ + "subscription_id": "123e4567-e89b-12d3-a456-426614174001", + "resource_group_name": "test-rg", + "virtual_network_name": "test-vnet", + "subnet_name": "test-subnet", + }, + "unexpected_attr": "test", + }, + ), + ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), + }, + // Error: Invalid UUID for "capacity_id" + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": "test gateway", + "capacity_id": "not-a-valid-uuid", + "inactivity_minutes_before_sleep": 30, + "number_of_member_gateways": 3, + "virtual_network_azure_resource": map[string]any{ + "subscription_id": "123e4567-e89b-12d3-a456-426614174001", + "resource_group_name": "test-rg", + "virtual_network_name": "test-vnet", + "subnet_name": "test-subnet", + }, + }, + ), + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + // Add a test step for a successful creation/read with all required attributes. + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": "test gateway", + "capacity_id": "123e4567-e89b-12d3-a456-426614174000", + "inactivity_minutes_before_sleep": 30, + "number_of_member_gateways": 3, + "virtual_network_azure_resource": map[string]any{ + "subscription_id": "123e4567-e89b-12d3-a456-426614174001", + "resource_group_name": "test-rg", + "virtual_network_name": "test-vnet", + "subnet_name": "test-subnet", + }, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "display_name", "test gateway"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "capacity_id", "123e4567-e89b-12d3-a456-426614174000"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "inactivity_minutes_before_sleep", "30"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "number_of_member_gateways", "3"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subscription_id", "123e4567-e89b-12d3-a456-426614174001"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.resource_group_name", "test-rg"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.virtual_network_name", "test-vnet"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subnet_name", "test-subnet"), + ), + }, + }, + )) +} + +func TestUnit_VirtualNetworkGatewayResource_ImportState(t *testing.T) { + // Create a fake Virtual Network Gateway. + entity := fakes.NewRandomVirtualNetworkGateway() + + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + fakes.FakeServer.Upsert(entity) + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + + testConfig := at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": *entity.DisplayName, + // Other required attributes will be populated from state. + }, + ) + + resource.Test(t, testhelp.NewTestUnitCase( + t, + &testResourceVirtualNetworkGatewayFQN, + fakes.FakeServer.ServerFactory, + nil, + []resource.TestStep{ + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: testConfig, + ImportStateId: "not-a-valid-uuid", + ImportState: true, + ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), + }, + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: testConfig, + ImportStateId: *entity.ID, + ImportState: true, + ImportStatePersist: true, + ImportStateCheck: func(is []*terraform.InstanceState) error { + // Optionally, add additional state validations here. + if len(is) != 1 { + return errors.New("expected one instance state") + } + + if is[0].ID != *entity.ID { + return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected ID") + } + + if is[0].Attributes["display_name"] != *entity.DisplayName { + return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected display_name") + } + + if is[0].Attributes["capacity_id"] != *entity.CapacityID { + return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected capacity_id") + } + + // // Convert inactivity_minutes_before_sleep from string to int32. + // inactivityStr := is[0].Attributes["inactivity_minutes_before_sleep"] + // inactivityVal, err := strconv.ParseInt(inactivityStr, 10, 32) + // if err != nil { + // return fmt.Errorf("failed to parse inactivity_minutes_before_sleep: %w", err) + // } + // if int32(inactivityVal) != *entity.InactivityMinutesBeforeSleep { + // return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected inactivity_minutes_before_sleep") + // } + + // // Convert number_of_member_gateways from string to int32. + // memberGatewaysStr := is[0].Attributes["number_of_member_gateways"] + // memberGatewaysVal, err := strconv.ParseInt(memberGatewaysStr, 10, 32) + // if err != nil { + // return fmt.Errorf("failed to parse number_of_member_gateways: %w", err) + // } + // if int32(memberGatewaysVal) != *entity.NumberOfMemberGateways { + // return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected number_of_member_gateways") + // } + + if is[0].Attributes["virtual_network_azure_resource.0.subscription_id"] != *entity.VirtualNetworkAzureResource.SubscriptionID { + return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected virtual_network_azure_resource.subscription_id") + } + + if is[0].Attributes["virtual_network_azure_resource.0.resource_group_name"] != *entity.VirtualNetworkAzureResource.ResourceGroupName { + return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected virtual_network_azure_resource.resource_group_name") + } + + if is[0].Attributes["virtual_network_azure_resource.0.virtual_network_name"] != *entity.VirtualNetworkAzureResource.VirtualNetworkName { + return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected virtual_network_azure_resource.virtual_network_name") + } + + if is[0].Attributes["virtual_network_azure_resource.0.subnet_name"] != *entity.VirtualNetworkAzureResource.SubnetName { + return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected virtual_network_azure_resource.subnet_name") + } + + return nil + }, + }, + }, + )) +} + +func TestUnit_VirtualNetworkGatewayResource_CRUD(t *testing.T) { + // Create fake entities. + entityExist := fakes.NewRandomVirtualNetworkGateway() + entityBefore := fakes.NewRandomVirtualNetworkGateway() + entityAfter := fakes.NewRandomVirtualNetworkGateway() + + // Upsert some fake virtual network gateways. + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + fakes.FakeServer.Upsert(entityExist) + fakes.FakeServer.Upsert(fakes.NewRandomVirtualNetworkGateway()) + + resource.Test(t, testhelp.NewTestUnitCase( + t, + &testResourceVirtualNetworkGatewayFQN, + fakes.FakeServer.ServerFactory, + nil, + []resource.TestStep{ + // Error: Attempting to create a duplicate gateway (existing entity). + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": *entityExist.DisplayName, + }, + ), + ExpectError: regexp.MustCompile(common.ErrorCreateHeader), + }, + // Create and Read + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": *entityBefore.DisplayName, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPtr(testResourceVirtualNetworkGatewayFQN, "display_name", entityBefore.DisplayName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "capacity_id", *entityBefore.CapacityID), + ), + }, + // Update and Read + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": *entityBefore.DisplayName, + // Simulate update by changing capacity_id. + "capacity_id": *entityAfter.CapacityID, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPtr(testResourceVirtualNetworkGatewayFQN, "display_name", entityBefore.DisplayName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "capacity_id", *entityAfter.CapacityID), + ), + }, + }, + )) +} diff --git a/internal/testhelp/fakes/fabric_gateways.go b/internal/testhelp/fakes/fabric_gateways.go index 903da93c..e43bd73b 100644 --- a/internal/testhelp/fakes/fabric_gateways.go +++ b/internal/testhelp/fakes/fabric_gateways.go @@ -124,6 +124,12 @@ func configureOnPremisesGatewayPersonal(server *fakeServer) fabcore.OnPremisesGa return fabcore.OnPremisesGatewayPersonal{} } +func configureOnPremisesGateway(server *fakeServer) fabcore.OnPremisesGateway { + configureGatewayClassification(server) + + return fabcore.OnPremisesGateway{} +} + func configureGatewayClassification(server *fakeServer) { type concreteEntityOperations interface { simpleIDOperations[ @@ -186,8 +192,8 @@ func NewRandomOnPremisesGateway() *fabcore.OnPremisesGateway { } } -func NewRandomOnPremisesGatewayPersonal() fabcore.OnPremisesGatewayPersonal { - return fabcore.OnPremisesGatewayPersonal{ +func NewRandomOnPremisesGatewayPersonal() *fabcore.OnPremisesGatewayPersonal { + return &fabcore.OnPremisesGatewayPersonal{ ID: to.Ptr(testhelp.RandomUUID()), Type: to.Ptr(fabcore.GatewayTypeOnPremisesPersonal), Version: to.Ptr("1.0"), diff --git a/internal/testhelp/fakes/fake_server.go b/internal/testhelp/fakes/fake_server.go index 7cd4a57b..d1ae4a26 100644 --- a/internal/testhelp/fakes/fake_server.go +++ b/internal/testhelp/fakes/fake_server.go @@ -35,6 +35,7 @@ func newFakeServer() *fakeServer { handleEntity(server, configureDomain) handleEntity(server, configureEventhouse) handleEntity(server, configureEnvironment) + handleEntity(server, configureOnPremisesGateway) handleEntity(server, configureOnPremisesGatewayPersonal) handleEntity(server, configureVirtualNetworkGateway) handleEntity(server, configureKQLDatabase) From 9d0d3aaa7096d1629356c01baa2db942e45e8ed1 Mon Sep 17 00:00:00 2001 From: Or Bauberg Date: Thu, 6 Feb 2025 15:41:41 +0000 Subject: [PATCH 6/7] before sync with main --- .../data_on_premises_gateway_personal.go | 7 ++ .../data_on_premises_gateway_personal_test.go | 4 +- .../gateway/models_resource_gateway.go | 1 + .../resource_virtual_network_gateway_test.go | 83 ++++++++++--------- .../{fabric_gateways.go => fabric_gateway.go} | 0 5 files changed, 56 insertions(+), 39 deletions(-) rename internal/testhelp/fakes/{fabric_gateways.go => fabric_gateway.go} (100%) diff --git a/internal/services/gateway/data_on_premises_gateway_personal.go b/internal/services/gateway/data_on_premises_gateway_personal.go index c109dbab..1ab88879 100644 --- a/internal/services/gateway/data_on_premises_gateway_personal.go +++ b/internal/services/gateway/data_on_premises_gateway_personal.go @@ -7,6 +7,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -61,12 +62,18 @@ func (d *dataSourceOnPremisesGatewayPersonal) Schema(ctx context.Context, _ data }, }, }, + "timeouts": timeouts.Attributes(ctx), }, } } func (d *dataSourceOnPremisesGatewayPersonal) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) + if !ok { resp.Diagnostics.AddError( common.ErrorDataSourceConfigType, diff --git a/internal/services/gateway/data_on_premises_gateway_personal_test.go b/internal/services/gateway/data_on_premises_gateway_personal_test.go index 8510eed6..8baaf584 100644 --- a/internal/services/gateway/data_on_premises_gateway_personal_test.go +++ b/internal/services/gateway/data_on_premises_gateway_personal_test.go @@ -57,10 +57,10 @@ func TestUnit_OnPremisesGatewayPersonalDataSource(t *testing.T) { Config: at.CompileConfig( testDataSourceOnPremisesPersonalHeader, map[string]any{ - "id": "not-a-valid-uuid", + "id": "invalid uuid", }, ), - ExpectError: regexp.MustCompile(`invalid UUID`), + ExpectError: regexp.MustCompile(`invalid uuid`), }, // Step 4: Valid read test using the entity's ID. { diff --git a/internal/services/gateway/models_resource_gateway.go b/internal/services/gateway/models_resource_gateway.go index f760f9fc..f725ba0f 100644 --- a/internal/services/gateway/models_resource_gateway.go +++ b/internal/services/gateway/models_resource_gateway.go @@ -66,6 +66,7 @@ func (to *requestUpdateGateway) set(from ResourceVirtualNetworkGatewayModel) dia DisplayName: from.DisplayName.ValueStringPointer(), CapacityID: from.CapacityId.ValueStringPointer(), InactivityMinutesBeforeSleep: from.InactivityMinutesBeforeSleep.ValueInt32Pointer(), + NumberOfMemberGateways: from.NumberOfMemberGateways.ValueInt32Pointer(), } default: diags.AddError("Unsupported Gateway type", fmt.Sprintf("The Gateway type '%T' is not supported.", gatewayType)) diff --git a/internal/services/gateway/resource_virtual_network_gateway_test.go b/internal/services/gateway/resource_virtual_network_gateway_test.go index fd221142..f84080f2 100644 --- a/internal/services/gateway/resource_virtual_network_gateway_test.go +++ b/internal/services/gateway/resource_virtual_network_gateway_test.go @@ -5,14 +5,15 @@ package gateway_test import ( "errors" + "fmt" "regexp" + "strconv" "testing" at "github.com/dcarbone/terraform-plugin-framework-utils/v3/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/microsoft/terraform-provider-fabric/internal/common" "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" "github.com/microsoft/terraform-provider-fabric/internal/testhelp" "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" @@ -176,39 +177,39 @@ func TestUnit_VirtualNetworkGatewayResource_ImportState(t *testing.T) { return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected capacity_id") } - // // Convert inactivity_minutes_before_sleep from string to int32. - // inactivityStr := is[0].Attributes["inactivity_minutes_before_sleep"] - // inactivityVal, err := strconv.ParseInt(inactivityStr, 10, 32) - // if err != nil { - // return fmt.Errorf("failed to parse inactivity_minutes_before_sleep: %w", err) - // } - // if int32(inactivityVal) != *entity.InactivityMinutesBeforeSleep { - // return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected inactivity_minutes_before_sleep") - // } + // Convert inactivity_minutes_before_sleep from string to int32. + inactivityStr := is[0].Attributes["inactivity_minutes_before_sleep"] + inactivityVal, err := strconv.ParseInt(inactivityStr, 10, 32) + if err != nil { + return fmt.Errorf("failed to parse inactivity_minutes_before_sleep: %w", err) + } + if int32(inactivityVal) != *entity.InactivityMinutesBeforeSleep { + return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected inactivity_minutes_before_sleep") + } - // // Convert number_of_member_gateways from string to int32. - // memberGatewaysStr := is[0].Attributes["number_of_member_gateways"] - // memberGatewaysVal, err := strconv.ParseInt(memberGatewaysStr, 10, 32) - // if err != nil { - // return fmt.Errorf("failed to parse number_of_member_gateways: %w", err) - // } - // if int32(memberGatewaysVal) != *entity.NumberOfMemberGateways { - // return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected number_of_member_gateways") - // } + // Convert number_of_member_gateways from string to int32. + memberGatewaysStr := is[0].Attributes["number_of_member_gateways"] + memberGatewaysVal, err := strconv.ParseInt(memberGatewaysStr, 10, 32) + if err != nil { + return fmt.Errorf("failed to parse number_of_member_gateways: %w", err) + } + if int32(memberGatewaysVal) != *entity.NumberOfMemberGateways { + return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected number_of_member_gateways") + } - if is[0].Attributes["virtual_network_azure_resource.0.subscription_id"] != *entity.VirtualNetworkAzureResource.SubscriptionID { + if is[0].Attributes["virtual_network_azure_resource.subscription_id"] != *entity.VirtualNetworkAzureResource.SubscriptionID { return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected virtual_network_azure_resource.subscription_id") } - if is[0].Attributes["virtual_network_azure_resource.0.resource_group_name"] != *entity.VirtualNetworkAzureResource.ResourceGroupName { + if is[0].Attributes["virtual_network_azure_resource.resource_group_name"] != *entity.VirtualNetworkAzureResource.ResourceGroupName { return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected virtual_network_azure_resource.resource_group_name") } - if is[0].Attributes["virtual_network_azure_resource.0.virtual_network_name"] != *entity.VirtualNetworkAzureResource.VirtualNetworkName { + if is[0].Attributes["virtual_network_azure_resource.virtual_network_name"] != *entity.VirtualNetworkAzureResource.VirtualNetworkName { return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected virtual_network_azure_resource.virtual_network_name") } - if is[0].Attributes["virtual_network_azure_resource.0.subnet_name"] != *entity.VirtualNetworkAzureResource.SubnetName { + if is[0].Attributes["virtual_network_azure_resource.subnet_name"] != *entity.VirtualNetworkAzureResource.SubnetName { return errors.New(testResourceVirtualNetworkGatewayFQN + ": unexpected virtual_network_azure_resource.subnet_name") } @@ -236,24 +237,33 @@ func TestUnit_VirtualNetworkGatewayResource_CRUD(t *testing.T) { fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ - // Error: Attempting to create a duplicate gateway (existing entity). - { - ResourceName: testResourceVirtualNetworkGatewayFQN, - Config: at.CompileConfig( - testResourceVirtualNetworkGatewayHeader, - map[string]any{ - "display_name": *entityExist.DisplayName, - }, - ), - ExpectError: regexp.MustCompile(common.ErrorCreateHeader), - }, + // // Error: Attempting to create a duplicate gateway (existing entity). + // { + // ResourceName: testResourceVirtualNetworkGatewayFQN, + // Config: at.CompileConfig( + // testResourceVirtualNetworkGatewayHeader, + // map[string]any{ + // "display_name": *entityExist.DisplayName, + // }, + // ), + // ExpectError: regexp.MustCompile(common.ErrorCreateHeader), + // }, // Create and Read { ResourceName: testResourceVirtualNetworkGatewayFQN, Config: at.CompileConfig( testResourceVirtualNetworkGatewayHeader, map[string]any{ - "display_name": *entityBefore.DisplayName, + "display_name": *entityBefore.DisplayName, + "capacity_id": *entityBefore.CapacityID, + "inactivity_minutes_before_sleep": 30, + "number_of_member_gateways": 3, + "virtual_network_azure_resource": map[string]any{ + "subscription_id": "123e4567-e89b-12d3-a456-426614174001", + "resource_group_name": "test-rg", + "virtual_network_name": "test-vnet", + "subnet_name": "test-subnet", + }, }, ), Check: resource.ComposeAggregateTestCheckFunc( @@ -268,8 +278,7 @@ func TestUnit_VirtualNetworkGatewayResource_CRUD(t *testing.T) { testResourceVirtualNetworkGatewayHeader, map[string]any{ "display_name": *entityBefore.DisplayName, - // Simulate update by changing capacity_id. - "capacity_id": *entityAfter.CapacityID, + "capacity_id": *entityAfter.CapacityID, }, ), Check: resource.ComposeAggregateTestCheckFunc( diff --git a/internal/testhelp/fakes/fabric_gateways.go b/internal/testhelp/fakes/fabric_gateway.go similarity index 100% rename from internal/testhelp/fakes/fabric_gateways.go rename to internal/testhelp/fakes/fabric_gateway.go From 6f0899a153b764a36b3aa0df68a68a76a54bf5ad Mon Sep 17 00:00:00 2001 From: Or Bauberg Date: Mon, 17 Feb 2025 15:53:39 +0000 Subject: [PATCH 7/7] acc tests & set well known --- .../gateway/data_gateway_role_assignments.go | 25 -- .../data_gateway_role_assignments_test.go | 3 +- .../gateway/data_on_premises_gateway.go | 13 +- .../data_on_premises_gateway_personal.go | 40 +-- .../data_on_premises_gateway_personal_test.go | 48 +--- .../gateway/data_on_premises_gateway_test.go | 33 --- .../gateway/data_on_premises_gateways_test.go | 20 -- .../gateway/data_virtual_network_gateway.go | 17 +- .../data_virtual_network_gateway_test.go | 11 +- internal/services/gateway/fake_test.go | 2 +- .../models_gateway_data_role_assignment.go | 54 +--- ...odels_gateway_resource_role_assignment.go} | 0 .../resource_gateway_role_assignments_test.go | 22 +- .../resource_virtual_network_gateway_test.go | 163 ++++++++++-- internal/testhelp/fakes/fabric_gateway.go | 10 +- internal/testhelp/utils.go | 10 +- tools/scripts/Set-WellKnown.ps1 | 240 +++++++++++++++++- 17 files changed, 466 insertions(+), 245 deletions(-) rename internal/services/gateway/{models_resource_gateway_role_assignment.go => models_gateway_resource_role_assignment.go} (100%) diff --git a/internal/services/gateway/data_gateway_role_assignments.go b/internal/services/gateway/data_gateway_role_assignments.go index 3edde4d8..463129b5 100644 --- a/internal/services/gateway/data_gateway_role_assignments.go +++ b/internal/services/gateway/data_gateway_role_assignments.go @@ -71,31 +71,6 @@ func (d *dataSourceGatewayRoleAssignments) Schema(ctx context.Context, _ datasou MarkdownDescription: "The type of the principal.", Computed: true, }, - "details": schema.SingleNestedAttribute{ - MarkdownDescription: "The principal details.", - Computed: true, - CustomType: supertypes.NewSingleNestedObjectTypeOf[principalDetailsModel](ctx), - Attributes: map[string]schema.Attribute{ - "user_principal_name": schema.StringAttribute{ - MarkdownDescription: "The user principal name.", - Computed: true, - }, - "group_type": schema.StringAttribute{ - MarkdownDescription: "The group type.", - Computed: true, - }, - "app_id": schema.StringAttribute{ - MarkdownDescription: "The Service Principal's Microsoft Entra App ID.", - Computed: true, - CustomType: customtypes.UUIDType{}, - }, - "parent_principal_id": schema.StringAttribute{ - MarkdownDescription: "The parent principal ID of Service Principal Profile.", - Computed: true, - CustomType: customtypes.UUIDType{}, - }, - }, - }, }, }, }, diff --git a/internal/services/gateway/data_gateway_role_assignments_test.go b/internal/services/gateway/data_gateway_role_assignments_test.go index 633247ae..4c40d6e0 100644 --- a/internal/services/gateway/data_gateway_role_assignments_test.go +++ b/internal/services/gateway/data_gateway_role_assignments_test.go @@ -52,7 +52,6 @@ func TestUnit_GatewayRoleAssignmentsDataSource(t *testing.T) { resource.TestCheckResourceAttrPtr(testDataSourceGatewayRoleAssignments, "values.1.role", (*string)(entity.Role)), resource.TestCheckResourceAttrPtr(testDataSourceGatewayRoleAssignments, "values.1.display_name", entity.Principal.DisplayName), resource.TestCheckResourceAttrPtr(testDataSourceGatewayRoleAssignments, "values.1.type", (*string)(entity.Principal.Type)), - // Additional nested details checks can be added here. ), }, })) @@ -60,7 +59,7 @@ func TestUnit_GatewayRoleAssignmentsDataSource(t *testing.T) { func TestAcc_GatewayRoleAssignmentsDataSource(t *testing.T) { // For acceptance testing, assume a well-known gateway is provided. - gateway := testhelp.WellKnown()["GatewayDS"].(map[string]any) + gateway := testhelp.WellKnown()["GatewayVirtualNetwork"].(map[string]any) gatewayID := gateway["id"].(string) resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ diff --git a/internal/services/gateway/data_on_premises_gateway.go b/internal/services/gateway/data_on_premises_gateway.go index 80113217..7c9d6871 100644 --- a/internal/services/gateway/data_on_premises_gateway.go +++ b/internal/services/gateway/data_on_premises_gateway.go @@ -132,10 +132,9 @@ func (d *dataSourceOnPremisesGateway) Configure(_ context.Context, req datasourc } d.pConfigData = pConfigData - d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) + d.client = fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient() } -// Read refreshes the Terraform state with the latest data. func (d *dataSourceOnPremisesGateway) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { tflog.Debug(ctx, "READ", map[string]any{ "action": "start", @@ -209,15 +208,13 @@ func (d *dataSourceOnPremisesGateway) getByDisplayName(ctx context.Context, mode } for _, gw := range gateways { - if OnPremisesGateway, ok := gw.(*fabcore.OnPremisesGateway); ok { - if *OnPremisesGateway.DisplayName == model.DisplayName.ValueString() { - model.set(ctx, *OnPremisesGateway) + if onPremisesGateway, ok := gw.(*fabcore.OnPremisesGateway); ok { + if *onPremisesGateway.DisplayName == model.DisplayName.ValueString() { + model.set(ctx, *onPremisesGateway) return nil } } } - var diags diag.Diagnostics - diags.AddError(common.ErrorReadHeader, "no on-premises gateway with display name found") - return diags + return diag.Diagnostics{diag.NewErrorDiagnostic(common.ErrorReadHeader, "on-premises gateway not found")} } diff --git a/internal/services/gateway/data_on_premises_gateway_personal.go b/internal/services/gateway/data_on_premises_gateway_personal.go index 1ab88879..88141650 100644 --- a/internal/services/gateway/data_on_premises_gateway_personal.go +++ b/internal/services/gateway/data_on_premises_gateway_personal.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-log/tflog" fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" @@ -38,9 +39,8 @@ func (d *dataSourceOnPremisesGatewayPersonal) Schema(ctx context.Context, _ data MarkdownDescription: "Retrieve an on-premises gateway in its 'personal' form (ID, public key, type, version).", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ + Required: true, MarkdownDescription: "The gateway ID.", - Optional: true, - Computed: true, CustomType: customtypes.UUIDType{}, }, "version": schema.StringAttribute{ @@ -73,7 +73,6 @@ func (d *dataSourceOnPremisesGatewayPersonal) Configure(ctx context.Context, req } pConfigData, ok := req.ProviderData.(*pconfig.ProviderData) - if !ok { resp.Diagnostics.AddError( common.ErrorDataSourceConfigType, @@ -82,27 +81,33 @@ func (d *dataSourceOnPremisesGatewayPersonal) Configure(ctx context.Context, req return } d.pConfigData = pConfigData - d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) + d.client = fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient() } func (d *dataSourceOnPremisesGatewayPersonal) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data datasourceOnPremisesGatewayPersonalModel + tflog.Debug(ctx, "READ", map[string]any{ + "action": "start", + }) + tflog.Trace(ctx, "READ", map[string]any{ + "config": req.Config, + }) + var data datasourceOnPremisesGatewayPersonalModel if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { return } - if data.ID.ValueString() == "" { - resp.Diagnostics.AddError( - "Missing ID", - "An ID is required to look up a personal on-premises gateway.", - ) + timeout, diags := data.Timeouts.Read(ctx, d.pConfigData.Timeout) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { return } - gatewayResp, errResp := d.client.GetGateway(ctx, data.ID.ValueString(), nil) - if errResp != nil { - resp.Diagnostics.AddError("GetGateway failed", errResp.Error()) + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + gatewayResp, err := d.client.GetGateway(ctx, data.ID.ValueString(), nil) + if err != nil { + resp.Diagnostics.AddError("GetGateway failed", err.Error()) return } @@ -112,14 +117,17 @@ func (d *dataSourceOnPremisesGatewayPersonal) Read(ctx context.Context, req data return } - gateway := datasourceOnPremisesGatewayPersonalModel{} - diags := gateway.set(ctx, *realGw) + data.set(ctx, *realGw) if diags.HasError() { resp.Diagnostics.Append(diags...) return } - if diags := resp.State.Set(ctx, gateway); diags.HasError() { + tflog.Debug(ctx, "READ", map[string]any{ + "action": "end", + }) + + if diags := resp.State.Set(ctx, data); diags.HasError() { resp.Diagnostics.Append(diags...) return } diff --git a/internal/services/gateway/data_on_premises_gateway_personal_test.go b/internal/services/gateway/data_on_premises_gateway_personal_test.go index 8baaf584..cd11d063 100644 --- a/internal/services/gateway/data_on_premises_gateway_personal_test.go +++ b/internal/services/gateway/data_on_premises_gateway_personal_test.go @@ -49,10 +49,19 @@ func TestUnit_OnPremisesGatewayPersonalDataSource(t *testing.T) { testDataSourceOnPremisesPersonalHeader, map[string]any{}, ), - // "Missing ID" error is raised in Read when data.ID is empty. - ExpectError: regexp.MustCompile(`Missing ID`), + ExpectError: regexp.MustCompile(`The argument "id" is required`), }, - // Step 3: Invalid UUID string should trigger an error. + // Step 3: Read by id - not found + { + Config: at.CompileConfig( + testDataSourceOnPremisesItemHeader, + map[string]any{ + "id": testhelp.RandomUUID(), + }, + ), + ExpectError: regexp.MustCompile(common.ErrorReadHeader), + }, + // Step 4: Invalid UUID string should trigger an error. { Config: at.CompileConfig( testDataSourceOnPremisesPersonalHeader, @@ -62,7 +71,7 @@ func TestUnit_OnPremisesGatewayPersonalDataSource(t *testing.T) { ), ExpectError: regexp.MustCompile(`invalid uuid`), }, - // Step 4: Valid read test using the entity's ID. + // Step 5: Valid read test using the entity's ID. { Config: at.CompileConfig( testDataSourceOnPremisesPersonalHeader, @@ -80,34 +89,3 @@ func TestUnit_OnPremisesGatewayPersonalDataSource(t *testing.T) { }, )) } - -func TestAcc_OnPremisesGatewayPersonalDataSource(t *testing.T) { - entity := testhelp.WellKnown()["OnPremisesGatewayPersonal"].(map[string]any) - entityID := entity["id"].(string) - entityDescription := entity["description"].(string) - - resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ - { - Config: at.CompileConfig( - testDataSourceOnPremisesPersonalHeader, - map[string]any{ - "id": entityID, - }, - ), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(testDataSourceOnPremisesPersonalFQN, "id", entityID), - resource.TestCheckResourceAttr(testDataSourceOnPremisesPersonalFQN, "description", entityDescription), - ), - }, - // read by id - not found - { - Config: at.CompileConfig( - testDataSourceOnPremisesPersonalHeader, - map[string]any{ - "id": testhelp.RandomUUID(), - }, - ), - ExpectError: regexp.MustCompile(common.ErrorReadHeader), - }, - })) -} diff --git a/internal/services/gateway/data_on_premises_gateway_test.go b/internal/services/gateway/data_on_premises_gateway_test.go index b8f78570..1853ba3d 100644 --- a/internal/services/gateway/data_on_premises_gateway_test.go +++ b/internal/services/gateway/data_on_premises_gateway_test.go @@ -131,36 +131,3 @@ func TestUnit_OnPremisesGatewayDataSource(t *testing.T) { }, })) } - -func TestAcc_OnPremisesGatewayDataSource(t *testing.T) { - entity := testhelp.WellKnown()["OnPremisesGateway"].(map[string]any) - entityID := entity["id"].(string) - entityDisplayName := entity["displayName"].(string) - entityDescription := entity["description"].(string) - - resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ - { - Config: at.CompileConfig( - testDataSourceOnPremisesItemHeader, - map[string]any{ - "id": entityID, - }, - ), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "id", entityID), - resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "display_name", entityDisplayName), - resource.TestCheckResourceAttr(testDataSourceOnPremisesItemFabricFQN, "description", entityDescription), - ), - }, - // read by id - not found - { - Config: at.CompileConfig( - testDataSourceOnPremisesItemHeader, - map[string]any{ - "id": testhelp.RandomUUID(), - }, - ), - ExpectError: regexp.MustCompile(common.ErrorReadHeader), - }, - })) -} diff --git a/internal/services/gateway/data_on_premises_gateways_test.go b/internal/services/gateway/data_on_premises_gateways_test.go index 91ed3699..39544eb2 100644 --- a/internal/services/gateway/data_on_premises_gateways_test.go +++ b/internal/services/gateway/data_on_premises_gateways_test.go @@ -52,23 +52,3 @@ func TestUnit_OnPremisesGatewaysDataSource(t *testing.T) { }, )) } - -func TestAcc_OnPremisesGatewaysDataSource(t *testing.T) { - resource.ParallelTest(t, testhelp.NewTestAccCase( - t, - nil, - nil, - []resource.TestStep{ - // read - { - Config: at.CompileConfig( - testDataSourceOnPremisesGatewaysHeader, - map[string]any{}, - ), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(testDataSourceOnPremisesGatewaysFQN, "values.0.id"), - ), - }, - }, - )) -} diff --git a/internal/services/gateway/data_virtual_network_gateway.go b/internal/services/gateway/data_virtual_network_gateway.go index f7ceed07..75b585d5 100644 --- a/internal/services/gateway/data_virtual_network_gateway.go +++ b/internal/services/gateway/data_virtual_network_gateway.go @@ -130,7 +130,7 @@ func (d *dataSourceVirtualNetworkGateway) Configure(_ context.Context, req datas } d.pConfigData = pConfigData - d.client = (*fabcore.GatewaysClient)(fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient()) + d.client = fabcore.NewClientFactoryWithClient(*pConfigData.FabricClient).NewGatewaysClient() } // Read refreshes the Terraform state with the latest data. @@ -190,10 +190,13 @@ func (d *dataSourceVirtualNetworkGateway) getByID(ctx context.Context, model *da if gw, ok := respGet.GatewayClassification.(*fabcore.VirtualNetworkGateway); ok { model.set(ctx, *gw) return nil - } else { - var diags diag.Diagnostics - diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") - return diags + } + + return diag.Diagnostics{ + diag.NewErrorDiagnostic( + common.ErrorReadHeader, + "expected gateway to be a virtual network gateway", + ), } } @@ -215,7 +218,5 @@ func (d *dataSourceVirtualNetworkGateway) getByDisplayName(ctx context.Context, } } - var diags diag.Diagnostics - diags.AddError(common.ErrorReadHeader, "expected gateway to be an on-premises gateway") - return diags + return diag.Diagnostics{diag.NewErrorDiagnostic(common.ErrorReadHeader, "virtual network gateway not found")} } diff --git a/internal/services/gateway/data_virtual_network_gateway_test.go b/internal/services/gateway/data_virtual_network_gateway_test.go index 3e331f93..f3974a34 100644 --- a/internal/services/gateway/data_virtual_network_gateway_test.go +++ b/internal/services/gateway/data_virtual_network_gateway_test.go @@ -133,10 +133,9 @@ func TestUnit_VirtualNetworkGatewayDataSource(t *testing.T) { } func TestAcc_VirtualNetworkGatewayDataSource(t *testing.T) { - entity := testhelp.WellKnown()["VirtualNetworkGateway"].(map[string]any) + entity := testhelp.WellKnown()["GatewayVirtualNetwork"].(map[string]any) entityID := entity["id"].(string) entityDisplayName := entity["displayName"].(string) - entityDescription := entity["description"].(string) resource.ParallelTest(t, testhelp.NewTestAccCase(t, nil, nil, []resource.TestStep{ { @@ -149,7 +148,13 @@ func TestAcc_VirtualNetworkGatewayDataSource(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "id", entityID), resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "display_name", entityDisplayName), - resource.TestCheckResourceAttr(testDataSourceVirtualNetworkFQN, "description", entityDescription), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "inactivity_minutes_before_sleep"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "capacity_id"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "number_of_member_gateways"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.subscription_id"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.resource_group_name"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.virtual_network_name"), + resource.TestCheckResourceAttrSet(testDataSourceVirtualNetworkFQN, "virtual_network_azure_resource.subnet_name"), ), }, // read by id - not found diff --git a/internal/services/gateway/fake_test.go b/internal/services/gateway/fake_test.go index ea701fb8..64f8149d 100644 --- a/internal/services/gateway/fake_test.go +++ b/internal/services/gateway/fake_test.go @@ -46,7 +46,7 @@ func NewRandomGatewayRoleAssignments() fabcore.GatewayRoleAssignments { }, { ID: azto.Ptr(assignmentID1), - Role: azto.Ptr(fabcore.GatewayRoleAdmin), + Role: azto.Ptr(fabcore.GatewayRoleConnectionCreator), Principal: &fabcore.Principal{ ID: azto.Ptr(principalID1), Type: azto.Ptr(fabcore.PrincipalTypeUser), diff --git a/internal/services/gateway/models_gateway_data_role_assignment.go b/internal/services/gateway/models_gateway_data_role_assignment.go index b8354c15..d4e37c47 100644 --- a/internal/services/gateway/models_gateway_data_role_assignment.go +++ b/internal/services/gateway/models_gateway_data_role_assignment.go @@ -27,7 +27,7 @@ func (to *dataSourceGatewayRoleAssignmentsModel) setValues(ctx context.Context, for _, entity := range from { var entityModel gatewayRoleAssignmentModel - if diags := entityModel.set(ctx, entity); diags.HasError() { + if diags := entityModel.set(entity); diags.HasError() { return diags } @@ -37,58 +37,16 @@ func (to *dataSourceGatewayRoleAssignmentsModel) setValues(ctx context.Context, return to.Values.Set(ctx, slice) } -// maybe use common infra from WS type gatewayRoleAssignmentModel struct { - ID customtypes.UUID `tfsdk:"id"` - Role types.String `tfsdk:"role"` - DisplayName types.String `tfsdk:"display_name"` - Type types.String `tfsdk:"type"` - Details supertypes.SingleNestedObjectValueOf[principalDetailsModel] `tfsdk:"details"` + ID customtypes.UUID `tfsdk:"id"` + Role types.String `tfsdk:"role"` + DisplayName types.String `tfsdk:"display_name"` + Type types.String `tfsdk:"type"` } -func (to *gatewayRoleAssignmentModel) set(ctx context.Context, from fabcore.GatewayRoleAssignment) diag.Diagnostics { +func (to *gatewayRoleAssignmentModel) set(from fabcore.GatewayRoleAssignment) diag.Diagnostics { to.ID = customtypes.NewUUIDPointerValue(from.ID) to.Role = types.StringPointerValue((*string)(from.Role)) - // Initialize the details model and set its values. - detailsModel := &principalDetailsModel{} - detailsModel.set(from.Principal, to) - - if diags := to.Details.Set(ctx, detailsModel); diags.HasError() { - return diags - } - - // Set common attributes from the principal. - to.DisplayName = types.StringPointerValue(from.Principal.DisplayName) - to.Type = types.StringPointerValue((*string)(from.Principal.Type)) return nil } - -type principalDetailsModel struct { - UserPrincipalName types.String `tfsdk:"user_principal_name"` - GroupType types.String `tfsdk:"group_type"` - AppID customtypes.UUID `tfsdk:"app_id"` - ParentPrincipalID customtypes.UUID `tfsdk:"parent_principal_id"` -} - -func (to *principalDetailsModel) set(from *fabcore.Principal, roleAssignment *gatewayRoleAssignmentModel) { - to.UserPrincipalName = types.StringNull() - to.GroupType = types.StringNull() - to.AppID = customtypes.NewUUIDNull() - to.ParentPrincipalID = customtypes.NewUUIDNull() - - // Set the DisplayName and Type on the role assignment (if not already set). - roleAssignment.DisplayName = types.StringPointerValue(from.DisplayName) - roleAssignment.Type = types.StringPointerValue((*string)(from.Type)) - - switch *from.Type { - case fabcore.PrincipalTypeUser: - to.UserPrincipalName = types.StringPointerValue(from.UserDetails.UserPrincipalName) - case fabcore.PrincipalTypeGroup: - to.GroupType = types.StringPointerValue((*string)(from.GroupDetails.GroupType)) - case fabcore.PrincipalTypeServicePrincipal: - to.AppID = customtypes.NewUUIDPointerValue(from.ServicePrincipalDetails.AADAppID) - case fabcore.PrincipalTypeServicePrincipalProfile: - to.ParentPrincipalID = customtypes.NewUUIDPointerValue(from.ServicePrincipalProfileDetails.ParentPrincipal.ID) - } -} diff --git a/internal/services/gateway/models_resource_gateway_role_assignment.go b/internal/services/gateway/models_gateway_resource_role_assignment.go similarity index 100% rename from internal/services/gateway/models_resource_gateway_role_assignment.go rename to internal/services/gateway/models_gateway_resource_role_assignment.go diff --git a/internal/services/gateway/resource_gateway_role_assignments_test.go b/internal/services/gateway/resource_gateway_role_assignments_test.go index 67203452..d066cafc 100644 --- a/internal/services/gateway/resource_gateway_role_assignments_test.go +++ b/internal/services/gateway/resource_gateway_role_assignments_test.go @@ -31,7 +31,7 @@ func TestUnit_GatewayRoleAssignmentResource_Attributes(t *testing.T) { map[string]any{ "principal_id": "00000000-0000-0000-0000-000000000000", "principal_type": "User", - "role": "Member", + "role": "ConnectionCreator", }, ), ExpectError: regexp.MustCompile(`The argument "gateway_id" is required, but no definition was found.`), @@ -44,7 +44,7 @@ func TestUnit_GatewayRoleAssignmentResource_Attributes(t *testing.T) { map[string]any{ "gateway_id": "00000000-0000-0000-0000-000000000000", "principal_type": "User", - "role": "Member", + "role": "Admin", }, ), ExpectError: regexp.MustCompile(`The argument "principal_id" is required, but no definition was found.`), @@ -57,7 +57,7 @@ func TestUnit_GatewayRoleAssignmentResource_Attributes(t *testing.T) { map[string]any{ "gateway_id": "00000000-0000-0000-0000-000000000000", "principal_id": "00000000-0000-0000-0000-000000000000", - "role": "Member", + "role": "ConnectionCreator", }, ), ExpectError: regexp.MustCompile(`The argument "principal_type" is required, but no definition was found.`), @@ -70,7 +70,7 @@ func TestUnit_GatewayRoleAssignmentResource_Attributes(t *testing.T) { map[string]any{ "gateway_id": "00000000-0000-0000-0000-000000000000", "principal_id": "00000000-0000-0000-0000-000000000000", - "principal_type": "User", + "principal_type": "Admin", }, ), ExpectError: regexp.MustCompile(`The argument "role" is required, but no definition was found.`), @@ -84,7 +84,7 @@ func TestUnit_GatewayRoleAssignmentResource_Attributes(t *testing.T) { "gateway_id": "invalid uuid", "principal_id": "00000000-0000-0000-0000-000000000000", "principal_type": "User", - "role": "Member", + "role": "ConnectionCreator", }, ), ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), @@ -98,7 +98,7 @@ func TestUnit_GatewayRoleAssignmentResource_Attributes(t *testing.T) { "gateway_id": "00000000-0000-0000-0000-000000000000", "principal_id": "invalid uuid", "principal_type": "User", - "role": "Member", + "role": "ConnectionCreator", }, ), ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), @@ -146,7 +146,7 @@ func TestUnit_GatewayRoleAssignmentResource_ImportState(t *testing.T) { func TestAcc_GatewayRoleAssignmentResource_CRUD(t *testing.T) { // Assume a well-known gateway is defined in the test environment. - gateway := testhelp.WellKnown()["Gateway"].(map[string]any) + gateway := testhelp.WellKnown()["GatewayVirtualNetwork"].(map[string]any) gatewayID := gateway["id"].(string) // Assume a known principal is available. @@ -164,14 +164,14 @@ func TestAcc_GatewayRoleAssignmentResource_CRUD(t *testing.T) { "gateway_id": gatewayID, "principal_id": principalID, "principal_type": principalType, - "role": "Member", + "role": "ConnectionCreator", }, ), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "gateway_id", gatewayID), resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "principal_id", principalID), resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "principal_type", principalType), - resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "role", "Member"), + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "role", "ConnectionCreator"), ), }, // Update and Read @@ -183,14 +183,14 @@ func TestAcc_GatewayRoleAssignmentResource_CRUD(t *testing.T) { "gateway_id": gatewayID, "principal_id": principalID, "principal_type": principalType, - "role": "Viewer", + "role": "Admin", }, ), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "gateway_id", gatewayID), resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "principal_id", principalID), resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "principal_type", principalType), - resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "role", "Viewer"), + resource.TestCheckResourceAttr(testResourceGatewayRoleAssignment, "role", "Admin"), ), }, })) diff --git a/internal/services/gateway/resource_virtual_network_gateway_test.go b/internal/services/gateway/resource_virtual_network_gateway_test.go index f84080f2..688f22d1 100644 --- a/internal/services/gateway/resource_virtual_network_gateway_test.go +++ b/internal/services/gateway/resource_virtual_network_gateway_test.go @@ -14,7 +14,9 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/microsoft/terraform-provider-fabric/internal/common" "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" + "github.com/microsoft/terraform-provider-fabric/internal/services/gateway" "github.com/microsoft/terraform-provider-fabric/internal/testhelp" "github.com/microsoft/terraform-provider-fabric/internal/testhelp/fakes" ) @@ -135,8 +137,16 @@ func TestUnit_VirtualNetworkGatewayResource_ImportState(t *testing.T) { testConfig := at.CompileConfig( testResourceVirtualNetworkGatewayHeader, map[string]any{ - "display_name": *entity.DisplayName, - // Other required attributes will be populated from state. + "display_name": *entity.DisplayName, + "capacity_id": *entity.CapacityID, + "inactivity_minutes_before_sleep": 30, + "number_of_member_gateways": 3, + "virtual_network_azure_resource": map[string]any{ + "subscription_id": "123e4567-e89b-12d3-a456-426614174001", + "resource_group_name": "test-rg", + "virtual_network_name": "test-vnet", + "subnet_name": "test-subnet", + }, }, ) @@ -237,17 +247,26 @@ func TestUnit_VirtualNetworkGatewayResource_CRUD(t *testing.T) { fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ - // // Error: Attempting to create a duplicate gateway (existing entity). - // { - // ResourceName: testResourceVirtualNetworkGatewayFQN, - // Config: at.CompileConfig( - // testResourceVirtualNetworkGatewayHeader, - // map[string]any{ - // "display_name": *entityExist.DisplayName, - // }, - // ), - // ExpectError: regexp.MustCompile(common.ErrorCreateHeader), - // }, + // Error: Attempting to create a duplicate gateway (existing entity). + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": *entityExist.DisplayName, + "capacity_id": *entityBefore.CapacityID, + "inactivity_minutes_before_sleep": 30, + "number_of_member_gateways": 3, + "virtual_network_azure_resource": map[string]any{ + "subscription_id": "123e4567-e89b-12d3-a456-426614174001", + "resource_group_name": "test-rg", + "virtual_network_name": "test-vnet", + "subnet_name": "test-subnet", + }, + }, + ), + ExpectError: regexp.MustCompile(common.ErrorCreateHeader), + }, // Create and Read { ResourceName: testResourceVirtualNetworkGatewayFQN, @@ -268,7 +287,13 @@ func TestUnit_VirtualNetworkGatewayResource_CRUD(t *testing.T) { ), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPtr(testResourceVirtualNetworkGatewayFQN, "display_name", entityBefore.DisplayName), - resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "capacity_id", *entityBefore.CapacityID), + resource.TestCheckResourceAttrPtr(testResourceVirtualNetworkGatewayFQN, "capacity_id", entityBefore.CapacityID), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "inactivity_minutes_before_sleep", "30"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "number_of_member_gateways", "3"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subscription_id", "123e4567-e89b-12d3-a456-426614174001"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.resource_group_name", "test-rg"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.virtual_network_name", "test-vnet"), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subnet_name", "test-subnet"), ), }, // Update and Read @@ -277,15 +302,117 @@ func TestUnit_VirtualNetworkGatewayResource_CRUD(t *testing.T) { Config: at.CompileConfig( testResourceVirtualNetworkGatewayHeader, map[string]any{ - "display_name": *entityBefore.DisplayName, - "capacity_id": *entityAfter.CapacityID, + "display_name": *entityAfter.DisplayName, + "capacity_id": *entityAfter.CapacityID, + "inactivity_minutes_before_sleep": int(*entityAfter.InactivityMinutesBeforeSleep), + "number_of_member_gateways": int(*entityAfter.NumberOfMemberGateways), + "virtual_network_azure_resource": map[string]any{ + "subscription_id": *entityAfter.VirtualNetworkAzureResource.SubscriptionID, + "resource_group_name": *entityAfter.VirtualNetworkAzureResource.ResourceGroupName, + "virtual_network_name": *entityAfter.VirtualNetworkAzureResource.VirtualNetworkName, + "subnet_name": *entityAfter.VirtualNetworkAzureResource.SubnetName, + }, }, ), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrPtr(testResourceVirtualNetworkGatewayFQN, "display_name", entityBefore.DisplayName), - resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "capacity_id", *entityAfter.CapacityID), + resource.TestCheckResourceAttrPtr(testResourceVirtualNetworkGatewayFQN, "display_name", entityAfter.DisplayName), + resource.TestCheckResourceAttrPtr(testResourceVirtualNetworkGatewayFQN, "capacity_id", entityAfter.CapacityID), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "inactivity_minutes_before_sleep", strconv.Itoa(int(*entityAfter.InactivityMinutesBeforeSleep))), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "number_of_member_gateways", strconv.Itoa(int(*entityAfter.NumberOfMemberGateways))), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subscription_id", *entityAfter.VirtualNetworkAzureResource.SubscriptionID), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.resource_group_name", *entityAfter.VirtualNetworkAzureResource.ResourceGroupName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.virtual_network_name", *entityAfter.VirtualNetworkAzureResource.VirtualNetworkName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subnet_name", *entityAfter.VirtualNetworkAzureResource.SubnetName), ), }, }, )) } + +func TestAcc_VirtualNetworkGatewayResource_CRUD(t *testing.T) { + // Get well-known test values + capacity := testhelp.WellKnown()["Capacity"].(map[string]any) + capacityID := capacity["id"].(string) + + // Generate random names for testing + entityCreateDisplayName := testhelp.RandomName() + entityUpdateDisplayName := testhelp.RandomName() + + initialVirtualNetworkAzureResource := testhelp.WellKnown()["VirtualNetworkInitial"].(map[string]any) + initSubscriptionID := initialVirtualNetworkAzureResource["subscriptionId"].(string) + initResourceGroupName := initialVirtualNetworkAzureResource["resourceGroupName"].(string) + initVirtualNetworkName := initialVirtualNetworkAzureResource["name"].(string) + initSubnetName := initialVirtualNetworkAzureResource["subnetName"].(string) + + entityInitialInactivityMinutesBeforeSleep := 30 + entityUpdateInactivityMinutesBeforeSleep := 60 + entityInitialNumberOfMemberGateways := int(testhelp.RandomInt32Range(gateway.MinNumberOfMemberGatewaysValues, gateway.MaxNumberOfMemberGatewaysValues)) + entityUpdateNumberOfMemberGateways := int(testhelp.RandomInt32Range(gateway.MinNumberOfMemberGatewaysValues, gateway.MaxNumberOfMemberGatewaysValues)) + + updateVirtualNetworkAzureResource := testhelp.WellKnown()["VirtualNetworkUpdate"].(map[string]any) + updateVirtualNetworkName := updateVirtualNetworkAzureResource["name"].(string) + updateResourceGroupName := updateVirtualNetworkAzureResource["resourceGroupName"].(string) + updateSubnetName := updateVirtualNetworkAzureResource["subnetName"].(string) + updateSubscriptionID := updateVirtualNetworkAzureResource["subscriptionId"].(string) + + resource.Test(t, testhelp.NewTestAccCase(t, &testResourceVirtualNetworkGatewayFQN, nil, []resource.TestStep{ + // Create and Read + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": entityCreateDisplayName, + "capacity_id": capacityID, + "inactivity_minutes_before_sleep": entityInitialInactivityMinutesBeforeSleep, + "number_of_member_gateways": entityInitialNumberOfMemberGateways, + "virtual_network_azure_resource": map[string]any{ + "subscription_id": initSubscriptionID, + "resource_group_name": initResourceGroupName, + "virtual_network_name": initVirtualNetworkName, + "subnet_name": initSubnetName, + }, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "display_name", entityCreateDisplayName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "capacity_id", capacityID), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "inactivity_minutes_before_sleep", strconv.Itoa(entityInitialInactivityMinutesBeforeSleep)), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "number_of_member_gateways", strconv.Itoa(entityInitialNumberOfMemberGateways)), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subscription_id", initSubscriptionID), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.resource_group_name", initResourceGroupName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.virtual_network_name", initVirtualNetworkName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subnet_name", initSubnetName), + ), + }, + // Update and Read + { + ResourceName: testResourceVirtualNetworkGatewayFQN, + Config: at.CompileConfig( + testResourceVirtualNetworkGatewayHeader, + map[string]any{ + "display_name": entityUpdateDisplayName, + "capacity_id": capacityID, + "inactivity_minutes_before_sleep": entityUpdateInactivityMinutesBeforeSleep, + "number_of_member_gateways": entityUpdateNumberOfMemberGateways, + "virtual_network_azure_resource": map[string]any{ + "subscription_id": updateSubscriptionID, + "resource_group_name": updateResourceGroupName, + "virtual_network_name": updateVirtualNetworkName, + "subnet_name": updateSubnetName, + }, + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "display_name", entityUpdateDisplayName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "capacity_id", capacityID), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "inactivity_minutes_before_sleep", strconv.Itoa(entityUpdateInactivityMinutesBeforeSleep)), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "number_of_member_gateways", strconv.Itoa(entityUpdateNumberOfMemberGateways)), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subscription_id", updateSubscriptionID), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.resource_group_name", updateResourceGroupName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.virtual_network_name", updateVirtualNetworkName), + resource.TestCheckResourceAttr(testResourceVirtualNetworkGatewayFQN, "virtual_network_azure_resource.subnet_name", updateSubnetName), + ), + }, + })) +} diff --git a/internal/testhelp/fakes/fabric_gateway.go b/internal/testhelp/fakes/fabric_gateway.go index e43bd73b..fb20f22f 100644 --- a/internal/testhelp/fakes/fabric_gateway.go +++ b/internal/testhelp/fakes/fabric_gateway.go @@ -10,6 +10,7 @@ import ( fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" fabfake "github.com/microsoft/fabric-sdk-go/fabric/fake" + "github.com/microsoft/terraform-provider-fabric/internal/services/gateway" "github.com/microsoft/terraform-provider-fabric/internal/testhelp" ) @@ -85,9 +86,6 @@ func (o *operationsGateway) Update(base fabcore.GatewayClassification, data fabc // Validate implements concreteEntityOperations. func (o *operationsGateway) Validate(newEntity fabcore.GatewayClassification, existing []fabcore.GatewayClassification) (statusCode int, err error) { for _, existingGateway := range existing { - if existingGateway.GetGateway().Type != newEntity.GetGateway().Type { - continue - } switch gateway := newEntity.(type) { case *fabcore.VirtualNetworkGateway: vng := existingGateway.(*fabcore.VirtualNetworkGateway) @@ -162,8 +160,8 @@ func NewRandomVirtualNetworkGateway() *fabcore.VirtualNetworkGateway { return &fabcore.VirtualNetworkGateway{ ID: to.Ptr(testhelp.RandomUUID()), DisplayName: to.Ptr(testhelp.RandomName()), - InactivityMinutesBeforeSleep: to.Ptr(testhelp.RandomInt32(100)), - NumberOfMemberGateways: to.Ptr(testhelp.RandomInt32(100)), + InactivityMinutesBeforeSleep: to.Ptr(testhelp.RandomElement(gateway.PossibleInactivityMinutesBeforeSleepValues)), + NumberOfMemberGateways: to.Ptr(testhelp.RandomInt32Max(7)), CapacityID: to.Ptr(testhelp.RandomUUID()), Type: to.Ptr(fabcore.GatewayTypeVirtualNetwork), VirtualNetworkAzureResource: &fabcore.VirtualNetworkAzureResource{ @@ -179,7 +177,7 @@ func NewRandomOnPremisesGateway() *fabcore.OnPremisesGateway { return &fabcore.OnPremisesGateway{ ID: to.Ptr(testhelp.RandomUUID()), DisplayName: to.Ptr(testhelp.RandomName()), - NumberOfMemberGateways: to.Ptr(testhelp.RandomInt32(100)), + NumberOfMemberGateways: to.Ptr(testhelp.RandomInt32Max(7)), Type: to.Ptr(fabcore.GatewayTypeOnPremises), AllowCloudConnectionRefresh: to.Ptr(true), AllowCustomConnectors: to.Ptr(false), diff --git a/internal/testhelp/utils.go b/internal/testhelp/utils.go index e2ce3265..656e7db1 100644 --- a/internal/testhelp/utils.go +++ b/internal/testhelp/utils.go @@ -51,10 +51,18 @@ func RandomP12Cert(password string) []byte { return p12 } -func RandomInt32(max int32) int32 { +func RandomInt32Max(max int32) int32 { return rand.Int31n(max) } +func RandomInt32Range(min int32, max int32) int32 { + return min + rand.Int31n(max-min+1) +} + +func RandomElement[T any](slice []T) T { + return slice[rand.Intn(len(slice))] +} + func createP12Bundle(certPEMStr, privateKeyPEMStr, password string) ([]byte, error) { // Decode the private key PEM block block, _ := pem.Decode([]byte(privateKeyPEMStr)) diff --git a/tools/scripts/Set-WellKnown.ps1 b/tools/scripts/Set-WellKnown.ps1 index c6324ba4..e741413e 100644 --- a/tools/scripts/Set-WellKnown.ps1 +++ b/tools/scripts/Set-WellKnown.ps1 @@ -474,8 +474,152 @@ function Set-FabricWorkspaceRoleAssignment { } } +function Set-FabricGatewayVirtualNetwork { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$DisplayName, + + [Parameter(Mandatory = $true)] + [string]$CapacityId, + + # Inactivity time (in minutes) before the gateway goes to auto-sleep. + # Allowed values: 30, 60, 90, 120, 150, 240, 360, 480, 720, 1440. + [Parameter(Mandatory = $true)] + [int]$InactivityMinutesBeforeSleep, + + # Number of member gateways (between 1 and 7). + [Parameter(Mandatory = $true)] + [int]$NumberOfMemberGateways, + + # Azure virtual network details: + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + + [Parameter(Mandatory = $true)] + [string]$VirtualNetworkName, + + [Parameter(Mandatory = $true)] + [string]$SubnetName + ) + + # Attempt to check for an existing gateway with the same display name. + $existingGateways = Invoke-FabricRest -Method 'GET' -Endpoint "gateways" + $result = $existingGateways.Response.value | Where-Object { $_.displayName -eq $DisplayName } + if (!$result) { + # Construct the payload for creating a Virtual Network gateway. + # Refer to the API documentation for details on the request format :contentReference[oaicite:1]{index=1} and the Virtual Network Azure Resource :contentReference[oaicite:2]{index=2}. + $payload = @{ + type = "VirtualNetwork" + displayName = $DisplayName + capacityId = $CapacityId + inactivityMinutesBeforeSleep = $InactivityMinutesBeforeSleep + numberOfMemberGateways = $NumberOfMemberGateways + virtualNetworkAzureResource = @{ + subscriptionId = $SubscriptionId + resourceGroupName = $ResourceGroupName + virtualNetworkName = $VirtualNetworkName + subnetName = $SubnetName + } + } + + Write-Log -Message "Creating Virtual Network Gateway: $DisplayName" -Level 'WARN' + $result = Invoke-FabricRest -Method 'POST' -Endpoint "gateways" -Payload $payload + } + + Write-Log -Message "Gateway Virtual Network - Name: $($result.displayName) / ID: $($result.id)" + return $result +} + + +function Set-AzureVirtualNetwork { + param( + [Parameter(Mandatory = $true)] + [string]$ResourceGroupName, + + [Parameter(Mandatory = $true)] + [string]$VNetName, + + [Parameter(Mandatory = $true)] + [string]$Location, + + [Parameter(Mandatory = $true)] + [string[]]$AddressPrefixes, + + [Parameter(Mandatory = $true)] + [string]$SubnetName, + + [Parameter(Mandatory = $true)] + [string[]]$SubnetAddressPrefixes + ) + + # Attempt to get the existing Virtual Network + $vnet = Get-AzVirtualNetwork -Name $VNetName -ResourceGroupName $ResourceGroupName + if (!$vnet) { + # VNet does not exist, so create it + Write-Log -Message "Creating VNet: $VNetName in Resource Group: $ResourceGroupName" -Level 'WARN' + $subnetConfig = New-AzVirtualNetworkSubnetConfig ` + -Name $SubnetName ` + -AddressPrefix $SubnetAddressPrefixes ` + + $subnetConfig = Add-AzDelegation ` + -Name 'PowerPlatformVnetAccess' ` + -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` + -Subnet $subnetConfig + + $vnet = New-AzVirtualNetwork ` + -Name $VNetName ` + -ResourceGroupName $ResourceGroupName ` + -Location $Location ` + -AddressPrefix $AddressPrefixes ` + -Subnet $subnetConfig + + # Commit creation + $vnet = $vnet | Set-AzVirtualNetwork + Write-Log -Message "Created VNet: $VNetName" -Level 'INFO' + return $vnet + } + + # If the VNet already exists, check for the subnet + $subnet = $vnet.Subnets | Where-Object { $_.Name -eq $SubnetName } + if (-not $subnet) { + # Subnet does not exist; add one with the delegation + Write-Log -Message "Adding subnet '$SubnetName' with delegation 'Microsoft.PowerPlatform/vnetaccesslinks' to VNet '$VNetName'." -Level 'WARN' + $subnetConfig = New-AzVirtualNetworkSubnetConfig ` + -Name $SubnetName ` + -AddressPrefix $SubnetAddressPrefixes ` + + $subnetConfig = Add-AzDelegation ` + -Name 'PowerPlatformVnetAccess' ` + -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` + -Subnet $subnetConfig + + $vnet = $vnet | Set-AzVirtualNetwork + } + else { + # Subnet exists; ensure it has the correct delegation + $existingDelegation = $subnet.Delegations | Where-Object { $_.ServiceName -eq 'Microsoft.PowerPlatform/vnetaccesslinks' } + if (-not $existingDelegation) { + Write-Log -Message "Subnet '$SubnetName' found but missing delegation to 'Microsoft.PowerPlatform/vnetaccesslinks'. Adding Microsoft.PowerPlatform/vnetaccesslinks delegation..." -Level 'WARN' + + $subnetConfig = Add-AzDelegation ` + -Name 'PowerPlatformVnetAccess' ` + -ServiceName 'Microsoft.PowerPlatform/vnetaccesslinks' ` + -Subnet $subnet + + $vnet = $vnet | Set-AzVirtualNetwork + Write-Log -Message "Added missing delegation to subnet '$SubnetName'." -Level 'INFO' + } + } + Write-Log -Message "Az Virtual Network - Name: $($vnet.Name)" + return $vnet +} + # Define an array of modules to install -$modules = @('Az.Accounts', 'Az.Resources', 'Az.Fabric', 'pwsh-dotenv', 'ADOPS') +$modules = @('Az.Accounts', 'Az.Network', 'Az.Resources', 'Az.Fabric', 'pwsh-dotenv', 'ADOPS') # Loop through each module and install if not installed foreach ($module in $modules) { @@ -488,8 +632,8 @@ if (Test-Path -Path './wellknown.env') { Import-Dotenv -Path ./wellknown.env -AllowClobber } -if (!$Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID -or !$Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX) { - Write-Log -Message 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID, FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID, FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME, FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME and FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX are required environment variables.' -Level 'ERROR' +if (!$Env:FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID -or !$Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME -or !$Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION) { + Write-Log -Message 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID, FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID, FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME, FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME and FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX and FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME and FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION are required environment variables.' -Level 'ERROR' } # Check if already logged in to Azure, if not then login @@ -521,7 +665,7 @@ $capacity = $capacities.Response.value | Where-Object { $_.displayName -eq $Env: if (!$capacity) { Write-Log -Message "Fabric Capacity: $($Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME)" } -Write-Log -Message "Fabric Capacity - Name: $($Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME) / ID: $($capacity.id)" +Write-Log -Message "Fabric Capacity - Name: $($Env:FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME) / ID: $($capacity.ID)" $wellKnown['Capacity'] = @{ id = $capacity.id displayName = $capacity.displayName @@ -535,6 +679,7 @@ $itemNaming = @{ 'Environment' = 'env' 'Eventhouse' = 'eh' 'Eventstream' = 'es' + 'GatewayVirtualNetwork' = 'gvn' 'KQLDashboard' = 'kqldash' 'KQLDatabase' = 'kqldb' 'KQLQueryset' = 'kqlqs' @@ -559,6 +704,9 @@ $itemNaming = @{ 'EntraServicePrincipal' = 'sp' 'EntraGroup' = 'grp' 'AzDOProject' = 'proj' + 'VirtualNetworkSubnet' = 'subnet' + 'VirtualNetworkInitial' = 'vneti' + 'VirtualNetworkUpdate' = 'vnetu' } $baseName = Get-BaseName @@ -568,6 +716,8 @@ $Env:FABRIC_TESTACC_WELLKNOWN_NAME_BASE = $baseName $envVarNames = @( 'FABRIC_TESTACC_WELLKNOWN_ENTRA_TENANT_ID', 'FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID', + 'FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME' + 'FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION', 'FABRIC_TESTACC_WELLKNOWN_FABRIC_CAPACITY_NAME', 'FABRIC_TESTACC_WELLKNOWN_AZDO_ORGANIZATION_NAME', 'FABRIC_TESTACC_WELLKNOWN_NAME_PREFIX', @@ -660,7 +810,7 @@ $definition = @{ parts = @( @{ path = "mirroring.json" - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/mirrored_database/mirroring.json' + payload = Get-DefinitionPartBase64 -Path './internal/testhelp/fixtures/mirrored_database/mirroring.json' payloadType = 'InlineBase64' } ) @@ -678,12 +828,12 @@ $definition = @{ parts = @( @{ path = 'definition.pbism' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/semantic_model_tmsl/definition.pbism' + payload = Get-DefinitionPartBase64 -Path './internal/testhelp/fixtures/semantic_model_tmsl/definition.pbism' payloadType = 'InlineBase64' } @{ path = 'model.bim' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/semantic_model_tmsl/model.bim.tmpl' -Values @(@{ key = '{{ .ColumnName }}'; value = 'ColumnTest1' }) + payload = Get-DefinitionPartBase64 -Path './internal/testhelp/fixtures/semantic_model_tmsl/model.bim.tmpl' -Values @(@{ key = '{{ .ColumnName }}'; value = 'ColumnTest1' }) payloadType = 'InlineBase64' } ) @@ -701,17 +851,17 @@ $definition = @{ parts = @( @{ path = 'definition.pbir' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/definition.pbir.tmpl' -Values @(@{ key = '{{ .SemanticModelID }}'; value = $semanticModel.id }) + payload = Get-DefinitionPartBase64 -Path './internal/testhelp/fixtures/report_pbir_legacy/definition.pbir.tmpl' -Values @(@{ key = '{{ .SemanticModelID }}'; value = $semanticModel.id }) payloadType = 'InlineBase64' }, @{ path = 'report.json' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/report.json' + payload = Get-DefinitionPartBase64 -Path './internal/testhelp/fixtures/report_pbir_legacy/report.json' payloadType = 'InlineBase64' }, @{ path = 'StaticResources/SharedResources/BaseThemes/CY24SU06.json' - payload = Get-DefinitionPartBase64 -Path 'internal/testhelp/fixtures/report_pbir_legacy/StaticResources/SharedResources/BaseThemes/CY24SU06.json' + payload = Get-DefinitionPartBase64 -Path './internal/testhelp/fixtures/report_pbir_legacy/StaticResources/SharedResources/BaseThemes/CY24SU06.json' payloadType = 'InlineBase64' } ) @@ -741,6 +891,76 @@ $wellKnown['DomainChild'] = @{ description = $childDomain.description } + +# Register the Microsoft.PowerPlatform resource provider +Write-Log -Message "Registering Microsoft.PowerPlatform resource provider" -Level 'WARN' +Register-AzResourceProvider -ProviderNamespace "Microsoft.PowerPlatform" + +# Create Azure initial Virtual Network if not exists +$vnetName = "${displayName}_$($itemNaming['VirtualNetworkInitial'])" +$addrRange = "10.10.0.0/16" +$subName = "${displayName}_$($itemNaming['VirtualNetworkSubnet'])" +$subRange = "10.10.1.0/24" + +$vnet = Set-AzureVirtualNetwork ` + -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` + -VNetName $vnetName ` + -Location $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION ` + -AddressPrefixes $addrRange ` + -SubnetName $subName ` + -SubnetAddressPrefixes $subRange + +$wellKnown['VirtualNetworkInitial'] = @{ + name = $vnet.Name + resourceGroupName = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME + subnetName = $subName + subscriptionId = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID +} + + +# Create Azure update Virtual Network if not exists +$vnetName = "${displayName}_$($itemNaming['VirtualNetworkUpdate'])" +$addrRange = "10.10.0.0/16" +$subName = "${displayName}_$($itemNaming['VirtualNetworkSubnet'])" +$subRange = "10.10.1.0/24" + +$vnet = Set-AzureVirtualNetwork ` + -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` + -VNetName $vnetName ` + -Location $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_LOCATION ` + -AddressPrefixes $addrRange ` + -SubnetName $subName ` + -SubnetAddressPrefixes $subRange + +$wellKnown['VirtualNetworkUpdate'] = @{ + name = $vnet.Name + resourceGroupName = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME + subnetName = $subName + subscriptionId = $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID +} + +# Create Fabric Gateway Virtual Network if not exists +$displayNameTemp = "${displayName}_$($itemNaming['GatewayVirtualNetwork'])" +$inactivityMinutesBeforeSleep = 30 +$numberOfMemberGateways = 1 + +$gateway = Set-FabricGatewayVirtualNetwork ` + -DisplayName $displayNameTemp ` + -CapacityId $capacity.id ` + -InactivityMinutesBeforeSleep $inactivityMinutesBeforeSleep ` + -NumberOfMemberGateways $numberOfMemberGateways ` + -SubscriptionId $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_SUBSCRIPTION_ID ` + -ResourceGroupName $Env:FABRIC_TESTACC_WELLKNOWN_AZURE_RESOURCE_GROUP_NAME ` + -VirtualNetworkName $wellKnown['VirtualNetworkInitial'].name ` + -SubnetName $wellKnown['VirtualNetworkInitial'].subnetName + +$wellKnown['GatewayVirtualNetwork'] = @{ + id = $gateway.id + displayName = $gateway.displayName + type = $gateway.type + description = $gateway.description +} + $results = Invoke-FabricRest -Method 'GET' -Endpoint "workspaces/$($workspace.id)/lakehouses/$($wellKnown['Lakehouse']['id'])/tables" $result = $results.Response.data | Where-Object { $_.name -eq 'publicholidays' } if (!$result) {