From 715bfff7a70260c5522391522cff4923549061b0 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Sat, 22 Jun 2024 19:21:24 +0200 Subject: [PATCH 01/30] Setup workspace_service_account resource --- go.mod | 1 + go.sum | 2 + internal/conns/awsclient_gen.go | 5 + .../grafana/service_endpoint_resolver_gen.go | 70 ++++ .../grafana/service_endpoints_gen_test.go | 169 +++++++-- .../service/grafana/service_package_gen.go | 22 +- .../grafana/worksapce_service_account.go | 99 ++++++ .../grafana/worksapce_service_account_test.go | 327 ++++++++++++++++++ names/data/names_data.hcl | 2 +- 9 files changed, 673 insertions(+), 24 deletions(-) create mode 100644 internal/service/grafana/worksapce_service_account.go create mode 100644 internal/service/grafana/worksapce_service_account_test.go diff --git a/go.mod b/go.mod index 14b30cabb56c..e517e0674583 100644 --- a/go.mod +++ b/go.mod @@ -103,6 +103,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/fms v1.34.1 github.com/aws/aws-sdk-go-v2/service/glacier v1.23.1 github.com/aws/aws-sdk-go-v2/service/globalaccelerator v1.25.1 + github.com/aws/aws-sdk-go-v2/service/grafana v1.23.1 github.com/aws/aws-sdk-go-v2/service/groundstation v1.28.1 github.com/aws/aws-sdk-go-v2/service/guardduty v1.44.1 github.com/aws/aws-sdk-go-v2/service/healthlake v1.25.1 diff --git a/go.sum b/go.sum index 981ec8fbe8af..a3223cfb56e8 100644 --- a/go.sum +++ b/go.sum @@ -226,6 +226,8 @@ github.com/aws/aws-sdk-go-v2/service/glacier v1.23.1 h1:nUOHgAUOnQth5+fMWgCQiOEq github.com/aws/aws-sdk-go-v2/service/glacier v1.23.1/go.mod h1:29ythAZEVnLPIOCpzENgCzvZLoAHt8S8BHryPpm8EyI= github.com/aws/aws-sdk-go-v2/service/globalaccelerator v1.25.1 h1:7C51ZbOLStv+VJwdiKGzJeKK0LPX6+XTJv199Q9xe0U= github.com/aws/aws-sdk-go-v2/service/globalaccelerator v1.25.1/go.mod h1:VSQN6t3mMZfL/CEC2sg+MaSDv7A9sTFFhIB82k7zexA= +github.com/aws/aws-sdk-go-v2/service/grafana v1.23.1 h1:J1nNR2/admfa8nDN178GQM3nIv0rrpZylP6/N/llWAg= +github.com/aws/aws-sdk-go-v2/service/grafana v1.23.1/go.mod h1:6/0Jo9jruzMV1rlBnvQZxxLECA50Roq9EaASk6eGpOw= github.com/aws/aws-sdk-go-v2/service/groundstation v1.28.1 h1:XTtOVIG/v1yaff76zGqNLB9LDW/+hY7dQTuwvh4y5Qo= github.com/aws/aws-sdk-go-v2/service/groundstation v1.28.1/go.mod h1:GF6S/WXbeYIIdbsIAKsYdEvndnn8wksiolvsk27TaBo= github.com/aws/aws-sdk-go-v2/service/guardduty v1.44.1 h1:0LOU9mO7AI2rjuuO9p8iymDR3jhY91d+P7XEjJkaa7w= diff --git a/internal/conns/awsclient_gen.go b/internal/conns/awsclient_gen.go index c7a3894ffd22..aa9dde5c1a3e 100644 --- a/internal/conns/awsclient_gen.go +++ b/internal/conns/awsclient_gen.go @@ -95,6 +95,7 @@ import ( fms_sdkv2 "github.com/aws/aws-sdk-go-v2/service/fms" glacier_sdkv2 "github.com/aws/aws-sdk-go-v2/service/glacier" globalaccelerator_sdkv2 "github.com/aws/aws-sdk-go-v2/service/globalaccelerator" + grafana_sdkv2 "github.com/aws/aws-sdk-go-v2/service/grafana" groundstation_sdkv2 "github.com/aws/aws-sdk-go-v2/service/groundstation" guardduty_sdkv2 "github.com/aws/aws-sdk-go-v2/service/guardduty" healthlake_sdkv2 "github.com/aws/aws-sdk-go-v2/service/healthlake" @@ -716,6 +717,10 @@ func (c *AWSClient) GrafanaConn(ctx context.Context) *managedgrafana_sdkv1.Manag return errs.Must(conn[*managedgrafana_sdkv1.ManagedGrafana](ctx, c, names.Grafana, make(map[string]any))) } +func (c *AWSClient) GrafanaClient(ctx context.Context) *grafana_sdkv2.Client { + return errs.Must(client[*grafana_sdkv2.Client](ctx, c, names.Grafana, make(map[string]any))) +} + func (c *AWSClient) GreengrassConn(ctx context.Context) *greengrass_sdkv1.Greengrass { return errs.Must(conn[*greengrass_sdkv1.Greengrass](ctx, c, names.Greengrass, make(map[string]any))) } diff --git a/internal/service/grafana/service_endpoint_resolver_gen.go b/internal/service/grafana/service_endpoint_resolver_gen.go index 2a0ee666576c..16bb7c5bc399 100644 --- a/internal/service/grafana/service_endpoint_resolver_gen.go +++ b/internal/service/grafana/service_endpoint_resolver_gen.go @@ -8,7 +8,10 @@ import ( "net" "net/url" + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + grafana_sdkv2 "github.com/aws/aws-sdk-go-v2/service/grafana" endpoints_sdkv1 "github.com/aws/aws-sdk-go/aws/endpoints" + smithyendpoints "github.com/aws/smithy-go/endpoints" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-aws/internal/errs" ) @@ -74,3 +77,70 @@ func (r resolverSDKv1) EndpointFor(service, region string, opts ...func(*endpoin return defaultResolver.EndpointFor(service, region, opts...) } + +var _ grafana_sdkv2.EndpointResolverV2 = resolverSDKv2{} + +type resolverSDKv2 struct { + defaultResolver grafana_sdkv2.EndpointResolverV2 +} + +func newEndpointResolverSDKv2() resolverSDKv2 { + return resolverSDKv2{ + defaultResolver: grafana_sdkv2.NewDefaultEndpointResolverV2(), + } +} + +func (r resolverSDKv2) ResolveEndpoint(ctx context.Context, params grafana_sdkv2.EndpointParameters) (endpoint smithyendpoints.Endpoint, err error) { + params = params.WithDefaults() + useFIPS := aws_sdkv2.ToBool(params.UseFIPS) + + if eps := params.Endpoint; aws_sdkv2.ToString(eps) != "" { + tflog.Debug(ctx, "setting endpoint", map[string]any{ + "tf_aws.endpoint": endpoint, + }) + + if useFIPS { + tflog.Debug(ctx, "endpoint set, ignoring UseFIPSEndpoint setting") + params.UseFIPS = aws_sdkv2.Bool(false) + } + + return r.defaultResolver.ResolveEndpoint(ctx, params) + } else if useFIPS { + ctx = tflog.SetField(ctx, "tf_aws.use_fips", useFIPS) + + endpoint, err = r.defaultResolver.ResolveEndpoint(ctx, params) + if err != nil { + return endpoint, err + } + + tflog.Debug(ctx, "endpoint resolved", map[string]any{ + "tf_aws.endpoint": endpoint.URI.String(), + }) + + hostname := endpoint.URI.Hostname() + _, err = net.LookupHost(hostname) + if err != nil { + if dnsErr, ok := errs.As[*net.DNSError](err); ok && dnsErr.IsNotFound { + tflog.Debug(ctx, "default endpoint host not found, disabling FIPS", map[string]any{ + "tf_aws.hostname": hostname, + }) + params.UseFIPS = aws_sdkv2.Bool(false) + } else { + err = fmt.Errorf("looking up grafana endpoint %q: %s", hostname, err) + return + } + } else { + return endpoint, err + } + } + + return r.defaultResolver.ResolveEndpoint(ctx, params) +} + +func withBaseEndpoint(endpoint string) func(*grafana_sdkv2.Options) { + return func(o *grafana_sdkv2.Options) { + if endpoint != "" { + o.BaseEndpoint = aws_sdkv2.String(endpoint) + } + } +} diff --git a/internal/service/grafana/service_endpoints_gen_test.go b/internal/service/grafana/service_endpoints_gen_test.go index 5412815afe93..ef91f72b743b 100644 --- a/internal/service/grafana/service_endpoints_gen_test.go +++ b/internal/service/grafana/service_endpoints_gen_test.go @@ -4,18 +4,24 @@ package grafana_test import ( "context" + "errors" "fmt" "maps" "net" "net/url" "os" "path/filepath" + "reflect" "strings" "testing" + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" + grafana_sdkv2 "github.com/aws/aws-sdk-go-v2/service/grafana" aws_sdkv1 "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/endpoints" managedgrafana_sdkv1 "github.com/aws/aws-sdk-go/service/managedgrafana" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/google/go-cmp/cmp" "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" "github.com/hashicorp/go-cty/cty" @@ -343,52 +349,88 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S }, } - for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv - testcase := testcase + t.Run("v1", func(t *testing.T) { + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase - t.Run(name, func(t *testing.T) { - testEndpointCase(t, providerRegion, testcase, callService) - }) - } + t.Run(name, func(t *testing.T) { + testEndpointCase(t, providerRegion, testcase, callServiceV1) + }) + } + }) + + t.Run("v2", func(t *testing.T) { + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, providerRegion, testcase, callServiceV2) + }) + } + }) } func defaultEndpoint(region string) (url.URL, error) { - r := endpoints.DefaultResolver() + r := grafana_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.EndpointFor(managedgrafana_sdkv1.EndpointsID, region) + ep, err := r.ResolveEndpoint(context.Background(), grafana_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) if err != nil { return url.URL{}, err } - url, _ := url.Parse(ep.URL) - - if url.Path == "" { - url.Path = "/" + if ep.URI.Path == "" { + ep.URI.Path = "/" } - return *url, nil + return ep.URI, nil } func defaultFIPSEndpoint(region string) (url.URL, error) { - r := endpoints.DefaultResolver() + r := grafana_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.EndpointFor(managedgrafana_sdkv1.EndpointsID, region, func(opt *endpoints.Options) { - opt.UseFIPSEndpoint = endpoints.FIPSEndpointStateEnabled + ep, err := r.ResolveEndpoint(context.Background(), grafana_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + UseFIPS: aws_sdkv2.Bool(true), }) if err != nil { return url.URL{}, err } - url, _ := url.Parse(ep.URL) + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI, nil +} - if url.Path == "" { - url.Path = "/" +func callServiceV2(ctx context.Context, t *testing.T, meta *conns.AWSClient) apiCallParams { + t.Helper() + + client := meta.GrafanaClient(ctx) + + var result apiCallParams + + _, err := client.ListWorkspaces(ctx, &grafana_sdkv2.ListWorkspacesInput{}, + func(opts *grafana_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &result.endpoint), + addRetrieveRegionMiddleware(&result.region), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) } - return *url, nil + return result } -func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) apiCallParams { +func callServiceV1(ctx context.Context, t *testing.T, meta *conns.AWSClient) apiCallParams { t.Helper() client := meta.GrafanaConn(ctx) @@ -623,6 +665,89 @@ func testEndpointCase(t *testing.T, region string, testcase endpointTestCase, ca } } +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +func addRetrieveRegionMiddleware(region *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Serialize.Add( + retrieveRegionMiddleware(region), + middleware.After, + ) + } +} + +func retrieveRegionMiddleware(region *string) middleware.SerializeMiddleware { + return middleware.SerializeMiddlewareFunc( + "Test: Retrieve Region", + func(ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler) (middleware.SerializeOutput, middleware.Metadata, error) { + *region = awsmiddleware.GetRegion(ctx) + + return next.HandleSerialize(ctx, in) + }, + ) +} + +var errCancelOperation = fmt.Errorf("Test: Canceling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + func generateSharedConfigFile(config configFile) string { var buf strings.Builder diff --git a/internal/service/grafana/service_package_gen.go b/internal/service/grafana/service_package_gen.go index 4df97d2ff10c..71461fae9039 100644 --- a/internal/service/grafana/service_package_gen.go +++ b/internal/service/grafana/service_package_gen.go @@ -5,6 +5,8 @@ package grafana import ( "context" + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + grafana_sdkv2 "github.com/aws/aws-sdk-go-v2/service/grafana" aws_sdkv1 "github.com/aws/aws-sdk-go/aws" session_sdkv1 "github.com/aws/aws-sdk-go/aws/session" managedgrafana_sdkv1 "github.com/aws/aws-sdk-go/service/managedgrafana" @@ -21,7 +23,15 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} + return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceWorkspaceServiceAccount, + Name: "ServiceAccount", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "Id", + }, + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { @@ -84,6 +94,16 @@ func (p *servicePackage) NewConn(ctx context.Context, config map[string]any) (*m return managedgrafana_sdkv1.New(sess.Copy(&cfg)), nil } +// NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. +func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*grafana_sdkv2.Client, error) { + cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) + + return grafana_sdkv2.NewFromConfig(cfg, + grafana_sdkv2.WithEndpointResolverV2(newEndpointResolverSDKv2()), + withBaseEndpoint(config[names.AttrEndpoint].(string)), + ), nil +} + func ServicePackage(ctx context.Context) conns.ServicePackage { return &servicePackage{} } diff --git a/internal/service/grafana/worksapce_service_account.go b/internal/service/grafana/worksapce_service_account.go new file mode 100644 index 000000000000..1fc4b51137c0 --- /dev/null +++ b/internal/service/grafana/worksapce_service_account.go @@ -0,0 +1,99 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package grafana + +import ( + "context" + "go/types" + // "fmt" + // "log" + // "strings" + + // "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-timeouts/resource/timeouts" + "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-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_grafana_workspace_service_account", name="ServiceAccount") +// @Tags(identifierAttribute="Id") +// @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/grafana/types;types.ServiceAccountSummary") +func newResourceWorkspaceServiceAccount(_ context.Context) (resource.ResourceWithConfigure, error) { + return &resourceWorkspaceServiceAccount{}, nil +} + +type resourceWorkspaceServiceAccount struct { + framework.ResourceWithConfigure + framework.WithImportByID +} + +func (r *resourceWorkspaceServiceAccount) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_grafana_workspace_service_account" +} + +func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrID: framework.IDAttribute(), + names.AttrRoleARN: schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "service_account_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "service_account_role": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "workspace_id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *resourceWorkspaceServiceAccount) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().GrafanaClient() + +} + +func (r *resourceWorkspaceServiceAccount) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +} + +func (r *resourceWorkspaceServiceAccount) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +} + +func (r *resourceWorkspaceServiceAccount) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type resourceWorkspaceServiceAccountModel struct { + Alias types.String `tfsdk:"alias"` + ARN types.String `tfsdk:"arn"` + Destination fwtypes.ListNestedObjectValueOf[scraperDestinationModel] `tfsdk:"destination"` + ID types.String `tfsdk:"id"` + RoleARN types.String `tfsdk:"role_arn"` + ScrapeConfiguration types.String `tfsdk:"scrape_configuration"` + Source fwtypes.ListNestedObjectValueOf[scraperSourceModel] `tfsdk:"source"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} diff --git a/internal/service/grafana/worksapce_service_account_test.go b/internal/service/grafana/worksapce_service_account_test.go new file mode 100644 index 000000000000..df97202f6232 --- /dev/null +++ b/internal/service/grafana/worksapce_service_account_test.go @@ -0,0 +1,327 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package grafana_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/amp/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfamp "github.com/hashicorp/terraform-provider-aws/internal/service/amp" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccWorkspaceServiceAccount_basic(t *testing.T) { + ctx := acctest.Context(t) + var v types.WorkspaceDescription + resourceName := "aws_grafana_workspace_service_account.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWorkspaceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceConfig_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, names.AttrAlias, ""), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyARN, ""), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct0), + resource.TestCheckResourceAttrSet(resourceName, "prometheus_endpoint"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAMPWorkspace_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v types.WorkspaceDescription + resourceName := "aws_prometheus_workspace.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWorkspaceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceConfig_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfamp.ResourceWorkspace(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAMPWorkspace_kms(t *testing.T) { + ctx := acctest.Context(t) + var v types.WorkspaceDescription + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_prometheus_workspace.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWorkspaceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceConfig_kms(rName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, names.AttrKMSKeyARN), + ), + }, + }, + }) +} + +func TestAccAMPWorkspace_alias(t *testing.T) { + ctx := acctest.Context(t) + var v1, v2, v3, v4 types.WorkspaceDescription + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_prometheus_workspace.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWorkspaceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceConfig_alias(rName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, names.AttrAlias, rName1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccWorkspaceConfig_alias(rName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v2), + testAccCheckWorkspaceNotRecreated(&v1, &v2), + resource.TestCheckResourceAttr(resourceName, names.AttrAlias, rName2), + ), + }, + { + Config: testAccWorkspaceConfig_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v3), + testAccCheckWorkspaceRecreated(&v2, &v3), + resource.TestCheckResourceAttr(resourceName, names.AttrAlias, ""), + ), + }, + { + Config: testAccWorkspaceConfig_alias(rName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v4), + testAccCheckWorkspaceNotRecreated(&v3, &v4), + resource.TestCheckResourceAttr(resourceName, names.AttrAlias, rName1), + ), + }, + }, + }) +} + +func TestAccAMPWorkspace_loggingConfiguration(t *testing.T) { + ctx := acctest.Context(t) + var v types.WorkspaceDescription + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_prometheus_workspace.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWorkspaceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceConfig_loggingConfiguration(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttrSet(resourceName, "logging_configuration.0.log_group_arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccWorkspaceConfig_loggingConfiguration(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttrSet(resourceName, "logging_configuration.0.log_group_arn"), + ), + }, + { + Config: testAccWorkspaceConfig_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct0), + ), + }, + { + Config: testAccWorkspaceConfig_loggingConfiguration(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttrSet(resourceName, "logging_configuration.0.log_group_arn"), + ), + }, + }, + }) +} + +func testAccCheckWorkspaceExists(ctx context.Context, n string, v *types.WorkspaceDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).AMPClient(ctx) + + output, err := tfamp.FindWorkspaceByID(ctx, conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckWorkspaceDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).AMPClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_prometheus_workspace" { + continue + } + + _, err := tfamp.FindWorkspaceByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Prometheus Workspace %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckWorkspaceRecreated(i, j *types.WorkspaceDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before, after := aws.ToString(i.WorkspaceId), aws.ToString(j.WorkspaceId); before == after { + return fmt.Errorf("Prometheus Workspace (%s) not recreated", before) + } + + return nil + } +} + +func testAccCheckWorkspaceNotRecreated(i, j *types.WorkspaceDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before, after := aws.ToString(i.WorkspaceId), aws.ToString(j.WorkspaceId); before != after { + return fmt.Errorf("Prometheus Workspace (%s) recreated", before) + } + + return nil + } +} + +func testAccWorkspaceConfig_basic() string { + return ` +resource "aws_prometheus_workspace" "test" {} +` +} + +func testAccWorkspaceConfig_alias(rName string) string { + return fmt.Sprintf(` +resource "aws_prometheus_workspace" "test" { + alias = %[1]q +} +`, rName) +} + +func testAccWorkspaceConfig_loggingConfiguration(rName string, idx int) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_log_group" "test" { + count = 2 + + name = "%[1]s-${count.index}" +} + +resource "aws_prometheus_workspace" "test" { + logging_configuration { + log_group_arn = "${aws_cloudwatch_log_group.test[%[2]d].arn}:*" + } +} +`, rName, idx) +} + +func testAccWorkspaceConfig_kms(rName string) string { + return fmt.Sprintf(` +resource "aws_prometheus_workspace" "test" { + alias = %[1]q + kms_key_arn = aws_kms_key.test.arn +} + +resource "aws_kms_key" "test" { + description = "Test" + deletion_window_in_days = 7 +} +`, rName) +} diff --git a/names/data/names_data.hcl b/names/data/names_data.hcl index 9979ce2b085e..c5645e244f5e 100644 --- a/names/data/names_data.hcl +++ b/names/data/names_data.hcl @@ -5864,7 +5864,7 @@ service "grafana" { sdk { id = "grafana" - client_version = [1] + client_version = [1, 2] } names { From 3a0299518d43b20e845baf1cac0bd506f293f03b Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Sat, 22 Jun 2024 22:58:46 +0200 Subject: [PATCH 02/30] Implement create, read and delete --- internal/service/grafana/generate.go | 1 + .../service/grafana/service_package_gen.go | 4 +- .../grafana/worksapce_service_account.go | 158 ++++++++--- .../grafana/worksapce_service_account_test.go | 256 ++++-------------- 4 files changed, 188 insertions(+), 231 deletions(-) diff --git a/internal/service/grafana/generate.go b/internal/service/grafana/generate.go index 488ce62ad195..017d429abb28 100644 --- a/internal/service/grafana/generate.go +++ b/internal/service/grafana/generate.go @@ -1,6 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 //go:generate go run ../../generate/tags/main.go -ListTags -ServiceTagsMap -UpdateTags //go:generate go run ../../generate/servicepackage/main.go // ONLY generate directives and package declaration! Do not add anything else to this file. diff --git a/internal/service/grafana/service_package_gen.go b/internal/service/grafana/service_package_gen.go index 71461fae9039..96a8fc38b2de 100644 --- a/internal/service/grafana/service_package_gen.go +++ b/internal/service/grafana/service_package_gen.go @@ -25,10 +25,10 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ { - Factory: newResourceWorkspaceServiceAccount, + Factory: newWorkspaceServiceAccountResource, Name: "ServiceAccount", Tags: &types.ServicePackageResourceTags{ - IdentifierAttribute: "Id", + IdentifierAttribute: names.AttrID, }, }, } diff --git a/internal/service/grafana/worksapce_service_account.go b/internal/service/grafana/worksapce_service_account.go index 1fc4b51137c0..092d03f69e81 100644 --- a/internal/service/grafana/worksapce_service_account.go +++ b/internal/service/grafana/worksapce_service_account.go @@ -5,33 +5,38 @@ package grafana import ( "context" - "go/types" - // "fmt" - // "log" - // "strings" + "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-timeouts/resource/timeouts" + "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/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/types" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) // @FrameworkResource("aws_grafana_workspace_service_account", name="ServiceAccount") -// @Tags(identifierAttribute="Id") -// @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/grafana/types;types.ServiceAccountSummary") -func newResourceWorkspaceServiceAccount(_ context.Context) (resource.ResourceWithConfigure, error) { +// @Tags(identifierAttribute="id") +func newWorkspaceServiceAccountResource(_ context.Context) (resource.ResourceWithConfigure, error) { return &resourceWorkspaceServiceAccount{}, nil } +const ( + ResNameServiceAccount = "ServiceAccount" +) + type resourceWorkspaceServiceAccount struct { framework.ResourceWithConfigure + framework.WithNoOpUpdate[workspaceServiceAccountResourceModel] framework.WithImportByID } @@ -43,12 +48,6 @@ func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resour resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ names.AttrID: framework.IDAttribute(), - names.AttrRoleARN: schema.StringAttribute{ - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, "service_account_name": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -72,28 +71,123 @@ func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resour } func (r *resourceWorkspaceServiceAccount) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - conn := r.Meta().GrafanaClient() + var data workspaceServiceAccountResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().GrafanaClient(ctx) + + input := &grafana.CreateWorkspaceServiceAccountInput{} + resp.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) + + if resp.Diagnostics.HasError() { + return + } + + output, err := conn.CreateWorkspaceServiceAccount(ctx, input) + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Grafana, create.ErrActionCreating, ResNameServiceAccount, "", err), + err.Error(), + ) + return + } + data.ID = fwflex.StringToFramework(ctx, output.Id) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *resourceWorkspaceServiceAccount) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { -} + var data workspaceServiceAccountResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().GrafanaClient(ctx) + + output, err := findWorkspaceServiceAccountByID(ctx, conn, data.ID.ValueString(), data.WorkspaceID.ValueString()) + if tfresource.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Grafana, create.ErrActionSetting, ResNameServiceAccount, data.ID.String(), err), + err.Error(), + ) + return + } -func (r *resourceWorkspaceServiceAccount) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *resourceWorkspaceServiceAccount) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data workspaceServiceAccountResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().GrafanaClient(ctx) + + _, err := conn.DeleteWorkspaceServiceAccount(ctx, &grafana.DeleteWorkspaceServiceAccountInput{ + ServiceAccountId: fwflex.StringFromFramework(ctx, data.ID), + WorkspaceId: fwflex.StringFromFramework(ctx, data.WorkspaceID), + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Grafana, create.ErrActionDeleting, ResNameServiceAccount, data.ID.String(), err), + err.Error(), + ) + return + } +} + +func findWorkspaceServiceAccountByID(ctx context.Context, conn *grafana.Client, id, workspaceID string) (*awstypes.ServiceAccountSummary, error) { + + input := &grafana.ListWorkspaceServiceAccountsInput{ + WorkspaceId: aws.String(workspaceID), + } + + output, err := conn.ListWorkspaceServiceAccounts(ctx, input) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + for _, sa := range output.ServiceAccounts { + if aws.ToString(sa.Id) == id { + return &sa, nil + } + } + + return nil, errs.NewErrorWithMessage(fmt.Errorf("service account %s on workspaceId %s not found", id, workspaceID)) } -type resourceWorkspaceServiceAccountModel struct { - Alias types.String `tfsdk:"alias"` - ARN types.String `tfsdk:"arn"` - Destination fwtypes.ListNestedObjectValueOf[scraperDestinationModel] `tfsdk:"destination"` - ID types.String `tfsdk:"id"` - RoleARN types.String `tfsdk:"role_arn"` - ScrapeConfiguration types.String `tfsdk:"scrape_configuration"` - Source fwtypes.ListNestedObjectValueOf[scraperSourceModel] `tfsdk:"source"` - Tags types.Map `tfsdk:"tags"` - TagsAll types.Map `tfsdk:"tags_all"` - Timeouts timeouts.Value `tfsdk:"timeouts"` +type workspaceServiceAccountResourceModel struct { + ID types.String `tfsdk:"id"` + ServiceAccountName types.String `tfsdk:"service_account_name"` + ServiceAccountRole types.String `tfsdk:"service_account_role"` + WorkspaceID types.String `tfsdk:"workspace_id"` } diff --git a/internal/service/grafana/worksapce_service_account_test.go b/internal/service/grafana/worksapce_service_account_test.go index df97202f6232..8b9ea5664216 100644 --- a/internal/service/grafana/worksapce_service_account_test.go +++ b/internal/service/grafana/worksapce_service_account_test.go @@ -8,42 +8,38 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/amp/types" - sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/aws/aws-sdk-go-v2/service/grafana/types" + "github.com/aws/aws-sdk-go-v2/service/grafana" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" - tfamp "github.com/hashicorp/terraform-provider-aws/internal/service/amp" + tfgrafana "github.com/hashicorp/terraform-provider-aws/internal/service/grafana" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccWorkspaceServiceAccount_basic(t *testing.T) { ctx := acctest.Context(t) - var v types.WorkspaceDescription resourceName := "aws_grafana_workspace_service_account.test" + var v types.ServiceAccountSummary resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) }, - ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceDestroy(ctx), + CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccWorkspaceConfig_basic(), + Config: testAccWorkspaceServiceAccountConfig_basic(), Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v), + testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), resource.TestCheckResourceAttr(resourceName, names.AttrAlias, ""), - resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyARN, ""), - resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct0), - resource.TestCheckResourceAttrSet(resourceName, "prometheus_endpoint"), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttrSet(resourceName, "service_account_role"), + resource.TestCheckResourceAttrSet(resourceName, "service_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "workspace_id"), ), }, { @@ -55,176 +51,42 @@ func TestAccWorkspaceServiceAccount_basic(t *testing.T) { }) } -func TestAccAMPWorkspace_disappears(t *testing.T) { - ctx := acctest.Context(t) - var v types.WorkspaceDescription - resourceName := "aws_prometheus_workspace.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) - }, - ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccWorkspaceConfig_basic(), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfamp.ResourceWorkspace(), resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -func TestAccAMPWorkspace_kms(t *testing.T) { - ctx := acctest.Context(t) - var v types.WorkspaceDescription - rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_prometheus_workspace.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) - }, - ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccWorkspaceConfig_kms(rName1), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v), - resource.TestCheckResourceAttrSet(resourceName, names.AttrKMSKeyARN), - ), - }, - }, - }) -} - -func TestAccAMPWorkspace_alias(t *testing.T) { - ctx := acctest.Context(t) - var v1, v2, v3, v4 types.WorkspaceDescription - rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_prometheus_workspace.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) - }, - ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccWorkspaceConfig_alias(rName1), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v1), - resource.TestCheckResourceAttr(resourceName, names.AttrAlias, rName1), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccWorkspaceConfig_alias(rName2), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v2), - testAccCheckWorkspaceNotRecreated(&v1, &v2), - resource.TestCheckResourceAttr(resourceName, names.AttrAlias, rName2), - ), - }, - { - Config: testAccWorkspaceConfig_basic(), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v3), - testAccCheckWorkspaceRecreated(&v2, &v3), - resource.TestCheckResourceAttr(resourceName, names.AttrAlias, ""), - ), - }, - { - Config: testAccWorkspaceConfig_alias(rName1), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v4), - testAccCheckWorkspaceNotRecreated(&v3, &v4), - resource.TestCheckResourceAttr(resourceName, names.AttrAlias, rName1), - ), - }, - }, - }) -} - -func TestAccAMPWorkspace_loggingConfiguration(t *testing.T) { - ctx := acctest.Context(t) - var v types.WorkspaceDescription - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_prometheus_workspace.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccWorkspaceConfig_loggingConfiguration(rName, 0), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct1), - resource.TestCheckResourceAttrSet(resourceName, "logging_configuration.0.log_group_arn"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccWorkspaceConfig_loggingConfiguration(rName, 1), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct1), - resource.TestCheckResourceAttrSet(resourceName, "logging_configuration.0.log_group_arn"), - ), - }, - { - Config: testAccWorkspaceConfig_basic(), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct0), - ), - }, - { - Config: testAccWorkspaceConfig_loggingConfiguration(rName, 0), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", acctest.Ct1), - resource.TestCheckResourceAttrSet(resourceName, "logging_configuration.0.log_group_arn"), - ), - }, - }, - }) -} - -func testAccCheckWorkspaceExists(ctx context.Context, n string, v *types.WorkspaceDescription) resource.TestCheckFunc { +// func TestAccAMPWorkspace_disappears(t *testing.T) { +// ctx := acctest.Context(t) +// var v types.WorkspaceDescription +// resourceName := "aws_prometheus_workspace.test" + +// resource.ParallelTest(t, resource.TestCase{ +// PreCheck: func() { +// acctest.PreCheck(ctx, t) +// acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) +// }, +// ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), +// ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, +// CheckDestroy: testAccCheckWorkspaceDestroy(ctx), +// Steps: []resource.TestStep{ +// { +// Config: testAccWorkspaceConfig_basic(), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckWorkspaceExists(ctx, resourceName, &v), +// acctest.CheckResourceDisappears(ctx, acctest.Provider, tfamp.ResourceWorkspace(), resourceName), +// ), +// ExpectNonEmptyPlan: true, +// }, +// }, +// }) +// } + +func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v *types.WorkspaceDescription) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).AMPClient(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) - output, err := tfamp.FindWorkspaceByID(ctx, conn, rs.Primary.ID) + output, err := tfgrafana.(ctx, conn, rs.Primary.ID) if err != nil { return err @@ -236,7 +98,7 @@ func testAccCheckWorkspaceExists(ctx context.Context, n string, v *types.Workspa } } -func testAccCheckWorkspaceDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).AMPClient(ctx) @@ -262,29 +124,29 @@ func testAccCheckWorkspaceDestroy(ctx context.Context) resource.TestCheckFunc { } } -func testAccCheckWorkspaceRecreated(i, j *types.WorkspaceDescription) resource.TestCheckFunc { - return func(s *terraform.State) error { - if before, after := aws.ToString(i.WorkspaceId), aws.ToString(j.WorkspaceId); before == after { - return fmt.Errorf("Prometheus Workspace (%s) not recreated", before) - } +// func testAccCheckWorkspaceRecreated(i, j *types.WorkspaceDescription) resource.TestCheckFunc { +// return func(s *terraform.State) error { +// if before, after := aws.ToString(i.WorkspaceId), aws.ToString(j.WorkspaceId); before == after { +// return fmt.Errorf("Prometheus Workspace (%s) not recreated", before) +// } - return nil - } -} +// return nil +// } +// } -func testAccCheckWorkspaceNotRecreated(i, j *types.WorkspaceDescription) resource.TestCheckFunc { - return func(s *terraform.State) error { - if before, after := aws.ToString(i.WorkspaceId), aws.ToString(j.WorkspaceId); before != after { - return fmt.Errorf("Prometheus Workspace (%s) recreated", before) - } +// func testAccCheckWorkspaceNotRecreated(i, j *types.WorkspaceDescription) resource.TestCheckFunc { +// return func(s *terraform.State) error { +// if before, after := aws.ToString(i.WorkspaceId), aws.ToString(j.WorkspaceId); before != after { +// return fmt.Errorf("Prometheus Workspace (%s) recreated", before) +// } - return nil - } -} +// return nil +// } +// } -func testAccWorkspaceConfig_basic() string { +func testAccWorkspaceServiceAccountConfig_basic() string { return ` -resource "aws_prometheus_workspace" "test" {} +resource "aws_grafana_workspace_service_account" "test" {} ` } From d1243a5152a44c219b282ae11e04c22545424f93 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Sat, 22 Jun 2024 23:09:25 +0200 Subject: [PATCH 03/30] Rename typo --- ...ccount.go => workspace_service_account.go} | 12 ++-- ...t.go => workspace_service_account_test.go} | 0 ...na_workspace_service_account.html.markdown | 69 +++++++++++++++++++ 3 files changed, 75 insertions(+), 6 deletions(-) rename internal/service/grafana/{worksapce_service_account.go => workspace_service_account.go} (95%) rename internal/service/grafana/{worksapce_service_account_test.go => workspace_service_account_test.go} (100%) create mode 100644 website/docs/r/grafana_workspace_service_account.html.markdown diff --git a/internal/service/grafana/worksapce_service_account.go b/internal/service/grafana/workspace_service_account.go similarity index 95% rename from internal/service/grafana/worksapce_service_account.go rename to internal/service/grafana/workspace_service_account.go index 092d03f69e81..641d3fe95ca8 100644 --- a/internal/service/grafana/worksapce_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -31,12 +31,12 @@ func newWorkspaceServiceAccountResource(_ context.Context) (resource.ResourceWit } const ( - ResNameServiceAccount = "ServiceAccount" + ResNameServiceAccount = "WorkspaceServiceAccount" ) type resourceWorkspaceServiceAccount struct { framework.ResourceWithConfigure - framework.WithNoOpUpdate[workspaceServiceAccountResourceModel] + framework.WithNoOpUpdate[resourceWorkspaceServiceAccountData] framework.WithImportByID } @@ -71,7 +71,7 @@ func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resour } func (r *resourceWorkspaceServiceAccount) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data workspaceServiceAccountResourceModel + var data resourceWorkspaceServiceAccountData resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -102,7 +102,7 @@ func (r *resourceWorkspaceServiceAccount) Create(ctx context.Context, req resour } func (r *resourceWorkspaceServiceAccount) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data workspaceServiceAccountResourceModel + var data resourceWorkspaceServiceAccountData resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -134,7 +134,7 @@ func (r *resourceWorkspaceServiceAccount) Read(ctx context.Context, req resource } func (r *resourceWorkspaceServiceAccount) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data workspaceServiceAccountResourceModel + var data resourceWorkspaceServiceAccountData resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -185,7 +185,7 @@ func findWorkspaceServiceAccountByID(ctx context.Context, conn *grafana.Client, return nil, errs.NewErrorWithMessage(fmt.Errorf("service account %s on workspaceId %s not found", id, workspaceID)) } -type workspaceServiceAccountResourceModel struct { +type resourceWorkspaceServiceAccountData struct { ID types.String `tfsdk:"id"` ServiceAccountName types.String `tfsdk:"service_account_name"` ServiceAccountRole types.String `tfsdk:"service_account_role"` diff --git a/internal/service/grafana/worksapce_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go similarity index 100% rename from internal/service/grafana/worksapce_service_account_test.go rename to internal/service/grafana/workspace_service_account_test.go diff --git a/website/docs/r/grafana_workspace_service_account.html.markdown b/website/docs/r/grafana_workspace_service_account.html.markdown new file mode 100644 index 000000000000..bb9abfa3e6b4 --- /dev/null +++ b/website/docs/r/grafana_workspace_service_account.html.markdown @@ -0,0 +1,69 @@ +--- +subcategory: "Managed Grafana" +layout: "aws" +page_title: "AWS: aws_grafana_workspace_service_account" +description: |- + Terraform resource for managing an AWS Managed Grafana Workspace Service Account. +--- +` +# Resource: aws_grafana_workspace_service_account + +Terraform resource for managing an AWS Managed Grafana Workspace Service Account. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_grafana_workspace_service_account" "example" { +} +``` + +## Argument Reference + +The following arguments are required: + +* `example_arg` - (Required) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +The following arguments are optional: + +* `optional_arg` - (Optional) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Workspace Service Account. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `example_attribute` - Concise description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `60m`) +* `update` - (Default `180m`) +* `delete` - (Default `90m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Managed Grafana Workspace Service Account using the `example_id_arg`. For example: + +```terraform +import { + to = aws_grafana_workspace_service_account.example + id = "workspace_service_account-id-12345678" +} +``` + +Using `terraform import`, import Managed Grafana Workspace Service Account using the `example_id_arg`. For example: + +```console +% terraform import aws_grafana_workspace_service_account.example workspace_service_account-id-12345678 +``` From 9de6680f39b4373437ae2b527781d704080d3364 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Sun, 23 Jun 2024 10:28:46 +0200 Subject: [PATCH 04/30] Compile resource --- internal/service/grafana/exports_test.go | 11 ++++ .../service/grafana/service_package_gen.go | 7 +-- .../grafana/workspace_service_account.go | 5 +- .../grafana/workspace_service_account_test.go | 62 +++++++++---------- 4 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 internal/service/grafana/exports_test.go diff --git a/internal/service/grafana/exports_test.go b/internal/service/grafana/exports_test.go new file mode 100644 index 000000000000..95273eaea55f --- /dev/null +++ b/internal/service/grafana/exports_test.go @@ -0,0 +1,11 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package grafana + +// Exports for use in tests only. +var ( + ResourceWorkspaceServiceAccount = newResourceWorkspaceServiceAccount + + FindWorkspaceServiceAccountByID = findWorkspaceServiceAccountByID +) diff --git a/internal/service/grafana/service_package_gen.go b/internal/service/grafana/service_package_gen.go index 96a8fc38b2de..5a49193ac488 100644 --- a/internal/service/grafana/service_package_gen.go +++ b/internal/service/grafana/service_package_gen.go @@ -25,11 +25,8 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ { - Factory: newWorkspaceServiceAccountResource, - Name: "ServiceAccount", - Tags: &types.ServicePackageResourceTags{ - IdentifierAttribute: names.AttrID, - }, + Factory: newResourceWorkspaceServiceAccount, + Name: "WorkspaceServiceAccount", }, } } diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index 641d3fe95ca8..d42aa9505e77 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -24,9 +24,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @FrameworkResource("aws_grafana_workspace_service_account", name="ServiceAccount") -// @Tags(identifierAttribute="id") -func newWorkspaceServiceAccountResource(_ context.Context) (resource.ResourceWithConfigure, error) { +// @FrameworkResource("aws_grafana_workspace_service_account", name="WorkspaceServiceAccount") +func newResourceWorkspaceServiceAccount(_ context.Context) (resource.ResourceWithConfigure, error) { return &resourceWorkspaceServiceAccount{}, nil } diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index 8b9ea5664216..f3b76806373a 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -8,8 +8,8 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go-v2/service/grafana/types" "github.com/aws/aws-sdk-go-v2/service/grafana" + "github.com/aws/aws-sdk-go-v2/service/grafana/types" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -51,33 +51,33 @@ func TestAccWorkspaceServiceAccount_basic(t *testing.T) { }) } -// func TestAccAMPWorkspace_disappears(t *testing.T) { -// ctx := acctest.Context(t) -// var v types.WorkspaceDescription -// resourceName := "aws_prometheus_workspace.test" - -// resource.ParallelTest(t, resource.TestCase{ -// PreCheck: func() { -// acctest.PreCheck(ctx, t) -// acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) -// }, -// ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), -// ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, -// CheckDestroy: testAccCheckWorkspaceDestroy(ctx), -// Steps: []resource.TestStep{ -// { -// Config: testAccWorkspaceConfig_basic(), -// Check: resource.ComposeTestCheckFunc( -// testAccCheckWorkspaceExists(ctx, resourceName, &v), -// acctest.CheckResourceDisappears(ctx, acctest.Provider, tfamp.ResourceWorkspace(), resourceName), -// ), -// ExpectNonEmptyPlan: true, -// }, -// }, -// }) -// } +func TestAccWorkspaceServiceAccount_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v types.ServiceAccountSummary + resourceName := "aws_grafana_workspace_service_account.test" -func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v *types.WorkspaceDescription) resource.TestCheckFunc { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWorkspaceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceServiceAccountConfig_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), + // acctest.CheckResourceDisappears(ctx, acctest.Provider, tfgrafana.ResourceWorkspaceServiceAccount(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v *types.ServiceAccountSummary) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -86,7 +86,7 @@ func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) - output, err := tfgrafana.(ctx, conn, rs.Primary.ID) + output, err := tfgrafana.FindWorkspaceServiceAccountByID(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) if err != nil { return err @@ -100,14 +100,14 @@ func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).AMPClient(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_prometheus_workspace" { + if rs.Type != "aws_grafana_workspace_service_account" { continue } - _, err := tfamp.FindWorkspaceByID(ctx, conn, rs.Primary.ID) + _, err := tfgrafana.FindWorkspaceServiceAccountByID(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) if tfresource.NotFound(err) { continue From a021f109431dc0766bf81695d1ae9cc29830d44b Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Sun, 23 Jun 2024 11:12:51 +0200 Subject: [PATCH 05/30] Add validation for Grafana role --- .../grafana/workspace_service_account.go | 5 ++ .../grafana/workspace_service_account_test.go | 76 +++---------------- 2 files changed, 17 insertions(+), 64 deletions(-) diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index d42aa9505e77..d2522f75d804 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -14,8 +14,10 @@ import ( "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-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" @@ -58,6 +60,9 @@ func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resour PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, + Validators: []validator.String{ + enum.FrameworkValidate[awstypes.Role](), + }, }, "workspace_id": schema.StringAttribute{ Required: true, diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index f3b76806373a..081d740b6849 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/grafana" "github.com/aws/aws-sdk-go-v2/service/grafana/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -22,6 +23,7 @@ import ( func TestAccWorkspaceServiceAccount_basic(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_grafana_workspace_service_account.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) var v types.ServiceAccountSummary resource.ParallelTest(t, resource.TestCase{ @@ -33,10 +35,10 @@ func TestAccWorkspaceServiceAccount_basic(t *testing.T) { CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccWorkspaceServiceAccountConfig_basic(), + Config: testAccWorkspaceServiceAccountConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr(resourceName, names.AttrAlias, ""), + resource.TestCheckResourceAttr(resourceName, names.AttrID, ""), resource.TestCheckResourceAttrSet(resourceName, "service_account_role"), resource.TestCheckResourceAttrSet(resourceName, "service_account_name"), resource.TestCheckResourceAttrSet(resourceName, "workspace_id"), @@ -66,7 +68,7 @@ func TestAccWorkspaceServiceAccount_disappears(t *testing.T) { CheckDestroy: testAccCheckWorkspaceDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccWorkspaceServiceAccountConfig_basic(), + Config: testAccWorkspaceServiceAccountConfig_basic(resourceName), Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), // acctest.CheckResourceDisappears(ctx, acctest.Provider, tfgrafana.ResourceWorkspaceServiceAccount(), resourceName), @@ -124,66 +126,12 @@ func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.Te } } -// func testAccCheckWorkspaceRecreated(i, j *types.WorkspaceDescription) resource.TestCheckFunc { -// return func(s *terraform.State) error { -// if before, after := aws.ToString(i.WorkspaceId), aws.ToString(j.WorkspaceId); before == after { -// return fmt.Errorf("Prometheus Workspace (%s) not recreated", before) -// } - -// return nil -// } -// } - -// func testAccCheckWorkspaceNotRecreated(i, j *types.WorkspaceDescription) resource.TestCheckFunc { -// return func(s *terraform.State) error { -// if before, after := aws.ToString(i.WorkspaceId), aws.ToString(j.WorkspaceId); before != after { -// return fmt.Errorf("Prometheus Workspace (%s) recreated", before) -// } - -// return nil -// } -// } - -func testAccWorkspaceServiceAccountConfig_basic() string { - return ` -resource "aws_grafana_workspace_service_account" "test" {} -` +func testAccWorkspaceServiceAccountConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccWorkspaceConfig_authenticationProvider(rName, "AWS_SSO"), fmt.Sprintf(` +resource "aws_grafana_workspace_service_account" "this" { + service_account_name = %[1]q + service_account_role = "ADMIN" + workspace_id = aws_grafana_workspace.test.id } - -func testAccWorkspaceConfig_alias(rName string) string { - return fmt.Sprintf(` -resource "aws_prometheus_workspace" "test" { - alias = %[1]q -} -`, rName) -} - -func testAccWorkspaceConfig_loggingConfiguration(rName string, idx int) string { - return fmt.Sprintf(` -resource "aws_cloudwatch_log_group" "test" { - count = 2 - - name = "%[1]s-${count.index}" -} - -resource "aws_prometheus_workspace" "test" { - logging_configuration { - log_group_arn = "${aws_cloudwatch_log_group.test[%[2]d].arn}:*" - } -} -`, rName, idx) -} - -func testAccWorkspaceConfig_kms(rName string) string { - return fmt.Sprintf(` -resource "aws_prometheus_workspace" "test" { - alias = %[1]q - kms_key_arn = aws_kms_key.test.arn -} - -resource "aws_kms_key" "test" { - description = "Test" - deletion_window_in_days = 7 -} -`, rName) +`, rName)) } From bc6cff96b022b9869fc8e595598caeb8222a6a21 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Sun, 23 Jun 2024 12:14:48 +0200 Subject: [PATCH 06/30] Add pagination for list, align schema with grafana api --- .../grafana/workspace_service_account.go | 43 ++++++++----------- .../grafana/workspace_service_account_test.go | 42 +++++++++++++++--- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index d2522f75d804..4d7f0cf4cdc5 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -49,13 +49,13 @@ func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resour resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ names.AttrID: framework.IDAttribute(), - "service_account_name": schema.StringAttribute{ + names.AttrName: schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, - "service_account_role": schema.StringAttribute{ + "grafana_role": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), @@ -76,7 +76,6 @@ func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resour func (r *resourceWorkspaceServiceAccount) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data resourceWorkspaceServiceAccountData - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -84,15 +83,13 @@ func (r *resourceWorkspaceServiceAccount) Create(ctx context.Context, req resour conn := r.Meta().GrafanaClient(ctx) - input := &grafana.CreateWorkspaceServiceAccountInput{} - resp.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) - - if resp.Diagnostics.HasError() { - return + input := &grafana.CreateWorkspaceServiceAccountInput{ + Name: aws.String(data.ServiceAccountName.ValueString()), + GrafanaRole: awstypes.Role(data.ServiceAccountRole.ValueString()), + WorkspaceId: aws.String(data.WorkspaceID.ValueString()), } output, err := conn.CreateWorkspaceServiceAccount(ctx, input) - if err != nil { resp.Diagnostics.AddError( create.ProblemStandardMessage(names.Grafana, create.ErrActionCreating, ResNameServiceAccount, "", err), @@ -165,25 +162,23 @@ func (r *resourceWorkspaceServiceAccount) Delete(ctx context.Context, req resour } func findWorkspaceServiceAccountByID(ctx context.Context, conn *grafana.Client, id, workspaceID string) (*awstypes.ServiceAccountSummary, error) { - input := &grafana.ListWorkspaceServiceAccountsInput{ WorkspaceId: aws.String(workspaceID), } + paginator := grafana.NewListWorkspaceServiceAccountsPaginator(conn, input) - output, err := conn.ListWorkspaceServiceAccounts(ctx, input) - - if err != nil { - return nil, err - } - - if output == nil { - return nil, tfresource.NewEmptyResultError(input) - } + for paginator.HasMorePages() { + page, err := paginator.NextPage(ctx) + if err != nil { + return nil, err + } - for _, sa := range output.ServiceAccounts { - if aws.ToString(sa.Id) == id { - return &sa, nil + for _, sa := range page.ServiceAccounts { + if aws.ToString(sa.Id) == id { + return &sa, nil + } } + } return nil, errs.NewErrorWithMessage(fmt.Errorf("service account %s on workspaceId %s not found", id, workspaceID)) @@ -191,7 +186,7 @@ func findWorkspaceServiceAccountByID(ctx context.Context, conn *grafana.Client, type resourceWorkspaceServiceAccountData struct { ID types.String `tfsdk:"id"` - ServiceAccountName types.String `tfsdk:"service_account_name"` - ServiceAccountRole types.String `tfsdk:"service_account_role"` + ServiceAccountName types.String `tfsdk:"name"` + ServiceAccountRole types.String `tfsdk:"grafana_role"` WorkspaceID types.String `tfsdk:"workspace_id"` } diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index 081d740b6849..96b171cf0ccf 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -61,9 +61,9 @@ func TestAccWorkspaceServiceAccount_disappears(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.AMPEndpointID) + acctest.PreCheckPartitionHasService(t, names.Grafana) }, - ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckWorkspaceDestroy(ctx), Steps: []resource.TestStep{ @@ -71,7 +71,6 @@ func TestAccWorkspaceServiceAccount_disappears(t *testing.T) { Config: testAccWorkspaceServiceAccountConfig_basic(resourceName), Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), - // acctest.CheckResourceDisappears(ctx, acctest.Provider, tfgrafana.ResourceWorkspaceServiceAccount(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -119,18 +118,47 @@ func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.Te return err } - return fmt.Errorf("Prometheus Workspace %s still exists", rs.Primary.ID) + return fmt.Errorf("Grafana workspace service account %s still exists", rs.Primary.ID) } return nil } } +func testAccWorkspaceServiceAccount_disappears(t *testing.T) { + ctx := acctest.Context(t) + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var serviceAccount types.ServiceAccountSummary + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_prometheus_scraper.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceServiceAccountConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &serviceAccount), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfgrafana.ResourceWorkspaceServiceAccount, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccWorkspaceServiceAccountConfig_basic(rName string) string { return acctest.ConfigCompose(testAccWorkspaceConfig_authenticationProvider(rName, "AWS_SSO"), fmt.Sprintf(` -resource "aws_grafana_workspace_service_account" "this" { - service_account_name = %[1]q - service_account_role = "ADMIN" +resource "aws_grafana_workspace_service_account" "test" { + name = %[1]q + grafana_role = "ADMIN" workspace_id = aws_grafana_workspace.test.id } `, rName)) From 6ba9b54a24c2d8c73b16a9dedd627ecd5670d170 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Tue, 25 Jun 2024 00:04:09 +0200 Subject: [PATCH 07/30] Fix acceptance tests --- .ci/.semgrep-constants.yml | 15 ++++++ internal/service/grafana/exports_test.go | 2 +- .../grafana/workspace_service_account.go | 35 ++++++++++--- .../grafana/workspace_service_account_test.go | 49 ++++++------------- names/attr_constants.csv | 1 + names/attr_consts_gen.go | 1 + names/generate/const_or_quote_gen.go | 1 + 7 files changed, 61 insertions(+), 43 deletions(-) diff --git a/.ci/.semgrep-constants.yml b/.ci/.semgrep-constants.yml index 3cd569366e12..7bf957b1394b 100644 --- a/.ci/.semgrep-constants.yml +++ b/.ci/.semgrep-constants.yml @@ -3435,3 +3435,18 @@ rules: - pattern-not-inside: 'provider.ConflictingEndpointsWarningDiag(...)' severity: ERROR fix: "names.AttrWeight" + + - id: literal-workspace_id-string-constant + languages: [go] + message: Use the constant `names.AttrWorkspaceID` for the string literal "workspace_id" + paths: + include: + - "internal/service/**/*.go" + patterns: + - pattern: '"workspace_id"' + - pattern-not-regex: '"workspace_id":\s+test\w+,' + - pattern-not-inside: 'config.Variables{ ... }' + - pattern-not-inside: 'packageName = ...' + - pattern-not-inside: 'provider.ConflictingEndpointsWarningDiag(...)' + severity: ERROR + fix: "names.AttrWorkspaceID" diff --git a/internal/service/grafana/exports_test.go b/internal/service/grafana/exports_test.go index 95273eaea55f..feb28ff0505e 100644 --- a/internal/service/grafana/exports_test.go +++ b/internal/service/grafana/exports_test.go @@ -7,5 +7,5 @@ package grafana var ( ResourceWorkspaceServiceAccount = newResourceWorkspaceServiceAccount - FindWorkspaceServiceAccountByID = findWorkspaceServiceAccountByID + FindWorkspaceServiceAccount = findWorkspaceServiceAccount ) diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index 4d7f0cf4cdc5..7ab916a730d6 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -10,6 +10,7 @@ import ( "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/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -20,6 +21,9 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/enum" "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/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -84,7 +88,7 @@ func (r *resourceWorkspaceServiceAccount) Create(ctx context.Context, req resour conn := r.Meta().GrafanaClient(ctx) input := &grafana.CreateWorkspaceServiceAccountInput{ - Name: aws.String(data.ServiceAccountName.ValueString()), + Name: aws.String(data.Name.ValueString()), GrafanaRole: awstypes.Role(data.ServiceAccountRole.ValueString()), WorkspaceId: aws.String(data.WorkspaceID.ValueString()), } @@ -111,7 +115,7 @@ func (r *resourceWorkspaceServiceAccount) Read(ctx context.Context, req resource conn := r.Meta().GrafanaClient(ctx) - output, err := findWorkspaceServiceAccountByID(ctx, conn, data.ID.ValueString(), data.WorkspaceID.ValueString()) + output, err := findWorkspaceServiceAccount(ctx, conn, data.ID.ValueString(), data.WorkspaceID.ValueString()) if tfresource.NotFound(err) { resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) resp.State.RemoveResource(ctx) @@ -161,12 +165,30 @@ func (r *resourceWorkspaceServiceAccount) Delete(ctx context.Context, req resour } } -func findWorkspaceServiceAccountByID(ctx context.Context, conn *grafana.Client, id, workspaceID string) (*awstypes.ServiceAccountSummary, error) { +func (r *resourceWorkspaceServiceAccount) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + + const ( + partCount = 3 + ) + parts, err := flex.ExpandResourceId(req.ID, partCount, false) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("importing Workspace Service Account ID (%s)", req.ID), err.Error()) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root(names.AttrID), parts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("grafana_role"), parts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root(names.AttrWorkspaceID), parts[2])...) +} + +func findWorkspaceServiceAccount(ctx context.Context, conn *grafana.Client, id, workspaceID string) (*awstypes.ServiceAccountSummary, error) { + if workspaceID == "" { + return nil, errs.NewErrorWithMessage(fmt.Errorf("workspace_id is required to find the service account")) + } input := &grafana.ListWorkspaceServiceAccountsInput{ WorkspaceId: aws.String(workspaceID), } - paginator := grafana.NewListWorkspaceServiceAccountsPaginator(conn, input) + paginator := grafana.NewListWorkspaceServiceAccountsPaginator(conn, input) for paginator.HasMorePages() { page, err := paginator.NextPage(ctx) if err != nil { @@ -178,15 +200,14 @@ func findWorkspaceServiceAccountByID(ctx context.Context, conn *grafana.Client, return &sa, nil } } - } - return nil, errs.NewErrorWithMessage(fmt.Errorf("service account %s on workspaceId %s not found", id, workspaceID)) + return nil, tfresource.NewEmptyResultError(input) } type resourceWorkspaceServiceAccountData struct { ID types.String `tfsdk:"id"` - ServiceAccountName types.String `tfsdk:"name"` + Name types.String `tfsdk:"name"` ServiceAccountRole types.String `tfsdk:"grafana_role"` WorkspaceID types.String `tfsdk:"workspace_id"` } diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index 96b171cf0ccf..fa46f809b808 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -27,9 +27,7 @@ func TestAccWorkspaceServiceAccount_basic(t *testing.T) { var v types.ServiceAccountSummary resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(ctx, t) - }, + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.Grafana) }, ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), @@ -38,14 +36,15 @@ func TestAccWorkspaceServiceAccount_basic(t *testing.T) { Config: testAccWorkspaceServiceAccountConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr(resourceName, names.AttrID, ""), - resource.TestCheckResourceAttrSet(resourceName, "service_account_role"), - resource.TestCheckResourceAttrSet(resourceName, "service_account_name"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrID), + resource.TestCheckResourceAttrSet(resourceName, "grafana_role"), + resource.TestCheckResourceAttrSet(resourceName, "name"), resource.TestCheckResourceAttrSet(resourceName, "workspace_id"), ), }, { ResourceName: resourceName, + ImportStateIdFunc: testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName), ImportState: true, ImportStateVerify: true, }, @@ -86,9 +85,7 @@ func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v } conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) - - output, err := tfgrafana.FindWorkspaceServiceAccountByID(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) - + output, err := tfgrafana.FindWorkspaceServiceAccount(ctx, conn, rs.Primary.ID, rs.Primary.Attributes[names.AttrWorkspaceID]) if err != nil { return err } @@ -108,7 +105,7 @@ func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.Te continue } - _, err := tfgrafana.FindWorkspaceServiceAccountByID(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) + _, err := tfgrafana.FindWorkspaceServiceAccount(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) if tfresource.NotFound(err) { continue @@ -125,33 +122,15 @@ func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.Te } } -func testAccWorkspaceServiceAccount_disappears(t *testing.T) { - ctx := acctest.Context(t) +func testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("not found: %s", resourceName) + } - if testing.Short() { - t.Skip("skipping long-running test in short mode") + return fmt.Sprintf("%s,%s,%s", rs.Primary.Attributes[names.AttrID], rs.Primary.Attributes["grafana_role"], rs.Primary.Attributes[names.AttrWorkspaceID]), nil } - - var serviceAccount types.ServiceAccountSummary - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_prometheus_scraper.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccWorkspaceServiceAccountConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &serviceAccount), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfgrafana.ResourceWorkspaceServiceAccount, resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) } func testAccWorkspaceServiceAccountConfig_basic(rName string) string { diff --git a/names/attr_constants.csv b/names/attr_constants.csv index 292ca28a602c..36685c7ec39d 100644 --- a/names/attr_constants.csv +++ b/names/attr_constants.csv @@ -227,3 +227,4 @@ vpc_endpoint_id,VPCEndpointID vpc_id,VPCID vpc_security_group_ids,VPCSecurityGroupIDs weight,Weight +workspace_id,WorkspaceID diff --git a/names/attr_consts_gen.go b/names/attr_consts_gen.go index 783e418eb0f4..d1c22ac539b2 100644 --- a/names/attr_consts_gen.go +++ b/names/attr_consts_gen.go @@ -234,4 +234,5 @@ const ( AttrVolumeSize = "volume_size" AttrVolumeType = "volume_type" AttrWeight = "weight" + AttrWorkspaceID = "workspace_id" ) diff --git a/names/generate/const_or_quote_gen.go b/names/generate/const_or_quote_gen.go index 81bf94ad7643..f3a905498ca5 100644 --- a/names/generate/const_or_quote_gen.go +++ b/names/generate/const_or_quote_gen.go @@ -242,6 +242,7 @@ func ConstOrQuote(constant string) string { "volume_size": "AttrVolumeSize", "volume_type": "AttrVolumeType", "weight": "AttrWeight", + "workspace_id": "AttrWorkspaceID", } if v, ok := allConstants[constant]; ok { From d78b3e536005648e2c2dd63c1b2c6f3cc8c5b703 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Tue, 25 Jun 2024 01:17:34 +0200 Subject: [PATCH 08/30] Add doc --- ...na_workspace_service_account.html.markdown | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/website/docs/r/grafana_workspace_service_account.html.markdown b/website/docs/r/grafana_workspace_service_account.html.markdown index bb9abfa3e6b4..927c32d6beea 100644 --- a/website/docs/r/grafana_workspace_service_account.html.markdown +++ b/website/docs/r/grafana_workspace_service_account.html.markdown @@ -3,19 +3,21 @@ subcategory: "Managed Grafana" layout: "aws" page_title: "AWS: aws_grafana_workspace_service_account" description: |- - Terraform resource for managing an AWS Managed Grafana Workspace Service Account. + Terraform resource for managing an Amazon Managed Grafana Workspace Service Account. --- -` + # Resource: aws_grafana_workspace_service_account -Terraform resource for managing an AWS Managed Grafana Workspace Service Account. + +https://docs.aws.amazon.com/grafana/latest/userguide/service-accounts.html + +-> **Note:** You cannot update a service account. If you change any attribute, Terraform +will delete the current and create a new one. + +Provides an Amazon Managed Service for Prometheus fully managed collector +(scraper). + +Read about Service Accounts in the [Amazon Managed Grafana user guide](https://docs.aws.amazon.com/grafana/latest/userguide/service-accounts.html). ## Example Usage @@ -23,6 +25,9 @@ Terraform resource for managing an AWS Managed Grafana Workspace Service Account ```terraform resource "aws_grafana_workspace_service_account" "example" { + name = "example-admin" + grafana_role = "ADMIN" + workspace_id = aws_grafana_workspace.example.id } ``` @@ -30,40 +35,33 @@ resource "aws_grafana_workspace_service_account" "example" { The following arguments are required: -* `example_arg` - (Required) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `name` - (Required) A name for the service account. The name must be unique within the workspace, as it determines the ID associated with the service account. + +* `grafana_role` - (Required) The permission level to use for this service account. For more information about the roles and the permissions each has, see the [User roles](https://docs.aws.amazon.com/grafana/latest/userguide/Grafana-user-roles.html) documentation. -The following arguments are optional: +* `workspace_id` - (Required) The Grafana workspace with which the service account is associated. -* `optional_arg` - (Optional) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. ## Attribute Reference This resource exports the following attributes in addition to the arguments above: -* `arn` - ARN of the Workspace Service Account. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. -* `example_attribute` - Concise description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. - -## Timeouts - -[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): +* `id` - Identifier of the service account in the given Grafana workspace -* `create` - (Default `60m`) -* `update` - (Default `180m`) -* `delete` - (Default `90m`) ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Managed Grafana Workspace Service Account using the `example_id_arg`. For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Managed Grafana Workspace Service Account using the `id`, `grafana_role` and `workspace_id` separated by a comma (`,`). For example: ```terraform import { to = aws_grafana_workspace_service_account.example - id = "workspace_service_account-id-12345678" + id = "1,ADMIN,g-abc12345" } ``` -Using `terraform import`, import Managed Grafana Workspace Service Account using the `example_id_arg`. For example: +Using `terraform import`, import Managed Grafana Workspace Service Account using the `id`, `grafana_role` and `workspace_id` separated by a comma (`,`). For example: ```console -% terraform import aws_grafana_workspace_service_account.example workspace_service_account-id-12345678 +% terraform import aws_grafana_workspace_service_account.example 1,ADMIN,g-abc12345 ``` From a27d8cbd07aed0ff3ac4e5bdb6365bdfa7ad9a25 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Tue, 25 Jun 2024 01:37:09 +0200 Subject: [PATCH 09/30] Lint doc and code --- internal/service/grafana/workspace_service_account_test.go | 6 +++--- .../docs/r/grafana_workspace_service_account.html.markdown | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index fa46f809b808..87b7d21a6ddc 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -136,9 +136,9 @@ func testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName string) r func testAccWorkspaceServiceAccountConfig_basic(rName string) string { return acctest.ConfigCompose(testAccWorkspaceConfig_authenticationProvider(rName, "AWS_SSO"), fmt.Sprintf(` resource "aws_grafana_workspace_service_account" "test" { - name = %[1]q - grafana_role = "ADMIN" - workspace_id = aws_grafana_workspace.test.id + name = %[1]q + grafana_role = "ADMIN" + workspace_id = aws_grafana_workspace.test.id } `, rName)) } diff --git a/website/docs/r/grafana_workspace_service_account.html.markdown b/website/docs/r/grafana_workspace_service_account.html.markdown index 927c32d6beea..22578071df0f 100644 --- a/website/docs/r/grafana_workspace_service_account.html.markdown +++ b/website/docs/r/grafana_workspace_service_account.html.markdown @@ -8,7 +8,6 @@ description: |- # Resource: aws_grafana_workspace_service_account - https://docs.aws.amazon.com/grafana/latest/userguide/service-accounts.html -> **Note:** You cannot update a service account. If you change any attribute, Terraform @@ -25,7 +24,7 @@ Read about Service Accounts in the [Amazon Managed Grafana user guide](https://d ```terraform resource "aws_grafana_workspace_service_account" "example" { - name = "example-admin" + name = "example-admin" grafana_role = "ADMIN" workspace_id = aws_grafana_workspace.example.id } @@ -41,14 +40,12 @@ The following arguments are required: * `workspace_id` - (Required) The Grafana workspace with which the service account is associated. - ## Attribute Reference This resource exports the following attributes in addition to the arguments above: * `id` - Identifier of the service account in the given Grafana workspace - ## Import In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Managed Grafana Workspace Service Account using the `id`, `grafana_role` and `workspace_id` separated by a comma (`,`). For example: From 70e9250c0ba0ca72f49f6fcf50116a2943838837 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Tue, 25 Jun 2024 01:37:09 +0200 Subject: [PATCH 10/30] Lint doc and code --- internal/service/grafana/workspace_service_account_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index 87b7d21a6ddc..8b66bc6beef0 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -71,7 +71,6 @@ func TestAccWorkspaceServiceAccount_disappears(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), ), - ExpectNonEmptyPlan: true, }, }, }) From 6a94b7b1be7e312e8476984467651ff6401fef21 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Tue, 25 Jun 2024 16:36:12 +0200 Subject: [PATCH 11/30] Refactor and rename --- .../grafana/workspace_service_account.go | 2 -- .../grafana/workspace_service_account_test.go | 26 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index 7ab916a730d6..381fc307d93e 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -22,8 +22,6 @@ import ( "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/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index 8b66bc6beef0..a96c5beda72a 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -20,7 +20,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccWorkspaceServiceAccount_basic(t *testing.T) { +func TestAccGrafanaWorkspaceServiceAccount_basic(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_grafana_workspace_service_account.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -30,12 +30,12 @@ func TestAccWorkspaceServiceAccount_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.Grafana) }, ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), + CheckDestroy: testAccCheckGrafanaWorkspaceServiceAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccWorkspaceServiceAccountConfig_basic(rName), + Config: testAccGrafanaWorkspaceServiceAccountConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), + testAccCheckGrafanaWorkspaceServiceAccountExists(ctx, resourceName, &v), resource.TestCheckResourceAttrSet(resourceName, names.AttrID), resource.TestCheckResourceAttrSet(resourceName, "grafana_role"), resource.TestCheckResourceAttrSet(resourceName, "name"), @@ -44,7 +44,7 @@ func TestAccWorkspaceServiceAccount_basic(t *testing.T) { }, { ResourceName: resourceName, - ImportStateIdFunc: testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName), + ImportStateIdFunc: testAccCheckGrafanaWorkspaceServiceAccountImportStateIdFunc(resourceName), ImportState: true, ImportStateVerify: true, }, @@ -52,7 +52,7 @@ func TestAccWorkspaceServiceAccount_basic(t *testing.T) { }) } -func TestAccWorkspaceServiceAccount_disappears(t *testing.T) { +func TestAccGrafanaWorkspaceServiceAccount_disappears(t *testing.T) { ctx := acctest.Context(t) var v types.ServiceAccountSummary resourceName := "aws_grafana_workspace_service_account.test" @@ -67,16 +67,16 @@ func TestAccWorkspaceServiceAccount_disappears(t *testing.T) { CheckDestroy: testAccCheckWorkspaceDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccWorkspaceServiceAccountConfig_basic(resourceName), + Config: testAccGrafanaWorkspaceServiceAccountConfig_basic(resourceName), Check: resource.ComposeTestCheckFunc( - testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), + testAccCheckGrafanaWorkspaceServiceAccountExists(ctx, resourceName, &v), ), }, }, }) } -func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v *types.ServiceAccountSummary) resource.TestCheckFunc { +func testAccCheckGrafanaWorkspaceServiceAccountExists(ctx context.Context, n string, v *types.ServiceAccountSummary) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -95,7 +95,7 @@ func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v } } -func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckGrafanaWorkspaceServiceAccountDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) @@ -121,7 +121,7 @@ func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.Te } } -func testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { +func testAccCheckGrafanaWorkspaceServiceAccountImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { return func(s *terraform.State) (string, error) { rs, ok := s.RootModule().Resources[resourceName] if !ok { @@ -132,10 +132,10 @@ func testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName string) r } } -func testAccWorkspaceServiceAccountConfig_basic(rName string) string { +func testAccGrafanaWorkspaceServiceAccountConfig_basic(rName string) string { return acctest.ConfigCompose(testAccWorkspaceConfig_authenticationProvider(rName, "AWS_SSO"), fmt.Sprintf(` resource "aws_grafana_workspace_service_account" "test" { - name = %[1]q + name = %[1]q grafana_role = "ADMIN" workspace_id = aws_grafana_workspace.test.id } From c09bb4e0c2445887c1c736b3c80be0dfccc4bba7 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Tue, 25 Jun 2024 16:48:16 +0200 Subject: [PATCH 12/30] fixup! Refactor and rename --- .../grafana/workspace_service_account_test.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index a96c5beda72a..c6b5dadabd19 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -30,12 +30,12 @@ func TestAccGrafanaWorkspaceServiceAccount_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.Grafana) }, ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckGrafanaWorkspaceServiceAccountDestroy(ctx), + CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccGrafanaWorkspaceServiceAccountConfig_basic(rName), + Config: testAccWorkspaceServiceAccountConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckGrafanaWorkspaceServiceAccountExists(ctx, resourceName, &v), + testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), resource.TestCheckResourceAttrSet(resourceName, names.AttrID), resource.TestCheckResourceAttrSet(resourceName, "grafana_role"), resource.TestCheckResourceAttrSet(resourceName, "name"), @@ -44,7 +44,7 @@ func TestAccGrafanaWorkspaceServiceAccount_basic(t *testing.T) { }, { ResourceName: resourceName, - ImportStateIdFunc: testAccCheckGrafanaWorkspaceServiceAccountImportStateIdFunc(resourceName), + ImportStateIdFunc: testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName), ImportState: true, ImportStateVerify: true, }, @@ -67,16 +67,16 @@ func TestAccGrafanaWorkspaceServiceAccount_disappears(t *testing.T) { CheckDestroy: testAccCheckWorkspaceDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccGrafanaWorkspaceServiceAccountConfig_basic(resourceName), + Config: testAccWorkspaceServiceAccountConfig_basic(resourceName), Check: resource.ComposeTestCheckFunc( - testAccCheckGrafanaWorkspaceServiceAccountExists(ctx, resourceName, &v), + testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), ), }, }, }) } -func testAccCheckGrafanaWorkspaceServiceAccountExists(ctx context.Context, n string, v *types.ServiceAccountSummary) resource.TestCheckFunc { +func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v *types.ServiceAccountSummary) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -95,7 +95,7 @@ func testAccCheckGrafanaWorkspaceServiceAccountExists(ctx context.Context, n str } } -func testAccCheckGrafanaWorkspaceServiceAccountDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) @@ -121,7 +121,7 @@ func testAccCheckGrafanaWorkspaceServiceAccountDestroy(ctx context.Context) reso } } -func testAccCheckGrafanaWorkspaceServiceAccountImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { +func testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { return func(s *terraform.State) (string, error) { rs, ok := s.RootModule().Resources[resourceName] if !ok { @@ -132,7 +132,7 @@ func testAccCheckGrafanaWorkspaceServiceAccountImportStateIdFunc(resourceName st } } -func testAccGrafanaWorkspaceServiceAccountConfig_basic(rName string) string { +func testAccWorkspaceServiceAccountConfig_basic(rName string) string { return acctest.ConfigCompose(testAccWorkspaceConfig_authenticationProvider(rName, "AWS_SSO"), fmt.Sprintf(` resource "aws_grafana_workspace_service_account" "test" { name = %[1]q From 94f11412b3d351b019550e8aa411fb6efd828fc8 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Wed, 26 Jun 2024 22:58:02 +0200 Subject: [PATCH 13/30] remove unnecessary leading space --- internal/service/grafana/workspace_service_account.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index 381fc307d93e..48ae02dbe76a 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -164,7 +164,6 @@ func (r *resourceWorkspaceServiceAccount) Delete(ctx context.Context, req resour } func (r *resourceWorkspaceServiceAccount) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - const ( partCount = 3 ) From c16045c471a5c07da2ac6a2415685c5b14b44b89 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Thu, 27 Jun 2024 13:06:03 +0200 Subject: [PATCH 14/30] Add schema validators --- internal/service/grafana/workspace_service_account.go | 7 ++++++- internal/service/grafana/workspace_service_account_test.go | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index 48ae02dbe76a..b262c3c3848a 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -10,6 +10,7 @@ import ( "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/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -56,6 +57,10 @@ func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resour PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + stringvalidator.LengthAtMost(128), + }, }, "grafana_role": schema.StringAttribute{ Required: true, @@ -66,7 +71,7 @@ func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resour enum.FrameworkValidate[awstypes.Role](), }, }, - "workspace_id": schema.StringAttribute{ + names.AttrWorkspaceID: schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index c6b5dadabd19..25104441ed8b 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -64,7 +64,7 @@ func TestAccGrafanaWorkspaceServiceAccount_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceDestroy(ctx), + CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccWorkspaceServiceAccountConfig_basic(resourceName), From a72a23a465517f44d648352836608ffc3a694ed3 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Thu, 27 Jun 2024 16:28:41 +0200 Subject: [PATCH 15/30] Optimize tests --- .../grafana/workspace_service_account_test.go | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index 25104441ed8b..f297bd1843ab 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -16,7 +16,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfgrafana "github.com/hashicorp/terraform-provider-aws/internal/service/grafana" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -30,7 +29,7 @@ func TestAccGrafanaWorkspaceServiceAccount_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.Grafana) }, ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccWorkspaceServiceAccountConfig_basic(rName), @@ -64,7 +63,7 @@ func TestAccGrafanaWorkspaceServiceAccount_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccWorkspaceServiceAccountConfig_basic(resourceName), @@ -95,32 +94,6 @@ func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v } } -func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_grafana_workspace_service_account" { - continue - } - - _, err := tfgrafana.FindWorkspaceServiceAccount(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) - - if tfresource.NotFound(err) { - continue - } - - if err != nil { - return err - } - - return fmt.Errorf("Grafana workspace service account %s still exists", rs.Primary.ID) - } - - return nil - } -} - func testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { return func(s *terraform.State) (string, error) { rs, ok := s.RootModule().Resources[resourceName] From 65e029f8260e76e0143f763cf2daf9288a6a71b1 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Thu, 27 Jun 2024 16:29:05 +0200 Subject: [PATCH 16/30] Clean docs --- .../docs/r/grafana_workspace_service_account.html.markdown | 5 ----- 1 file changed, 5 deletions(-) diff --git a/website/docs/r/grafana_workspace_service_account.html.markdown b/website/docs/r/grafana_workspace_service_account.html.markdown index 22578071df0f..c99e39cc3962 100644 --- a/website/docs/r/grafana_workspace_service_account.html.markdown +++ b/website/docs/r/grafana_workspace_service_account.html.markdown @@ -8,14 +8,9 @@ description: |- # Resource: aws_grafana_workspace_service_account -https://docs.aws.amazon.com/grafana/latest/userguide/service-accounts.html - -> **Note:** You cannot update a service account. If you change any attribute, Terraform will delete the current and create a new one. -Provides an Amazon Managed Service for Prometheus fully managed collector -(scraper). - Read about Service Accounts in the [Amazon Managed Grafana user guide](https://docs.aws.amazon.com/grafana/latest/userguide/service-accounts.html). ## Example Usage From 1f51bc6612d48b5e1c63c58ba6198ad30834b201 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Thu, 27 Jun 2024 22:02:46 +0200 Subject: [PATCH 17/30] Update docs --- website/docs/r/grafana_workspace_service_account.html.markdown | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/docs/r/grafana_workspace_service_account.html.markdown b/website/docs/r/grafana_workspace_service_account.html.markdown index c99e39cc3962..fe089752051d 100644 --- a/website/docs/r/grafana_workspace_service_account.html.markdown +++ b/website/docs/r/grafana_workspace_service_account.html.markdown @@ -30,9 +30,7 @@ resource "aws_grafana_workspace_service_account" "example" { The following arguments are required: * `name` - (Required) A name for the service account. The name must be unique within the workspace, as it determines the ID associated with the service account. - * `grafana_role` - (Required) The permission level to use for this service account. For more information about the roles and the permissions each has, see the [User roles](https://docs.aws.amazon.com/grafana/latest/userguide/Grafana-user-roles.html) documentation. - * `workspace_id` - (Required) The Grafana workspace with which the service account is associated. ## Attribute Reference From 3b62fa3ebc67dfb6aeca8ec114276890cf121a5a Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Thu, 27 Jun 2024 22:19:35 +0200 Subject: [PATCH 18/30] Adding service account token As service account by itself is not really useful, I merged locally my branch on service account token. Happy to drop this commit if it needs to be submitted separately commit 6b70e6e59b7dc0bd1ac652ce3038413f65c11e8b Author: Rodrigue Koffi Date: Thu Jun 27 21:55:00 2024 +0200 Add service account token doc commit bd3fad4e84eb75815516cd658dc155ece76f5791 Author: Rodrigue Koffi Date: Thu Jun 27 16:05:28 2024 +0200 Optimize tests paths commit 560148e983736a2e193442d24782e5c755be2fca Author: Rodrigue Koffi Date: Thu Jun 27 12:41:41 2024 +0200 Add acceptance testing commit 39090f6e22252ff979405715a6e0f094f383933b Author: Rodrigue Koffi Date: Thu Jun 27 11:32:00 2024 +0200 Add service account token resource --- internal/service/grafana/exports_test.go | 3 +- .../service/grafana/service_package_gen.go | 4 + .../workspace_service_account_token.go | 235 ++++++++++++++++++ .../workspace_service_account_token_test.go | 127 ++++++++++ ...kspace_service_account_token.html.markdown | 51 ++++ 5 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 internal/service/grafana/workspace_service_account_token.go create mode 100644 internal/service/grafana/workspace_service_account_token_test.go create mode 100644 website/docs/r/grafana_workspace_service_account_token.html.markdown diff --git a/internal/service/grafana/exports_test.go b/internal/service/grafana/exports_test.go index feb28ff0505e..7e905dc3f45f 100644 --- a/internal/service/grafana/exports_test.go +++ b/internal/service/grafana/exports_test.go @@ -7,5 +7,6 @@ package grafana var ( ResourceWorkspaceServiceAccount = newResourceWorkspaceServiceAccount - FindWorkspaceServiceAccount = findWorkspaceServiceAccount + FindWorkspaceServiceAccount = findWorkspaceServiceAccount + FindWorkspaceServiceAccountToken = findWorkspaceServiceAccountToken ) diff --git a/internal/service/grafana/service_package_gen.go b/internal/service/grafana/service_package_gen.go index 5a49193ac488..904364bbfe86 100644 --- a/internal/service/grafana/service_package_gen.go +++ b/internal/service/grafana/service_package_gen.go @@ -28,6 +28,10 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newResourceWorkspaceServiceAccount, Name: "WorkspaceServiceAccount", }, + { + Factory: newResourceWorkspaceServiceAccountToken, + Name: "WorkspaceServiceAccountToken", + }, } } diff --git a/internal/service/grafana/workspace_service_account_token.go b/internal/service/grafana/workspace_service_account_token.go new file mode 100644 index 000000000000..fb624f6c2838 --- /dev/null +++ b/internal/service/grafana/workspace_service_account_token.go @@ -0,0 +1,235 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package grafana + +import ( + "context" + "time" + + "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/int64validator" + "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/int64planmodifier" + "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-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_grafana_workspace_service_account_token", name="WorkspaceServiceAccountToken") +func newResourceWorkspaceServiceAccountToken(_ context.Context) (resource.ResourceWithConfigure, error) { + return &resourceWorkspaceServiceAccountToken{}, nil +} + +const ( + ResNameServiceAccountToken = "WorkspaceServiceAccountToken" +) + +type resourceWorkspaceServiceAccountToken struct { + framework.ResourceWithConfigure + framework.WithNoOpUpdate[resourceWorkspaceServiceAccountTokenData] + framework.WithImportByID +} + +func (r *resourceWorkspaceServiceAccountToken) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_grafana_workspace_service_account_token" +} + +func (r *resourceWorkspaceServiceAccountToken) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrCreatedAt: schema.StringAttribute{ + Computed: true, + }, + names.AttrName: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + stringvalidator.LengthAtMost(128), + }, + }, + "expires_at": schema.StringAttribute{ + Computed: true, + }, + names.AttrID: framework.IDAttribute(), + names.AttrKey: schema.StringAttribute{ + Computed: true, + Sensitive: true, + }, + "seconds_to_live": schema.Int64Attribute{ + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + Validators: []validator.Int64{ + int64validator.Between(1, 2592000), + }, + }, + "service_account_id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + names.AttrWorkspaceID: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *resourceWorkspaceServiceAccountToken) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data resourceWorkspaceServiceAccountTokenData + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().GrafanaClient(ctx) + + input := &grafana.CreateWorkspaceServiceAccountTokenInput{ + Name: aws.String(data.Name.ValueString()), + SecondsToLive: aws.Int32(int32(data.SecondsToLive.ValueInt64())), + ServiceAccountId: aws.String(data.ServiceAccountID.ValueString()), + WorkspaceId: aws.String(data.WorkspaceID.ValueString()), + } + + output, err := conn.CreateWorkspaceServiceAccountToken(ctx, input) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Grafana, create.ErrActionCreating, ResNameServiceAccountToken, "", err), + err.Error(), + ) + return + } + + //update unknowns + saTokenID := aws.ToString(output.ServiceAccountToken.Id) + out, err := findWorkspaceServiceAccountToken(ctx, conn, saTokenID, data.ServiceAccountID.ValueString(), data.WorkspaceID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Grafana, create.ErrActionReading, ResNameServiceAccountToken, "", err), + err.Error(), + ) + return + } + + data.ID = fwflex.StringValueToFramework(ctx, saTokenID) + data.Key = fwflex.StringToFramework(ctx, output.ServiceAccountToken.Key) + data.CreatedAt = fwflex.StringValueToFramework(ctx, out.CreatedAt.Format(time.RFC3339)) + data.ExpiresAt = fwflex.StringValueToFramework(ctx, out.ExpiresAt.Format(time.RFC3339)) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *resourceWorkspaceServiceAccountToken) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data resourceWorkspaceServiceAccountTokenData + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().GrafanaClient(ctx) + + output, err := findWorkspaceServiceAccountToken(ctx, conn, data.ID.ValueString(), data.ServiceAccountID.ValueString(), data.WorkspaceID.ValueString()) + if tfresource.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Grafana, create.ErrActionSetting, ResNameServiceAccount, data.ID.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *resourceWorkspaceServiceAccountToken) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data resourceWorkspaceServiceAccountTokenData + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().GrafanaClient(ctx) + + _, err := conn.DeleteWorkspaceServiceAccountToken(ctx, &grafana.DeleteWorkspaceServiceAccountTokenInput{ + ServiceAccountId: fwflex.StringFromFramework(ctx, data.ServiceAccountID), + TokenId: fwflex.StringFromFramework(ctx, data.ID), + WorkspaceId: fwflex.StringFromFramework(ctx, data.WorkspaceID), + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Grafana, create.ErrActionDeleting, ResNameServiceAccount, data.ID.String(), err), + err.Error(), + ) + return + } +} + +func findWorkspaceServiceAccountToken(ctx context.Context, conn *grafana.Client, id, serviceAccountID, workspaceID string) (*awstypes.ServiceAccountTokenSummary, error) { + input := &grafana.ListWorkspaceServiceAccountTokensInput{ + WorkspaceId: aws.String(workspaceID), + ServiceAccountId: aws.String(serviceAccountID), + } + + paginator := grafana.NewListWorkspaceServiceAccountTokensPaginator(conn, input) + for paginator.HasMorePages() { + page, err := paginator.NextPage(ctx) + if err != nil { + return nil, err + } + + for _, sa := range page.ServiceAccountTokens { + if aws.ToString(sa.Id) == id { + return &sa, nil + } + } + } + + return nil, tfresource.NewEmptyResultError(input) +} + +type resourceWorkspaceServiceAccountTokenData struct { + ID types.String `tfsdk:"id"` + CreatedAt types.String `tfsdk:"created_at"` + ExpiresAt types.String `tfsdk:"expires_at"` + Key types.String `tfsdk:"key"` + Name types.String `tfsdk:"name"` + SecondsToLive types.Int64 `tfsdk:"seconds_to_live"` + ServiceAccountID types.String `tfsdk:"service_account_id"` + WorkspaceID types.String `tfsdk:"workspace_id"` +} diff --git a/internal/service/grafana/workspace_service_account_token_test.go b/internal/service/grafana/workspace_service_account_token_test.go new file mode 100644 index 000000000000..fcf73d8921c0 --- /dev/null +++ b/internal/service/grafana/workspace_service_account_token_test.go @@ -0,0 +1,127 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package grafana_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/grafana" + "github.com/aws/aws-sdk-go-v2/service/grafana/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfgrafana "github.com/hashicorp/terraform-provider-aws/internal/service/grafana" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccGrafanaWorkspaceServiceAccountToken_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_grafana_workspace_service_account_token.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + var v types.ServiceAccountTokenSummary + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.Grafana) }, + ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceServiceAccountTokenConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceServiceAccountTokenExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, names.AttrID), + resource.TestCheckResourceAttrSet(resourceName, names.AttrKey), + resource.TestCheckResourceAttrSet(resourceName, names.AttrName), + resource.TestCheckResourceAttrSet(resourceName, "service_account_id"), + resource.TestCheckResourceAttrSet(resourceName, "created_at"), + resource.TestCheckResourceAttrSet(resourceName, "expires_at"), + ), + }, + }, + }) +} + +func TestAccGrafanaWorkspaceServiceAccountToken_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v types.ServiceAccountTokenSummary + resourceName := "aws_grafana_workspace_service_account_token.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.Grafana) + }, + ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccWorkspaceServiceAccountTokenConfig_basic(resourceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWorkspaceServiceAccountTokenExists(ctx, resourceName, &v), + ), + }, + }, + }) +} + +func testAccCheckWorkspaceServiceAccountTokenExists(ctx context.Context, n string, v *types.ServiceAccountTokenSummary) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) + output, err := tfgrafana.FindWorkspaceServiceAccountToken(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["service_account_id"], rs.Primary.Attributes[names.AttrWorkspaceID]) + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckWorkspaceServiceAccountTokenDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_grafana_workspace_service_account_token" { + continue + } + + _, err := tfgrafana.FindWorkspaceServiceAccountToken(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["service_account_id"], rs.Primary.Attributes[names.AttrWorkspaceID]) + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Grafana workspace service account token %s still exists", rs.Primary.ID) + } + return nil + } +} + +func testAccWorkspaceServiceAccountTokenConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccWorkspaceServiceAccountConfig_basic(rName), fmt.Sprintf(` +resource "aws_grafana_workspace_service_account_token" "test" { + name = %[1]q + service_account_id = aws_grafana_workspace_service_account.test.id + seconds_to_live = 3600 + workspace_id = aws_grafana_workspace.test.id +} +`, rName)) +} diff --git a/website/docs/r/grafana_workspace_service_account_token.html.markdown b/website/docs/r/grafana_workspace_service_account_token.html.markdown new file mode 100644 index 000000000000..850bdfd5ce6a --- /dev/null +++ b/website/docs/r/grafana_workspace_service_account_token.html.markdown @@ -0,0 +1,51 @@ +--- +subcategory: "Managed Grafana" +layout: "aws" +page_title: "AWS: aws_grafana_workspace_service_account_token" +description: |- + Terraform resource for managing an Amazon Managed Grafana Workspace Service Account Token. +--- + +# Resource: aws_grafana_workspace_service_account_token + +-> **Note:** You cannot update a service account token. If you change any attribute, Terraform +will delete the current and create a new one. + +Read about Service Accounts Tokens in the [Amazon Managed Grafana user guide](https://docs.aws.amazon.com/grafana/latest/userguide/service-accounts.html#service-account-tokens). + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_grafana_workspace_service_account" "example" { + name = "example-admin" + grafana_role = "ADMIN" + workspace_id = aws_grafana_workspace.example.id +} + +resource "aws_grafana_workspace_service_account_token" "example" { + name = "example-key" + service_account_id = aws_grafana_workspace_service_account.example.id + seconds_to_live = 3600 + workspace_id = aws_grafana_workspace.example.id +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) A name for the token to create. The name must be unique within the workspace. +* `seconds_to_live` - (Required) Sets how long the token will be valid, in seconds. You can set the time up to 30 days in the future. +* `service_account_id` - (Required) The ID of the service account for which to create a token. +* `workspace_id` - (Required) The Grafana workspace with which the service account token is associated. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - Identifier of the service account token in the given Grafana workspace. +* `created_at` - Specifies when the service account token was created. +* `expires_at` - Specifies when the service account token will expire. +* `key` - The key for the service account token. Used when making calls to the Grafana HTTP APIs to authenticate and authorize the requests. From b62c77ce5317cf9b41dc24537a6e944a970f8da4 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Thu, 27 Jun 2024 22:25:15 +0200 Subject: [PATCH 19/30] fixup! Adding service account token --- .../service/grafana/workspace_service_account_token_test.go | 6 +++--- .../r/grafana_workspace_service_account_token.html.markdown | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/service/grafana/workspace_service_account_token_test.go b/internal/service/grafana/workspace_service_account_token_test.go index fcf73d8921c0..3ddbc822307e 100644 --- a/internal/service/grafana/workspace_service_account_token_test.go +++ b/internal/service/grafana/workspace_service_account_token_test.go @@ -118,10 +118,10 @@ func testAccCheckWorkspaceServiceAccountTokenDestroy(ctx context.Context) resour func testAccWorkspaceServiceAccountTokenConfig_basic(rName string) string { return acctest.ConfigCompose(testAccWorkspaceServiceAccountConfig_basic(rName), fmt.Sprintf(` resource "aws_grafana_workspace_service_account_token" "test" { - name = %[1]q + name = %[1]q service_account_id = aws_grafana_workspace_service_account.test.id - seconds_to_live = 3600 - workspace_id = aws_grafana_workspace.test.id + seconds_to_live = 3600 + workspace_id = aws_grafana_workspace.test.id } `, rName)) } diff --git a/website/docs/r/grafana_workspace_service_account_token.html.markdown b/website/docs/r/grafana_workspace_service_account_token.html.markdown index 850bdfd5ce6a..7acd57a68b13 100644 --- a/website/docs/r/grafana_workspace_service_account_token.html.markdown +++ b/website/docs/r/grafana_workspace_service_account_token.html.markdown @@ -25,10 +25,10 @@ resource "aws_grafana_workspace_service_account" "example" { } resource "aws_grafana_workspace_service_account_token" "example" { - name = "example-key" + name = "example-key" service_account_id = aws_grafana_workspace_service_account.example.id - seconds_to_live = 3600 - workspace_id = aws_grafana_workspace.example.id + seconds_to_live = 3600 + workspace_id = aws_grafana_workspace.example.id } ``` From 47c7d7aebacf78d36e3fbddb5c4271485e6a3b41 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Thu, 27 Jun 2024 22:56:45 +0200 Subject: [PATCH 20/30] Drop dead code --- .../workspace_service_account_token_test.go | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/internal/service/grafana/workspace_service_account_token_test.go b/internal/service/grafana/workspace_service_account_token_test.go index 3ddbc822307e..9bcb87f9434d 100644 --- a/internal/service/grafana/workspace_service_account_token_test.go +++ b/internal/service/grafana/workspace_service_account_token_test.go @@ -16,7 +16,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfgrafana "github.com/hashicorp/terraform-provider-aws/internal/service/grafana" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -91,30 +90,6 @@ func testAccCheckWorkspaceServiceAccountTokenExists(ctx context.Context, n strin } } -func testAccCheckWorkspaceServiceAccountTokenDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_grafana_workspace_service_account_token" { - continue - } - - _, err := tfgrafana.FindWorkspaceServiceAccountToken(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["service_account_id"], rs.Primary.Attributes[names.AttrWorkspaceID]) - if tfresource.NotFound(err) { - continue - } - - if err != nil { - return err - } - - return fmt.Errorf("Grafana workspace service account token %s still exists", rs.Primary.ID) - } - return nil - } -} - func testAccWorkspaceServiceAccountTokenConfig_basic(rName string) string { return acctest.ConfigCompose(testAccWorkspaceServiceAccountConfig_basic(rName), fmt.Sprintf(` resource "aws_grafana_workspace_service_account_token" "test" { From 6b89e4c38e54e84eb1fc4dacbbc67d6665040449 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 10:53:45 -0400 Subject: [PATCH 21/30] Remove 'workspace_id' constant. --- names/attr_constants.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/names/attr_constants.csv b/names/attr_constants.csv index 36685c7ec39d..292ca28a602c 100644 --- a/names/attr_constants.csv +++ b/names/attr_constants.csv @@ -227,4 +227,3 @@ vpc_endpoint_id,VPCEndpointID vpc_id,VPCID vpc_security_group_ids,VPCSecurityGroupIDs weight,Weight -workspace_id,WorkspaceID From e7a6a6d5ea6320516f7337c17ceb83a2eff18f54 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 11:09:37 -0400 Subject: [PATCH 22/30] Add CHANGELOG entries. --- .changelog/38101.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/38101.txt diff --git a/.changelog/38101.txt b/.changelog/38101.txt new file mode 100644 index 000000000000..9cf29553c922 --- /dev/null +++ b/.changelog/38101.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_grafana_workspace_service_account_token +``` + +```release-note:new-resource +aws_grafana_workspace_service_account +``` \ No newline at end of file From 6db522d0b4832eb39a90cf79c38368b2e9426688 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 12:10:17 -0400 Subject: [PATCH 23/30] r/aws_grafana_workspace_service_account: Tidy up. --- internal/service/grafana/exports_test.go | 14 +- .../service/grafana/service_package_gen.go | 2 +- .../grafana/workspace_service_account.go | 225 +++++++++++------- .../grafana/workspace_service_account_test.go | 45 ++-- ...na_workspace_service_account.html.markdown | 10 +- 5 files changed, 176 insertions(+), 120 deletions(-) diff --git a/internal/service/grafana/exports_test.go b/internal/service/grafana/exports_test.go index 6c715cdcb2d8..4368a615b427 100644 --- a/internal/service/grafana/exports_test.go +++ b/internal/service/grafana/exports_test.go @@ -8,12 +8,12 @@ var ( ResourceWorkspace = resourceWorkspace ResourceWorkspaceAPIKey = resourceWorkspaceAPIKey ResourceWorkspaceSAMLConfiguration = resourceWorkspaceSAMLConfiguration - ResourceWorkspaceServiceAccount = newResourceWorkspaceServiceAccount + ResourceWorkspaceServiceAccount = newWorkspaceServiceAccountResource - FindLicensedWorkspaceByID = findLicensedWorkspaceByID - FindRoleAssociationsByTwoPartKey = findRoleAssociationsByTwoPartKey - FindSAMLConfigurationByID = findSAMLConfigurationByID - FindWorkspaceByID = findWorkspaceByID - FindWorkspaceServiceAccount = findWorkspaceServiceAccount - FindWorkspaceServiceAccountToken = findWorkspaceServiceAccountToken + FindLicensedWorkspaceByID = findLicensedWorkspaceByID + FindRoleAssociationsByTwoPartKey = findRoleAssociationsByTwoPartKey + FindSAMLConfigurationByID = findSAMLConfigurationByID + FindWorkspaceByID = findWorkspaceByID + FindWorkspaceServiceAccountByTwoPartKey = findWorkspaceServiceAccountByTwoPartKey + FindWorkspaceServiceAccountToken = findWorkspaceServiceAccountToken ) diff --git a/internal/service/grafana/service_package_gen.go b/internal/service/grafana/service_package_gen.go index 6357074850a7..182484e29724 100644 --- a/internal/service/grafana/service_package_gen.go +++ b/internal/service/grafana/service_package_gen.go @@ -21,7 +21,7 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ { - Factory: newResourceWorkspaceServiceAccount, + Factory: newWorkspaceServiceAccountResource, Name: "WorkspaceServiceAccount", }, { diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index 6fc5589572c1..7ec64c426dad 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -11,66 +11,61 @@ import ( "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/path" "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-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/enum" + "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="WorkspaceServiceAccount") -func newResourceWorkspaceServiceAccount(_ context.Context) (resource.ResourceWithConfigure, error) { - return &resourceWorkspaceServiceAccount{}, nil +// @FrameworkResource("aws_grafana_workspace_service_account", name="Workspace Service Account") +func newWorkspaceServiceAccountResource(_ context.Context) (resource.ResourceWithConfigure, error) { + return &workspaceServiceAccountResource{}, nil } -const ( - ResNameServiceAccount = "WorkspaceServiceAccount" -) - -type resourceWorkspaceServiceAccount struct { +type workspaceServiceAccountResource struct { framework.ResourceWithConfigure - framework.WithNoOpUpdate[resourceWorkspaceServiceAccountData] + framework.WithNoUpdate framework.WithImportByID } -func (r *resourceWorkspaceServiceAccount) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { +func (*workspaceServiceAccountResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { response.TypeName = "aws_grafana_workspace_service_account" } -func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *workspaceServiceAccountResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - names.AttrID: framework.IDAttribute(), - names.AttrName: schema.StringAttribute{ - Required: true, + "grafana_role": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.Role](), + Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - stringvalidator.LengthAtMost(128), - }, }, - "grafana_role": schema.StringAttribute{ + names.AttrID: framework.IDAttribute(), + names.AttrName: schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, Validators: []validator.String{ - enum.FrameworkValidate[awstypes.Role](), + stringvalidator.LengthAtLeast(1), + stringvalidator.LengthAtMost(128), }, }, + "service_account_id": framework.IDAttribute(), "workspace_id": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -81,135 +76,181 @@ func (r *resourceWorkspaceServiceAccount) Schema(ctx context.Context, req resour } } -func (r *resourceWorkspaceServiceAccount) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data resourceWorkspaceServiceAccountData - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { +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) - input := &grafana.CreateWorkspaceServiceAccountInput{ - Name: aws.String(data.Name.ValueString()), - GrafanaRole: awstypes.Role(data.ServiceAccountRole.ValueString()), - WorkspaceId: aws.String(data.WorkspaceID.ValueString()), + 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 { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Grafana, create.ErrActionCreating, ResNameServiceAccount, "", err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("creating Grafana Workspace Service Account (%s)", name), err.Error()) + return } - data.ID = fwflex.StringToFramework(ctx, output.Id) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + // Set values for unknowns. + data.ServiceAccountID = fwflex.StringToFramework(ctx, output.Id) + data.setID() + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceWorkspaceServiceAccount) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data resourceWorkspaceServiceAccountData - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { +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 := findWorkspaceServiceAccount(ctx, conn, data.ID.ValueString(), data.WorkspaceID.ValueString()) + output, err := findWorkspaceServiceAccountByTwoPartKey(ctx, conn, data.WorkspaceID.ValueString(), data.ServiceAccountID.String()) + if tfresource.NotFound(err) { - resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) - resp.State.RemoveResource(ctx) + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return } if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Grafana, create.ErrActionSetting, ResNameServiceAccount, data.ID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Grafana Workspace Service Account (%s)", data.ID.ValueString()), err.Error()) + return } - resp.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) - if resp.Diagnostics.HasError() { + // Set attributes for import. + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + // Restore resource ID. + // It has been overwritten by the 'Id' field from the API response. + data.setID() + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceWorkspaceServiceAccount) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data resourceWorkspaceServiceAccountData - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { +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) - _, err := conn.DeleteWorkspaceServiceAccount(ctx, &grafana.DeleteWorkspaceServiceAccountInput{ - ServiceAccountId: fwflex.StringFromFramework(ctx, data.ID), - WorkspaceId: fwflex.StringFromFramework(ctx, data.WorkspaceID), - }) + 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 { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Grafana, create.ErrActionDeleting, ResNameServiceAccount, data.ID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("deleting Grafana Workspace Service Account (%s)", data.ID.ValueString()), err.Error()) + return } } -func (r *resourceWorkspaceServiceAccount) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - const ( - partCount = 3 - ) - parts, err := flex.ExpandResourceId(req.ID, partCount, false) +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 { - resp.Diagnostics.AddError(fmt.Sprintf("importing Workspace Service Account ID (%s)", req.ID), err.Error()) - return + return nil, err } - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root(names.AttrID), parts[0])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("grafana_role"), parts[1])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("workspace_id"), parts[2])...) + + return tfresource.AssertSingleValueResult(output) } -func findWorkspaceServiceAccount(ctx context.Context, conn *grafana.Client, id, workspaceID string) (*awstypes.ServiceAccountSummary, error) { - if workspaceID == "" { - return nil, errs.NewErrorWithMessage(fmt.Errorf("workspace_id is required to find the service account")) - } - input := &grafana.ListWorkspaceServiceAccountsInput{ - WorkspaceId: aws.String(workspaceID), - } +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) - paginator := grafana.NewListWorkspaceServiceAccountsPaginator(conn, input) - for paginator.HasMorePages() { - page, err := paginator.NextPage(ctx) if err != nil { return nil, err } - for _, sa := range page.ServiceAccounts { - if aws.ToString(sa.Id) == id { - return &sa, nil + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + for _, v := range page.ServiceAccounts { + if filter(&v) { + output = append(output, v) } } } - return nil, tfresource.NewEmptyResultError(input) + 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 } -type resourceWorkspaceServiceAccountData struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - ServiceAccountRole types.String `tfsdk:"grafana_role"` - WorkspaceID types.String `tfsdk:"workspace_id"` +func (data *workspaceServiceAccountResourceModel) setID() { + data.ID = types.StringValue(errs.Must(flex.FlattenResourceId([]string{data.WorkspaceID.ValueString(), data.ServiceAccountID.ValueString()}, workspaceServiceAccountResourceIDPartCount, false))) } diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index 45b6531cfa3e..f71652287bf0 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfgrafana "github.com/hashicorp/terraform-provider-aws/internal/service/grafana" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -29,21 +30,17 @@ func TestAccGrafanaWorkspaceServiceAccount_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.Grafana) }, ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccWorkspaceServiceAccountConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), - resource.TestCheckResourceAttrSet(resourceName, names.AttrID), - resource.TestCheckResourceAttrSet(resourceName, "grafana_role"), - resource.TestCheckResourceAttrSet(resourceName, "name"), - resource.TestCheckResourceAttrSet(resourceName, "workspace_id"), + resource.TestCheckResourceAttrSet(resourceName, "service_account_id"), ), }, { ResourceName: resourceName, - ImportStateIdFunc: testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName), ImportState: true, ImportStateVerify: true, }, @@ -63,13 +60,15 @@ func TestAccGrafanaWorkspaceServiceAccount_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccWorkspaceServiceAccountConfig_basic(resourceName), Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountExists(ctx, resourceName, &v), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfgrafana.ResourceWorkspaceServiceAccount, resourceName), ), + ExpectNonEmptyPlan: true, }, }, }) @@ -83,7 +82,9 @@ func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v } conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) - output, err := tfgrafana.FindWorkspaceServiceAccount(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) + + output, err := tfgrafana.FindWorkspaceServiceAccountByTwoPartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) + if err != nil { return err } @@ -94,14 +95,28 @@ func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v } } -func testAccCheckWorkspaceServiceAccountImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { - return func(s *terraform.State) (string, error) { - rs, ok := s.RootModule().Resources[resourceName] - if !ok { - return "", fmt.Errorf("not found: %s", resourceName) - } +func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) - return fmt.Sprintf("%s,%s,%s", rs.Primary.Attributes[names.AttrID], rs.Primary.Attributes["grafana_role"], rs.Primary.Attributes["workspace_id"]), nil + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_grafana_workspace_service_account" { + continue + } + + _, err := tfgrafana.FindWorkspaceServiceAccountByTwoPartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Grafana Workspace Service Account %s still exists", rs.Primary.ID) + } + return nil } } diff --git a/website/docs/r/grafana_workspace_service_account.html.markdown b/website/docs/r/grafana_workspace_service_account.html.markdown index fe089752051d..309bee2eeb30 100644 --- a/website/docs/r/grafana_workspace_service_account.html.markdown +++ b/website/docs/r/grafana_workspace_service_account.html.markdown @@ -37,21 +37,21 @@ The following arguments are required: This resource exports the following attributes in addition to the arguments above: -* `id` - Identifier of the service account in the given Grafana workspace +* `service_account_id` - Identifier of the service account in the given Grafana workspace ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Managed Grafana Workspace Service Account using the `id`, `grafana_role` and `workspace_id` separated by a comma (`,`). For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Managed Grafana Workspace Service Account using the `workspace_id` and `service_account_id` separated by a comma (`,`). For example: ```terraform import { to = aws_grafana_workspace_service_account.example - id = "1,ADMIN,g-abc12345" + id = "g-abc12345,1" } ``` -Using `terraform import`, import Managed Grafana Workspace Service Account using the `id`, `grafana_role` and `workspace_id` separated by a comma (`,`). For example: +Using `terraform import`, import Managed Grafana Workspace Service Account using the `workspace_id` and `service_account_id` separated by a comma (`,`). For example: ```console -% terraform import aws_grafana_workspace_service_account.example 1,ADMIN,g-abc12345 +% terraform import aws_grafana_workspace_service_account.example g-abc12345,1 ``` From 1e5ab73b472d4ef18887e7893fba394cdd51472d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 14:23:32 -0400 Subject: [PATCH 24/30] r/aws_grafana_workspace_service_account_token: Tidy up. --- internal/service/grafana/exports_test.go | 21 +- .../service/grafana/service_package_gen.go | 6 +- .../workspace_service_account_token.go | 246 +++++++++++------- .../workspace_service_account_token_test.go | 41 ++- ...kspace_service_account_token.html.markdown | 2 +- 5 files changed, 207 insertions(+), 109 deletions(-) diff --git a/internal/service/grafana/exports_test.go b/internal/service/grafana/exports_test.go index 4368a615b427..9e966b718539 100644 --- a/internal/service/grafana/exports_test.go +++ b/internal/service/grafana/exports_test.go @@ -5,15 +5,16 @@ package grafana // Exports for use in tests only. var ( - ResourceWorkspace = resourceWorkspace - ResourceWorkspaceAPIKey = resourceWorkspaceAPIKey - ResourceWorkspaceSAMLConfiguration = resourceWorkspaceSAMLConfiguration - ResourceWorkspaceServiceAccount = newWorkspaceServiceAccountResource + ResourceWorkspace = resourceWorkspace + ResourceWorkspaceAPIKey = resourceWorkspaceAPIKey + ResourceWorkspaceSAMLConfiguration = resourceWorkspaceSAMLConfiguration + ResourceWorkspaceServiceAccount = newWorkspaceServiceAccountResource + ResourceWorkspaceServiceAccountToken = newWorkspaceServiceAccountTokenResource - FindLicensedWorkspaceByID = findLicensedWorkspaceByID - FindRoleAssociationsByTwoPartKey = findRoleAssociationsByTwoPartKey - FindSAMLConfigurationByID = findSAMLConfigurationByID - FindWorkspaceByID = findWorkspaceByID - FindWorkspaceServiceAccountByTwoPartKey = findWorkspaceServiceAccountByTwoPartKey - FindWorkspaceServiceAccountToken = findWorkspaceServiceAccountToken + FindLicensedWorkspaceByID = findLicensedWorkspaceByID + FindRoleAssociationsByTwoPartKey = findRoleAssociationsByTwoPartKey + FindSAMLConfigurationByID = findSAMLConfigurationByID + FindWorkspaceByID = findWorkspaceByID + FindWorkspaceServiceAccountByTwoPartKey = findWorkspaceServiceAccountByTwoPartKey + FindWorkspaceServiceAccountTokenByThreePartKey = findWorkspaceServiceAccountTokenByThreePartKey ) diff --git a/internal/service/grafana/service_package_gen.go b/internal/service/grafana/service_package_gen.go index 182484e29724..4bd1a7d34b9c 100644 --- a/internal/service/grafana/service_package_gen.go +++ b/internal/service/grafana/service_package_gen.go @@ -22,11 +22,11 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic return []*types.ServicePackageFrameworkResource{ { Factory: newWorkspaceServiceAccountResource, - Name: "WorkspaceServiceAccount", + Name: "Workspace Service Account", }, { - Factory: newResourceWorkspaceServiceAccountToken, - Name: "WorkspaceServiceAccountToken", + Factory: newWorkspaceServiceAccountTokenResource, + Name: "Workspace Service Account Token", }, } } diff --git a/internal/service/grafana/workspace_service_account_token.go b/internal/service/grafana/workspace_service_account_token.go index 5f29d6b44fd3..584a0e18227f 100644 --- a/internal/service/grafana/workspace_service_account_token.go +++ b/internal/service/grafana/workspace_service_account_token.go @@ -5,11 +5,12 @@ package grafana import ( "context" - "time" + "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-timetypes/timetypes" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -19,39 +20,40 @@ import ( "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-provider-aws/internal/create" + "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" + 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_token", name="WorkspaceServiceAccountToken") -func newResourceWorkspaceServiceAccountToken(_ context.Context) (resource.ResourceWithConfigure, error) { - return &resourceWorkspaceServiceAccountToken{}, nil +// @FrameworkResource("aws_grafana_workspace_service_account_token", name="Workspace Service Account Token") +func newWorkspaceServiceAccountTokenResource(_ context.Context) (resource.ResourceWithConfigure, error) { + return &workspaceServiceAccountTokenResource{}, nil } -const ( - ResNameServiceAccountToken = "WorkspaceServiceAccountToken" -) - -type resourceWorkspaceServiceAccountToken struct { +type workspaceServiceAccountTokenResource struct { framework.ResourceWithConfigure - framework.WithNoOpUpdate[resourceWorkspaceServiceAccountTokenData] - framework.WithImportByID + framework.WithNoUpdate } -func (r *resourceWorkspaceServiceAccountToken) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { +func (r *workspaceServiceAccountTokenResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { response.TypeName = "aws_grafana_workspace_service_account_token" } -func (r *resourceWorkspaceServiceAccountToken) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *workspaceServiceAccountTokenResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ names.AttrCreatedAt: schema.StringAttribute{ - Computed: true, + CustomType: timetypes.RFC3339Type{}, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, names.AttrName: schema.StringAttribute{ Required: true, @@ -64,12 +66,19 @@ func (r *resourceWorkspaceServiceAccountToken) Schema(ctx context.Context, req r }, }, "expires_at": schema.StringAttribute{ - Computed: true, + CustomType: timetypes.RFC3339Type{}, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, names.AttrID: framework.IDAttribute(), names.AttrKey: schema.StringAttribute{ Computed: true, Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "seconds_to_live": schema.Int64Attribute{ Required: true, @@ -86,6 +95,7 @@ func (r *resourceWorkspaceServiceAccountToken) Schema(ctx context.Context, req r stringplanmodifier.RequiresReplace(), }, }, + "service_account_token_id": framework.IDAttribute(), "workspace_id": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -96,140 +106,200 @@ func (r *resourceWorkspaceServiceAccountToken) Schema(ctx context.Context, req r } } -func (r *resourceWorkspaceServiceAccountToken) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data resourceWorkspaceServiceAccountTokenData - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { +func (r *workspaceServiceAccountTokenResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data workspaceServiceAccountTokenResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } conn := r.Meta().GrafanaClient(ctx) - input := &grafana.CreateWorkspaceServiceAccountTokenInput{ - Name: aws.String(data.Name.ValueString()), - SecondsToLive: aws.Int32(int32(data.SecondsToLive.ValueInt64())), - ServiceAccountId: aws.String(data.ServiceAccountID.ValueString()), - WorkspaceId: aws.String(data.WorkspaceID.ValueString()), + name := data.Name.ValueString() + input := &grafana.CreateWorkspaceServiceAccountTokenInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, data, &input)...) + if response.Diagnostics.HasError() { + return } output, err := conn.CreateWorkspaceServiceAccountToken(ctx, input) + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Grafana, create.ErrActionCreating, ResNameServiceAccountToken, "", err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("creating Grafana Workspace Service Account Token (%s)", name), err.Error()) + return } - //update unknowns - saTokenID := aws.ToString(output.ServiceAccountToken.Id) - out, err := findWorkspaceServiceAccountToken(ctx, conn, saTokenID, data.ServiceAccountID.ValueString(), data.WorkspaceID.ValueString()) + // Set values for unknowns. + data.Key = fwflex.StringToFramework(ctx, output.ServiceAccountToken.Key) + data.TokenID = fwflex.StringToFramework(ctx, output.ServiceAccountToken.Id) + data.setID() + + serviceAccountToken, err := findWorkspaceServiceAccountTokenByThreePartKey(ctx, conn, data.WorkspaceID.ValueString(), data.ServiceAccountID.ValueString(), data.TokenID.ValueString()) + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Grafana, create.ErrActionReading, ResNameServiceAccountToken, "", err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Grafana Workspace Service Account Token (%s)", data.ID.ValueString()), err.Error()) + return } - data.ID = fwflex.StringValueToFramework(ctx, saTokenID) - data.Key = fwflex.StringToFramework(ctx, output.ServiceAccountToken.Key) - data.CreatedAt = fwflex.StringValueToFramework(ctx, out.CreatedAt.Format(time.RFC3339)) - data.ExpiresAt = fwflex.StringValueToFramework(ctx, out.ExpiresAt.Format(time.RFC3339)) + data.CreatedAt = fwflex.TimeToFramework(ctx, serviceAccountToken.CreatedAt) + data.ExpiresAt = fwflex.TimeToFramework(ctx, serviceAccountToken.ExpiresAt) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceWorkspaceServiceAccountToken) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data resourceWorkspaceServiceAccountTokenData - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { +func (r *workspaceServiceAccountTokenResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data workspaceServiceAccountTokenResourceModel + 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 := findWorkspaceServiceAccountToken(ctx, conn, data.ID.ValueString(), data.ServiceAccountID.ValueString(), data.WorkspaceID.ValueString()) + output, err := findWorkspaceServiceAccountTokenByThreePartKey(ctx, conn, data.WorkspaceID.ValueString(), data.ServiceAccountID.ValueString(), data.TokenID.ValueString()) + if tfresource.NotFound(err) { - resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) - resp.State.RemoveResource(ctx) + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return } if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Grafana, create.ErrActionSetting, ResNameServiceAccount, data.ID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Grafana Workspace Service Account Token (%s)", data.ID.ValueString()), err.Error()) + return } - resp.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) - if resp.Diagnostics.HasError() { + // Set attributes for import. + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + // Restore resource ID. + // It has been overwritten by the 'Id' field from the API response. + data.setID() + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceWorkspaceServiceAccountToken) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data resourceWorkspaceServiceAccountTokenData - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { +func (r *workspaceServiceAccountTokenResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data workspaceServiceAccountTokenResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } conn := r.Meta().GrafanaClient(ctx) - _, err := conn.DeleteWorkspaceServiceAccountToken(ctx, &grafana.DeleteWorkspaceServiceAccountTokenInput{ - ServiceAccountId: fwflex.StringFromFramework(ctx, data.ServiceAccountID), - TokenId: fwflex.StringFromFramework(ctx, data.ID), - WorkspaceId: fwflex.StringFromFramework(ctx, data.WorkspaceID), - }) + input := &grafana.DeleteWorkspaceServiceAccountTokenInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, data, &input)...) + if response.Diagnostics.HasError() { + return + } + + _, err := conn.DeleteWorkspaceServiceAccountToken(ctx, input) if errs.IsA[*awstypes.ResourceNotFoundException](err) { return } if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Grafana, create.ErrActionDeleting, ResNameServiceAccount, data.ID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("deleting Grafana Workspace Service Account Token (%s)", data.ID.ValueString()), err.Error()) + return } } -func findWorkspaceServiceAccountToken(ctx context.Context, conn *grafana.Client, id, serviceAccountID, workspaceID string) (*awstypes.ServiceAccountTokenSummary, error) { - input := &grafana.ListWorkspaceServiceAccountTokensInput{ - WorkspaceId: aws.String(workspaceID), - ServiceAccountId: aws.String(serviceAccountID), +func findWorkspaceServiceAccountToken(ctx context.Context, conn *grafana.Client, input *grafana.ListWorkspaceServiceAccountTokensInput, filter tfslices.Predicate[*awstypes.ServiceAccountTokenSummary]) (*awstypes.ServiceAccountTokenSummary, error) { + output, err := findWorkspaceServiceAccountTokens(ctx, conn, input, filter) + + if err != nil { + return nil, err } - paginator := grafana.NewListWorkspaceServiceAccountTokensPaginator(conn, input) - for paginator.HasMorePages() { - page, err := paginator.NextPage(ctx) + return tfresource.AssertSingleValueResult(output) +} + +func findWorkspaceServiceAccountTokens(ctx context.Context, conn *grafana.Client, input *grafana.ListWorkspaceServiceAccountTokensInput, filter tfslices.Predicate[*awstypes.ServiceAccountTokenSummary]) ([]awstypes.ServiceAccountTokenSummary, error) { + var output []awstypes.ServiceAccountTokenSummary + + pages := grafana.NewListWorkspaceServiceAccountTokensPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if err != nil { return nil, err } - for _, sa := range page.ServiceAccountTokens { - if aws.ToString(sa.Id) == id { - return &sa, nil + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + for _, v := range page.ServiceAccountTokens { + if filter(&v) { + output = append(output, v) } } } - return nil, tfresource.NewEmptyResultError(input) + return output, nil +} + +func findWorkspaceServiceAccountTokenByThreePartKey(ctx context.Context, conn *grafana.Client, workspaceID, serviceAccountID, tokenID string) (*awstypes.ServiceAccountTokenSummary, error) { + input := &grafana.ListWorkspaceServiceAccountTokensInput{ + ServiceAccountId: aws.String(serviceAccountID), + WorkspaceId: aws.String(workspaceID), + } + + return findWorkspaceServiceAccountToken(ctx, conn, input, func(v *awstypes.ServiceAccountTokenSummary) bool { + return aws.ToString(v.Id) == tokenID + }) +} + +type workspaceServiceAccountTokenResourceModel struct { + CreatedAt timetypes.RFC3339 `tfsdk:"created_at"` + ExpiresAt timetypes.RFC3339 `tfsdk:"expires_at"` + ID types.String `tfsdk:"id"` + Key types.String `tfsdk:"key"` + Name types.String `tfsdk:"name"` + SecondsToLive types.Int64 `tfsdk:"seconds_to_live"` + ServiceAccountID types.String `tfsdk:"service_account_id"` + TokenID types.String `tfsdk:"service_account_token_id"` + WorkspaceID types.String `tfsdk:"workspace_id"` +} + +const ( + workspaceServiceAccountTokenResourceIDPartCount = 3 +) + +func (data *workspaceServiceAccountTokenResourceModel) InitFromID() error { + id := data.ID.ValueString() + parts, err := flex.ExpandResourceId(id, workspaceServiceAccountTokenResourceIDPartCount, false) + + if err != nil { + return err + } + + data.WorkspaceID = types.StringValue(parts[0]) + data.ServiceAccountID = types.StringValue(parts[1]) + data.TokenID = types.StringValue(parts[2]) + + return nil } -type resourceWorkspaceServiceAccountTokenData struct { - ID types.String `tfsdk:"id"` - CreatedAt types.String `tfsdk:"created_at"` - ExpiresAt types.String `tfsdk:"expires_at"` - Key types.String `tfsdk:"key"` - Name types.String `tfsdk:"name"` - SecondsToLive types.Int64 `tfsdk:"seconds_to_live"` - ServiceAccountID types.String `tfsdk:"service_account_id"` - WorkspaceID types.String `tfsdk:"workspace_id"` +func (data *workspaceServiceAccountTokenResourceModel) setID() { + data.ID = types.StringValue(errs.Must(flex.FlattenResourceId([]string{data.WorkspaceID.ValueString(), data.ServiceAccountID.ValueString(), data.TokenID.ValueString()}, workspaceServiceAccountTokenResourceIDPartCount, false))) } diff --git a/internal/service/grafana/workspace_service_account_token_test.go b/internal/service/grafana/workspace_service_account_token_test.go index ce16519c0799..5fd71205ede0 100644 --- a/internal/service/grafana/workspace_service_account_token_test.go +++ b/internal/service/grafana/workspace_service_account_token_test.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfgrafana "github.com/hashicorp/terraform-provider-aws/internal/service/grafana" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -29,18 +30,15 @@ func TestAccGrafanaWorkspaceServiceAccountToken_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.Grafana) }, ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckWorkspaceServiceAccountTokenDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccWorkspaceServiceAccountTokenConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountTokenExists(ctx, resourceName, &v), - resource.TestCheckResourceAttrSet(resourceName, names.AttrID), - resource.TestCheckResourceAttrSet(resourceName, names.AttrKey), - resource.TestCheckResourceAttrSet(resourceName, names.AttrName), - resource.TestCheckResourceAttrSet(resourceName, "service_account_id"), resource.TestCheckResourceAttrSet(resourceName, "created_at"), resource.TestCheckResourceAttrSet(resourceName, "expires_at"), + resource.TestCheckResourceAttrSet(resourceName, "service_account_token_id"), ), }, }, @@ -59,13 +57,15 @@ func TestAccGrafanaWorkspaceServiceAccountToken_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckWorkspaceServiceAccountTokenDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccWorkspaceServiceAccountTokenConfig_basic(resourceName), Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountTokenExists(ctx, resourceName, &v), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfgrafana.ResourceWorkspaceServiceAccountToken, resourceName), ), + ExpectNonEmptyPlan: true, }, }, }) @@ -79,7 +79,9 @@ func testAccCheckWorkspaceServiceAccountTokenExists(ctx context.Context, n strin } conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) - output, err := tfgrafana.FindWorkspaceServiceAccountToken(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["service_account_id"], rs.Primary.Attributes["workspace_id"]) + + output, err := tfgrafana.FindWorkspaceServiceAccountTokenByThreePartKey(ctx, conn, rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["service_account_id"], rs.Primary.Attributes["service_account_token_id"]) + if err != nil { return err } @@ -90,6 +92,31 @@ func testAccCheckWorkspaceServiceAccountTokenExists(ctx context.Context, n strin } } +func testAccCheckWorkspaceServiceAccountTokenDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_grafana_workspace_service_account_token" { + continue + } + + _, err := tfgrafana.FindWorkspaceServiceAccountTokenByThreePartKey(ctx, conn, rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["service_account_id"], rs.Primary.Attributes["service_account_token_id"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Grafana Workspace Service Account Token %s still exists", rs.Primary.ID) + } + return nil + } +} + func testAccWorkspaceServiceAccountTokenConfig_basic(rName string) string { return acctest.ConfigCompose(testAccWorkspaceServiceAccountConfig_basic(rName), fmt.Sprintf(` resource "aws_grafana_workspace_service_account_token" "test" { diff --git a/website/docs/r/grafana_workspace_service_account_token.html.markdown b/website/docs/r/grafana_workspace_service_account_token.html.markdown index 7acd57a68b13..5d4e20ff0634 100644 --- a/website/docs/r/grafana_workspace_service_account_token.html.markdown +++ b/website/docs/r/grafana_workspace_service_account_token.html.markdown @@ -45,7 +45,7 @@ The following arguments are required: This resource exports the following attributes in addition to the arguments above: -* `id` - Identifier of the service account token in the given Grafana workspace. +* `service_account_token_id` - Identifier of the service account token in the given Grafana workspace. * `created_at` - Specifies when the service account token was created. * `expires_at` - Specifies when the service account token will expire. * `key` - The key for the service account token. Used when making calls to the Grafana HTTP APIs to authenticate and authorize the requests. From 2e1b4e1bccaa72f68add9f5c3d60ad38a6b53863 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 15:09:03 -0400 Subject: [PATCH 25/30] Fixes after some testing. --- .../service/grafana/workspace_service_account.go | 4 ++-- .../grafana/workspace_service_account_test.go | 13 +++++++++---- .../grafana/workspace_service_account_token.go | 4 ++-- .../grafana/workspace_service_account_token_test.go | 9 +++++++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index 7ec64c426dad..d94b73d54d69 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -87,7 +87,7 @@ func (r *workspaceServiceAccountResource) Create(ctx context.Context, request re name := data.Name.ValueString() input := &grafana.CreateWorkspaceServiceAccountInput{} - response.Diagnostics.Append(fwflex.Expand(ctx, data, &input)...) + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) if response.Diagnostics.HasError() { return } @@ -160,7 +160,7 @@ func (r *workspaceServiceAccountResource) Delete(ctx context.Context, request re conn := r.Meta().GrafanaClient(ctx) input := &grafana.DeleteWorkspaceServiceAccountInput{} - response.Diagnostics.Append(fwflex.Expand(ctx, data, &input)...) + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) if response.Diagnostics.HasError() { return } diff --git a/internal/service/grafana/workspace_service_account_test.go b/internal/service/grafana/workspace_service_account_test.go index f71652287bf0..2f1a9512927a 100644 --- a/internal/service/grafana/workspace_service_account_test.go +++ b/internal/service/grafana/workspace_service_account_test.go @@ -27,7 +27,11 @@ func TestAccGrafanaWorkspaceServiceAccount_basic(t *testing.T) { var v types.ServiceAccountSummary resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.Grafana) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.GrafanaEndpointID) + acctest.PreCheckSSOAdminInstances(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckWorkspaceServiceAccountDestroy(ctx), @@ -56,7 +60,8 @@ func TestAccGrafanaWorkspaceServiceAccount_disappears(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.Grafana) + acctest.PreCheckPartitionHasService(t, names.GrafanaEndpointID) + acctest.PreCheckSSOAdminInstances(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -83,7 +88,7 @@ func testAccCheckWorkspaceServiceAccountExists(ctx context.Context, n string, v conn := acctest.Provider.Meta().(*conns.AWSClient).GrafanaClient(ctx) - output, err := tfgrafana.FindWorkspaceServiceAccountByTwoPartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) + output, err := tfgrafana.FindWorkspaceServiceAccountByTwoPartKey(ctx, conn, rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["service_account_id"]) if err != nil { return err @@ -104,7 +109,7 @@ func testAccCheckWorkspaceServiceAccountDestroy(ctx context.Context) resource.Te continue } - _, err := tfgrafana.FindWorkspaceServiceAccountByTwoPartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["workspace_id"]) + _, err := tfgrafana.FindWorkspaceServiceAccountByTwoPartKey(ctx, conn, rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["service_account_id"]) if tfresource.NotFound(err) { continue diff --git a/internal/service/grafana/workspace_service_account_token.go b/internal/service/grafana/workspace_service_account_token.go index 584a0e18227f..bf688986983d 100644 --- a/internal/service/grafana/workspace_service_account_token.go +++ b/internal/service/grafana/workspace_service_account_token.go @@ -117,7 +117,7 @@ func (r *workspaceServiceAccountTokenResource) Create(ctx context.Context, reque name := data.Name.ValueString() input := &grafana.CreateWorkspaceServiceAccountTokenInput{} - response.Diagnostics.Append(fwflex.Expand(ctx, data, &input)...) + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) if response.Diagnostics.HasError() { return } @@ -202,7 +202,7 @@ func (r *workspaceServiceAccountTokenResource) Delete(ctx context.Context, reque conn := r.Meta().GrafanaClient(ctx) input := &grafana.DeleteWorkspaceServiceAccountTokenInput{} - response.Diagnostics.Append(fwflex.Expand(ctx, data, &input)...) + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) if response.Diagnostics.HasError() { return } diff --git a/internal/service/grafana/workspace_service_account_token_test.go b/internal/service/grafana/workspace_service_account_token_test.go index 5fd71205ede0..daaa186340cc 100644 --- a/internal/service/grafana/workspace_service_account_token_test.go +++ b/internal/service/grafana/workspace_service_account_token_test.go @@ -27,7 +27,11 @@ func TestAccGrafanaWorkspaceServiceAccountToken_basic(t *testing.T) { var v types.ServiceAccountTokenSummary resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.Grafana) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.GrafanaEndpointID) + acctest.PreCheckSSOAdminInstances(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, grafana.ServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckWorkspaceServiceAccountTokenDestroy(ctx), @@ -53,7 +57,8 @@ func TestAccGrafanaWorkspaceServiceAccountToken_disappears(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, names.Grafana) + acctest.PreCheckPartitionHasService(t, names.GrafanaEndpointID) + acctest.PreCheckSSOAdminInstances(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.GrafanaServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, From 140f97ce06129451a73049dae15456855dc71aa3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 15:11:18 -0400 Subject: [PATCH 26/30] Run 'make fix-constants PKG=grafana'. --- .../service/grafana/workspace_service_account_token_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/grafana/workspace_service_account_token_test.go b/internal/service/grafana/workspace_service_account_token_test.go index daaa186340cc..b7261baf1901 100644 --- a/internal/service/grafana/workspace_service_account_token_test.go +++ b/internal/service/grafana/workspace_service_account_token_test.go @@ -40,7 +40,7 @@ func TestAccGrafanaWorkspaceServiceAccountToken_basic(t *testing.T) { Config: testAccWorkspaceServiceAccountTokenConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckWorkspaceServiceAccountTokenExists(ctx, resourceName, &v), - resource.TestCheckResourceAttrSet(resourceName, "created_at"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrCreatedAt), resource.TestCheckResourceAttrSet(resourceName, "expires_at"), resource.TestCheckResourceAttrSet(resourceName, "service_account_token_id"), ), From 7b96b4f862112208e59025a41b5a8d879620b231 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 15:34:36 -0400 Subject: [PATCH 27/30] Fix typos. --- internal/service/grafana/workspace_service_account.go | 8 ++++---- .../service/grafana/workspace_service_account_token.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index d94b73d54d69..51ebc9302aa7 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -194,10 +194,6 @@ func findWorkspaceServiceAccounts(ctx context.Context, conn *grafana.Client, inp for pages.HasMorePages() { page, err := pages.NextPage(ctx) - if err != nil { - return nil, err - } - if errs.IsA[*awstypes.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, @@ -205,6 +201,10 @@ func findWorkspaceServiceAccounts(ctx context.Context, conn *grafana.Client, inp } } + if err != nil { + return nil, err + } + for _, v := range page.ServiceAccounts { if filter(&v) { output = append(output, v) diff --git a/internal/service/grafana/workspace_service_account_token.go b/internal/service/grafana/workspace_service_account_token.go index bf688986983d..0673cf313e17 100644 --- a/internal/service/grafana/workspace_service_account_token.go +++ b/internal/service/grafana/workspace_service_account_token.go @@ -237,10 +237,6 @@ func findWorkspaceServiceAccountTokens(ctx context.Context, conn *grafana.Client for pages.HasMorePages() { page, err := pages.NextPage(ctx) - if err != nil { - return nil, err - } - if errs.IsA[*awstypes.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, @@ -248,6 +244,10 @@ func findWorkspaceServiceAccountTokens(ctx context.Context, conn *grafana.Client } } + if err != nil { + return nil, err + } + for _, v := range page.ServiceAccountTokens { if filter(&v) { output = append(output, v) From b796cbaa77d44cece7cc9ad1659a259312ab7141 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 17:09:30 -0400 Subject: [PATCH 28/30] More fixes after testing. --- internal/framework/types/string_enum.go | 5 +++++ internal/service/grafana/workspace_service_account.go | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/framework/types/string_enum.go b/internal/framework/types/string_enum.go index 163de73ba190..4e87548e3dea 100644 --- a/internal/framework/types/string_enum.go +++ b/internal/framework/types/string_enum.go @@ -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" @@ -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]) diff --git a/internal/service/grafana/workspace_service_account.go b/internal/service/grafana/workspace_service_account.go index 51ebc9302aa7..56941d5153ea 100644 --- a/internal/service/grafana/workspace_service_account.go +++ b/internal/service/grafana/workspace_service_account.go @@ -122,7 +122,7 @@ func (r *workspaceServiceAccountResource) Read(ctx context.Context, request reso conn := r.Meta().GrafanaClient(ctx) - output, err := findWorkspaceServiceAccountByTwoPartKey(ctx, conn, data.WorkspaceID.ValueString(), data.ServiceAccountID.String()) + output, err := findWorkspaceServiceAccountByTwoPartKey(ctx, conn, data.WorkspaceID.ValueString(), data.ServiceAccountID.ValueString()) if tfresource.NotFound(err) { response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) @@ -147,6 +147,9 @@ func (r *workspaceServiceAccountResource) Read(ctx context.Context, request reso // 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)...) } From da430af05552f3695edd53881677330cabf96a76 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 17:32:07 -0400 Subject: [PATCH 29/30] r/aws_grafana_workspace_service_account_token: Fix acceptance test configuration. --- .../service/grafana/workspace_service_account_token_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/grafana/workspace_service_account_token_test.go b/internal/service/grafana/workspace_service_account_token_test.go index b7261baf1901..131386ad7645 100644 --- a/internal/service/grafana/workspace_service_account_token_test.go +++ b/internal/service/grafana/workspace_service_account_token_test.go @@ -126,7 +126,7 @@ func testAccWorkspaceServiceAccountTokenConfig_basic(rName string) string { return acctest.ConfigCompose(testAccWorkspaceServiceAccountConfig_basic(rName), fmt.Sprintf(` resource "aws_grafana_workspace_service_account_token" "test" { name = %[1]q - service_account_id = aws_grafana_workspace_service_account.test.id + service_account_id = aws_grafana_workspace_service_account.test.service_account_id seconds_to_live = 3600 workspace_id = aws_grafana_workspace.test.id } From 9b3012d9114d714d6c7f0651e6b0986710c1f33e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 12 Jul 2024 17:56:51 -0400 Subject: [PATCH 30/30] r/aws_grafana_workspace_service_account_token: Tweak documentation example. --- .../r/grafana_workspace_service_account_token.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/grafana_workspace_service_account_token.html.markdown b/website/docs/r/grafana_workspace_service_account_token.html.markdown index 5d4e20ff0634..87cba4631b10 100644 --- a/website/docs/r/grafana_workspace_service_account_token.html.markdown +++ b/website/docs/r/grafana_workspace_service_account_token.html.markdown @@ -26,7 +26,7 @@ resource "aws_grafana_workspace_service_account" "example" { resource "aws_grafana_workspace_service_account_token" "example" { name = "example-key" - service_account_id = aws_grafana_workspace_service_account.example.id + service_account_id = aws_grafana_workspace_service_account.example.service_account_id seconds_to_live = 3600 workspace_id = aws_grafana_workspace.example.id }