From c90a7e6e5b03873cb43e07f7b5ca4814ca897a0e Mon Sep 17 00:00:00 2001 From: Alistair Yan <52126234+1riatsila1@users.noreply.github.com> Date: Mon, 16 Sep 2024 10:39:05 +0200 Subject: [PATCH] fix: make root node data source compatible with all nodes (#27) * Make rood node data source compatible with all nodes * Add other attributes --- .../provider/data-sources/group.go | 138 ++++++++++++++++++ .../provider/data-sources/root_group.go | 73 --------- pkg/config-api-provider/provider/provider.go | 2 +- .../test/data_source_group_test.go | 86 +++++++++++ .../test/data_source_root_group_test.go | 35 ----- 5 files changed, 225 insertions(+), 109 deletions(-) create mode 100644 pkg/config-api-provider/provider/data-sources/group.go delete mode 100644 pkg/config-api-provider/provider/data-sources/root_group.go create mode 100644 pkg/config-api-provider/test/data_source_group_test.go delete mode 100644 pkg/config-api-provider/test/data_source_root_group_test.go diff --git a/pkg/config-api-provider/provider/data-sources/group.go b/pkg/config-api-provider/provider/data-sources/group.go new file mode 100644 index 00000000..bfe2e41f --- /dev/null +++ b/pkg/config-api-provider/provider/data-sources/group.go @@ -0,0 +1,138 @@ +package datasources + +import ( + "context" + + config_api_client "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/config-api-client" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &groupDataSource{} + _ datasource.DataSourceWithConfigure = &groupDataSource{} +) + +func NewGroupDataSource() datasource.DataSource { + return &groupDataSource{} +} + +type groupDataSource struct { + client *config_api_client.APIClient +} + +type groupDataSourceModel struct { + ID types.String `tfsdk:"id"` + Path types.String `tfsdk:"path"` + ParentGroupID types.String `tfsdk:"parent_group_id"` + Name types.String `tfsdk:"name"` + Filter struct { + GroupID *string `tfsdk:"group_id"` + IsRoot *bool `tfsdk:"is_root"` + } `tfsdk:"filter"` +} + +func (d *groupDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_group" +} + +func (d *groupDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "path": schema.StringAttribute{ + Computed: true, + Optional: true, + }, + "parent_group_id": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + "filter": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "group_id": schema.StringAttribute{ + Optional: true, + }, + "is_root": schema.BoolAttribute{ + Optional: true, + }, + }, + }, + }, + } +} + +func (d *groupDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state groupDataSourceModel + + // Read configuration from request + diags := req.Config.Get(ctx, &state) + if state.Filter.GroupID == nil && (state.Filter.IsRoot == nil || !*state.Filter.IsRoot) { + diags.AddError("invalid Group data source", "either filter.group_id must be set or 'filter.is_root = true' is required") + } else if state.Filter.GroupID != nil && state.Filter.IsRoot != nil && *state.Filter.IsRoot { + diags.AddError("invalid Group data source", "group_id and 'is_root = true' cannot both be set") + } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + request := d.client.ConfigurationAPI.GroupsGetConfigurationAppV1GroupsGet(context.Background()) + + if state.Filter.IsRoot != nil && *state.Filter.IsRoot { + request = request.Uid(*state.Filter.GroupID) // TODO: use root group filter here + } else { + request = request.Uid(*state.Filter.GroupID) + } + + groupResponse, _, err := request.Execute() + + if err != nil || len(groupResponse.Groups) != 1 { + resp.Diagnostics.AddError( + "Error reading Group", + "Could not retrieve Group, unexpected error: "+err.Error(), + ) + return + } + + group := groupResponse.Groups[0] + state.ID = types.StringValue(group.Uid) + state.Name = types.StringValue(group.Name) + state.Path = types.StringValue(group.Path) + if group.ParentUid.IsSet() { + state.ParentGroupID = types.StringValue(*group.ParentUid.Get()) + } + + // Set state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (d *groupDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Add a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*config_api_client.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + "Data Source type: Group. Please report this issue to the provider developers.", + ) + return + } + + d.client = client +} diff --git a/pkg/config-api-provider/provider/data-sources/root_group.go b/pkg/config-api-provider/provider/data-sources/root_group.go deleted file mode 100644 index 313c82e7..00000000 --- a/pkg/config-api-provider/provider/data-sources/root_group.go +++ /dev/null @@ -1,73 +0,0 @@ -package datasources - -import ( - "context" - - "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/resources" - "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -// Ensure the implementation satisfies the expected interfaces. -var ( - _ datasource.DataSource = &rootGroupDataSource{} - _ datasource.DataSourceWithConfigure = &rootGroupDataSource{} -) - -func NewRootGroupDataSource() datasource.DataSource { - return &rootGroupDataSource{} -} - -type rootGroupDataSource struct{} - -type rootGroupDataSourceModel struct { - ID types.String `tfsdk:"id"` -} - -func (d *rootGroupDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_root_group" -} - -func (d *rootGroupDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - }, - }, - } -} - -func (d *rootGroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var state rootGroupDataSourceModel - - rootGroup := GetRootGroup() - - state.ID = types.StringValue(rootGroup.UID) - - // Set state - diags := resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -func (d *rootGroupDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - // Add a nil check when handling ProviderData because Terraform - // sets that data after it calls the ConfigureProvider RPC. - if req.ProviderData == nil { - return - } -} - -// TODO: Switch this to use the Client Model when that becomes available -var GetRootGroup = func() resources.GroupResponseModel { - return resources.GroupResponseModel{ - UID: "mock_uid", - Name: "root", - ParentUid: nil, - Path: "mock_uid", - } -} diff --git a/pkg/config-api-provider/provider/provider.go b/pkg/config-api-provider/provider/provider.go index 82d1268a..0f7daabc 100644 --- a/pkg/config-api-provider/provider/provider.go +++ b/pkg/config-api-provider/provider/provider.go @@ -155,7 +155,7 @@ func (p *uxiConfigurationProvider) Configure(ctx context.Context, req provider.C // DataSources defines the data sources implemented in the provider. func (p *uxiConfigurationProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ - datasources.NewRootGroupDataSource, + datasources.NewGroupDataSource, datasources.NewWiredNetworkDataSource, datasources.NewWirelessNetworkDataSource, datasources.NewSensorGroupAssignmentDataSource, diff --git a/pkg/config-api-provider/test/data_source_group_test.go b/pkg/config-api-provider/test/data_source_group_test.go new file mode 100644 index 00000000..cae0f0a6 --- /dev/null +++ b/pkg/config-api-provider/test/data_source_group_test.go @@ -0,0 +1,86 @@ +package test + +import ( + "regexp" + "testing" + + "github.com/h2non/gock" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestGroupDataSource(t *testing.T) { + defer gock.Off() + MockOAuth() + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Test no filters set + { + Config: providerConfig + ` + data "uxi_group" "my_group" { + filter = {} + } + `, + ExpectError: regexp.MustCompile(`either filter.group_id must be set or 'filter.is_root = true' is required`), + }, + // Test too many filters set + { + Config: providerConfig + ` + data "uxi_group" "my_group" { + filter = { + is_root = true + group_id = "uid" + } + } + `, + ExpectError: regexp.MustCompile(`group_id and 'is_root = true' cannot both be set`), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.uxi_root_group.my_root_group", "id", "mock_uid"), + ), + }, + // Test Read, is_root not set + { + PreConfig: func() { + MockGetGroup( + "uid", + GenerateGroupPaginatedResponse([]map[string]interface{}{StructToMap(GenerateGroupResponseModel("uid", "", ""))}), + 3, + ) + }, + Config: providerConfig + ` + data "uxi_group" "my_group" { + filter = { + group_id = "uid" + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.uxi_group.my_group", "id", "uid"), + ), + }, + // Test Read, is_root is false + { + PreConfig: func() { + MockGetGroup( + "uid", + GenerateGroupPaginatedResponse([]map[string]interface{}{StructToMap(GenerateGroupResponseModel("uid", "", ""))}), + 3, + ) + }, + Config: providerConfig + ` + data "uxi_group" "my_group" { + filter = { + is_root = false + group_id = "uid" + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.uxi_group.my_group", "id", "uid"), + ), + }, + // TODO: Test retrieving the root group + }, + }) +} diff --git a/pkg/config-api-provider/test/data_source_root_group_test.go b/pkg/config-api-provider/test/data_source_root_group_test.go deleted file mode 100644 index 8ca16746..00000000 --- a/pkg/config-api-provider/test/data_source_root_group_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package test - -import ( - "testing" - - datasources "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/data-sources" - "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/resources" - "github.com/h2non/gock" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func TestRootGroupDataSource(t *testing.T) { - defer gock.Off() - MockOAuth() - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Read testing - { - PreConfig: func() { - datasources.GetRootGroup = func() resources.GroupResponseModel { - return GenerateRootGroupResponseModel("mock_uid") - } - }, - Config: providerConfig + ` - data "uxi_root_group" "my_root_group" {} - `, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.uxi_root_group.my_root_group", "id", "mock_uid"), - ), - }, - }, - }) -}