Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: Standardize and verify ID formats #1380

Merged
merged 3 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/resources/cloud_access_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,5 @@ Required:
Import is supported using the following syntax:

```shell
terraform import grafana_cloud_access_policy.policyname {{region}}/{{policy_id}}
terraform import grafana_cloud_access_policy.name "{{ region }}:{{ policyId }}"
```
8 changes: 8 additions & 0 deletions docs/resources/cloud_access_policy_token.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,11 @@ resource "grafana_cloud_access_policy_token" "test" {
- `id` (String) The ID of this resource.
- `token` (String, Sensitive)
- `updated_at` (String) Last update date of the access policy token.

## Import

Import is supported using the following syntax:

```shell
terraform import grafana_cloud_access_policy_token.name "{{ region }}:{{ tokenId }}"
```
2 changes: 1 addition & 1 deletion docs/resources/cloud_api_key.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ resource "grafana_cloud_api_key" "test" {
Import is supported using the following syntax:

```shell
terraform import grafana_cloud_api_key.resource_name "{{org-name}}-{{api_key_name}}"
terraform import grafana_cloud_api_key.name "{{ orgSlug }}:{{ apiKeyName }}"
```
2 changes: 1 addition & 1 deletion docs/resources/cloud_plugin_installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ resource "grafana_cloud_plugin_installation" "test" {
Import is supported using the following syntax:

```shell
terraform import grafana_cloud_plugin_installation.plugin_name {{stack_slug}}_{{plugin_slug}}
terraform import grafana_cloud_plugin_installation.name "{{ stackSlug }}:{{ pluginSlug }}"
```
2 changes: 1 addition & 1 deletion examples/resources/grafana_cloud_access_policy/import.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
terraform import grafana_cloud_access_policy.policyname {{region}}/{{policy_id}}
terraform import grafana_cloud_access_policy.name "{{ region }}:{{ policyId }}"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import grafana_cloud_access_policy_token.name "{{ region }}:{{ tokenId }}"
2 changes: 1 addition & 1 deletion examples/resources/grafana_cloud_api_key/import.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
terraform import grafana_cloud_api_key.resource_name "{{org-name}}-{{api_key_name}}"
terraform import grafana_cloud_api_key.name "{{ orgSlug }}:{{ apiKeyName }}"
Original file line number Diff line number Diff line change
@@ -1 +1 @@
terraform import grafana_cloud_plugin_installation.plugin_name {{stack_slug}}_{{plugin_slug}}
terraform import grafana_cloud_plugin_installation.name "{{ stackSlug }}:{{ pluginSlug }}"
84 changes: 84 additions & 0 deletions internal/common/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package common

import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
)

var (
defaultSeparator = ":"
allIDs = []*TFID{}
)

type TFID struct {
resourceName string
separators []string
expectedFields []string
}

func NewTFID(resourceName string, expectedFields ...string) *TFID {
return newTFIDWithSeparators(resourceName, []string{defaultSeparator}, expectedFields...)
}

// Deprecated: Use NewTFID instead
// We should standardize on a single separator, so that function should only be used for old resources
// On major versions, switch to NewTFID and remove uses of this function
func NewTFIDWithLegacySeparator(resourceName, legacySeparator string, expectedFields ...string) *TFID {
return newTFIDWithSeparators(resourceName, []string{defaultSeparator, legacySeparator}, expectedFields...)
}

func newTFIDWithSeparators(resourceName string, separators []string, expectedFields ...string) *TFID {
tfID := &TFID{
resourceName: resourceName,
separators: separators,
expectedFields: expectedFields,
}
allIDs = append(allIDs, tfID)
return tfID
}

func (id *TFID) Example() string {
fields := make([]string, len(id.expectedFields))
for i := range fields {
fields[i] = fmt.Sprintf("{{ %s }}", id.expectedFields[i])
}
return fmt.Sprintf(`terraform import %s.name %q
`, id.resourceName, strings.Join(fields, defaultSeparator))
}

func (id *TFID) Make(parts ...any) string {
if len(parts) != len(id.expectedFields) {
panic(fmt.Sprintf("expected %d fields, got %d", len(id.expectedFields), len(parts))) // This is a coding error, so panic is appropriate
}
stringParts := make([]string, len(parts))
for i, part := range parts {
stringParts[i] = fmt.Sprintf("%v", part)
}
return strings.Join(stringParts, defaultSeparator)
}

func (id *TFID) Split(resourceID string) ([]string, error) {
for _, sep := range id.separators {
parts := strings.Split(resourceID, sep)
if len(parts) == len(id.expectedFields) {
return parts, nil
}
}
return nil, fmt.Errorf("id %q does not match expected format. Should be in the format: %s", resourceID, strings.Join(id.expectedFields, defaultSeparator))
}

// GenerateImportFiles generates import files for all resources that use a helper defined in this package
func GenerateImportFiles(path string) error {
for _, id := range allIDs {
resourcePath := filepath.Join(path, "resources", id.resourceName, "import.sh")
log.Printf("Generating import file for %s (writing to %s)\n", id.resourceName, resourcePath)
err := os.WriteFile(resourcePath, []byte(id.Example()), 0600)
if err != nil {
return err
}
}
return nil
}
33 changes: 25 additions & 8 deletions internal/resources/cloud/resource_cloud_access_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cloud

import (
"context"
"fmt"
"strings"
"time"

Expand All @@ -14,6 +13,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

var ResourceAccessPolicyID = common.NewTFIDWithLegacySeparator("grafana_cloud_access_policy", "/", "region", "policyId") //nolint:staticcheck

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

Expand Down Expand Up @@ -140,14 +141,19 @@ func CreateCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, meta i
return apiError(err)
}

d.SetId(fmt.Sprintf("%s/%s", region, *result.Id))
d.SetId(ResourceAccessPolicyID.Make(region, result.Id))

return ReadCloudAccessPolicy(ctx, d, meta)
}

func UpdateCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*common.Client).GrafanaCloudAPI
region, id, _ := strings.Cut(d.Id(), "/")

