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 Grafana service account #38101

Merged
merged 31 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
715bfff
Setup workspace_service_account resource
bonclay7 Jun 22, 2024
3a02995
Implement create, read and delete
bonclay7 Jun 22, 2024
d1243a5
Rename typo
bonclay7 Jun 22, 2024
9de6680
Compile resource
bonclay7 Jun 23, 2024
a021f10
Add validation for Grafana role
bonclay7 Jun 23, 2024
bc6cff9
Add pagination for list, align schema with grafana api
bonclay7 Jun 23, 2024
6ba9b54
Fix acceptance tests
bonclay7 Jun 24, 2024
d78b3e5
Add doc
bonclay7 Jun 24, 2024
a27d8cb
Lint doc and code
bonclay7 Jun 24, 2024
70e9250
Lint doc and code
bonclay7 Jun 24, 2024
6a94b7b
Refactor and rename
bonclay7 Jun 25, 2024
c09bb4e
fixup! Refactor and rename
bonclay7 Jun 25, 2024
94f1141
remove unnecessary leading space
bonclay7 Jun 26, 2024
c16045c
Add schema validators
bonclay7 Jun 27, 2024
a72a23a
Optimize tests
bonclay7 Jun 27, 2024
65e029f
Clean docs
bonclay7 Jun 27, 2024
1f51bc6
Update docs
bonclay7 Jun 27, 2024
3b62fa3
Adding service account token
bonclay7 Jun 27, 2024
b62c77c
fixup! Adding service account token
bonclay7 Jun 27, 2024
47c7d7a
Drop dead code
bonclay7 Jun 27, 2024
6b89e4c
Remove 'workspace_id' constant.
ewbankkit Jul 12, 2024
a75d8e4
Merge branch 'main' into HEAD
ewbankkit Jul 12, 2024
e7a6a6d
Add CHANGELOG entries.
ewbankkit Jul 12, 2024
6db522d
r/aws_grafana_workspace_service_account: Tidy up.
ewbankkit Jul 12, 2024
1e5ab73
r/aws_grafana_workspace_service_account_token: Tidy up.
ewbankkit Jul 12, 2024
2e1b4e1
Fixes after some testing.
ewbankkit Jul 12, 2024
140f97c
Run 'make fix-constants PKG=grafana'.
ewbankkit Jul 12, 2024
7b96b4f
Fix typos.
ewbankkit Jul 12, 2024
b796cba
More fixes after testing.
ewbankkit Jul 12, 2024
da430af
r/aws_grafana_workspace_service_account_token: Fix acceptance test co…
ewbankkit Jul 12, 2024
9b3012d
r/aws_grafana_workspace_service_account_token: Tweak documentation ex…
ewbankkit Jul 12, 2024
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
7 changes: 7 additions & 0 deletions .changelog/38101.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:new-resource
aws_grafana_workspace_service_account_token
```

```release-note:new-resource
aws_grafana_workspace_service_account
```
5 changes: 5 additions & 0 deletions internal/framework/types/string_enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package types
import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
Expand Down Expand Up @@ -122,6 +123,10 @@ func StringEnumValue[T enum.Valueser[T]](value T) StringEnum[T] {
return StringEnum[T]{StringValue: basetypes.NewStringValue(string(value))}
}

func StringEnumValueToUpper[T enum.Valueser[T]](value T) StringEnum[T] {
return StringEnumValue(T(strings.ToUpper(string(value))))
}

func (v StringEnum[T]) Equal(o attr.Value) bool {
other, ok := o.(StringEnum[T])

Expand Down
20 changes: 11 additions & 9 deletions internal/service/grafana/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ package grafana

// Exports for use in tests only.
var (
ResourceLicenseAssociation = resourceLicenseAssociation
ResourceRoleAssociation = resourceRoleAssociation
ResourceWorkspace = resourceWorkspace
ResourceWorkspaceAPIKey = resourceWorkspaceAPIKey
ResourceWorkspaceSAMLConfiguration = resourceWorkspaceSAMLConfiguration
ResourceWorkspace = resourceWorkspace
ResourceWorkspaceAPIKey = resourceWorkspaceAPIKey
ResourceWorkspaceSAMLConfiguration = resourceWorkspaceSAMLConfiguration
ResourceWorkspaceServiceAccount = newWorkspaceServiceAccountResource
ResourceWorkspaceServiceAccountToken = newWorkspaceServiceAccountTokenResource

FindLicensedWorkspaceByID = findLicensedWorkspaceByID
FindRoleAssociationsByTwoPartKey = findRoleAssociationsByTwoPartKey
FindSAMLConfigurationByID = findSAMLConfigurationByID
FindWorkspaceByID = findWorkspaceByID
FindLicensedWorkspaceByID = findLicensedWorkspaceByID
FindRoleAssociationsByTwoPartKey = findRoleAssociationsByTwoPartKey
FindSAMLConfigurationByID = findSAMLConfigurationByID
FindWorkspaceByID = findWorkspaceByID
FindWorkspaceServiceAccountByTwoPartKey = findWorkspaceServiceAccountByTwoPartKey
FindWorkspaceServiceAccountTokenByThreePartKey = findWorkspaceServiceAccountTokenByThreePartKey
)
11 changes: 10 additions & 1 deletion internal/service/grafana/service_package_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

259 changes: 259 additions & 0 deletions internal/service/grafana/workspace_service_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package grafana

import (
"context"
"fmt"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/grafana"
awstypes "github.com/aws/aws-sdk-go-v2/service/grafana/types"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
"github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @FrameworkResource("aws_grafana_workspace_service_account", name="Workspace Service Account")
func newWorkspaceServiceAccountResource(_ context.Context) (resource.ResourceWithConfigure, error) {
return &workspaceServiceAccountResource{}, nil
}

type workspaceServiceAccountResource struct {
framework.ResourceWithConfigure
framework.WithNoUpdate
framework.WithImportByID
}

func (*workspaceServiceAccountResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = "aws_grafana_workspace_service_account"
}

func (r *workspaceServiceAccountResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"grafana_role": schema.StringAttribute{
CustomType: fwtypes.StringEnumType[awstypes.Role](),
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
names.AttrID: framework.IDAttribute(),
names.AttrName: schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.LengthAtMost(128),
},
},
"service_account_id": framework.IDAttribute(),
"workspace_id": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
}
}

func (r *workspaceServiceAccountResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var data workspaceServiceAccountResourceModel
response.Diagnostics.Append(request.Plan.Get(ctx, &data)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().GrafanaClient(ctx)

name := data.Name.ValueString()
input := &grafana.CreateWorkspaceServiceAccountInput{}
response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...)
if response.Diagnostics.HasError() {
return
}

output, err := conn.CreateWorkspaceServiceAccount(ctx, input)

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("creating Grafana Workspace Service Account (%s)", name), err.Error())

return
}

// Set values for unknowns.
data.ServiceAccountID = fwflex.StringToFramework(ctx, output.Id)
data.setID()

response.Diagnostics.Append(response.State.Set(ctx, &data)...)
}

func (r *workspaceServiceAccountResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
var data workspaceServiceAccountResourceModel
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
if response.Diagnostics.HasError() {
return
}

if err := data.InitFromID(); err != nil {
response.Diagnostics.AddError("parsing resource ID", err.Error())

return
}

conn := r.Meta().GrafanaClient(ctx)

output, err := findWorkspaceServiceAccountByTwoPartKey(ctx, conn, data.WorkspaceID.ValueString(), data.ServiceAccountID.ValueString())

if tfresource.NotFound(err) {
response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err))
response.State.RemoveResource(ctx)

return
}

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("reading Grafana Workspace Service Account (%s)", data.ID.ValueString()), err.Error())

return
}

// Set attributes for import.
response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...)
if response.Diagnostics.HasError() {
return
}

// Restore resource ID.
// It has been overwritten by the 'Id' field from the API response.
data.setID()

// Role is returned from the API in lowercase.
data.GrafanaRole = fwtypes.StringEnumValueToUpper(output.GrafanaRole)

response.Diagnostics.Append(response.State.Set(ctx, &data)...)
}

func (r *workspaceServiceAccountResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
var data workspaceServiceAccountResourceModel
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().GrafanaClient(ctx)

input := &grafana.DeleteWorkspaceServiceAccountInput{}
response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...)
if response.Diagnostics.HasError() {
return
}
_, err := conn.DeleteWorkspaceServiceAccount(ctx, input)

if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return
}

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("deleting Grafana Workspace Service Account (%s)", data.ID.ValueString()), err.Error())

return
}
}

func findWorkspaceServiceAccount(ctx context.Context, conn *grafana.Client, input *grafana.ListWorkspaceServiceAccountsInput, filter tfslices.Predicate[*awstypes.ServiceAccountSummary]) (*awstypes.ServiceAccountSummary, error) {
output, err := findWorkspaceServiceAccounts(ctx, conn, input, filter)

if err != nil {
return nil, err
}

return tfresource.AssertSingleValueResult(output)
}

func findWorkspaceServiceAccounts(ctx context.Context, conn *grafana.Client, input *grafana.ListWorkspaceServiceAccountsInput, filter tfslices.Predicate[*awstypes.ServiceAccountSummary]) ([]awstypes.ServiceAccountSummary, error) {
var output []awstypes.ServiceAccountSummary

pages := grafana.NewListWorkspaceServiceAccountsPaginator(conn, input)
for pages.HasMorePages() {
page, err := pages.NextPage(ctx)

if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

for _, v := range page.ServiceAccounts {
if filter(&v) {
output = append(output, v)
}
}
}

return output, nil
}

func findWorkspaceServiceAccountByTwoPartKey(ctx context.Context, conn *grafana.Client, workspaceID, serviceAccountID string) (*awstypes.ServiceAccountSummary, error) {
input := &grafana.ListWorkspaceServiceAccountsInput{
WorkspaceId: aws.String(workspaceID),
}

return findWorkspaceServiceAccount(ctx, conn, input, func(v *awstypes.ServiceAccountSummary) bool {
return aws.ToString(v.Id) == serviceAccountID
})
}

type workspaceServiceAccountResourceModel struct {
GrafanaRole fwtypes.StringEnum[awstypes.Role] `tfsdk:"grafana_role"`
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
ServiceAccountID types.String `tfsdk:"service_account_id"`
WorkspaceID types.String `tfsdk:"workspace_id"`
}

const (
workspaceServiceAccountResourceIDPartCount = 2
)

func (data *workspaceServiceAccountResourceModel) InitFromID() error {
id := data.ID.ValueString()
parts, err := flex.ExpandResourceId(id, workspaceServiceAccountResourceIDPartCount, false)

if err != nil {
return err
}

data.WorkspaceID = types.StringValue(parts[0])
data.ServiceAccountID = types.StringValue(parts[1])

return nil
}

func (data *workspaceServiceAccountResourceModel) setID() {
data.ID = types.StringValue(errs.Must(flex.FlattenResourceId([]string{data.WorkspaceID.ValueString(), data.ServiceAccountID.ValueString()}, workspaceServiceAccountResourceIDPartCount, false)))
}
Loading
Loading