Skip to content

Commit 603ca62

Browse files
cloud_stack_service_account: Use new GET and DELETE endpoints (#1437)
Rather than having to POST a temporary service account and using that to do full CRUD management, the gcom now exposes full CRUD capability that we can use. This means that doing a `terraform plan` will no longer POST a temporary SA for every `cloud_stack_service_account` and `cloud_stack_service_account_token` resource in the state This should help users that are being throttled by the gcom API
1 parent aaa20a2 commit 603ca62

5 files changed

+52
-138
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/go-openapi/runtime v0.28.0
1010
github.com/go-openapi/strfmt v0.23.0
1111
github.com/grafana/amixr-api-go-client v0.0.11
12-
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240305213348-af081a82f063
12+
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240322153219-42c6a1d2bcab
1313
github.com/grafana/grafana-openapi-client-go v0.0.0-20240306142804-861284d1ba83
1414
github.com/grafana/machine-learning-go-client v0.5.0
1515
github.com/grafana/slo-openapi-client/go v0.0.0-20240112175006-de02e75b9d73

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
9494
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
9595
github.com/grafana/amixr-api-go-client v0.0.11 h1:jlE+5t0tRuCtjbpM81j70Dr2J4eCySuWyNGdfLMGdhE=
9696
github.com/grafana/amixr-api-go-client v0.0.11/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE=
97-
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240305213348-af081a82f063 h1:DI3xC2krNY3Su40eQn50rfkodv92Ce1i5Updr0TMBH4=
98-
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240305213348-af081a82f063/go.mod h1:6sYY1qgwYfSDNQhKQA0tar8Oc38cIGfyqwejhxoOsPs=
97+
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240322153219-42c6a1d2bcab h1:/5R8NO996/keDkZqKXEkU3/QgFs1wzChKYkakjsBpRk=
98+
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20240322153219-42c6a1d2bcab/go.mod h1:6sYY1qgwYfSDNQhKQA0tar8Oc38cIGfyqwejhxoOsPs=
9999
github.com/grafana/grafana-openapi-client-go v0.0.0-20240306142804-861284d1ba83 h1:n5vecnZOTpRAZu7rIyNmg54k860JPvAuMvnz2AYQD0E=
100100
github.com/grafana/grafana-openapi-client-go v0.0.0-20240306142804-861284d1ba83/go.mod h1:FiVsMOTtVuo/Ajmt1efuk3/KmDpFQ3qMurQf7e6HwQg=
101101
github.com/grafana/machine-learning-go-client v0.5.0 h1:Q1K+MPSy8vfMm2jsk3WQ7O77cGr2fM5hxwtPSoPc5NU=

internal/resources/cloud/resource_cloud_stack_service_account.go

+23-80
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99

1010
"github.com/grafana/grafana-com-public-clients/go/gcom"
1111
goapi "github.com/grafana/grafana-openapi-client-go/client"
12-
"github.com/grafana/grafana-openapi-client-go/client/service_accounts"
13-
"github.com/grafana/grafana-openapi-client-go/models"
1412
"github.com/grafana/terraform-provider-grafana/v2/internal/common"
1513
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1614
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -42,7 +40,6 @@ Required access policy scopes:
4240

4341
CreateContext: withClient[schema.CreateContextFunc](createStackServiceAccount),
4442
ReadContext: withClient[schema.ReadContextFunc](readStackServiceAccount),
45-
UpdateContext: withClient[schema.UpdateContextFunc](updateStackServiceAccount),
4643
DeleteContext: withClient[schema.DeleteContextFunc](deleteStackServiceAccount),
4744
Importer: &schema.ResourceImporter{
4845
StateContext: schema.ImportStatePassthroughContext,
@@ -56,21 +53,22 @@ Required access policy scopes:
5653
"name": {
5754
Type: schema.TypeString,
5855
Required: true,
59-
ForceNew: true,
6056
Description: "The name of the service account.",
57+
ForceNew: true,
6158
},
6259
"role": {
6360
Type: schema.TypeString,
6461
Optional: true,
6562
ValidateFunc: validation.StringInSlice([]string{"Viewer", "Editor", "Admin"}, false),
6663
Description: "The basic role of the service account in the organization.",
64+
ForceNew: true, // The grafana API does not support updating the service account
6765
},
6866
"is_disabled": {
6967
Type: schema.TypeBool,
7068
Optional: true,
7169
Default: false,
72-
ForceNew: true,
7370
Description: "The disabled status for the service account.",
71+
ForceNew: true, // The grafana API does not support updating the service account
7472
},
7573
},
7674
}
@@ -84,25 +82,21 @@ Required access policy scopes:
8482

