Skip to content

Commit

Permalink
Service accounts: Create Grafana service accounts through cloud provi…
Browse files Browse the repository at this point in the history
…der (#858)

* add resource for creating Grafana service accounts through the cloud provider

* documentation for the new resources

* deprecated Grafana API key resources

* small change to example and import ordering

* more import ordering

* remove deprecation notices

* fix the example
  • Loading branch information
IevaVasiljeva authored Apr 4, 2023
1 parent a11b175 commit 5668d84
Show file tree
Hide file tree
Showing 12 changed files with 612 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/resources/api_key.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ output "api_key_bar" {

### Optional

- `cloud_stack_slug` (String, Deprecated) Deprecated: Use the `grafana_cloud_stack_api_key` resource instead
- `cloud_stack_slug` (String, Deprecated) Deprecated: Use `grafana_cloud_stack_service_account` and `grafana_cloud_stack_service_account_token` resources instead
- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
- `seconds_to_live` (Number)

Expand Down
51 changes: 51 additions & 0 deletions docs/resources/cloud_stack_service_account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "grafana_cloud_stack_service_account Resource - terraform-provider-grafana"
subcategory: "Cloud"
description: |-
Note: This resource is available only with Grafana 9.1+.
Manages service accounts of a Grafana Cloud stack using the Cloud API
This can be used to bootstrap a management service account for a new stack
Official documentation https://grafana.com/docs/grafana/latest/administration/service-accounts/HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/serviceaccount/#service-account-api
---

# grafana_cloud_stack_service_account (Resource)

**Note:** This resource is available only with Grafana 9.1+.

Manages service accounts of a Grafana Cloud stack using the Cloud API
This can be used to bootstrap a management service account for a new stack

* [Official documentation](https://grafana.com/docs/grafana/latest/administration/service-accounts/)
* [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/serviceaccount/#service-account-api)

## Example Usage

```terraform
resource "grafana_cloud_stack_service_account" "cloud_sa" {
stack_slug = "<your stack slug>"
name = "cloud service account"
role = "Admin"
is_disabled = false
}
```

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

### Required

- `name` (String) The name of the service account.
- `stack_slug` (String)

### Optional

- `is_disabled` (Boolean) The disabled status for the service account. Defaults to `false`.
- `role` (String) The basic role of the service account in the organization.

### Read-Only

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


64 changes: 64 additions & 0 deletions docs/resources/cloud_stack_service_account_token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "grafana_cloud_stack_service_account_token Resource - terraform-provider-grafana"
subcategory: "Cloud"
description: |-
Note: This resource is available only with Grafana 9.1+.
Manages service account tokens of a Grafana Cloud stack using the Cloud API
This can be used to bootstrap a management service account token for a new stack
Official documentation https://grafana.com/docs/grafana/latest/administration/service-accounts/HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/serviceaccount/#service-account-api
---

# grafana_cloud_stack_service_account_token (Resource)

**Note:** This resource is available only with Grafana 9.1+.

Manages service account tokens of a Grafana Cloud stack using the Cloud API
This can be used to bootstrap a management service account token for a new stack

* [Official documentation](https://grafana.com/docs/grafana/latest/administration/service-accounts/)
* [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/serviceaccount/#service-account-api)

## Example Usage

```terraform
resource "grafana_cloud_stack_service_account" "cloud_sa" {
stack_slug = "<your stack slug>"
name = "cloud service account"
role = "Admin"
is_disabled = false
}
resource "grafana_cloud_stack_service_account_token" "foo" {
name = "key_foo"
service_account_id = grafana_cloud_stack_service_account.cloud_sa.id
}
output "service_account_token_foo_key" {
value = grafana_cloud_stack_service_account_token.foo.key
sensitive = true
}
```

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

### Required

- `name` (String)
- `service_account_id` (String)
- `stack_slug` (String)

### Optional

- `seconds_to_live` (Number)

### Read-Only

- `expiration` (String)
- `has_expired` (Boolean)
- `id` (String) The ID of this resource.
- `key` (String, Sensitive)


Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resource "grafana_cloud_stack_service_account" "cloud_sa" {
stack_slug = "<your stack slug>"

name = "cloud service account"
role = "Admin"
is_disabled = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
resource "grafana_cloud_stack_service_account" "cloud_sa" {
stack_slug = "<your stack slug>"

name = "cloud service account"
role = "Admin"
is_disabled = false
}

resource "grafana_cloud_stack_service_account_token" "foo" {
name = "key_foo"
service_account_id = grafana_cloud_stack_service_account.cloud_sa.id
}

output "service_account_token_foo_key" {
value = grafana_cloud_stack_service_account_token.foo.key
sensitive = true
}
14 changes: 8 additions & 6 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,14 @@ func Provider(version string) func() *schema.Provider {

// Resources that require the Cloud client to exist.
cloudClientResources = addResourcesMetadataValidation(cloudClientPresent, map[string]*schema.Resource{
"grafana_cloud_access_policy": cloud.ResourceAccessPolicy(),
"grafana_cloud_access_policy_token": cloud.ResourceAccessPolicyToken(),
"grafana_cloud_api_key": cloud.ResourceAPIKey(),
"grafana_cloud_plugin_installation": cloud.ResourcePluginInstallation(),
"grafana_cloud_stack": cloud.ResourceStack(),
"grafana_cloud_stack_api_key": cloud.ResourceStackAPIKey(),
"grafana_cloud_access_policy": cloud.ResourceAccessPolicy(),
"grafana_cloud_access_policy_token": cloud.ResourceAccessPolicyToken(),
"grafana_cloud_api_key": cloud.ResourceAPIKey(),
"grafana_cloud_plugin_installation": cloud.ResourcePluginInstallation(),
"grafana_cloud_stack": cloud.ResourceStack(),
"grafana_cloud_stack_api_key": cloud.ResourceStackAPIKey(),
"grafana_cloud_stack_service_account": cloud.ResourceStackServiceAccount(),
"grafana_cloud_stack_service_account_token": cloud.ResourceStackServiceAccountToken(),
})

// Resources that require the OnCall client to exist.
Expand Down
176 changes: 176 additions & 0 deletions internal/resources/cloud/resource_cloud_stack_service_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package cloud

import (
"context"
"log"
"strconv"
"time"

gapi "github.com/grafana/grafana-api-golang-client"
"github.com/grafana/terraform-provider-grafana/internal/common"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func ResourceStackServiceAccount() *schema.Resource {
return &schema.Resource{

Description: `
**Note:** This resource is available only with Grafana 9.1+.
Manages service accounts of a Grafana Cloud stack using the Cloud API
This can be used to bootstrap a management service account for a new stack
* [Official documentation](https://grafana.com/docs/grafana/latest/administration/service-accounts/)
* [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/serviceaccount/#service-account-api)`,

CreateContext: createStackServiceAccount,
ReadContext: readStackServiceAccount,
UpdateContext: updateStackServiceAccount,
DeleteContext: deleteStackServiceAccount,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"stack_slug": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The name of the service account.",
},
"role": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"Viewer", "Editor", "Admin"}, false),
Description: "The basic role of the service account in the organization.",
},
"is_disabled": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "The disabled status for the service account.",
},
},
}
}

func createStackServiceAccount(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, cleanup, err := getClientForSAManagement(d, meta)
if err != nil {
return diag.FromErr(err)
}
defer cleanup()

isDisabled := d.Get("is_disabled").(bool)
req := gapi.CreateServiceAccountRequest{
Name: d.Get("name").(string),
Role: d.Get("role").(string),
IsDisabled: &isDisabled,
}
sa, err := client.CreateServiceAccount(req)
if err != nil {
return diag.FromErr(err)
}

d.SetId(strconv.FormatInt(sa.ID, 10))
return readStackServiceAccount(ctx, d, meta)
}

func readStackServiceAccount(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, cleanup, err := getClientForSAManagement(d, meta)
if err != nil {
return diag.FromErr(err)
}
defer cleanup()

id, err := strconv.ParseInt(d.Id(), 10, 64)
if err != nil {
return diag.FromErr(err)
}

sas, err := client.GetServiceAccounts()
if err != nil {
return diag.FromErr(err)
}

for _, sa := range sas {
if sa.ID == id {
err = d.Set("name", sa.Name)
if err != nil {
return diag.FromErr(err)
}
err = d.Set("role", sa.Role)
if err != nil {
return diag.FromErr(err)
}
err = d.Set("is_disabled", sa.IsDisabled)
if err != nil {
return diag.FromErr(err)
}

return nil
}
}
log.Printf("[WARN] removing service account %d from state because it no longer exists in grafana", id)
d.SetId("")

return nil
}

func updateStackServiceAccount(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, cleanup, err := getClientForSAManagement(d, meta)
if err != nil {
return diag.FromErr(err)
}
defer cleanup()

id, err := strconv.ParseInt(d.Id(), 10, 64)
if err != nil {
return diag.FromErr(err)
}

updateRequest := gapi.UpdateServiceAccountRequest{}
if d.HasChange("name") {
updateRequest.Name = d.Get("name").(string)
}
if d.HasChange("role") {
updateRequest.Role = d.Get("role").(string)
}
if d.HasChange("is_disabled") {
isDisabled := d.Get("is_disabled").(bool)
updateRequest.IsDisabled = &isDisabled
}

if _, err := client.UpdateServiceAccount(id, updateRequest); err != nil {
return diag.FromErr(err)
}

return readStackServiceAccount(ctx, d, meta)
}

func deleteStackServiceAccount(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, cleanup, err := getClientForSAManagement(d, meta)
if err != nil {
return diag.FromErr(err)
}
defer cleanup()

id, err := strconv.ParseInt(d.Id(), 10, 64)
if err != nil {
return diag.FromErr(err)
}

_, err = client.DeleteServiceAccount(id)
return diag.FromErr(err)
}

func getClientForSAManagement(d *schema.ResourceData, m interface{}) (c *gapi.Client, cleanup func() error, err error) {
cloudClient := m.(*common.Client).GrafanaCloudAPI
return cloudClient.CreateTemporaryStackGrafanaClient(d.Get("stack_slug").(string), "terraform-temp-sa-", 60*time.Second)
}
Loading

0 comments on commit 5668d84

Please sign in to comment.