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

feat: add service_account_token resource #86

Merged
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
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 {
srevinsaju marked this conversation as resolved.
Show resolved Hide resolved
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 }}