8583
func createStackServiceAccount(ctx context.Context, d *schema.ResourceData, cloudClient *gcom.APIClient) diag.Diagnostics {
8684
stackSlug := d.Get("stack_slug").(string)
87-
client, cleanup, err := CreateTemporaryStackGrafanaClient(ctx, cloudClient, stackSlug, "terraform-temp-")
88-
if err != nil {
89-
return diag.FromErr(err)
90-
}
91-
defer cleanup()
92-
93-
req := service_accounts.NewCreateServiceAccountParams().WithBody(&models.CreateServiceAccountForm{
85+
req := gcom.PostInstanceServiceAccountsRequest{
9486
Name: d.Get("name").(string),
9587
Role: d.Get("role").(string),
96-
IsDisabled: d.Get("is_disabled").(bool),
97-
})
98-
resp, err := client.ServiceAccounts.CreateServiceAccount(req)
88+
IsDisabled: common.Ref(d.Get("is_disabled").(bool)),
89+
}
90+
resp, _, err := cloudClient.InstancesAPI.PostInstanceServiceAccounts(ctx, stackSlug).
91+
PostInstanceServiceAccountsRequest(req).
92+
XRequestId(ClientRequestID()).
93+
Execute()
9994
if err != nil {
10095
return diag.FromErr(err)
10196
}
102-
sa := resp.Payload
10397

104-
d.SetId(resourceStackServiceAccountID.Make(stackSlug, sa.ID))
105-
return readStackServiceAccountWithClient(client, d, sa.ID)
98+
d.SetId(resourceStackServiceAccountID.Make(stackSlug, resp.Id))
99+
return readStackServiceAccount(ctx, d, cloudClient)
106100
}
107101

