Skip to content

Commit

Permalink
Merge pull request #72 from DopplerHQ/rgharris/group-members
Browse files Browse the repository at this point in the history
Add resources for managing users in groups
  • Loading branch information
rgharris authored Feb 8, 2024
2 parents 07e956f + 5b2c682 commit 5630cc3
Show file tree
Hide file tree
Showing 16 changed files with 450 additions and 11 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,9 @@ New work should branch from `master` and target `master` in PRs.

To release, create a GitHub Release (and associated tag) on `master` in the format `vX.X.X`, following [semantic versioning](https://semver.org/).
The `release` GitHub Actions workflow will automatically build and ship the new version.

# Doc Generation

Everything in the `docs` directory of this repo is automatically generated by [terraform-docs](https://github.com/terraform-docs/terraform-docs) and therefore should not be modified by hand.

To add or update docs for resources or data sources, modify the files in `examples/` and `templates/` and run `make tfdocs` to regenerate the `docs/` markdown.
8 changes: 3 additions & 5 deletions docs/data-sources/secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ Retrieve all secrets in the config.

## Example Usage

Basic usage:

```terraform
### Basic Usage
data "doppler_secrets" "this" {}
# Access individual secrets
Expand All @@ -32,11 +32,9 @@ output "max_workers" {
output "json_parsing_values" {
value = nonsensitive(jsondecode(data.doppler_secrets.this.map.FEATURE_FLAGS)["TOP_SPEED"])
}
```
Referencing secrets from multiple projects:
### Referencing secrets from multiple projects
```terraform
variable "doppler_token_dev" {
type = string
description = "A token to authenticate with Doppler for the dev config"
Expand Down
30 changes: 30 additions & 0 deletions docs/data-sources/user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
page_title: "doppler_user Data Source - terraform-provider-doppler"
subcategory: "User"
description: |-
Retrieve a Doppler user
---

# doppler_user (Data Source)

Retrieve all secrets in the config.

## Example Usage

```terraform
data "doppler_user" "nic" {
email = "[email protected]"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `email` (String) The email address of the Doppler user

### Read-Only

- `id` (String) The ID of this resource.
- `slug` (String) The slug of the Doppler user
1 change: 0 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ provider "doppler" {
```

<!-- schema generated by tfplugindocs -->

## Schema

### Required
Expand Down
48 changes: 48 additions & 0 deletions docs/resources/group_member.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
page_title: "doppler_group_member Resource - terraform-provider-doppler"
subcategory: ""
description: |-
Manage a Doppler user/group membership.
---

# doppler_group_member (Resource)

Manage a Doppler user/group membership.

## Example Usage

```terraform
resource "doppler_group" "engineering" {
name = "engineering"
}
data "doppler_user" "nic" {
email = "[email protected]"
}
data "doppler_user" "andre" {
email = "[email protected]"
}
resource "doppler_group_member" "engineering" {
for_each = toset([data.doppler_user.nic.slug, data.doppler_user.andre.slug])
group_slug = doppler_group.engineering.slug
user_slug = each.value
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `group_slug` (String) The slug of the Doppler group
- `user_slug` (String) The slug of the Doppler workplace user

### Read-Only

- `id` (String) The ID of this resource.

## Resource ID Format

Resource IDs are in the format `<group_slug>.workplace_user.<user_slug>`.
54 changes: 54 additions & 0 deletions doppler/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -907,3 +907,57 @@ func (client APIClient) DeleteGroup(ctx context.Context, slug string) error {
}
return nil
}

// Group Members

func (client APIClient) CreateGroupMember(ctx context.Context, group string, memberType string, memberSlug string) error {
payload := map[string]interface{}{
"type": memberType,
"slug": memberSlug,
}
body, err := json.Marshal(payload)
if err != nil {
return &APIError{Err: err, Message: "Unable to serialize group member"}
}
_, err = client.PerformRequestWithRetry(ctx, "POST", fmt.Sprintf("/v3/workplace/groups/group/%s/members", url.QueryEscape(group)), []QueryParam{}, body)
if err != nil {
return err
}
return nil
}

func (client APIClient) GetGroupMember(ctx context.Context, group string, memberType string, memberSlug string) error {
_, err := client.PerformRequestWithRetry(ctx, "GET", fmt.Sprintf("/v3/workplace/groups/group/%s/members/%s/%s", url.QueryEscape(group), url.QueryEscape(memberType), url.QueryEscape(memberSlug)), []QueryParam{}, nil)
return err
}

func (client APIClient) DeleteGroupMember(ctx context.Context, group string, memberType string, memberSlug string) error {
_, err := client.PerformRequestWithRetry(ctx, "DELETE", fmt.Sprintf("/v3/workplace/groups/group/%s/members/%s/%s", url.QueryEscape(group), url.QueryEscape(memberType), url.QueryEscape(memberSlug)), []QueryParam{}, nil)
if err != nil {
return err
}
return nil
}

// Workplace Users

func (client APIClient) GetWorkplaceUser(ctx context.Context, email string) (*WorkplaceUser, error) {
params := []QueryParam{
{Key: "email", Value: email},
}
response, err := client.PerformRequestWithRetry(ctx, "GET", "/v3/workplace/users", params, nil)
if err != nil {
return nil, err
}
var result WorkplaceUsersListResponse
if err = json.Unmarshal(response.Body, &result); err != nil {
return nil, &APIError{Err: err, Message: "Unable to parse workplace user"}
}
if len(result.WorkplaceUsers) > 1 {
return nil, &APIError{Err: err, Message: "Multiple workplace users returned"}
}
if len(result.WorkplaceUsers) == 0 {
return nil, &CustomNotFoundError{Message: "Could not find requested workplace user"}
}
return &result.WorkplaceUsers[0], nil
}
43 changes: 43 additions & 0 deletions doppler/data_source_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package doppler

import (
"context"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataSourceUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
client := m.(APIClient)

result, err := client.GetWorkplaceUser(ctx, d.Get("email").(string))
if err != nil {
return diag.FromErr(err)
}

d.SetId(result.Slug)
if err := d.Set("slug", result.Slug); err != nil {
return diag.FromErr(err)
}

return diags
}

func dataSourceUser() *schema.Resource {
return &schema.Resource{
ReadContext: dataSourceUserRead,
Schema: map[string]*schema.Schema{
"slug": {
Description: "The slug of the Doppler user",
Type: schema.TypeString,
Computed: true,
},
"email": {
Description: "The email address of the Doppler user",
Type: schema.TypeString,
Required: true,
},
},
}
}
30 changes: 27 additions & 3 deletions doppler/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ type WorkplaceRole struct {
}

type ServiceAccount struct {
Slug string `json:"slug"`
Name string `json:"name"`
CreatedAt string `json:"created_at"`
Slug string `json:"slug"`
Name string `json:"name"`
CreatedAt string `json:"created_at"`
WorkplaceRole WorkplaceRole `json:"workplace_role"`
}

Expand All @@ -242,3 +242,27 @@ type Group struct {
type GroupResponse struct {
Group Group `json:"group"`
}

type GroupIsMemberResponse struct {
IsMember bool `json:"isMember"`
}

type WorkplaceUser struct {
Slug string `json:"id"`
}

type WorkplaceUsersListResponse struct {
WorkplaceUsers []WorkplaceUser `json:"workplace_users"`
}

func getGroupMemberId(group string, memberType string, memberSlug string) string {
return strings.Join([]string{group, memberType, memberSlug}, ".")
}

func parseGroupMemberId(id string) (group string, memberType string, memberSlug string, err error) {
tokens := strings.Split(id, ".")
if len(tokens) != 3 {
return "", "", "", errors.New("invalid group member ID")
}
return tokens[0], tokens[1], tokens[2], nil
}
6 changes: 4 additions & 2 deletions doppler/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ func Provider() *schema.Provider {

"doppler_service_account": resourceServiceAccount(),

"doppler_group": resourceGroup(),
"doppler_group": resourceGroup(),
"doppler_group_member": resourceGroupMemberWorkplaceUser(),

"doppler_project_member_group": resourceProjectMemberGroup(),
"doppler_project_member_service_account": resourceProjectMemberServiceAccount(),
Expand All @@ -51,11 +52,12 @@ func Provider() *schema.Provider {
"doppler_integration_aws_parameter_store": resourceIntegrationAWSAssumeRoleIntegration("aws_parameter_store"),
"doppler_secrets_sync_aws_parameter_store": resourceSyncAWSParameterStore(),

"doppler_integration_terraform_cloud": resourceIntegrationTerraformCloud(),
"doppler_integration_terraform_cloud": resourceIntegrationTerraformCloud(),
"doppler_secrets_sync_terraform_cloud": resourceSyncTerraformCloud(),
},
DataSourcesMap: map[string]*schema.Resource{
"doppler_secrets": dataSourceSecrets(),
"doppler_user": dataSourceUser(),
},
ConfigureContextFunc: providerConfigure,
}
Expand Down
Loading

0 comments on commit 5630cc3

Please sign in to comment.