Skip to content

Commit

Permalink
feat: add service_account_token resource
Browse files Browse the repository at this point in the history
* adds a new resource `doppler_service_account_token`
* added example usage
* autogenerated docs for newly added resource
  • Loading branch information
srevinsaju committed May 28, 2024
1 parent 1d5e488 commit c11e379
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 1 deletion.
41 changes: 41 additions & 0 deletions docs/resources/service_account_token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
page_title: "doppler_service_account_token Resource - terraform-provider-doppler"
subcategory: ""
description: |-
Manage a Doppler service_account_token.
---

# doppler_service_account_token (Resource)

Manage a Doppler service account token.

## Example Usage

```terraform
resource "doppler_service_account_token" "builder_ci_token" {
service_account = "builder"
name = "Builder CI Token"
expires_at = "2024-05-30T11:00:00.000Z"
}
# Service token key available as `doppler_service_account_token.builder_ci_token.api_key`
```

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

### Required

- `name` (String) The display name of the API token
- `service_account_slug` (String) Slug of the service account

### Optional

- `expires_at` (String) The datetime at which the API token should expire. If not provided, the API token will remain valid indefinitely unless manually revoked

### Read-Only

- `api_key` (String, Sensitive) The api key used to authenticate the service account
- `created_at` (String) The datetime that the token was created.
- `id` (String) The ID of this resource.
- `slug` (String) Slug of the service account token
44 changes: 44 additions & 0 deletions doppler/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,50 @@ func (client APIClient) DeleteServiceAccount(ctx context.Context, slug string) e
return nil
}

// Service Account Tokens

func (client APIClient) GetServiceAccountToken(ctx context.Context, serviceAccountSlug string, slug string) (ServiceAccountTokenResponse, error) {
response, err := client.PerformRequestWithRetry(ctx, "GET", fmt.Sprintf("/v3/workplace/service_accounts/service_account/%s/tokens/token/%s", url.QueryEscape(serviceAccountSlug), url.QueryEscape(slug)), []QueryParam{}, nil)
if err != nil {
return ServiceAccountTokenResponse{}, err
}
var result ServiceAccountTokenResponse
if err = json.Unmarshal(response.Body, &result); err != nil {
return ServiceAccountTokenResponse{}, &APIError{Err: err, Message: "Unable to parse service account tokens"}
}
return result, nil
}

func (client APIClient) CreateServiceAccountToken(ctx context.Context, serviceAccountSlug string, name string, expiresAt string) (*ServiceAccountTokenResponse, error) {
payload := map[string]interface{}{
"name": name,
}
if expiresAt != "" {
payload["expires_at"] = expiresAt
}
body, err := json.Marshal(payload)
if err != nil {
return nil, &APIError{Err: err, Message: "Unable to serialize account service token"}
}
response, err := client.PerformRequestWithRetry(ctx, "POST", fmt.Sprintf("/v3/workplace/service_accounts/service_account/%s/tokens", url.QueryEscape(serviceAccountSlug)), []QueryParam{}, body)
if err != nil {
return nil, err
}
var result ServiceAccountTokenResponse
if err = json.Unmarshal(response.Body, &result); err != nil {
return nil, &APIError{Err: err, Message: "Unable to parse service account token"}
}
return &result, nil
}

func (client APIClient) DeleteServiceAccountToken(ctx context.Context, serviceAccountSlug string, slug string) error {
_, err := client.PerformRequestWithRetry(ctx, "DELETE", fmt.Sprintf("/v3/workplace/service_accounts/service_account/%s/tokens/token/%s", url.QueryEscape(serviceAccountSlug), url.QueryEscape(slug)), []QueryParam{}, nil)
if err != nil {
return err
}
return nil
}

// Groups