split, err := ResourceAccessPolicyID.Split(d.Id())
if err != nil {
return diag.FromErr(err)
}
region, id := split[0], split[1]

displayName := d.Get("display_name").(string)
if displayName == "" {
Expand All @@ -160,8 +166,7 @@ func UpdateCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, meta i
Scopes: common.ListToStringSlice(d.Get("scopes").(*schema.Set).List()),
Realms: expandCloudAccessPolicyRealm(d.Get("realm").(*schema.Set).List()),
})
_, _, err := req.Execute()
if err != nil {
if _, _, err = req.Execute(); err != nil {
return apiError(err)
}

Expand All @@ -171,7 +176,12 @@ func UpdateCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, meta i
func ReadCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*common.Client).GrafanaCloudAPI

region, id, _ := strings.Cut(d.Id(), "/")
split, err := ResourceAccessPolicyID.Split(d.Id())
if err != nil {
return diag.FromErr(err)
}
region, id := split[0], split[1]

result, _, err := client.AccesspoliciesAPI.GetAccessPolicy(ctx, id).Region(region).Execute()
if err, shouldReturn := common.CheckReadError("access policy", d, err); shouldReturn {
return err
Expand All @@ -187,14 +197,21 @@ func ReadCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, meta int
if updated := result.UpdatedAt; updated != nil {
d.Set("updated_at", updated.Format(time.RFC3339))
}
d.SetId(ResourceAccessPolicyID.Make(region, result.Id))

return nil
}

func DeleteCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*common.Client).GrafanaCloudAPI
region, id, _ := strings.Cut(d.Id(), "/")
_, _, err := client.AccesspoliciesAPI.DeleteAccessPolicy(ctx, id).Region(region).XRequestId(ClientRequestID()).Execute()

split, err := ResourceAccessPolicyID.Split(d.Id())
if err != nil {
return diag.FromErr(err)
}
region, id := split[0], split[1]

_, _, err = client.AccesspoliciesAPI.DeleteAccessPolicy(ctx, id).Region(region).XRequestId(ClientRequestID()).Execute()
return apiError(err)
}

Expand Down
29 changes: 22 additions & 7 deletions internal/resources/cloud/resource_cloud_access_policy_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package cloud

import (
"context"
"fmt"
"strings"
"time"

"github.com/grafana/grafana-com-public-clients/go/gcom"
Expand All @@ -13,6 +11,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

var ResourceAccessPolicyTokenID = common.NewTFIDWithLegacySeparator("grafana_cloud_access_policy_token", "/", "region", "tokenId") //nolint:staticcheck

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

Expand Down Expand Up @@ -112,15 +112,20 @@ func CreateCloudAccessPolicyToken(ctx context.Context, d *schema.ResourceData, m
return apiError(err)
}

d.SetId(fmt.Sprintf("%s/%s", region, *result.Id))
d.SetId(ResourceAccessPolicyTokenID.Make(region, result.Id))
d.Set("token", result.Token)

return ReadCloudAccessPolicyToken(ctx, d, meta)
}

