From 69dc1e08f6edfce07b127573b58c67e5214173a6 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Tue, 28 May 2024 14:05:06 +0200 Subject: [PATCH] Add resource dbtcloud_partial_license_map --- CHANGELOG.md | 9 +- docs/resources/partial_license_map.md | 54 +++ .../dbtcloud_partial_license_map/resource.tf | 17 + pkg/dbt_cloud/paginate.go | 19 + .../group_partial_permissions/resource.go | 2 +- .../objects/partial_license_map/model.go | 20 + .../objects/partial_license_map/resource.go | 358 ++++++++++++++++++ .../resource_acceptance_test.go | 181 +++++++++ .../objects/partial_license_map/schema.go | 55 +++ pkg/provider/framework_provider.go | 2 + 10 files changed, 715 insertions(+), 2 deletions(-) create mode 100644 docs/resources/partial_license_map.md create mode 100644 examples/resources/dbtcloud_partial_license_map/resource.tf create mode 100644 pkg/framework/objects/partial_license_map/model.go create mode 100644 pkg/framework/objects/partial_license_map/resource.go create mode 100644 pkg/framework/objects/partial_license_map/resource_acceptance_test.go create mode 100644 pkg/framework/objects/partial_license_map/schema.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cdcaa9a..7c404f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,14 @@ All notable changes to this project will be documented in this file. -## [Unreleased](https://github.com/dbt-labs/terraform-provider-dbtcloud/compare/v0.3.5...HEAD) +## [Unreleased](https://github.com/dbt-labs/terraform-provider-dbtcloud/compare/v0.3.6...HEAD) + +## [0.3.6](https://github.com/dbt-labs/terraform-provider-dbtcloud/compare/v0.3.5...v0.3.6) + +### Changes + +- [#232](https://github.com/dbt-labs/terraform-provider-dbtcloud/issues/232) add deprecation notice for `dbtcloud_project_artefacts` as the resource is not required now that dbt Explorer is GA. +- [#208](https://github.com/dbt-labs/terraform-provider-dbtcloud/issues/208) add new `dbtcloud_partial_license_map` for defining SSO group mapping to license types from different Terraform projects/resources ## [0.3.5](https://github.com/dbt-labs/terraform-provider-dbtcloud/compare/v0.3.4...v0.3.5) diff --git a/docs/resources/partial_license_map.md b/docs/resources/partial_license_map.md new file mode 100644 index 00000000..34d63927 --- /dev/null +++ b/docs/resources/partial_license_map.md @@ -0,0 +1,54 @@ +--- +page_title: "dbtcloud_partial_license_map Resource - dbtcloud" +subcategory: "" +description: |- + Set up partial license maps with only a subset of SSO groups for a given license type. + This resource is different from dbtcloud_license_map as it allows having different resources setting up different groups for the same license type. + If a company uses only one Terraform project/workspace to manage all their dbt Cloud Account config, it is recommended to use dbt_cloud_license_map instead of dbt_cloud_group_partial_license_map. + ~> This is a new resource like other "partial" ones and any feedback is welcome in the GitHub repository. +--- + +# dbtcloud_partial_license_map (Resource) + + +Set up partial license maps with only a subset of SSO groups for a given license type. + +This resource is different from `dbtcloud_license_map` as it allows having different resources setting up different groups for the same license type. + +If a company uses only one Terraform project/workspace to manage all their dbt Cloud Account config, it is recommended to use `dbt_cloud_license_map` instead of `dbt_cloud_group_partial_license_map`. + +~> This is a new resource like other "partial" ones and any feedback is welcome in the GitHub repository. + +## Example Usage + +```terraform +# Developer license group mapping +resource "dbtcloud_partial_license_map" "dev_license_map" { + license_type = "developer" + sso_license_mapping_groups = ["DEV-SSO-GROUP"] +} + +# Read-only license mapping +resource "dbtcloud_partial_license_map" "read_only_license_map" { + license_type = "read_only" + sso_license_mapping_groups = ["READ-ONLY-SSO-GROUP"] +} + +# IT license mapping +resource "dbtcloud_partial_license_map" "it_license_map" { + license_type = "it" + sso_license_mapping_groups = ["IT-SSO-GROUP"] +} +``` + + +## Schema + +### Required + +- `license_type` (String) The license type to update +- `sso_license_mapping_groups` (Set of String) List of SSO groups to map to the license type. + +### Read-Only + +- `id` (Number) The ID of the notification diff --git a/examples/resources/dbtcloud_partial_license_map/resource.tf b/examples/resources/dbtcloud_partial_license_map/resource.tf new file mode 100644 index 00000000..8e241438 --- /dev/null +++ b/examples/resources/dbtcloud_partial_license_map/resource.tf @@ -0,0 +1,17 @@ +# Developer license group mapping +resource "dbtcloud_partial_license_map" "dev_license_map" { + license_type = "developer" + sso_license_mapping_groups = ["DEV-SSO-GROUP"] +} + +# Read-only license mapping +resource "dbtcloud_partial_license_map" "read_only_license_map" { + license_type = "read_only" + sso_license_mapping_groups = ["READ-ONLY-SSO-GROUP"] +} + +# IT license mapping +resource "dbtcloud_partial_license_map" "it_license_map" { + license_type = "it" + sso_license_mapping_groups = ["IT-SSO-GROUP"] +} diff --git a/pkg/dbt_cloud/paginate.go b/pkg/dbt_cloud/paginate.go index 53624c38..25239687 100644 --- a/pkg/dbt_cloud/paginate.go +++ b/pkg/dbt_cloud/paginate.go @@ -166,3 +166,22 @@ func (c *Client) GetAllServiceTokens() ([]ServiceToken, error) { } return allServiceTokens, nil } + +func (c *Client) GetAllLicenseMaps() ([]LicenseMap, error) { + url := fmt.Sprintf("%s/v3/accounts/%d/license-maps/", c.HostURL, c.AccountID) + + allLicenseMapsRaw := c.GetData(url) + + allLicenseMaps := []LicenseMap{} + for _, notification := range allLicenseMapsRaw { + + data, _ := json.Marshal(notification) + currentLicenseMap := LicenseMap{} + err := json.Unmarshal(data, ¤tLicenseMap) + if err != nil { + return nil, err + } + allLicenseMaps = append(allLicenseMaps, currentLicenseMap) + } + return allLicenseMaps, nil +} diff --git a/pkg/framework/objects/group_partial_permissions/resource.go b/pkg/framework/objects/group_partial_permissions/resource.go index 97f6e030..6cde18cd 100644 --- a/pkg/framework/objects/group_partial_permissions/resource.go +++ b/pkg/framework/objects/group_partial_permissions/resource.go @@ -208,7 +208,7 @@ func (r *groupPartialPermissionsResource) Create( } else { // if the group with the name given doesn't exist , create it - // TODO: Move this to the group resources in the Framework + // TODO: Move this to the group resources once the resource is move to the Framework group, err := r.client.CreateGroup(name, assignByDefault, ssoMappingGroups) if err != nil { diff --git a/pkg/framework/objects/partial_license_map/model.go b/pkg/framework/objects/partial_license_map/model.go new file mode 100644 index 00000000..2a6e9cb0 --- /dev/null +++ b/pkg/framework/objects/partial_license_map/model.go @@ -0,0 +1,20 @@ +package partial_license_map + +import ( + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// TODO: Move the model to the non partial when moved to the Framework +type LicenseMapResourceModel struct { + ID types.Int64 `tfsdk:"id"` + LicenseType types.String `tfsdk:"license_type"` + SSOLicenseMappingGroups types.Set `tfsdk:"sso_license_mapping_groups"` +} + +func matchPartial( + licenseMapModel LicenseMapResourceModel, + licenseTypeResponse dbt_cloud.LicenseMap, +) bool { + return licenseMapModel.LicenseType == types.StringValue(licenseTypeResponse.LicenseType) +} diff --git a/pkg/framework/objects/partial_license_map/resource.go b/pkg/framework/objects/partial_license_map/resource.go new file mode 100644 index 00000000..c81edce9 --- /dev/null +++ b/pkg/framework/objects/partial_license_map/resource.go @@ -0,0 +1,358 @@ +package partial_license_map + +import ( + "context" + "strings" + + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/helper" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/samber/lo" +) + +var ( + _ resource.Resource = &partialLicenseMapResource{} + _ resource.ResourceWithConfigure = &partialLicenseMapResource{} +) + +func PartialLicenseMapResource() resource.Resource { + return &partialLicenseMapResource{} +} + +type partialLicenseMapResource struct { + client *dbt_cloud.Client +} + +func (r *partialLicenseMapResource) Metadata( + _ context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_partial_license_map" +} + +func (r *partialLicenseMapResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state LicenseMapResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + // check if the ID exists + licenseMapID := int(state.ID.ValueInt64()) + licenseMap, err := r.client.GetLicenseMap(licenseMapID) + if err != nil { + if strings.HasPrefix(err.Error(), "resource-not-found") { + resp.Diagnostics.AddWarning( + "Resource not found", + "The license map resource was not found and has been removed from the state.", + ) + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("Error getting the license map", err.Error()) + return + } + + // if the ID exists, make sure that it is the one we are looking for + if !matchPartial(state, *licenseMap) { + // read all the objects and check if one exists + allLicenseMaps, err := r.client.GetAllLicenseMaps() + if err != nil { + resp.Diagnostics.AddError( + "Unable to get all license maps", + "Error: "+err.Error(), + ) + return + } + + var fullLicenseMap *dbt_cloud.LicenseMap + for _, licenseMap := range allLicenseMaps { + if matchPartial(state, licenseMap) { + // it exists, we stop here + fullLicenseMap = &licenseMap + break + } + } + + // if it was not found, it means that the object was deleted + if fullLicenseMap == nil { + resp.Diagnostics.AddWarning( + "Resource not found", + "The license map resource was not found and has been removed from the state.", + ) + resp.State.RemoveResource(ctx) + return + } + + // if it is found, we set it correctly + licenseMapID = *fullLicenseMap.ID + licenseMap = fullLicenseMap + } + + // we set the "global" values + state.ID = types.Int64Value(int64(licenseMapID)) + state.LicenseType = types.StringValue(licenseMap.LicenseType) + + // we set the "partial" values by intersecting the config with the remote + var ssoMappingConfigured []string + diags := state.SSOLicenseMappingGroups.ElementsAs( + context.Background(), + &ssoMappingConfigured, + false, + ) + if diags.HasError() { + resp.Diagnostics.AddError("Error extracting the list of SSO groups", "") + return + } + + state.SSOLicenseMappingGroups, _ = types.SetValueFrom( + context.Background(), + types.StringType, + lo.Intersect(ssoMappingConfigured, licenseMap.SSOLicenseMappingGroups), + ) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + +} + +func (r *partialLicenseMapResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan LicenseMapResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + // we read the values from the config + var configSsoMapping []string + diags := plan.SSOLicenseMappingGroups.ElementsAs( + context.Background(), + &configSsoMapping, + false, + ) + if diags.HasError() { + resp.Diagnostics.AddError("Error extracting the list of SSO groups", "") + return + } + + // check if it exists + // we don't need to check uniqueness and can just return the first as the API only allows one license type + allLicenseMaps, err := r.client.GetAllLicenseMaps() + if err != nil { + resp.Diagnostics.AddError( + "Unable to get all license maps", + "Error: "+err.Error(), + ) + return + } + + var fullLicenseMap *dbt_cloud.LicenseMap + for _, licenseMap := range allLicenseMaps { + if matchPartial(plan, licenseMap) { + // it exists, we stop here + fullLicenseMap = &licenseMap + break + } + } + + if fullLicenseMap != nil { + // if it exists, we get the ID + licenseMapID := fullLicenseMap.ID + plan.ID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(licenseMapID)) + + // and we calculate all the partial fields + // the global ones are already set in the plan + remoteSsoMapping := fullLicenseMap.SSOLicenseMappingGroups + missingSsoMapping := lo.Without(configSsoMapping, remoteSsoMapping...) + + // we only update if something global, but not part of the ID is different or if something partial needs to be added + if len(missingSsoMapping) == 0 { + // nothing to do if they are all the same + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + return + } else { + // if one of them is different, we get the new values for all + // and we update the object + allSsoMapping := append(remoteSsoMapping, missingSsoMapping...) + fullLicenseMap.SSOLicenseMappingGroups = allSsoMapping + + _, err := r.client.UpdateLicenseMap(*licenseMapID, *fullLicenseMap) + if err != nil { + resp.Diagnostics.AddError( + "Unable to update the existing license map", + "Error: "+err.Error(), + ) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + } + + } else { + // it doesn't exist so we create it + licenseMap, err := r.client.CreateLicenseMap( + plan.LicenseType.ValueString(), + configSsoMapping, + ) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to create license map", + "Error: "+err.Error(), + ) + return + } + + plan.ID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(licenseMap.ID)) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + } +} + +func (r *partialLicenseMapResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state LicenseMapResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + licenseMapID := int(state.ID.ValueInt64()) + licenseMap, err := r.client.GetLicenseMap(licenseMapID) + if err != nil { + resp.Diagnostics.AddError("Error getting the license map", err.Error()) + return + } + + // we read the values from the config + var configSsoMapping []string + diags := state.SSOLicenseMappingGroups.ElementsAs( + context.Background(), + &configSsoMapping, + false, + ) + if diags.HasError() { + resp.Diagnostics.AddError("Error extracting the list of SSO groups", "") + return + } + + remoteSsoMapping := licenseMap.SSOLicenseMappingGroups + requiredSsoMapping := lo.Without(remoteSsoMapping, configSsoMapping...) + + if len(requiredSsoMapping) > 0 { + // we update the object if there are some partial values left + // but we leave the object existing, without deleting it entirely + licenseMap.SSOLicenseMappingGroups = requiredSsoMapping + _, err = r.client.UpdateLicenseMap(licenseMapID, *licenseMap) + if err != nil { + resp.Diagnostics.AddError( + "Unable to update the existing license map", + "Error: "+err.Error(), + ) + return + } + } else { + // we delete the object if there is no config left at all + err = r.client.DestroyLicenseMap(licenseMapID) + if err != nil { + resp.Diagnostics.AddError("Error deleting the license map", err.Error()) + return + } + } +} + +func (r *partialLicenseMapResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan, state LicenseMapResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + licenseMapID := int(state.ID.ValueInt64()) + licenseMap, err := r.client.GetLicenseMap(licenseMapID) + if err != nil { + resp.Diagnostics.AddError( + "Error getting the license map", + "Error: "+err.Error(), + ) + return + } + + // we compare the partial objects and update them if needed + var planSsoMapping []string + diags := plan.SSOLicenseMappingGroups.ElementsAs( + context.Background(), + &planSsoMapping, + false, + ) + if diags.HasError() { + resp.Diagnostics.AddError("Error extracting the list of SSO groups from the plan", "") + return + } + + var stateSsoMapping []string + diags = state.SSOLicenseMappingGroups.ElementsAs( + context.Background(), + &stateSsoMapping, + false, + ) + if diags.HasError() { + resp.Diagnostics.AddError("Error extracting the list of SSO groups from the state", "") + return + } + + remoteSsoMapping := licenseMap.SSOLicenseMappingGroups + deletedSsoMapping := lo.Without(stateSsoMapping, planSsoMapping...) + newSsoMapping := lo.Without(planSsoMapping, stateSsoMapping...) + requiredSsoMapping := lo.Without( + lo.Union(remoteSsoMapping, newSsoMapping), + deletedSsoMapping...) + + // we check if there are changes to be sent, both global and local + if len(deletedSsoMapping) > 0 || + len(newSsoMapping) > 0 { + + // we update the values to be the plan ones for global + // and the calculated ones for the local ones + licenseMap.SSOLicenseMappingGroups = requiredSsoMapping + _, err = r.client.UpdateLicenseMap(licenseMapID, *licenseMap) + if err != nil { + resp.Diagnostics.AddError( + "Unable to update the existing license map", + "Error: "+err.Error(), + ) + return + } + } + + // Set the updated state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *partialLicenseMapResource) Configure( + _ context.Context, + req resource.ConfigureRequest, + _ *resource.ConfigureResponse, +) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*dbt_cloud.Client) +} diff --git a/pkg/framework/objects/partial_license_map/resource_acceptance_test.go b/pkg/framework/objects/partial_license_map/resource_acceptance_test.go new file mode 100644 index 00000000..19123480 --- /dev/null +++ b/pkg/framework/objects/partial_license_map/resource_acceptance_test.go @@ -0,0 +1,181 @@ +package partial_license_map_test + +import ( + "fmt" + "regexp" + "strconv" + "testing" + + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/acctest_helper" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccDbtCloudPartialLicenseMapResource(t *testing.T) { + + groupName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + groupName2 := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + groupName3 := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest_helper.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDbtCloudLicenseMapDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDbtCloudLicenseMapResourceBasicConfig("it", groupName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDbtCloudLicenseMapExists( + "dbtcloud_partial_license_map.test_license_map", + ), + resource.TestCheckResourceAttr( + "dbtcloud_partial_license_map.test_license_map", + "license_type", + "it", + ), + resource.TestCheckResourceAttr( + "dbtcloud_partial_license_map.test_license_map", + "sso_license_mapping_groups.#", + "1", + ), + resource.TestCheckResourceAttr( + "dbtcloud_partial_license_map.test_license_map", + "sso_license_mapping_groups.0", + groupName, + ), + ), + }, + // MODIFY + { + Config: testAccDbtCloudLicenseMapResourceMultipleConfig( + "it", + groupName, + groupName2, + groupName3, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckDbtCloudLicenseMapExists( + "dbtcloud_partial_license_map.test_license_map", + ), + resource.TestCheckResourceAttr( + "dbtcloud_partial_license_map.test_license_map", + "license_type", + "it", + ), + resource.TestCheckResourceAttr( + "dbtcloud_partial_license_map.test_license_map", + "sso_license_mapping_groups.#", + "2", + ), + resource.TestCheckTypeSetElemAttr( + "dbtcloud_partial_license_map.test_license_map", + "sso_license_mapping_groups.*", + groupName, + ), + resource.TestCheckTypeSetElemAttr( + "dbtcloud_partial_license_map.test_license_map", + "sso_license_mapping_groups.*", + groupName2, + ), + resource.TestCheckResourceAttr( + "dbtcloud_partial_license_map.test_license_map2", + "sso_license_mapping_groups.#", + "1", + ), + resource.TestCheckTypeSetElemAttr( + "dbtcloud_partial_license_map.test_license_map2", + "sso_license_mapping_groups.*", + groupName3, + ), + ), + }, + }, + }) +} + +func testAccDbtCloudLicenseMapResourceBasicConfig(licenseType string, groupName string) string { + return fmt.Sprintf(` + +resource "dbtcloud_partial_license_map" "test_license_map" { + license_type = "%s" + sso_license_mapping_groups = ["%s"] +} +`, licenseType, groupName) +} + +func testAccDbtCloudLicenseMapResourceMultipleConfig( + licenseType string, + groupName string, + groupName2 string, + groupName3 string, +) string { + return fmt.Sprintf(` + +resource "dbtcloud_partial_license_map" "test_license_map" { + license_type = "%s" + sso_license_mapping_groups = ["%s", "%s"] +} + +resource "dbtcloud_partial_license_map" "test_license_map2" { + license_type = "%s" + sso_license_mapping_groups = ["%s"] + + depends_on = [dbtcloud_partial_license_map.test_license_map] +} + +`, licenseType, groupName, groupName2, licenseType, groupName3) +} + +func testAccCheckDbtCloudLicenseMapExists(resource string) resource.TestCheckFunc { + return func(state *terraform.State) error { + rs, ok := state.RootModule().Resources[resource] + if !ok { + return fmt.Errorf("Not found: %s", resource) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Record ID is set") + } + apiClient, err := acctest_helper.SharedClient() + if err != nil { + return fmt.Errorf("Issue getting the client") + } + licenseMapID, err := strconv.Atoi(rs.Primary.ID) + if err != nil { + return fmt.Errorf("Can't get groupID") + } + _, err = apiClient.GetLicenseMap(licenseMapID) + if err != nil { + return fmt.Errorf("error fetching item with resource %s. %s", resource, err) + } + return nil + } +} + +func testAccCheckDbtCloudLicenseMapDestroy(s *terraform.State) error { + apiClient, err := acctest_helper.SharedClient() + if err != nil { + return fmt.Errorf("Issue getting the client") + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "dbtcloud_partial_license_map" { + continue + } + licenseMapID, err := strconv.Atoi(rs.Primary.ID) + if err != nil { + return fmt.Errorf("Can't get licenseMapID") + } + _, err = apiClient.GetLicenseMap(licenseMapID) + if err == nil { + return fmt.Errorf("License Map still exists") + } + notFoundErr := "resource-not-found" + expectedErr := regexp.MustCompile(notFoundErr) + if !expectedErr.Match([]byte(err.Error())) { + return fmt.Errorf("expected %s, got %s", notFoundErr, err) + } + } + + return nil +} diff --git a/pkg/framework/objects/partial_license_map/schema.go b/pkg/framework/objects/partial_license_map/schema.go new file mode 100644 index 00000000..7fca89ea --- /dev/null +++ b/pkg/framework/objects/partial_license_map/schema.go @@ -0,0 +1,55 @@ +package partial_license_map + +import ( + "context" + + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/helper" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *partialLicenseMapResource) Schema( + _ context.Context, + _ resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: helper.DocString( + `Set up partial license maps with only a subset of SSO groups for a given license type. + + This resource is different from ~~~dbtcloud_license_map~~~ as it allows having different resources setting up different groups for the same license type. + + If a company uses only one Terraform project/workspace to manage all their dbt Cloud Account config, it is recommended to use ~~~dbt_cloud_license_map~~~ instead of ~~~dbt_cloud_group_partial_license_map~~~. + + ~> This is a new resource like other "partial" ones and any feedback is welcome in the GitHub repository. + `, + ), + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + Description: "The ID of the notification", + // this is used so that we don't show that ID is going to change + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + "license_type": schema.StringAttribute{ + Required: true, + Description: "The license type to update", + // we need to replace the resource when we change the license type as this is the identifier + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "sso_license_mapping_groups": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Description: "List of SSO groups to map to the license type.", + }, + }, + } +} diff --git a/pkg/provider/framework_provider.go b/pkg/provider/framework_provider.go index ca428be8..760b11e7 100644 --- a/pkg/provider/framework_provider.go +++ b/pkg/provider/framework_provider.go @@ -9,6 +9,7 @@ import ( "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/environment" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/group_partial_permissions" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/notification" + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/partial_license_map" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/partial_notification" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/user" @@ -183,5 +184,6 @@ func (p *dbtCloudProvider) Resources(_ context.Context) []func() resource.Resour notification.NotificationResource, group_partial_permissions.GroupPartialPermissionsResource, partial_notification.PartialNotificationResource, + partial_license_map.PartialLicenseMapResource, } }