func (client APIClient) GetGroup(ctx context.Context, slug string) (*Group, error) {
Expand Down
16 changes: 16 additions & 0 deletions doppler/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,22 @@ func parseServiceTokenResourceId(id string) (project string, config string, slug
return tokens[0], tokens[1], tokens[2], nil
}

type ServiceAccountToken struct {
Name string `json:"name"`
ExpiresAt string `json:"expires_at"`
CreatedAt string `json:"created_at"`
Slug string `json:"slug"`
}

type ServiceAccountTokenResponse struct {
ServiceAccountToken ServiceAccountToken `json:"api_token"`
ApiKey string `json:"api_key"`
}

func (t ServiceAccountToken) getResourceId() string {
return t.Slug
}

type WorkplaceRole struct {
Name string `json:"name"`
Permissions []string `json:"permissions"`
Expand Down
3 changes: 2 additions & 1 deletion doppler/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ func Provider() *schema.Provider {
"doppler_config": resourceConfig(),
"doppler_service_token": resourceServiceToken(),

"doppler_service_account": resourceServiceAccount(),
"doppler_service_account": resourceServiceAccount(),
"doppler_service_account_token": resourceServiceAccountToken(),

"doppler_group": resourceGroup(),
"doppler_group_member": resourceGroupMemberWorkplaceUser(),
Expand Down
129 changes: 129 additions & 0 deletions doppler/resource_service_account_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package doppler

import (
"context"

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

func resourceServiceAccountToken() *schema.Resource {
return &schema.Resource{
CreateContext: resourceServiceAccountTokenCreate,
ReadContext: resourceServiceAccountTokenRead,
DeleteContext: resourceServiceAccountTokenDelete,
// ForceNew is specified for all user-specified fields
// Service account tokens cannot be moved, renamed, or edited to change their access
Schema: map[string]*schema.Schema{
"service_account_slug": {
Description: "Slug of the service account",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": {
Description: "The display name of the API token",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"slug": {
Description: "Slug of the service account token",
Type: schema.TypeString,
Computed: true,
},
"expires_at": {
Description: "The datetime at which the API token should expire. " +
"If not provided, the API token will remain valid indefinitely unless manually revoked",
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"created_at": {
Description: "The datetime that the token was created.",
Type: schema.TypeString,
Computed: true,
},
"api_key": {
Description: "The api key used to authenticate the service account",
Type: schema.TypeString,
Computed: true,
Sensitive: true,
},
},
}
}

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

var diags diag.Diagnostics
serviceAccount := d.Get("service_account_slug").(string)
name := d.Get("name").(string)
expiresAt, ok := d.Get("expires_at").(string)
if !ok {
expiresAt = ""
}

token, err := client.CreateServiceAccountToken(ctx, serviceAccount, name, expiresAt)
if err != nil {
return diag.FromErr(err)
}

d.SetId(token.ServiceAccountToken.getResourceId())

if err = d.Set("expires_at", token.ServiceAccountToken.ExpiresAt); err != nil {
return diag.FromErr(err)
}
if err = d.Set("created_at", token.ServiceAccountToken.CreatedAt); err != nil {
return diag.FromErr(err)
}
if err = d.Set("api_key", token.ApiKey); err != nil {
return diag.FromErr(err)
}
if err = d.Set("slug", token.ServiceAccountToken.Slug); err != nil {
return diag.FromErr(err)
}

return diags
}

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

var diags diag.Diagnostics
serviceAccount := d.Get("service_account_slug").(string)
slug := d.Id()

token, err := client.GetServiceAccountToken(ctx, serviceAccount, slug)
if err != nil {
return diag.FromErr(err)
}

if err = d.Set("name", token.ServiceAccountToken.Name); err != nil {
return diag.FromErr(err)
}
if err = d.Set("expires_at", token.ServiceAccountToken.ExpiresAt); err != nil {
return diag.FromErr(err)
}
if err = d.Set("created_at", token.ServiceAccountToken.CreatedAt); err != nil {
return diag.FromErr(err)
}
if err = d.Set("slug", token.ServiceAccountToken.Slug); err != nil {
return diag.FromErr(err)
}
return diags
}

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

var diags diag.Diagnostics
serviceAccount := d.Get("service_account_slug").(string)
slug := d.Id()

if err := client.DeleteServiceAccountToken(ctx, serviceAccount, slug); err != nil {
return diag.FromErr(err)
}
return diags
}
7 changes: 7 additions & 0 deletions examples/resources/service_account_token.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resource "doppler_service_account_token" "builder_ci_token" {
service_account = "builder"
name = "Builder CI Token"
expires_at = "2024-05-30T11:00:00.000Z"
}

# Service token key available as `doppler_service_account_token.builder_ci_token.api_key`
16 changes: 16 additions & 0 deletions templates/resources/service_account_token.md.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
page_title: "doppler_service_account_token Resource - terraform-provider-doppler"
subcategory: ""
description: |-
Manage a Doppler service_account_token.
---

# doppler_service_account_token (Resource)

Manage a Doppler service account token.

## Example Usage

{{tffile "examples/resources/service_account_token.tf"}}

{{ .SchemaMarkdown | trimspace }}

0 comments on commit c11e379

Please sign in to comment.