diff --git a/.changelog/25286.txt b/.changelog/25286.txt new file mode 100644 index 00000000000..8d875abb755 --- /dev/null +++ b/.changelog/25286.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_grafana_workspace_api_key +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 579d8888de9..e186974e761 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1550,6 +1550,7 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_grafana_license_association": grafana.ResourceLicenseAssociation(), "aws_grafana_role_association": grafana.ResourceRoleAssociation(), "aws_grafana_workspace": grafana.ResourceWorkspace(), + "aws_grafana_workspace_api_key": grafana.ResourceWorkspaceAPIKey(), "aws_grafana_workspace_saml_configuration": grafana.ResourceWorkspaceSAMLConfiguration(), "aws_guardduty_detector": guardduty.ResourceDetector(), diff --git a/internal/service/grafana/workspace_api_key.go b/internal/service/grafana/workspace_api_key.go new file mode 100644 index 00000000000..3991a18ab1f --- /dev/null +++ b/internal/service/grafana/workspace_api_key.go @@ -0,0 +1,124 @@ +package grafana + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/managedgrafana" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" +) + +func ResourceWorkspaceAPIKey() *schema.Resource { + return &schema.Resource{ + Create: resourceWorkspaceAPIKeyCreate, + Read: schema.Noop, + Update: schema.Noop, + Delete: resourceWorkspaceAPIKeyDelete, + + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Computed: true, + }, + "key_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 100), + }, + "key_role": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(managedgrafana.Role_Values(), false), + }, + "seconds_to_live": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 2592000), + }, + "workspace_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceWorkspaceAPIKeyCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).GrafanaConn + + keyName := d.Get("key_name").(string) + workspaceID := d.Get("workspace_id").(string) + id := WorkspaceAPIKeyCreateResourceID(workspaceID, keyName) + input := &managedgrafana.CreateWorkspaceApiKeyInput{ + KeyName: aws.String(keyName), + KeyRole: aws.String(d.Get("key_role").(string)), + SecondsToLive: aws.Int64(int64(d.Get("seconds_to_live").(int))), + WorkspaceId: aws.String(workspaceID), + } + + log.Printf("[DEBUG] Creating Grafana Workspace API Key: %s", input) + output, err := conn.CreateWorkspaceApiKey(input) + + if err != nil { + return fmt.Errorf("creating Grafana Workspace API Key (%s): %w", id, err) + } + + d.SetId(id) + d.Set("key", output.Key) + + return nil +} + +func resourceWorkspaceAPIKeyDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).GrafanaConn + + workspaceID, keyName, error := WorkspaceAPIKeyParseResourceID(d.Id()) + + if error != nil { + return error + } + + log.Printf("[DEBUG] Deleting Grafana Workspace API Key: %s", d.Id()) + _, err := conn.DeleteWorkspaceApiKey(&managedgrafana.DeleteWorkspaceApiKeyInput{ + KeyName: aws.String(keyName), + WorkspaceId: aws.String(workspaceID), + }) + + if tfawserr.ErrCodeEquals(err, managedgrafana.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("deleting Grafana Workspace API Key (%s): %w", d.Id(), err) + } + + return nil + +} + +const workspaceAPIKeyIDSeparator = "/" + +func WorkspaceAPIKeyCreateResourceID(workspaceID, keyName string) string { + parts := []string{workspaceID, keyName} + id := strings.Join(parts, workspaceAPIKeyIDSeparator) + + return id +} + +func WorkspaceAPIKeyParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, workspaceAPIKeyIDSeparator) + + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected workspace-id%[2]skey-name", id, workspaceAPIKeyIDSeparator) + } + + return parts[0], parts[1], nil +} diff --git a/internal/service/grafana/workspace_api_key_test.go b/internal/service/grafana/workspace_api_key_test.go new file mode 100644 index 00000000000..343edd08e11 --- /dev/null +++ b/internal/service/grafana/workspace_api_key_test.go @@ -0,0 +1,72 @@ +package grafana_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/managedgrafana" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func testAccWorkspaceAPIKey_basic(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_grafana_workspace_api_key.test" + workspaceResourceName := "aws_grafana_workspace.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(managedgrafana.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, managedgrafana.EndpointsID), + CheckDestroy: acctest.CheckDestroyNoop, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceAPIKeyConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "key"), + resource.TestCheckResourceAttr(resourceName, "key_name", rName), + resource.TestCheckResourceAttr(resourceName, "key_role", managedgrafana.RoleEditor), + resource.TestCheckResourceAttr(resourceName, "seconds_to_live", "3600"), + resource.TestCheckResourceAttrPair(resourceName, "workspace_id", workspaceResourceName, "id"), + ), + }, + }, + }) +} + +func testAccWorkspaceAPIKeyConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Sid = "" + Principal = { + Service = "grafana.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_grafana_workspace" "test" { + account_access_type = "CURRENT_ACCOUNT" + authentication_providers = ["SAML"] + permission_type = "SERVICE_MANAGED" + role_arn = aws_iam_role.test.arn +} + +resource "aws_grafana_workspace_api_key" "test" { + key_name = %[1]q + key_role = "EDITOR" + seconds_to_live = 3600 + workspace_id = aws_grafana_workspace.test.id +} +`, rName) +} diff --git a/internal/service/grafana/workspace_test.go b/internal/service/grafana/workspace_test.go index 0a8bdcae8d0..801529d0041 100644 --- a/internal/service/grafana/workspace_test.go +++ b/internal/service/grafana/workspace_test.go @@ -27,6 +27,9 @@ func TestAccGrafana_serial(t *testing.T) { "notificationDestinations": testAccWorkspace_notificationDestinations, "tags": testAccWorkspace_tags, }, + "ApiKey": { + "basic": testAccWorkspaceAPIKey_basic, + }, "DataSource": { "basic": testAccWorkspaceDataSource_basic, }, diff --git a/website/docs/r/grafana_workspace_api_key.html.markdown b/website/docs/r/grafana_workspace_api_key.html.markdown new file mode 100644 index 00000000000..48db2071d92 --- /dev/null +++ b/website/docs/r/grafana_workspace_api_key.html.markdown @@ -0,0 +1,39 @@ +--- +subcategory: "Managed Grafana" +layout: "aws" +page_title: "AWS: aws_grafana_workspace_api_key" +description: |- + Creates a Grafana API key for the workspace. This key can be used to authenticate requests sent to the workspace's HTTP API. +--- + +# Resource: aws_grafana_workspace_api_key + +Provides an Amazon Managed Grafana workspace API Key resource. + +## Example Usage + +### Basic configuration + +```terraform +resource "aws_grafana_workspace_api_key" "key" { + key_name = "test-key" + key_role = "VIEWER" + seconds_to_live = 3600 + workspace_id = aws_grafana_workspace.test.id +} +``` + +## Argument Reference + +The following arguments are required: + +- `key_name` - (Required) Specifies the name of the API key. Key names must be unique to the workspace. +- `key_role` - (Required) Specifies the permission level of the API key. Valid values are `VIEWER`, `EDITOR`, or `ADMIN`. +- `seconds_to_live` - (Required) Specifies the time in seconds until the API key expires. Keys can be valid for up to 30 days. +- `workspace_id` - (Required) The ID of the workspace that the API key is valid for. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `key` - The key token in JSON format. Use this value as a bearer token to authenticate HTTP requests to the workspace.