func UpdateCloudAccessPolicyToken(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*common.Client).GrafanaCloudAPI
region, id, _ := strings.Cut(d.Id(), "/")

split, err := ResourceAccessPolicyTokenID.Split(d.Id())
if err != nil {
return diag.FromErr(err)
}
region, id := split[0], split[1]

displayName := d.Get("display_name").(string)
if displayName == "" {
Expand All @@ -140,7 +145,11 @@ func UpdateCloudAccessPolicyToken(ctx context.Context, d *schema.ResourceData, m
func ReadCloudAccessPolicyToken(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*common.Client).GrafanaCloudAPI

region, id, _ := strings.Cut(d.Id(), "/")
split, err := ResourceAccessPolicyTokenID.Split(d.Id())
if err != nil {
return diag.FromErr(err)
}
region, id := split[0], split[1]

result, _, err := client.TokensAPI.GetToken(ctx, id).Region(region).Execute()
if err, shouldReturn := common.CheckReadError("policy token", d, err); shouldReturn {
Expand All @@ -158,14 +167,20 @@ func ReadCloudAccessPolicyToken(ctx context.Context, d *schema.ResourceData, met
if result.UpdatedAt != nil {
d.Set("updated_at", result.UpdatedAt.Format(time.RFC3339))
}
d.SetId(ResourceAccessPolicyTokenID.Make(region, result.Id))

return nil
}

func DeleteCloudAccessPolicyToken(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*common.Client).GrafanaCloudAPI
region, id, _ := strings.Cut(d.Id(), "/")

_, _, err := client.TokensAPI.DeleteToken(ctx, id).Region(region).XRequestId(ClientRequestID()).Execute()
split, err := ResourceAccessPolicyTokenID.Split(d.Id())
if err != nil {
return diag.FromErr(err)
}
region, id := split[0], split[1]

_, _, err = client.TokensAPI.DeleteToken(ctx, id).Region(region).XRequestId(ClientRequestID()).Execute()
return apiError(err)
}
20 changes: 15 additions & 5 deletions internal/resources/cloud/resource_cloud_api_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package cloud
import (
"context"
"fmt"
"strings"

"github.com/grafana/grafana-com-public-clients/go/gcom"
"github.com/grafana/terraform-provider-grafana/internal/common"
Expand All @@ -12,6 +11,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

var ResourceAPIKeyID = common.NewTFIDWithLegacySeparator("grafana_cloud_api_key", "-", "orgSlug", "apiKeyName") //nolint:staticcheck
var cloudAPIKeyRoles = []string{"Viewer", "Editor", "Admin", "MetricsPublisher", "PluginPublisher"}

func ResourceAPIKey() *schema.Resource {
Expand Down Expand Up @@ -77,16 +77,19 @@ func ResourceAPIKeyCreate(ctx context.Context, d *schema.ResourceData, meta inte
}

d.Set("key", *resp.Token)
d.SetId(org + "-" + resp.Name)
d.SetId(ResourceAPIKeyID.Make(org, resp.Name))

return ResourceAPIKeyRead(ctx, d, meta)
}

func ResourceAPIKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*common.Client).GrafanaCloudAPI

splitID := strings.SplitN(d.Id(), "-", 2)
org, name := splitID[0], splitID[1]
split, err := ResourceAPIKeyID.Split(d.Id())
if err != nil {
return diag.FromErr(err)
}
org, name := split[0], split[1]

resp, _, err := c.OrgsAPI.GetApiKey(ctx, name, org).Execute()
if err != nil {
Expand All @@ -96,14 +99,21 @@ func ResourceAPIKeyRead(ctx context.Context, d *schema.ResourceData, meta interf
d.Set("name", resp.Name)
d.Set("role", resp.Role)
d.Set("cloud_org_slug", org)
d.SetId(ResourceAPIKeyID.Make(org, resp.Name))

return nil
}

func ResourceAPIKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*common.Client).GrafanaCloudAPI

_, err := c.OrgsAPI.DelApiKey(ctx, d.Get("name").(string), d.Get("cloud_org_slug").(string)).XRequestId(ClientRequestID()).Execute()
split, err := ResourceAPIKeyID.Split(d.Id())
if err != nil {
return diag.FromErr(err)
}
org, name := split[0], split[1]

_, err = c.OrgsAPI.DelApiKey(ctx, name, org).XRequestId(ClientRequestID()).Execute()
d.SetId("")
return apiError(err)
}
Loading
Loading