108102
func readStackServiceAccount(ctx context.Context, d *schema.ResourceData, cloudClient *gcom.APIClient) diag.Diagnostics {
@@ -122,69 +116,22 @@ func readStackServiceAccount(ctx context.Context, d *schema.ResourceData, cloudC
122116
stackSlug, serviceAccountID = split[0].(string), split[1].(int64)
123117
}
124118

125-
client, cleanup, err := CreateTemporaryStackGrafanaClient(ctx, cloudClient, stackSlug, "terraform-temp-")
126-
if err != nil {
127-
return diag.FromErr(err)
128-
}
129-
defer cleanup()
130-
131-
d.Set("stack_slug", stackSlug)
132-
d.SetId(resourceStackServiceAccountID.Make(stackSlug, serviceAccountID))
133-
134-
return readStackServiceAccountWithClient(client, d, serviceAccountID)
135-
}
136-
137-
func readStackServiceAccountWithClient(client *goapi.GrafanaHTTPAPI, d *schema.ResourceData, serviceAccountID int64) diag.Diagnostics {
138-
resp, err := client.ServiceAccounts.RetrieveServiceAccount(serviceAccountID)
139-
if err != nil {
140-
return diag.FromErr(err)
141-
}
142-
sa := resp.Payload
143-
144-
err = d.Set("name", sa.Name)
145-
if err != nil {
146-
return diag.FromErr(err)
147-
}
148-
err = d.Set("role", sa.Role)
149-
if err != nil {
150-
return diag.FromErr(err)
151-
}
152-
err = d.Set("is_disabled", sa.IsDisabled)
153-
if err != nil {
154-
return diag.FromErr(err)
155-
}
156-
157-
return nil
158-
}
159-
160-
func updateStackServiceAccount(ctx context.Context, d *schema.ResourceData, cloudClient *gcom.APIClient) diag.Diagnostics {
161-
split, err := resourceStackServiceAccountID.Split(d.Id())
162-
if err != nil {
163-
return diag.FromErr(err)
119+
resp, httpResp, err := cloudClient.InstancesAPI.GetInstanceServiceAccount(ctx, stackSlug, strconv.FormatInt(serviceAccountID, 10)).Execute()
120+
if httpResp != nil && httpResp.StatusCode == 404 {
121+
d.SetId("")
122+
return nil
164123
}
165-
stackSlug, serviceAccountID := split[0].(string), split[1].(int64)
166-
167-
client, cleanup, err := CreateTemporaryStackGrafanaClient(ctx, cloudClient, stackSlug, "terraform-temp-")
168124
if err != nil {
169125
return diag.FromErr(err)
170126
}
171-
defer cleanup()
172-
173-
updateRequest := service_accounts.NewUpdateServiceAccountParams().
174-
WithBody(&models.UpdateServiceAccountForm{
175-
Name: d.Get("name").(string),
176-
Role: d.Get("role").(string),
177-
IsDisabled: d.Get("is_disabled").(bool),
178-
}).
179-
WithServiceAccountID(serviceAccountID)
180127

181-
if _, err := client.ServiceAccounts.UpdateServiceAccount(updateRequest); err != nil {
182-
return diag.FromErr(err)
183-
}
184128
d.Set("stack_slug", stackSlug)
129+
d.Set("name", resp.Name)
130+
d.Set("role", resp.Role)
131+
d.Set("is_disabled", resp.IsDisabled)
185132
d.SetId(resourceStackServiceAccountID.Make(stackSlug, serviceAccountID))
186133

187-
return readStackServiceAccountWithClient(client, d, serviceAccountID)
134+
return nil
188135
}
189136

190137
func deleteStackServiceAccount(ctx context.Context, d *schema.ResourceData, cloudClient *gcom.APIClient) diag.Diagnostics {
@@ -194,13 +141,9 @@ func deleteStackServiceAccount(ctx context.Context, d *schema.ResourceData, clou
194141
}
195142
stackSlug, serviceAccountID := split[0].(string), split[1].(int64)
196143

197-
client, cleanup, err := CreateTemporaryStackGrafanaClient(ctx, cloudClient, stackSlug, "terraform-temp-")
198-
if err != nil {
199-
return diag.FromErr(err)
200-
}
201-
defer cleanup()
202-
203-
_, err = client.ServiceAccounts.DeleteServiceAccount(serviceAccountID)
144+
_, err = cloudClient.InstancesAPI.DeleteInstanceServiceAccount(ctx, stackSlug, strconv.FormatInt(serviceAccountID, 10)).
145+
XRequestId(ClientRequestID()).
146+
Execute()
204147
return diag.FromErr(err)
205148
}
206149

internal/resources/cloud/resource_cloud_stack_service_account_token.go

+24-53
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import (
77
"strconv"
88

99
"github.com/grafana/grafana-com-public-clients/go/gcom"
10-
goapi "github.com/grafana/grafana-openapi-client-go/client"
11-
"github.com/grafana/grafana-openapi-client-go/client/service_accounts"
12-
"github.com/grafana/grafana-openapi-client-go/models"
1310
"github.com/grafana/terraform-provider-grafana/v2/internal/common"
1411
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1512
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -84,57 +81,43 @@ Required access policy scopes:
8481
}
8582

8683
func stackServiceAccountTokenCreate(ctx context.Context, d *schema.ResourceData, cloudClient *gcom.APIClient) diag.Diagnostics {
87-
c, cleanup, err := CreateTemporaryStackGrafanaClient(ctx, cloudClient, d.Get("stack_slug").(string), "terraform-temp-")
88-
if err != nil {
89-
return diag.FromErr(err)
90-
}
91-
defer cleanup()
92-
84+
stackSlug := d.Get("stack_slug").(string)
9385
serviceAccountID, err := getStackServiceAccountID(d.Get("service_account_id").(string))
9486
if err != nil {
9587
return diag.FromErr(err)
9688
}
9789

98-
name := d.Get("name").(string)
99-
ttl := d.Get("seconds_to_live").(int)
90+
req := gcom.PostInstanceServiceAccountTokensRequest{
91+
Name: d.Get("name").(string),
92+
SecondsToLive: common.Ref(int32(d.Get("seconds_to_live").(int))),
93+
}
10094

101-
request := service_accounts.NewCreateTokenParams().WithBody(&models.AddServiceAccountTokenCommand{
102-
Name: name,
103-
SecondsToLive: int64(ttl),
104-
}).WithServiceAccountID(serviceAccountID)
105-
response, err := c.ServiceAccounts.CreateToken(request)
95+
resp, _, err := cloudClient.InstancesAPI.PostInstanceServiceAccountTokens(ctx, stackSlug, strconv.FormatInt(serviceAccountID, 10)).
96+
PostInstanceServiceAccountTokensRequest(req).
97+
XRequestId(ClientRequestID()).
98+
Execute()
10699
if err != nil {
107100
return diag.FromErr(err)
108101
}
109-
t := response.Payload
110102

111-
d.SetId(strconv.FormatInt(t.ID, 10))
112-
err = d.Set("key", t.Key)
103+
d.SetId(strconv.FormatInt(*resp.Id, 10))
104+
err = d.Set("key", resp.Key)
113105
if err != nil {
114106
return diag.FromErr(err)
115107
}
116108

117109
// Fill the true resource's state by performing a read
118-
return stackServiceAccountTokenReadWithClient(c, d)
110+
return stackServiceAccountTokenRead(ctx, d, cloudClient)
119111
}
120112

121113
func stackServiceAccountTokenRead(ctx context.Context, d *schema.ResourceData, cloudClient *gcom.APIClient) diag.Diagnostics {
122-
c, cleanup, err := CreateTemporaryStackGrafanaClient(ctx, cloudClient, d.Get("stack_slug").(string), "terraform-temp-")
123-
if err != nil {
124-
return diag.FromErr(err)
125-
}
126-
defer cleanup()
127-
128-
return stackServiceAccountTokenReadWithClient(c, d)
129-
}
130-
131-
func stackServiceAccountTokenReadWithClient(c *goapi.GrafanaHTTPAPI, d *schema.ResourceData) diag.Diagnostics {
114+
stackSlug := d.Get("stack_slug").(string)
132115
serviceAccountID, err := getStackServiceAccountID(d.Get("service_account_id").(string))
133116
if err != nil {
134117
return diag.FromErr(err)
135118
}
136119

137-
response, err := c.ServiceAccounts.ListTokens(serviceAccountID)
120+
response, _, err := cloudClient.InstancesAPI.GetInstanceServiceAccountTokens(ctx, stackSlug, strconv.FormatInt(serviceAccountID, 10)).Execute()
138121
if err != nil {
139122
return diag.FromErr(err)
140123
}
@@ -143,14 +126,14 @@ func stackServiceAccountTokenReadWithClient(c *goapi.GrafanaHTTPAPI, d *schema.R
143126
if err != nil {
144127
return diag.FromErr(err)
145128
}
146-
for _, key := range response.Payload {
147-
if id == key.ID {
148-
d.SetId(strconv.FormatInt(key.ID, 10))
129+
for _, key := range response {
130+
if id == *key.Id {
131+
d.SetId(strconv.FormatInt(*key.Id, 10))
149132
err = d.Set("name", key.Name)
150133
if err != nil {
151134
return diag.FromErr(err)
152135
}
153-
if !key.Expiration.IsZero() {
136+
if key.Expiration != nil && !key.Expiration.IsZero() {
154137
err = d.Set("expiration", key.Expiration.String())
155138
if err != nil {
156139
return diag.FromErr(err)
@@ -162,35 +145,23 @@ func stackServiceAccountTokenReadWithClient(c *goapi.GrafanaHTTPAPI, d *schema.R
162145
}
163146
}
164147

165-
log.Printf("[WARN] removing service account token%d from state because it no longer exists in grafana", id)
148+
log.Printf("[WARN] removing service account token %d from state because it no longer exists in grafana", id)
166149
d.SetId("")
167150

168151
return nil
169152
}
170153

171154
func stackServiceAccountTokenDelete(ctx context.Context, d *schema.ResourceData, cloudClient *gcom.APIClient) diag.Diagnostics {
172-
c, cleanup, err := CreateTemporaryStackGrafanaClient(ctx, cloudClient, d.Get("stack_slug").(string), "terraform-temp-")
173-
if err != nil {
174-
return diag.FromErr(err)
175-
}
176-
defer cleanup()
177-
155+
stackSlug := d.Get("stack_slug").(string)
178156
serviceAccountID, err := getStackServiceAccountID(d.Get("service_account_id").(string))
179157
if err != nil {
180158
return diag.FromErr(err)
181159
}
182160

183-
id, err := strconv.ParseInt(d.Id(), 10, 32)
184-
if err != nil {
185-
return diag.FromErr(err)
186-
}
187-
188-
_, err = c.ServiceAccounts.DeleteToken(id, serviceAccountID)
189-
if err != nil {
190-
return diag.FromErr(err)
191-
}
192-
193-
return nil
161+
_, err = cloudClient.InstancesAPI.DeleteInstanceServiceAccountToken(ctx, stackSlug, strconv.FormatInt(serviceAccountID, 10), d.Id()).
162+
XRequestId(ClientRequestID()).
163+
Execute()
164+
return diag.FromErr(err)
194165
}
195166

196167
func getStackServiceAccountID(id string) (int64, error) {

internal/resources/cloud/resource_cloud_stack_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ func testAccStackCheckDestroy(a *gcom.FormattedApiInstance) resource.TestCheckFu
231231
return func(s *terraform.State) error {
232232
client := testutils.Provider.Meta().(*common.Client).GrafanaCloudAPI
233233
stack, _, err := client.InstancesAPI.GetInstance(context.Background(), a.Slug).Execute()
234-
if err == nil && stack.Name != "" {
235-
return fmt.Errorf("stack `%s` with ID `%d` still exists after destroy", stack.Name, int(stack.Id))
234+
if err == nil && stack.Name != "" && stack.Status != "deleting" {
235+
return fmt.Errorf("stack `%s` with ID `%d` still exists after destroy. Status: %s", stack.Name, int(stack.Id), stack.Status)
236236
}
237237

238238
return nil

0 commit comments

Comments
 (0)