Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add resources for managing users in groups #72

Merged
merged 3 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading