diff --git a/client/dsync_groups.go b/client/dsync_groups.go new file mode 100644 index 00000000..a0c7e53e --- /dev/null +++ b/client/dsync_groups.go @@ -0,0 +1,64 @@ +package client + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +type DsyncGroup struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type GetDsyncGroupsResponse struct { + TeamID string `json:"teamId"` + Groups []DsyncGroup `json:"groups"` +} + +func (c *Client) GetDsyncGroups(ctx context.Context, TeamID string) (GetDsyncGroupsResponse, error) { + var allGroups []DsyncGroup + var after *string + + var ResolvedTeamID = c.TeamID(TeamID) + + for { + url := fmt.Sprintf("%s/teams/%s/dsync/groups", c.baseURL, ResolvedTeamID) + if after != nil { + url = fmt.Sprintf("%s?after=%s", url, *after) + } + tflog.Info(ctx, "getting dsync groups", map[string]any{ + "url": url, + }) + + var response struct { + Groups []DsyncGroup `json:"groups"` + Pagination struct { + Before *string `json:"before"` + After *string `json:"after"` + } `json:"pagination"` + } + err := c.doRequest(clientRequest{ + ctx: ctx, + method: "GET", + url: url, + body: "", + }, &response) + if err != nil { + return GetDsyncGroupsResponse{}, err + } + + allGroups = append(allGroups, response.Groups...) + + if response.Pagination.After == nil { + break + } + after = response.Pagination.After + } + + return GetDsyncGroupsResponse{ + TeamID: ResolvedTeamID, + Groups: allGroups, + }, nil +} diff --git a/docs/data-sources/dsync_groups.md b/docs/data-sources/dsync_groups.md new file mode 100644 index 00000000..5b0b36d0 --- /dev/null +++ b/docs/data-sources/dsync_groups.md @@ -0,0 +1,64 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "vercel_dsync_groups Data Source - terraform-provider-vercel" +subcategory: "" +description: |- + Provides information about DSync groups for a team. +--- + +# vercel_dsync_groups (Data Source) + +Provides information about DSync groups for a team. + +## Example Usage + +```terraform +data "vercel_dsync_groups" "example" { + team_id = "team_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" +} + +resource "vercel_access_group" "contractor" { + team_id = "team_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + name = "contractor" + description = "Access group for contractors" +} + +resource "vercel_team_config" "example" { + id = "team_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + saml = { + enforced = true + roles = { + lookup(vercel_dsync_groups.example.map, "admin") = { + role = "OWNER" + } + lookup(vercel_dsync_groups.example.map, "finance") = { + role = "BILLING" + } + lookup(vercel_dsync_groups.example.map, "contractor") = { + role = "CONTRIBUTOR" + access_group_id = vercel_access_group.contractor.id + } + } + } +} +``` + + +## Schema + +### Optional + +- `team_id` (String) The ID of the team the Dsync Groups are associated to. Required when configuring a team resource if a default team has not been set in the provider. + +### Read-Only + +- `list` (Attributes List) A list of DSync groups for the team. (see [below for nested schema](#nestedatt--list)) +- `map` (Map of String) A map of Identity Provider group names to their Vercel IDs. This can be used to look up the ID of a group by its name using the [lookup](https://developer.hashicorp.com/terraform/language/functions/lookup) function. + + +### Nested Schema for `list` + +Read-Only: + +- `id` (String) The ID of the group on Vercel. +- `name` (String) The name of the group on the Identity Provider. diff --git a/examples/data-sources/vercel_dsync_groups/data-source.tf b/examples/data-sources/vercel_dsync_groups/data-source.tf new file mode 100644 index 00000000..4fd1de63 --- /dev/null +++ b/examples/data-sources/vercel_dsync_groups/data-source.tf @@ -0,0 +1,28 @@ +data "vercel_dsync_groups" "example" { + team_id = "team_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" +} + +resource "vercel_access_group" "contractor" { + team_id = "team_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + name = "contractor" + description = "Access group for contractors" +} + +resource "vercel_team_config" "example" { + id = "team_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + saml = { + enforced = true + roles = { + lookup(vercel_dsync_groups.example.map, "admin") = { + role = "OWNER" + } + lookup(vercel_dsync_groups.example.map, "finance") = { + role = "BILLING" + } + lookup(vercel_dsync_groups.example.map, "contractor") = { + role = "CONTRIBUTOR" + access_group_id = vercel_access_group.contractor.id + } + } + } +} diff --git a/vercel/data_source_dsync_groups.go b/vercel/data_source_dsync_groups.go new file mode 100644 index 00000000..53550ec9 --- /dev/null +++ b/vercel/data_source_dsync_groups.go @@ -0,0 +1,138 @@ +package vercel + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/vercel/terraform-provider-vercel/v3/client" +) + +var ( + _ datasource.DataSource = &dsyncGroupsDataSource{} + _ datasource.DataSourceWithConfigure = &dsyncGroupsDataSource{} +) + +func newDsyncGroupsDataSource() datasource.DataSource { + return &dsyncGroupsDataSource{} +} + +type dsyncGroupsDataSource struct { + client *client.Client +} + +func (d *dsyncGroupsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dsync_groups" +} + +func (d *dsyncGroupsDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected DataSource Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = client +} + +func (d *dsyncGroupsDataSource) Schema(_ context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Provides information about DSync groups for a team.", + Attributes: map[string]schema.Attribute{ + "team_id": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The ID of the team the Dsync Groups are associated to. Required when configuring a team resource if a default team has not been set in the provider.", + }, + "list": schema.ListNestedAttribute{ + Description: "A list of DSync groups for the team.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the group on Vercel.", + Computed: true, + }, + "name": schema.StringAttribute{ + Description: "The name of the group on the Identity Provider.", + Computed: true, + }, + }, + }, + }, + "map": schema.MapAttribute{ + Description: "A map of Identity Provider group names to their Vercel IDs. This can be used to look up the ID of a group by its name using the [lookup](https://developer.hashicorp.com/terraform/language/functions/lookup) function.", + Computed: true, + ElementType: types.StringType, + }, + }, + } +} + +type DsyncGroup struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` +} + +type DsyncGroups struct { + TeamID types.String `tfsdk:"team_id"` + List []DsyncGroup `tfsdk:"list"` + Map map[string]types.String `tfsdk:"map"` +} + +func (d *dsyncGroupsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config DsyncGroups + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, err := d.client.GetDsyncGroups(ctx, config.TeamID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error reading DSync Groups", + fmt.Sprintf("Could not get DSync Groups for team %s, unexpected error: %s", + config.TeamID.ValueString(), + err, + ), + ) + return + } + + var groupsList = make([]DsyncGroup, 0, len(out.Groups)) + var groupsMap = make(map[string]types.String, len(out.Groups)) + for _, g := range out.Groups { + groupsList = append(groupsList, DsyncGroup{ + ID: types.StringValue(g.ID), + Name: types.StringValue(g.Name), + }) + groupsMap[g.Name] = types.StringValue(g.ID) + } + + result := DsyncGroups{ + TeamID: types.StringValue(out.TeamID), + List: groupsList, + Map: groupsMap, + } + + tflog.Info(ctx, "read dsync groups", map[string]any{ + "team_id": result.TeamID.ValueString(), + }) + + diags = resp.State.Set(ctx, DsyncGroups(result)) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/vercel/data_source_dsync_groups_test.go b/vercel/data_source_dsync_groups_test.go new file mode 100644 index 00000000..5574039f --- /dev/null +++ b/vercel/data_source_dsync_groups_test.go @@ -0,0 +1,37 @@ +package vercel_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAcc_DsyncGroupsDataSource(t *testing.T) { + resourceName := "data.vercel_dsync_groups.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccVercelDsyncGroupsDataSource(testTeam(t)), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "list.#", "2"), + resource.TestCheckResourceAttr(resourceName, "map.%", "2"), + resource.TestCheckResourceAttrSet(resourceName, "list.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "list.0.name"), + resource.TestCheckResourceAttrSet(resourceName, "list.1.id"), + resource.TestCheckResourceAttrSet(resourceName, "list.1.name"), + ), + }, + }, + }) +} + +func testAccVercelDsyncGroupsDataSource(teamID string) string { + return fmt.Sprintf(` +data "vercel_dsync_groups" "test" { + team_id = "%s" +} +`, teamID) +} diff --git a/vercel/provider.go b/vercel/provider.go index a7cc8637..da2bf870 100644 --- a/vercel/provider.go +++ b/vercel/provider.go @@ -109,6 +109,7 @@ func (p *vercelProvider) DataSources(_ context.Context) []func() datasource.Data newTeamMemberDataSource, newMicrofrontendGroupDataSource, newMicrofrontendGroupMembershipDataSource, + newDsyncGroupsDataSource, } }