From bd8364d2962cb1291dac07003cb30aa5cb83184d Mon Sep 17 00:00:00 2001 From: andriikushch Date: Thu, 14 Nov 2024 15:50:55 +0100 Subject: [PATCH 01/15] Add support for the Azure credentials resource --- .../data-source.tf | 16 ++ .../import.sh | 1 + .../resource.tf | 7 + internal/common/cloudproviderapi/client.go | 62 +++++ .../cloudprovider/azure_credential_test.go | 92 +++++++ .../data_source_azure_credential.go | 127 +++++++++ .../resource_azure_credential.go | 252 ++++++++++++++++++ internal/resources/cloudprovider/resources.go | 2 + 8 files changed, 559 insertions(+) create mode 100644 examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf create mode 100644 examples/resources/grafana_cloud_provider_azure_credential/import.sh create mode 100644 examples/resources/grafana_cloud_provider_azure_credential/resource.tf create mode 100644 internal/resources/cloudprovider/azure_credential_test.go create mode 100644 internal/resources/cloudprovider/data_source_azure_credential.go create mode 100644 internal/resources/cloudprovider/resource_azure_credential.go diff --git a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf new file mode 100644 index 000000000..5bf23e4c1 --- /dev/null +++ b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf @@ -0,0 +1,16 @@ +resource "grafana_cloud_provider_azure_credential" "test" { + stack_id = "1" + name = "test-name" + client_id = "my-client-id" + client_secret = "my-client-secret" + tenant_id = "my-tenant-id" +} + +data "grafana_cloud_provider_azure_credential" "test" { + stack_id = grafana_cloud_provider_azure_credential.test.stack_id + name = grafana_cloud_provider_azure_credential.test.name + client_id = grafana_cloud_provider_azure_credential.test.client_id + client_secret = grafana_cloud_provider_azure_credential.test.client_secret + tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id + resource_id = grafana_cloud_provider_azure_credential.test.resource_id +} \ No newline at end of file diff --git a/examples/resources/grafana_cloud_provider_azure_credential/import.sh b/examples/resources/grafana_cloud_provider_azure_credential/import.sh new file mode 100644 index 000000000..76c776df4 --- /dev/null +++ b/examples/resources/grafana_cloud_provider_azure_credential/import.sh @@ -0,0 +1 @@ +terraform import grafana_cloud_provider_azure_credential.name "{{ stack_id }}:{{ resource_id }}" diff --git a/examples/resources/grafana_cloud_provider_azure_credential/resource.tf b/examples/resources/grafana_cloud_provider_azure_credential/resource.tf new file mode 100644 index 000000000..4c9a9447d --- /dev/null +++ b/examples/resources/grafana_cloud_provider_azure_credential/resource.tf @@ -0,0 +1,7 @@ +resource "grafana_cloud_provider_azure_credential" "test" { + stack_id = "1" + name = "test-name" + client_id = "my-client-id" + client_secret = "my-client-secret" + tenant_id = "my-tenant-id" +} diff --git a/internal/common/cloudproviderapi/client.go b/internal/common/cloudproviderapi/client.go index a7bdb9fb5..733d70416 100644 --- a/internal/common/cloudproviderapi/client.go +++ b/internal/common/cloudproviderapi/client.go @@ -193,6 +193,68 @@ func (c *Client) DeleteAWSCloudWatchScrapeJob(ctx context.Context, stackID strin return nil } +type AzureCredential struct { + // ID is the unique identifier for the Azure credential in our systems. + ID string `json:"id"` + + // Name is the user-defined name for the Azure credential. + Name string `json:"name"` + + // TenantID is the Azure tenant ID. + TenantID string `json:"tenant_id"` + + // ClientID is the Azure client ID. + ClientID string `json:"client_id"` + + // ClientSecret is the Azure client secret. + ClientSecret string `json:"client_secret"` + + // StackID is the unique identifier for the stack in our systems. + StackID string `json:"stack_id"` +} + +func (c *Client) CreateAzureCredential(ctx context.Context, stackID string, credentialData AzureCredential) (AzureCredential, error) { + path := fmt.Sprintf("/api/v2/stacks/%s/azure/credentials", stackID) + respData := apiResponseWrapper[AzureCredential]{} + err := c.doAPIRequest(ctx, http.MethodPost, path, &credentialData, &respData) + if err != nil { + return AzureCredential{}, fmt.Errorf("failed to create Azure credential: %w", err) + } + + return respData.Data, nil +} + +func (c *Client) GetAzureCredential(ctx context.Context, stackID string, credentialID string) (AzureCredential, error) { + path := fmt.Sprintf("/api/v2/stacks/%s/azure/credentials/%s", stackID, credentialID) + respData := apiResponseWrapper[AzureCredential]{} + err := c.doAPIRequest(ctx, http.MethodGet, path, nil, &respData) + if err != nil { + return AzureCredential{}, fmt.Errorf("failed to get Azure credential: %w", err) + } + + return respData.Data, nil +} + +func (c *Client) UpdateAzureCredential(ctx context.Context, stackID string, accountID string, credentialData AzureCredential) (AzureCredential, error) { + path := fmt.Sprintf("/api/v2/stacks/%s/azure/credentials/%s", stackID, accountID) + respData := apiResponseWrapper[AzureCredential]{} + err := c.doAPIRequest(ctx, http.MethodPut, path, &credentialData, &respData) + if err != nil { + return AzureCredential{}, fmt.Errorf("failed to update Azure credential: %w", err) + } + + return respData.Data, nil +} + +func (c *Client) DeleteAzureCredential(ctx context.Context, stackID string, credentialID string) error { + path := fmt.Sprintf("/api/v2/stacks/%s/azure/credentials/%s", stackID, credentialID) + err := c.doAPIRequest(ctx, http.MethodDelete, path, nil, nil) + if err != nil { + return fmt.Errorf("failed to delete Azure credential: %w", err) + } + return nil +} + func (c *Client) doAPIRequest(ctx context.Context, method string, path string, body any, responseData any) error { var reqBodyBytes io.Reader if body != nil { diff --git a/internal/resources/cloudprovider/azure_credential_test.go b/internal/resources/cloudprovider/azure_credential_test.go new file mode 100644 index 000000000..19074061f --- /dev/null +++ b/internal/resources/cloudprovider/azure_credential_test.go @@ -0,0 +1,92 @@ +package cloudprovider_test + +import ( + "fmt" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/grafana/terraform-provider-grafana/v3/internal/testutils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +// Tests both managed resource and data source +func TestAcc_AzureCredential(t *testing.T) { + resourceID := "3" + // Mock the Connections API response for Create, Get, and Delete + mux := http.NewServeMux() + mux.HandleFunc("/api/v2/stacks/1/azure/credentials", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte(fmt.Sprintf(` + { + "data": { + "id": "%s", + "name": "test-name", + "tenant_id": "my_tenant_id", + "client_id": "my_client_id", + "client_secret": "", + "stack_id":"1" + } + }`, resourceID))) + } + }) + + mux.HandleFunc("/api/v2/stacks/1/azure/credentials/"+resourceID, func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(fmt.Sprintf(` + { + "data": { + "id": "%s", + "name": "test-name", + "tenant_id": "my-tenant-id", + "client_id": "my-client-id", + "client_secret": "", + "stack_id":"1" + } + }`, resourceID))) + case http.MethodDelete: + w.WriteHeader(http.StatusNoContent) + } + }) + + server := httptest.NewServer(mux) + defer server.Close() + + _ = os.Setenv("GRAFANA_CLOUD_PROVIDER_URL", server.URL) + _ = os.Setenv("GRAFANA_CLOUD_PROVIDER_ACCESS_TOKEN", "some_token") + + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + // Creates a managed resource + Config: testutils.TestAccExample(t, "resources/grafana_cloud_provider_azure_credential/resource.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "id", "1:3"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "stack_id", "1"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "name", "test-name"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "tenant_id", "my-tenant-id"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "client_id", "my-client-id"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "client_secret", "my-client-secret"), + ), + }, + { + // Tests data source resource + Config: testutils.TestAccExample(t, "data-sources/grafana_cloud_provider_azure_credential/data-source.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "id", "1:3"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "stack_id", "1"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "name", "test-name"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "tenant_id", "my-tenant-id"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "client_id", "my-client-id"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "client_secret", ""), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_id", resourceID), + ), + }, + }, + }) +} diff --git a/internal/resources/cloudprovider/data_source_azure_credential.go b/internal/resources/cloudprovider/data_source_azure_credential.go new file mode 100644 index 000000000..e6d164943 --- /dev/null +++ b/internal/resources/cloudprovider/data_source_azure_credential.go @@ -0,0 +1,127 @@ +package cloudprovider + +import ( + "context" + + "github.com/grafana/terraform-provider-grafana/v3/internal/common" + "github.com/grafana/terraform-provider-grafana/v3/internal/common/cloudproviderapi" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type datasourceAzureCredential struct { + client *cloudproviderapi.Client +} + +func makeDataSourceAzureCredential() *common.DataSource { + return common.NewDataSource( + common.CategoryCloudProvider, + resourceAzureCredentialTerraformName, + &datasourceAzureCredential{}, + ) +} + +func (r *datasourceAzureCredential) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Configure is called multiple times (sometimes when ProviderData is not yet available), we only want to configure once + if req.ProviderData == nil || r.client != nil { + return + } + + client, err := withClientForDataSource(req, resp) + if err != nil { + return + } + + r.client = client +} + +func (r *datasourceAzureCredential) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = resourceAzureCredentialTerraformName +} + +func (r *datasourceAzureCredential) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The Terraform Resource ID. This has the format \"{{ stack_id }}:{{ resource_id }}\".", + Computed: true, + }, + "stack_id": schema.StringAttribute{ + Description: "The StackID of the Grafana Cloud instance. Part of the Terraform Resource ID.", + Required: true, + }, + "resource_id": schema.StringAttribute{ + Description: "The ID given by the Grafana Cloud Provider API to this Azure Credential resource.", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The name of the Azure Credential.", + Required: true, + }, + "client_id": schema.StringAttribute{ + Description: "The client ID of the Azure Credential.", + Required: true, + }, + "tenant_id": schema.StringAttribute{ + Description: "The tenant ID of the Azure Credential.", + Required: true, + }, + "client_secret": schema.StringAttribute{ + Description: "The client secret of the Azure Credential.", + Required: true, + Sensitive: true, + }, + }, + } +} + +func (r *datasourceAzureCredential) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data resourceAzureCredentialModel + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + credential, err := r.client.GetAzureCredential( + ctx, + data.StackID.ValueString(), + data.ResourceID.ValueString(), + ) + if err != nil { + resp.Diagnostics.AddError("Failed to get Azure Credential", err.Error()) + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("id"), types.StringValue(resourceAzureCredentialTerraformID.Make(data.StackID.ValueString(), data.ResourceID.ValueString()))) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("name"), credential.Name) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("client_id"), credential.ClientID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("tenant_id"), credential.TenantID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("client_secret"), credential.ClientSecret) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/internal/resources/cloudprovider/resource_azure_credential.go b/internal/resources/cloudprovider/resource_azure_credential.go new file mode 100644 index 000000000..6616977b9 --- /dev/null +++ b/internal/resources/cloudprovider/resource_azure_credential.go @@ -0,0 +1,252 @@ +package cloudprovider + +import ( + "context" + "github.com/grafana/terraform-provider-grafana/v3/internal/common" + "github.com/grafana/terraform-provider-grafana/v3/internal/common/cloudproviderapi" + "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/types" +) + +var ( + resourceAzureCredentialTerraformName = "grafana_cloud_provider_azure_credential" + resourceAzureCredentialTerraformID = common.NewResourceID(common.StringIDField("stack_id"), common.StringIDField("resource_id")) +) + +type resourceAzureCredentialModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + TenantID types.String `tfsdk:"tenant_id"` + ClientID types.String `tfsdk:"client_id"` + StackID types.String `tfsdk:"stack_id"` + ClientSecret types.String `tfsdk:"client_secret"` + ResourceID types.String `tfsdk:"resource_id"` +} + +type resourceAzureCredential struct { + client *cloudproviderapi.Client +} + +func makeResourceAzureCredential() *common.Resource { + return common.NewResource( + common.CategoryCloudProvider, + resourceAzureCredentialTerraformName, + resourceAzureCredentialTerraformID, + &resourceAzureCredential{}, + ) +} + +func (r *resourceAzureCredential) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Configure is called multiple times (sometimes when ProviderData is not yet available), we only want to configure once + if req.ProviderData == nil || r.client != nil { + return + } + + client, err := withClientForResource(req, resp) + if err != nil { + return + } + + r.client = client +} + +func (r *resourceAzureCredential) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = resourceAzureCredentialTerraformName +} + +func (r *resourceAzureCredential) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The Terraform Resource ID. This has the format \"{{ stack_id }}:{{ resource_id }}\".", + Computed: true, + PlanModifiers: []planmodifier.String{ + // See https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#usestateforunknown + // for details on how this works. + stringplanmodifier.UseStateForUnknown(), + }, + }, + "stack_id": schema.StringAttribute{ + Description: "The StackID of the Grafana Cloud instance. Part of the Terraform Resource ID.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "resource_id": schema.StringAttribute{ + Description: "The ID given by the Grafana Cloud Provider API to this AWS Account resource.", + Computed: true, + PlanModifiers: []planmodifier.String{ + // See https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#usestateforunknown + // for details on how this works. + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "The name of the Azure Credential.", + Required: true, + }, + "client_id": schema.StringAttribute{ + Description: "The client ID of the Azure Credential.", + Required: true, + }, + "tenant_id": schema.StringAttribute{ + Description: "The tenant ID of the Azure Credential.", + Required: true, + }, + "client_secret": schema.StringAttribute{ + Description: "The client secret of the Azure Credential.", + Required: true, + Sensitive: true, + }, + }, + } +} + +func (r *resourceAzureCredential) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data resourceAzureCredentialModel + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + azureCredential := cloudproviderapi.AzureCredential{ + Name: data.Name.ValueString(), + TenantID: data.TenantID.ValueString(), + ClientID: data.ClientID.ValueString(), + ClientSecret: data.ClientSecret.ValueString(), + } + + credential, err := r.client.CreateAzureCredential( + ctx, + data.StackID.ValueString(), + azureCredential, + ) + if err != nil { + resp.Diagnostics.AddError("Failed to create Azure credential", err.Error()) + return + } + + resp.State.Set(ctx, &resourceAzureCredentialModel{ + ID: types.StringValue(resourceAzureCredentialTerraformID.Make(data.StackID.ValueString(), credential.ID)), + Name: data.Name, + TenantID: data.TenantID, + ClientID: data.ClientID, + StackID: data.StackID, + ClientSecret: data.ClientSecret, + ResourceID: types.StringValue(credential.ID), + }) +} + +func (r *resourceAzureCredential) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data resourceAzureCredentialModel + diags := req.State.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + credential, err := r.client.GetAzureCredential( + ctx, + data.StackID.ValueString(), + data.ResourceID.ValueString(), + ) + if err != nil { + resp.Diagnostics.AddError("Failed to get Azure credential", err.Error()) + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("name"), credential.Name) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("tenant_id"), credential.TenantID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("client_id"), credential.ClientID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *resourceAzureCredential) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var planData resourceAzureCredentialModel + diags := req.Plan.Get(ctx, &planData) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + credential := cloudproviderapi.AzureCredential{} + credential.Name = planData.Name.ValueString() + credential.TenantID = planData.TenantID.ValueString() + credential.ClientID = planData.ClientID.ValueString() + credential.ClientSecret = planData.ClientSecret.ValueString() + + credentialResponse, err := r.client.UpdateAzureCredential( + ctx, + planData.StackID.ValueString(), + planData.ResourceID.ValueString(), + credential, + ) + if err != nil { + resp.Diagnostics.AddError("Failed to update Azure credential", err.Error()) + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("name"), credentialResponse.Name) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("tenant_id"), credentialResponse.TenantID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("client_id"), credentialResponse.ClientID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.SetAttribute(ctx, path.Root("client_secret"), planData.ClientSecret) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *resourceAzureCredential) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data resourceAzureCredentialModel + diags := req.State.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteAzureCredential( + ctx, + data.StackID.ValueString(), + data.ResourceID.ValueString(), + ) + if err != nil { + resp.Diagnostics.AddError("Failed to delete Azure credential", err.Error()) + return + } + + resp.State.Set(ctx, nil) +} diff --git a/internal/resources/cloudprovider/resources.go b/internal/resources/cloudprovider/resources.go index 92dd09c81..1f8895fbb 100644 --- a/internal/resources/cloudprovider/resources.go +++ b/internal/resources/cloudprovider/resources.go @@ -13,11 +13,13 @@ var DataSources = []*common.DataSource{ makeDataSourceAWSAccount(), makeDatasourceAWSCloudWatchScrapeJob(), makeDatasourceAWSCloudWatchScrapeJobs(), + makeDataSourceAzureCredential(), } var Resources = []*common.Resource{ makeResourceAWSAccount(), makeResourceAWSCloudWatchScrapeJob(), + makeResourceAzureCredential(), } func withClientForResource(req resource.ConfigureRequest, resp *resource.ConfigureResponse) (*cloudproviderapi.Client, error) { From 6df460141d0b6c35636a68b65dc6397c09e0417d Mon Sep 17 00:00:00 2001 From: andriikushch Date: Thu, 14 Nov 2024 15:55:11 +0100 Subject: [PATCH 02/15] Add docs --- .../cloud_provider_azure_credential.md | 48 +++++++++++++++++++ .../cloud_provider_azure_credential.md | 47 ++++++++++++++++++ .../data-source.tf | 18 +++---- .../resource.tf | 8 ++-- 4 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 docs/data-sources/cloud_provider_azure_credential.md create mode 100644 docs/resources/cloud_provider_azure_credential.md diff --git a/docs/data-sources/cloud_provider_azure_credential.md b/docs/data-sources/cloud_provider_azure_credential.md new file mode 100644 index 000000000..0c5b7e621 --- /dev/null +++ b/docs/data-sources/cloud_provider_azure_credential.md @@ -0,0 +1,48 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "grafana_cloud_provider_azure_credential Data Source - terraform-provider-grafana" +subcategory: "Cloud Provider" +description: |- + +--- + +# grafana_cloud_provider_azure_credential (Data Source) + + + +## Example Usage + +```terraform +resource "grafana_cloud_provider_azure_credential" "test" { + stack_id = "1" + name = "test-name" + client_id = "my-client-id" + client_secret = "my-client-secret" + tenant_id = "my-tenant-id" +} + +data "grafana_cloud_provider_azure_credential" "test" { + stack_id = grafana_cloud_provider_azure_credential.test.stack_id + name = grafana_cloud_provider_azure_credential.test.name + client_id = grafana_cloud_provider_azure_credential.test.client_id + client_secret = grafana_cloud_provider_azure_credential.test.client_secret + tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id + resource_id = grafana_cloud_provider_azure_credential.test.resource_id +} +``` + + +## Schema + +### Required + +- `client_id` (String) The client ID of the Azure Credential. +- `client_secret` (String, Sensitive) The client secret of the Azure Credential. +- `name` (String) The name of the Azure Credential. +- `resource_id` (String) The ID given by the Grafana Cloud Provider API to this Azure Credential resource. +- `stack_id` (String) The StackID of the Grafana Cloud instance. Part of the Terraform Resource ID. +- `tenant_id` (String) The tenant ID of the Azure Credential. + +### Read-Only + +- `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ resource_id }}". diff --git a/docs/resources/cloud_provider_azure_credential.md b/docs/resources/cloud_provider_azure_credential.md new file mode 100644 index 000000000..52a9b6bd4 --- /dev/null +++ b/docs/resources/cloud_provider_azure_credential.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "grafana_cloud_provider_azure_credential Resource - terraform-provider-grafana" +subcategory: "Cloud Provider" +description: |- + +--- + +# grafana_cloud_provider_azure_credential (Resource) + + + +## Example Usage + +```terraform +resource "grafana_cloud_provider_azure_credential" "test" { + stack_id = "1" + name = "test-name" + client_id = "my-client-id" + client_secret = "my-client-secret" + tenant_id = "my-tenant-id" +} +``` + + +## Schema + +### Required + +- `client_id` (String) The client ID of the Azure Credential. +- `client_secret` (String, Sensitive) The client secret of the Azure Credential. +- `name` (String) The name of the Azure Credential. +- `stack_id` (String) The StackID of the Grafana Cloud instance. Part of the Terraform Resource ID. +- `tenant_id` (String) The tenant ID of the Azure Credential. + +### Read-Only + +- `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ resource_id }}". +- `resource_id` (String) The ID given by the Grafana Cloud Provider API to this AWS Account resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import grafana_cloud_provider_azure_credential.name "{{ stack_id }}:{{ resource_id }}" +``` diff --git a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf index 5bf23e4c1..c4c9e8414 100644 --- a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf +++ b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf @@ -1,16 +1,16 @@ resource "grafana_cloud_provider_azure_credential" "test" { - stack_id = "1" - name = "test-name" - client_id = "my-client-id" + stack_id = "1" + name = "test-name" + client_id = "my-client-id" client_secret = "my-client-secret" - tenant_id = "my-tenant-id" + tenant_id = "my-tenant-id" } data "grafana_cloud_provider_azure_credential" "test" { - stack_id = grafana_cloud_provider_azure_credential.test.stack_id - name = grafana_cloud_provider_azure_credential.test.name - client_id = grafana_cloud_provider_azure_credential.test.client_id + stack_id = grafana_cloud_provider_azure_credential.test.stack_id + name = grafana_cloud_provider_azure_credential.test.name + client_id = grafana_cloud_provider_azure_credential.test.client_id client_secret = grafana_cloud_provider_azure_credential.test.client_secret - tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id - resource_id = grafana_cloud_provider_azure_credential.test.resource_id + tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id + resource_id = grafana_cloud_provider_azure_credential.test.resource_id } \ No newline at end of file diff --git a/examples/resources/grafana_cloud_provider_azure_credential/resource.tf b/examples/resources/grafana_cloud_provider_azure_credential/resource.tf index 4c9a9447d..e670b7c87 100644 --- a/examples/resources/grafana_cloud_provider_azure_credential/resource.tf +++ b/examples/resources/grafana_cloud_provider_azure_credential/resource.tf @@ -1,7 +1,7 @@ resource "grafana_cloud_provider_azure_credential" "test" { - stack_id = "1" - name = "test-name" - client_id = "my-client-id" + stack_id = "1" + name = "test-name" + client_id = "my-client-id" client_secret = "my-client-secret" - tenant_id = "my-tenant-id" + tenant_id = "my-tenant-id" } From 822e7532041bb9597018568d0824116fadb57172 Mon Sep 17 00:00:00 2001 From: andriikushch Date: Thu, 14 Nov 2024 16:23:53 +0100 Subject: [PATCH 03/15] Disable gosec linter --- internal/resources/cloudprovider/resource_azure_credential.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/resources/cloudprovider/resource_azure_credential.go b/internal/resources/cloudprovider/resource_azure_credential.go index 6616977b9..b6afc3b56 100644 --- a/internal/resources/cloudprovider/resource_azure_credential.go +++ b/internal/resources/cloudprovider/resource_azure_credential.go @@ -13,6 +13,7 @@ import ( ) var ( + //nolint:gosec resourceAzureCredentialTerraformName = "grafana_cloud_provider_azure_credential" resourceAzureCredentialTerraformID = common.NewResourceID(common.StringIDField("stack_id"), common.StringIDField("resource_id")) ) From f97ecf6313c91d4e976fbce79abee1a3cf00f466 Mon Sep 17 00:00:00 2001 From: andriikushch Date: Thu, 14 Nov 2024 16:27:16 +0100 Subject: [PATCH 04/15] Fix import --- internal/resources/cloudprovider/resource_azure_credential.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/resources/cloudprovider/resource_azure_credential.go b/internal/resources/cloudprovider/resource_azure_credential.go index b6afc3b56..25bc1a0ff 100644 --- a/internal/resources/cloudprovider/resource_azure_credential.go +++ b/internal/resources/cloudprovider/resource_azure_credential.go @@ -2,6 +2,7 @@ package cloudprovider import ( "context" + "github.com/grafana/terraform-provider-grafana/v3/internal/common" "github.com/grafana/terraform-provider-grafana/v3/internal/common/cloudproviderapi" "github.com/hashicorp/terraform-plugin-framework/path" From d55486f02aa53a8b4659d22f8f08c210c944c743 Mon Sep 17 00:00:00 2001 From: andriikushch Date: Thu, 14 Nov 2024 16:48:57 +0100 Subject: [PATCH 05/15] Fix tests --- internal/resources/cloudprovider/azure_credential_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/resources/cloudprovider/azure_credential_test.go b/internal/resources/cloudprovider/azure_credential_test.go index 19074061f..bef09819e 100644 --- a/internal/resources/cloudprovider/azure_credential_test.go +++ b/internal/resources/cloudprovider/azure_credential_test.go @@ -56,6 +56,10 @@ func TestAcc_AzureCredential(t *testing.T) { server := httptest.NewServer(mux) defer server.Close() + defer os.Setenv("GRAFANA_CLOUD_PROVIDER_URL", os.Getenv("GRAFANA_CLOUD_PROVIDER_URL")) + defer os.Setenv("GRAFANA_CLOUD_PROVIDER_ACCESS_TOKEN", os.Getenv("GRAFANA_CLOUD_PROVIDER_ACCESS_TOKEN")) + + // set this to use the mock server _ = os.Setenv("GRAFANA_CLOUD_PROVIDER_URL", server.URL) _ = os.Setenv("GRAFANA_CLOUD_PROVIDER_ACCESS_TOKEN", "some_token") From 184378ca36aa643540b00583f4f53cf4fc72bd18 Mon Sep 17 00:00:00 2001 From: andriikushch Date: Fri, 15 Nov 2024 10:40:28 +0100 Subject: [PATCH 06/15] Add import --- .../resource_azure_credential.go | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/internal/resources/cloudprovider/resource_azure_credential.go b/internal/resources/cloudprovider/resource_azure_credential.go index 25bc1a0ff..db665a07d 100644 --- a/internal/resources/cloudprovider/resource_azure_credential.go +++ b/internal/resources/cloudprovider/resource_azure_credential.go @@ -2,6 +2,8 @@ package cloudprovider import ( "context" + "fmt" + "strings" "github.com/grafana/terraform-provider-grafana/v3/internal/common" "github.com/grafana/terraform-provider-grafana/v3/internal/common/cloudproviderapi" @@ -109,6 +111,36 @@ func (r *resourceAzureCredential) Schema(ctx context.Context, req resource.Schem } } +func (r *resourceAzureCredential) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + parts := strings.SplitN(req.ID, ":", 2) + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + resp.Diagnostics.AddError("Invalid ID", fmt.Sprintf("Invalid ID: %s", req.ID)) + return + } + stackID := parts[0] + resourceID := parts[1] + + credentials, err := r.client.GetAzureCredential( + ctx, + stackID, + resourceID, + ) + if err != nil { + resp.Diagnostics.AddError("Failed to get Azure credential", err.Error()) + return + } + + resp.State.Set(ctx, &resourceAzureCredentialModel{ + ID: types.StringValue(req.ID), + Name: types.StringValue(credentials.Name), + TenantID: types.StringValue(credentials.TenantID), + ClientID: types.StringValue(credentials.ClientID), + StackID: types.StringValue(stackID), + ResourceID: types.StringValue(resourceID), + ClientSecret: types.StringValue(""), // We don't import the client secret + }) +} + func (r *resourceAzureCredential) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data resourceAzureCredentialModel diags := req.Plan.Get(ctx, &data) From c2633d8e7749cb1dc77d3d3636a70450a9628189 Mon Sep 17 00:00:00 2001 From: andriikushch Date: Mon, 25 Nov 2024 12:19:01 +0100 Subject: [PATCH 07/15] Add tag filtering support --- .../cloud_provider_azure_credential.md | 32 +++++ .../cloud_provider_azure_credential.md | 22 ++++ .../data-source.tf | 20 +++ .../resource.tf | 10 ++ internal/common/cloudproviderapi/client.go | 8 ++ .../cloudprovider/azure_credential_test.go | 95 ++++++++++++--- .../data_source_azure_credential.go | 17 +++ .../resource_azure_credential.go | 114 ++++++++++++++---- 8 files changed, 273 insertions(+), 45 deletions(-) diff --git a/docs/data-sources/cloud_provider_azure_credential.md b/docs/data-sources/cloud_provider_azure_credential.md index 0c5b7e621..af7599d57 100644 --- a/docs/data-sources/cloud_provider_azure_credential.md +++ b/docs/data-sources/cloud_provider_azure_credential.md @@ -19,8 +19,18 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_id = "my-client-id" client_secret = "my-client-secret" tenant_id = "my-tenant-id" + + resource_tag_filter { + key = "key-1" + value = "value-1" + } + resource_tag_filter { + key = "key-2" + value = "value-2" + } } + data "grafana_cloud_provider_azure_credential" "test" { stack_id = grafana_cloud_provider_azure_credential.test.stack_id name = grafana_cloud_provider_azure_credential.test.name @@ -28,6 +38,16 @@ data "grafana_cloud_provider_azure_credential" "test" { client_secret = grafana_cloud_provider_azure_credential.test.client_secret tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id resource_id = grafana_cloud_provider_azure_credential.test.resource_id + + resource_tag_filter { + key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[0].key + value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[0].value + } + + resource_tag_filter { + key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].key + value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].value + } } ``` @@ -43,6 +63,18 @@ data "grafana_cloud_provider_azure_credential" "test" { - `stack_id` (String) The StackID of the Grafana Cloud instance. Part of the Terraform Resource ID. - `tenant_id` (String) The tenant ID of the Azure Credential. +### Optional + +- `resource_tag_filter` (Block List) The list of tag filters to apply to resources. (see [below for nested schema](#nestedblock--resource_tag_filter)) + ### Read-Only - `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ resource_id }}". + + +### Nested Schema for `resource_tag_filter` + +Required: + +- `key` (String) The key of the tag filter. +- `value` (String) The value of the tag filter. diff --git a/docs/resources/cloud_provider_azure_credential.md b/docs/resources/cloud_provider_azure_credential.md index 52a9b6bd4..a1e44746c 100644 --- a/docs/resources/cloud_provider_azure_credential.md +++ b/docs/resources/cloud_provider_azure_credential.md @@ -19,6 +19,16 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_id = "my-client-id" client_secret = "my-client-secret" tenant_id = "my-tenant-id" + + resource_tag_filter { + key = "key-1" + value = "value-1" + } + + resource_tag_filter { + key = "key-2" + value = "value-2" + } } ``` @@ -33,11 +43,23 @@ resource "grafana_cloud_provider_azure_credential" "test" { - `stack_id` (String) The StackID of the Grafana Cloud instance. Part of the Terraform Resource ID. - `tenant_id` (String) The tenant ID of the Azure Credential. +### Optional + +- `resource_tag_filter` (Block List) The list of tag filters to apply to resources. (see [below for nested schema](#nestedblock--resource_tag_filter)) + ### Read-Only - `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ resource_id }}". - `resource_id` (String) The ID given by the Grafana Cloud Provider API to this AWS Account resource. + +### Nested Schema for `resource_tag_filter` + +Required: + +- `key` (String) The key of the tag filter. +- `value` (String) The value of the tag filter. + ## Import Import is supported using the following syntax: diff --git a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf index c4c9e8414..4baddc5d7 100644 --- a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf +++ b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf @@ -4,8 +4,18 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_id = "my-client-id" client_secret = "my-client-secret" tenant_id = "my-tenant-id" + + resource_tag_filter { + key = "key-1" + value = "value-1" + } + resource_tag_filter { + key = "key-2" + value = "value-2" + } } + data "grafana_cloud_provider_azure_credential" "test" { stack_id = grafana_cloud_provider_azure_credential.test.stack_id name = grafana_cloud_provider_azure_credential.test.name @@ -13,4 +23,14 @@ data "grafana_cloud_provider_azure_credential" "test" { client_secret = grafana_cloud_provider_azure_credential.test.client_secret tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id resource_id = grafana_cloud_provider_azure_credential.test.resource_id + + resource_tag_filter { + key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[0].key + value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[0].value + } + + resource_tag_filter { + key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].key + value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].value + } } \ No newline at end of file diff --git a/examples/resources/grafana_cloud_provider_azure_credential/resource.tf b/examples/resources/grafana_cloud_provider_azure_credential/resource.tf index e670b7c87..aa2b6bf9c 100644 --- a/examples/resources/grafana_cloud_provider_azure_credential/resource.tf +++ b/examples/resources/grafana_cloud_provider_azure_credential/resource.tf @@ -4,4 +4,14 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_id = "my-client-id" client_secret = "my-client-secret" tenant_id = "my-tenant-id" + + resource_tag_filter { + key = "key-1" + value = "value-1" + } + + resource_tag_filter { + key = "key-2" + value = "value-2" + } } diff --git a/internal/common/cloudproviderapi/client.go b/internal/common/cloudproviderapi/client.go index 733d70416..3427d58ea 100644 --- a/internal/common/cloudproviderapi/client.go +++ b/internal/common/cloudproviderapi/client.go @@ -211,6 +211,14 @@ type AzureCredential struct { // StackID is the unique identifier for the stack in our systems. StackID string `json:"stack_id"` + + // ResourceTagFilters is the list of Azure resource tag filters. + ResourceTagFilters []TagFilter `json:"resource_tag_filters"` +} + +type TagFilter struct { + Key string `json:"key"` + Value string `json:"value"` } func (c *Client) CreateAzureCredential(ctx context.Context, stackID string, credentialData AzureCredential) (AzureCredential, error) { diff --git a/internal/resources/cloudprovider/azure_credential_test.go b/internal/resources/cloudprovider/azure_credential_test.go index bef09819e..4dc749479 100644 --- a/internal/resources/cloudprovider/azure_credential_test.go +++ b/internal/resources/cloudprovider/azure_credential_test.go @@ -20,16 +20,27 @@ func TestAcc_AzureCredential(t *testing.T) { if r.Method == http.MethodPost { w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(fmt.Sprintf(` - { - "data": { - "id": "%s", - "name": "test-name", - "tenant_id": "my_tenant_id", - "client_id": "my_client_id", - "client_secret": "", - "stack_id":"1" - } - }`, resourceID))) +{ + "data": { + "id": "%s", + "name": "test-name", + "tenant_id": "my_tenant_id", + "client_id": "my_client_id", + "client_secret": "", + "stack_id":"1", + "resource_tag_filters": [ + { + "key": "key-1", + "value": "value-1" + }, + { + "key": "key-2", + "value": "value-2" + } + + ] + } +}`, resourceID))) } }) @@ -38,16 +49,52 @@ func TestAcc_AzureCredential(t *testing.T) { case http.MethodGet: w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(fmt.Sprintf(` - { - "data": { - "id": "%s", - "name": "test-name", - "tenant_id": "my-tenant-id", - "client_id": "my-client-id", - "client_secret": "", - "stack_id":"1" - } - }`, resourceID))) +{ + "data": { + "id": "%s", + "name": "test-name", + "tenant_id": "my-tenant-id", + "client_id": "my-client-id", + "client_secret": "", + "stack_id":"1", + "resource_tag_filters": [ + { + "key": "key-1", + "value": "value-1" + }, + { + "key": "key-2", + "value": "value-2" + } + + ] + } +}`, resourceID))) + case http.MethodPut: + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(fmt.Sprintf(` +{ + "data": { + "id": "%s", + "name": "test-name", + "tenant_id": "my-tenant-id", + "client_id": "my-client-id", + "client_secret": "", + "stack_id":"1", + "resource_tag_filters": [ + { + "key": "key-1", + "value": "value-1" + }, + { + "key": "key-2", + "value": "value-2" + } + + ] + } +}`, resourceID))) + case http.MethodDelete: w.WriteHeader(http.StatusNoContent) } @@ -76,6 +123,10 @@ func TestAcc_AzureCredential(t *testing.T) { resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "tenant_id", "my-tenant-id"), resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "client_id", "my-client-id"), resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "client_secret", "my-client-secret"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_tag_filter.0.key", "key-1"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_tag_filter.1.key", "key-2"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_tag_filter.0.value", "value-1"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_tag_filter.1.value", "value-2"), ), }, { @@ -89,6 +140,10 @@ func TestAcc_AzureCredential(t *testing.T) { resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "client_id", "my-client-id"), resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "client_secret", ""), resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_id", resourceID), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_tag_filter.0.key", "key-1"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_tag_filter.1.key", "key-2"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_tag_filter.0.value", "value-1"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_tag_filter.1.value", "value-2"), ), }, }, diff --git a/internal/resources/cloudprovider/data_source_azure_credential.go b/internal/resources/cloudprovider/data_source_azure_credential.go index e6d164943..d074dc3ef 100644 --- a/internal/resources/cloudprovider/data_source_azure_credential.go +++ b/internal/resources/cloudprovider/data_source_azure_credential.go @@ -74,6 +74,23 @@ func (r *datasourceAzureCredential) Schema(ctx context.Context, req datasource.S Sensitive: true, }, }, + Blocks: map[string]schema.Block{ + "resource_tag_filter": schema.ListNestedBlock{ + Description: "The list of tag filters to apply to resources.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Description: "The key of the tag filter.", + Required: true, + }, + "value": schema.StringAttribute{ + Description: "The value of the tag filter.", + Required: true, + }, + }, + }, + }, + }, } } diff --git a/internal/resources/cloudprovider/resource_azure_credential.go b/internal/resources/cloudprovider/resource_azure_credential.go index db665a07d..d762429e5 100644 --- a/internal/resources/cloudprovider/resource_azure_credential.go +++ b/internal/resources/cloudprovider/resource_azure_credential.go @@ -21,14 +21,20 @@ var ( resourceAzureCredentialTerraformID = common.NewResourceID(common.StringIDField("stack_id"), common.StringIDField("resource_id")) ) +type TagFilter struct { + Key types.String `tfsdk:"key"` + Value types.String `tfsdk:"value"` +} + type resourceAzureCredentialModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - TenantID types.String `tfsdk:"tenant_id"` - ClientID types.String `tfsdk:"client_id"` - StackID types.String `tfsdk:"stack_id"` - ClientSecret types.String `tfsdk:"client_secret"` - ResourceID types.String `tfsdk:"resource_id"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + TenantID types.String `tfsdk:"tenant_id"` + ClientID types.String `tfsdk:"client_id"` + StackID types.String `tfsdk:"stack_id"` + ClientSecret types.String `tfsdk:"client_secret"` + ResourceID types.String `tfsdk:"resource_id"` + ResourceTagFilters []TagFilter `tfsdk:"resource_tag_filter"` } type resourceAzureCredential struct { @@ -108,6 +114,23 @@ func (r *resourceAzureCredential) Schema(ctx context.Context, req resource.Schem Sensitive: true, }, }, + Blocks: map[string]schema.Block{ + "resource_tag_filter": schema.ListNestedBlock{ + Description: "The list of tag filters to apply to resources.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Description: "The key of the tag filter.", + Required: true, + }, + "value": schema.StringAttribute{ + Description: "The value of the tag filter.", + Required: true, + }, + }, + }, + }, + }, } } @@ -131,13 +154,14 @@ func (r *resourceAzureCredential) ImportState(ctx context.Context, req resource. } resp.State.Set(ctx, &resourceAzureCredentialModel{ - ID: types.StringValue(req.ID), - Name: types.StringValue(credentials.Name), - TenantID: types.StringValue(credentials.TenantID), - ClientID: types.StringValue(credentials.ClientID), - StackID: types.StringValue(stackID), - ResourceID: types.StringValue(resourceID), - ClientSecret: types.StringValue(""), // We don't import the client secret + ID: types.StringValue(req.ID), + Name: types.StringValue(credentials.Name), + TenantID: types.StringValue(credentials.TenantID), + ClientID: types.StringValue(credentials.ClientID), + StackID: types.StringValue(stackID), + ResourceID: types.StringValue(resourceID), + ClientSecret: types.StringValue(""), // We don't import the client secret + ResourceTagFilters: r.convertTagFilters(credentials.ResourceTagFilters), }) } @@ -149,11 +173,20 @@ func (r *resourceAzureCredential) Create(ctx context.Context, req resource.Creat return } + var requestTagFilters []cloudproviderapi.TagFilter + for _, tagFilter := range data.ResourceTagFilters { + requestTagFilters = append(requestTagFilters, cloudproviderapi.TagFilter{ + Key: tagFilter.Key.ValueString(), + Value: tagFilter.Value.ValueString(), + }) + } + azureCredential := cloudproviderapi.AzureCredential{ - Name: data.Name.ValueString(), - TenantID: data.TenantID.ValueString(), - ClientID: data.ClientID.ValueString(), - ClientSecret: data.ClientSecret.ValueString(), + Name: data.Name.ValueString(), + TenantID: data.TenantID.ValueString(), + ClientID: data.ClientID.ValueString(), + ClientSecret: data.ClientSecret.ValueString(), + ResourceTagFilters: requestTagFilters, } credential, err := r.client.CreateAzureCredential( @@ -167,16 +200,28 @@ func (r *resourceAzureCredential) Create(ctx context.Context, req resource.Creat } resp.State.Set(ctx, &resourceAzureCredentialModel{ - ID: types.StringValue(resourceAzureCredentialTerraformID.Make(data.StackID.ValueString(), credential.ID)), - Name: data.Name, - TenantID: data.TenantID, - ClientID: data.ClientID, - StackID: data.StackID, - ClientSecret: data.ClientSecret, - ResourceID: types.StringValue(credential.ID), + ID: types.StringValue(resourceAzureCredentialTerraformID.Make(data.StackID.ValueString(), credential.ID)), + Name: data.Name, + TenantID: data.TenantID, + ClientID: data.ClientID, + StackID: data.StackID, + ClientSecret: data.ClientSecret, + ResourceID: types.StringValue(credential.ID), + ResourceTagFilters: data.ResourceTagFilters, }) } +func (r *resourceAzureCredential) convertTagFilters(apiTagFilters []cloudproviderapi.TagFilter) []TagFilter { + var tagFilters []TagFilter + for _, apiTagFilter := range apiTagFilters { + tagFilters = append(tagFilters, TagFilter{ + Key: types.StringValue(apiTagFilter.Key), + Value: types.StringValue(apiTagFilter.Value), + }) + } + return tagFilters +} + func (r *resourceAzureCredential) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var data resourceAzureCredentialModel diags := req.State.Get(ctx, &data) @@ -212,6 +257,12 @@ func (r *resourceAzureCredential) Read(ctx context.Context, req resource.ReadReq if resp.Diagnostics.HasError() { return } + + diags = resp.State.SetAttribute(ctx, path.Root("resource_tag_filter"), r.convertTagFilters(credential.ResourceTagFilters)) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } } func (r *resourceAzureCredential) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { @@ -227,6 +278,13 @@ func (r *resourceAzureCredential) Update(ctx context.Context, req resource.Updat credential.TenantID = planData.TenantID.ValueString() credential.ClientID = planData.ClientID.ValueString() credential.ClientSecret = planData.ClientSecret.ValueString() + credential.ResourceTagFilters = make([]cloudproviderapi.TagFilter, len(planData.ResourceTagFilters)) + for i, tagFilter := range planData.ResourceTagFilters { + credential.ResourceTagFilters[i] = cloudproviderapi.TagFilter{ + Key: tagFilter.Key.ValueString(), + Value: tagFilter.Value.ValueString(), + } + } credentialResponse, err := r.client.UpdateAzureCredential( ctx, @@ -262,6 +320,12 @@ func (r *resourceAzureCredential) Update(ctx context.Context, req resource.Updat if resp.Diagnostics.HasError() { return } + + diags = resp.State.SetAttribute(ctx, path.Root("resource_tag_filter"), r.convertTagFilters(credential.ResourceTagFilters)) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } } func (r *resourceAzureCredential) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { From a56d705780b4cabba2607cb96582b0ac48ab8d47 Mon Sep 17 00:00:00 2001 From: andriikushch Date: Mon, 25 Nov 2024 12:22:32 +0100 Subject: [PATCH 08/15] Fix format --- docs/data-sources/cloud_provider_azure_credential.md | 6 +++--- .../grafana_cloud_provider_azure_credential/data-source.tf | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/data-sources/cloud_provider_azure_credential.md b/docs/data-sources/cloud_provider_azure_credential.md index af7599d57..84442387c 100644 --- a/docs/data-sources/cloud_provider_azure_credential.md +++ b/docs/data-sources/cloud_provider_azure_credential.md @@ -20,7 +20,7 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_secret = "my-client-secret" tenant_id = "my-tenant-id" - resource_tag_filter { + resource_tag_filter { key = "key-1" value = "value-1" } @@ -45,8 +45,8 @@ data "grafana_cloud_provider_azure_credential" "test" { } resource_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].key - value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].value + key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].key + value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].value } } ``` diff --git a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf index 4baddc5d7..1a5e1e631 100644 --- a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf +++ b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf @@ -5,7 +5,7 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_secret = "my-client-secret" tenant_id = "my-tenant-id" - resource_tag_filter { + resource_tag_filter { key = "key-1" value = "value-1" } @@ -30,7 +30,7 @@ data "grafana_cloud_provider_azure_credential" "test" { } resource_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].key - value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].value + key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].key + value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].value } } \ No newline at end of file From 094e584d658503e9e69d525c2c2d31e7a9e99638 Mon Sep 17 00:00:00 2001 From: andriikushch Date: Wed, 11 Dec 2024 14:49:03 +0100 Subject: [PATCH 09/15] Remove comment --- internal/resources/cloudprovider/azure_credential_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/resources/cloudprovider/azure_credential_test.go b/internal/resources/cloudprovider/azure_credential_test.go index 4dc749479..6285b02b6 100644 --- a/internal/resources/cloudprovider/azure_credential_test.go +++ b/internal/resources/cloudprovider/azure_credential_test.go @@ -14,7 +14,6 @@ import ( // Tests both managed resource and data source func TestAcc_AzureCredential(t *testing.T) { resourceID := "3" - // Mock the Connections API response for Create, Get, and Delete mux := http.NewServeMux() mux.HandleFunc("/api/v2/stacks/1/azure/credentials", func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { From 0dca871edd43dc58d2cc2913ba0695b56d0ff5d7 Mon Sep 17 00:00:00 2001 From: andriikushch Date: Wed, 11 Dec 2024 16:53:55 +0100 Subject: [PATCH 10/15] Rename resource_tag_filter -> resource_discovery_tag_filter --- .../cloud_provider_azure_credential.md | 22 +++++++++---------- .../cloud_provider_azure_credential.md | 10 ++++----- .../data-source.tf | 16 +++++++------- .../resource.tf | 4 ++-- internal/common/cloudproviderapi/client.go | 2 +- .../cloudprovider/azure_credential_test.go | 22 +++++++++---------- .../data_source_azure_credential.go | 2 +- .../resource_azure_credential.go | 8 +++---- 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docs/data-sources/cloud_provider_azure_credential.md b/docs/data-sources/cloud_provider_azure_credential.md index 84442387c..1f72d8e5d 100644 --- a/docs/data-sources/cloud_provider_azure_credential.md +++ b/docs/data-sources/cloud_provider_azure_credential.md @@ -20,11 +20,11 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_secret = "my-client-secret" tenant_id = "my-tenant-id" - resource_tag_filter { + resource_discovery_tag_filter { key = "key-1" value = "value-1" } - resource_tag_filter { + resource_discovery_tag_filter { key = "key-2" value = "value-2" } @@ -39,14 +39,14 @@ data "grafana_cloud_provider_azure_credential" "test" { tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id resource_id = grafana_cloud_provider_azure_credential.test.resource_id - resource_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[0].key - value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[0].value + resource_discovery_tag_filter { + key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].key + value = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].value } - resource_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].key - value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].value + resource_discovery_tag_filter { + key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[1].key + value = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[1].value } } ``` @@ -65,14 +65,14 @@ data "grafana_cloud_provider_azure_credential" "test" { ### Optional -- `resource_tag_filter` (Block List) The list of tag filters to apply to resources. (see [below for nested schema](#nestedblock--resource_tag_filter)) +- `resource_discovery_tag_filter` (Block List) The list of tag filters to apply to resources. (see [below for nested schema](#nestedblock--resource_discovery_tag_filter)) ### Read-Only - `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ resource_id }}". - -### Nested Schema for `resource_tag_filter` + +### Nested Schema for `resource_discovery_tag_filter` Required: diff --git a/docs/resources/cloud_provider_azure_credential.md b/docs/resources/cloud_provider_azure_credential.md index a1e44746c..a70d32c07 100644 --- a/docs/resources/cloud_provider_azure_credential.md +++ b/docs/resources/cloud_provider_azure_credential.md @@ -20,12 +20,12 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_secret = "my-client-secret" tenant_id = "my-tenant-id" - resource_tag_filter { + resource_discovery_tag_filter { key = "key-1" value = "value-1" } - resource_tag_filter { + resource_discovery_tag_filter { key = "key-2" value = "value-2" } @@ -45,15 +45,15 @@ resource "grafana_cloud_provider_azure_credential" "test" { ### Optional -- `resource_tag_filter` (Block List) The list of tag filters to apply to resources. (see [below for nested schema](#nestedblock--resource_tag_filter)) +- `resource_discovery_tag_filter` (Block List) The list of tag filters to apply to resources. (see [below for nested schema](#nestedblock--resource_discovery_tag_filter)) ### Read-Only - `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ resource_id }}". - `resource_id` (String) The ID given by the Grafana Cloud Provider API to this AWS Account resource. - -### Nested Schema for `resource_tag_filter` + +### Nested Schema for `resource_discovery_tag_filter` Required: diff --git a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf index 1a5e1e631..5bc1105b5 100644 --- a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf +++ b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf @@ -5,11 +5,11 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_secret = "my-client-secret" tenant_id = "my-tenant-id" - resource_tag_filter { + resource_discovery_tag_filter { key = "key-1" value = "value-1" } - resource_tag_filter { + resource_discovery_tag_filter { key = "key-2" value = "value-2" } @@ -24,13 +24,13 @@ data "grafana_cloud_provider_azure_credential" "test" { tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id resource_id = grafana_cloud_provider_azure_credential.test.resource_id - resource_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[0].key - value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[0].value + resource_discovery_tag_filter { + key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].key + value = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].value } - resource_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].key - value = grafana_cloud_provider_azure_credential.test.resource_tag_filter[1].value + resource_discovery_tag_filter { + key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[1].key + value = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[1].value } } \ No newline at end of file diff --git a/examples/resources/grafana_cloud_provider_azure_credential/resource.tf b/examples/resources/grafana_cloud_provider_azure_credential/resource.tf index aa2b6bf9c..d81002162 100644 --- a/examples/resources/grafana_cloud_provider_azure_credential/resource.tf +++ b/examples/resources/grafana_cloud_provider_azure_credential/resource.tf @@ -5,12 +5,12 @@ resource "grafana_cloud_provider_azure_credential" "test" { client_secret = "my-client-secret" tenant_id = "my-tenant-id" - resource_tag_filter { + resource_discovery_tag_filter { key = "key-1" value = "value-1" } - resource_tag_filter { + resource_discovery_tag_filter { key = "key-2" value = "value-2" } diff --git a/internal/common/cloudproviderapi/client.go b/internal/common/cloudproviderapi/client.go index 3427d58ea..339f2469d 100644 --- a/internal/common/cloudproviderapi/client.go +++ b/internal/common/cloudproviderapi/client.go @@ -213,7 +213,7 @@ type AzureCredential struct { StackID string `json:"stack_id"` // ResourceTagFilters is the list of Azure resource tag filters. - ResourceTagFilters []TagFilter `json:"resource_tag_filters"` + ResourceTagFilters []TagFilter `json:"resource_discovery_tag_filters"` } type TagFilter struct { diff --git a/internal/resources/cloudprovider/azure_credential_test.go b/internal/resources/cloudprovider/azure_credential_test.go index 6285b02b6..e1598e9e6 100644 --- a/internal/resources/cloudprovider/azure_credential_test.go +++ b/internal/resources/cloudprovider/azure_credential_test.go @@ -27,7 +27,7 @@ func TestAcc_AzureCredential(t *testing.T) { "client_id": "my_client_id", "client_secret": "", "stack_id":"1", - "resource_tag_filters": [ + "resource_discovery_tag_filters": [ { "key": "key-1", "value": "value-1" @@ -56,7 +56,7 @@ func TestAcc_AzureCredential(t *testing.T) { "client_id": "my-client-id", "client_secret": "", "stack_id":"1", - "resource_tag_filters": [ + "resource_discovery_tag_filters": [ { "key": "key-1", "value": "value-1" @@ -80,7 +80,7 @@ func TestAcc_AzureCredential(t *testing.T) { "client_id": "my-client-id", "client_secret": "", "stack_id":"1", - "resource_tag_filters": [ + "resource_discovery_tag_filters": [ { "key": "key-1", "value": "value-1" @@ -122,10 +122,10 @@ func TestAcc_AzureCredential(t *testing.T) { resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "tenant_id", "my-tenant-id"), resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "client_id", "my-client-id"), resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "client_secret", "my-client-secret"), - resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_tag_filter.0.key", "key-1"), - resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_tag_filter.1.key", "key-2"), - resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_tag_filter.0.value", "value-1"), - resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_tag_filter.1.value", "value-2"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_discovery_tag_filter.0.key", "key-1"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_discovery_tag_filter.1.key", "key-2"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_discovery_tag_filter.0.value", "value-1"), + resource.TestCheckResourceAttr("grafana_cloud_provider_azure_credential.test", "resource_discovery_tag_filter.1.value", "value-2"), ), }, { @@ -139,10 +139,10 @@ func TestAcc_AzureCredential(t *testing.T) { resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "client_id", "my-client-id"), resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "client_secret", ""), resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_id", resourceID), - resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_tag_filter.0.key", "key-1"), - resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_tag_filter.1.key", "key-2"), - resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_tag_filter.0.value", "value-1"), - resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_tag_filter.1.value", "value-2"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_discovery_tag_filter.0.key", "key-1"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_discovery_tag_filter.1.key", "key-2"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_discovery_tag_filter.0.value", "value-1"), + resource.TestCheckResourceAttr("data.grafana_cloud_provider_azure_credential.test", "resource_discovery_tag_filter.1.value", "value-2"), ), }, }, diff --git a/internal/resources/cloudprovider/data_source_azure_credential.go b/internal/resources/cloudprovider/data_source_azure_credential.go index d074dc3ef..de4c2d3ce 100644 --- a/internal/resources/cloudprovider/data_source_azure_credential.go +++ b/internal/resources/cloudprovider/data_source_azure_credential.go @@ -75,7 +75,7 @@ func (r *datasourceAzureCredential) Schema(ctx context.Context, req datasource.S }, }, Blocks: map[string]schema.Block{ - "resource_tag_filter": schema.ListNestedBlock{ + "resource_discovery_tag_filter": schema.ListNestedBlock{ Description: "The list of tag filters to apply to resources.", NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ diff --git a/internal/resources/cloudprovider/resource_azure_credential.go b/internal/resources/cloudprovider/resource_azure_credential.go index d762429e5..957486734 100644 --- a/internal/resources/cloudprovider/resource_azure_credential.go +++ b/internal/resources/cloudprovider/resource_azure_credential.go @@ -34,7 +34,7 @@ type resourceAzureCredentialModel struct { StackID types.String `tfsdk:"stack_id"` ClientSecret types.String `tfsdk:"client_secret"` ResourceID types.String `tfsdk:"resource_id"` - ResourceTagFilters []TagFilter `tfsdk:"resource_tag_filter"` + ResourceTagFilters []TagFilter `tfsdk:"resource_discovery_tag_filter"` } type resourceAzureCredential struct { @@ -115,7 +115,7 @@ func (r *resourceAzureCredential) Schema(ctx context.Context, req resource.Schem }, }, Blocks: map[string]schema.Block{ - "resource_tag_filter": schema.ListNestedBlock{ + "resource_discovery_tag_filter": schema.ListNestedBlock{ Description: "The list of tag filters to apply to resources.", NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ @@ -258,7 +258,7 @@ func (r *resourceAzureCredential) Read(ctx context.Context, req resource.ReadReq return } - diags = resp.State.SetAttribute(ctx, path.Root("resource_tag_filter"), r.convertTagFilters(credential.ResourceTagFilters)) + diags = resp.State.SetAttribute(ctx, path.Root("resource_discovery_tag_filter"), r.convertTagFilters(credential.ResourceTagFilters)) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -321,7 +321,7 @@ func (r *resourceAzureCredential) Update(ctx context.Context, req resource.Updat return } - diags = resp.State.SetAttribute(ctx, path.Root("resource_tag_filter"), r.convertTagFilters(credential.ResourceTagFilters)) + diags = resp.State.SetAttribute(ctx, path.Root("resource_discovery_tag_filter"), r.convertTagFilters(credential.ResourceTagFilters)) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return From f536551830eb09d07558f0e0839085c79a5568fc Mon Sep 17 00:00:00 2001 From: andriikushch Date: Thu, 12 Dec 2024 15:08:13 +0100 Subject: [PATCH 11/15] Change Required to Computed for fields in DS --- .../cloudprovider/data_source_azure_credential.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/resources/cloudprovider/data_source_azure_credential.go b/internal/resources/cloudprovider/data_source_azure_credential.go index de4c2d3ce..c8c3ac16a 100644 --- a/internal/resources/cloudprovider/data_source_azure_credential.go +++ b/internal/resources/cloudprovider/data_source_azure_credential.go @@ -58,19 +58,19 @@ func (r *datasourceAzureCredential) Schema(ctx context.Context, req datasource.S }, "name": schema.StringAttribute{ Description: "The name of the Azure Credential.", - Required: true, + Computed: true, }, "client_id": schema.StringAttribute{ Description: "The client ID of the Azure Credential.", - Required: true, + Computed: true, }, "tenant_id": schema.StringAttribute{ Description: "The tenant ID of the Azure Credential.", - Required: true, + Computed: true, }, "client_secret": schema.StringAttribute{ Description: "The client secret of the Azure Credential.", - Required: true, + Computed: true, Sensitive: true, }, }, From f7fef2eb378cc91c32ed71bf384ae916ad71db65 Mon Sep 17 00:00:00 2001 From: andriikushch Date: Fri, 13 Dec 2024 13:48:40 +0100 Subject: [PATCH 12/15] Fix List type --- .../data-source.tf | 4 - .../resource_azure_credential.go | 80 +++++++++++++++---- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf index 5bc1105b5..e0d82bf8c 100644 --- a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf +++ b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf @@ -18,10 +18,6 @@ resource "grafana_cloud_provider_azure_credential" "test" { data "grafana_cloud_provider_azure_credential" "test" { stack_id = grafana_cloud_provider_azure_credential.test.stack_id - name = grafana_cloud_provider_azure_credential.test.name - client_id = grafana_cloud_provider_azure_credential.test.client_id - client_secret = grafana_cloud_provider_azure_credential.test.client_secret - tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id resource_id = grafana_cloud_provider_azure_credential.test.resource_id resource_discovery_tag_filter { diff --git a/internal/resources/cloudprovider/resource_azure_credential.go b/internal/resources/cloudprovider/resource_azure_credential.go index 957486734..c360c54ef 100644 --- a/internal/resources/cloudprovider/resource_azure_credential.go +++ b/internal/resources/cloudprovider/resource_azure_credential.go @@ -5,14 +5,17 @@ import ( "fmt" "strings" - "github.com/grafana/terraform-provider-grafana/v3/internal/common" - "github.com/grafana/terraform-provider-grafana/v3/internal/common/cloudproviderapi" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "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/types" + + "github.com/grafana/terraform-provider-grafana/v3/internal/common" + "github.com/grafana/terraform-provider-grafana/v3/internal/common/cloudproviderapi" ) var ( @@ -26,6 +29,13 @@ type TagFilter struct { Value types.String `tfsdk:"value"` } +func (tf TagFilter) attrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "key": types.StringType, + "value": types.StringType, + } +} + type resourceAzureCredentialModel struct { ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` @@ -34,7 +44,7 @@ type resourceAzureCredentialModel struct { StackID types.String `tfsdk:"stack_id"` ClientSecret types.String `tfsdk:"client_secret"` ResourceID types.String `tfsdk:"resource_id"` - ResourceTagFilters []TagFilter `tfsdk:"resource_discovery_tag_filter"` + ResourceTagFilters types.List `tfsdk:"resource_discovery_tag_filter"` } type resourceAzureCredential struct { @@ -153,6 +163,12 @@ func (r *resourceAzureCredential) ImportState(ctx context.Context, req resource. return } + tagFilters, diags := r.convertTagFilters(ctx, credentials.ResourceTagFilters) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + resp.State.Set(ctx, &resourceAzureCredentialModel{ ID: types.StringValue(req.ID), Name: types.StringValue(credentials.Name), @@ -161,7 +177,7 @@ func (r *resourceAzureCredential) ImportState(ctx context.Context, req resource. StackID: types.StringValue(stackID), ResourceID: types.StringValue(resourceID), ClientSecret: types.StringValue(""), // We don't import the client secret - ResourceTagFilters: r.convertTagFilters(credentials.ResourceTagFilters), + ResourceTagFilters: tagFilters, }) } @@ -174,7 +190,15 @@ func (r *resourceAzureCredential) Create(ctx context.Context, req resource.Creat } var requestTagFilters []cloudproviderapi.TagFilter - for _, tagFilter := range data.ResourceTagFilters { + + var tagFilters []TagFilter + diags = data.ResourceTagFilters.ElementsAs(ctx, &tagFilters, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + for _, tagFilter := range tagFilters { requestTagFilters = append(requestTagFilters, cloudproviderapi.TagFilter{ Key: tagFilter.Key.ValueString(), Value: tagFilter.Value.ValueString(), @@ -211,15 +235,24 @@ func (r *resourceAzureCredential) Create(ctx context.Context, req resource.Creat }) } -func (r *resourceAzureCredential) convertTagFilters(apiTagFilters []cloudproviderapi.TagFilter) []TagFilter { - var tagFilters []TagFilter - for _, apiTagFilter := range apiTagFilters { - tagFilters = append(tagFilters, TagFilter{ +func (r *resourceAzureCredential) convertTagFilters(ctx context.Context, apiTagFilters []cloudproviderapi.TagFilter) (types.List, diag.Diagnostics) { + tagFiltersTF := make([]TagFilter, len(apiTagFilters)) + conversionDiags := diag.Diagnostics{} + tagFilterListObjType := types.ObjectType{AttrTypes: TagFilter{}.attrTypes()} + + for i, apiTagFilter := range apiTagFilters { + tagFiltersTF[i] = TagFilter{ Key: types.StringValue(apiTagFilter.Key), Value: types.StringValue(apiTagFilter.Value), - }) + } + } + + tagFiltersTFList, diags := types.ListValueFrom(ctx, tagFilterListObjType, tagFiltersTF) + conversionDiags.Append(diags...) + if conversionDiags.HasError() { + return types.ListNull(tagFilterListObjType), conversionDiags } - return tagFilters + return tagFiltersTFList, conversionDiags } func (r *resourceAzureCredential) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { @@ -258,7 +291,9 @@ func (r *resourceAzureCredential) Read(ctx context.Context, req resource.ReadReq return } - diags = resp.State.SetAttribute(ctx, path.Root("resource_discovery_tag_filter"), r.convertTagFilters(credential.ResourceTagFilters)) + tagFilters, diags := r.convertTagFilters(ctx, credential.ResourceTagFilters) + resp.Diagnostics.Append(diags...) + diags = resp.State.SetAttribute(ctx, path.Root("resource_discovery_tag_filter"), tagFilters) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -278,8 +313,16 @@ func (r *resourceAzureCredential) Update(ctx context.Context, req resource.Updat credential.TenantID = planData.TenantID.ValueString() credential.ClientID = planData.ClientID.ValueString() credential.ClientSecret = planData.ClientSecret.ValueString() - credential.ResourceTagFilters = make([]cloudproviderapi.TagFilter, len(planData.ResourceTagFilters)) - for i, tagFilter := range planData.ResourceTagFilters { + + var tagFilters []TagFilter + diags = planData.ResourceTagFilters.ElementsAs(ctx, &tagFilters, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + credential.ResourceTagFilters = make([]cloudproviderapi.TagFilter, len(tagFilters)) + for i, tagFilter := range tagFilters { credential.ResourceTagFilters[i] = cloudproviderapi.TagFilter{ Key: tagFilter.Key.ValueString(), Value: tagFilter.Value.ValueString(), @@ -321,7 +364,14 @@ func (r *resourceAzureCredential) Update(ctx context.Context, req resource.Updat return } - diags = resp.State.SetAttribute(ctx, path.Root("resource_discovery_tag_filter"), r.convertTagFilters(credential.ResourceTagFilters)) + convertedTagFilters, diags := r.convertTagFilters(ctx, credential.ResourceTagFilters) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + diags = resp.State.SetAttribute(ctx, path.Root("resource_discovery_tag_filter"), convertedTagFilters) + resp.Diagnostics.Append(diags...) + resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return From c522eaf53e5f02dd1553e8ef246c8c44bef3b1cd Mon Sep 17 00:00:00 2001 From: andriikushch Date: Fri, 13 Dec 2024 13:49:52 +0100 Subject: [PATCH 13/15] fmt --- .../cloud_provider_azure_credential.md | 16 ++++++---------- .../data-source.tf | 4 ++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/data-sources/cloud_provider_azure_credential.md b/docs/data-sources/cloud_provider_azure_credential.md index 1f72d8e5d..31957a481 100644 --- a/docs/data-sources/cloud_provider_azure_credential.md +++ b/docs/data-sources/cloud_provider_azure_credential.md @@ -32,12 +32,8 @@ resource "grafana_cloud_provider_azure_credential" "test" { data "grafana_cloud_provider_azure_credential" "test" { - stack_id = grafana_cloud_provider_azure_credential.test.stack_id - name = grafana_cloud_provider_azure_credential.test.name - client_id = grafana_cloud_provider_azure_credential.test.client_id - client_secret = grafana_cloud_provider_azure_credential.test.client_secret - tenant_id = grafana_cloud_provider_azure_credential.test.tenant_id - resource_id = grafana_cloud_provider_azure_credential.test.resource_id + stack_id = grafana_cloud_provider_azure_credential.test.stack_id + resource_id = grafana_cloud_provider_azure_credential.test.resource_id resource_discovery_tag_filter { key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].key @@ -56,12 +52,8 @@ data "grafana_cloud_provider_azure_credential" "test" { ### Required -- `client_id` (String) The client ID of the Azure Credential. -- `client_secret` (String, Sensitive) The client secret of the Azure Credential. -- `name` (String) The name of the Azure Credential. - `resource_id` (String) The ID given by the Grafana Cloud Provider API to this Azure Credential resource. - `stack_id` (String) The StackID of the Grafana Cloud instance. Part of the Terraform Resource ID. -- `tenant_id` (String) The tenant ID of the Azure Credential. ### Optional @@ -69,7 +61,11 @@ data "grafana_cloud_provider_azure_credential" "test" { ### Read-Only +- `client_id` (String) The client ID of the Azure Credential. +- `client_secret` (String, Sensitive) The client secret of the Azure Credential. - `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ resource_id }}". +- `name` (String) The name of the Azure Credential. +- `tenant_id` (String) The tenant ID of the Azure Credential. ### Nested Schema for `resource_discovery_tag_filter` diff --git a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf index e0d82bf8c..ca38f0a7d 100644 --- a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf +++ b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf @@ -17,8 +17,8 @@ resource "grafana_cloud_provider_azure_credential" "test" { data "grafana_cloud_provider_azure_credential" "test" { - stack_id = grafana_cloud_provider_azure_credential.test.stack_id - resource_id = grafana_cloud_provider_azure_credential.test.resource_id + stack_id = grafana_cloud_provider_azure_credential.test.stack_id + resource_id = grafana_cloud_provider_azure_credential.test.resource_id resource_discovery_tag_filter { key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].key From 30cf77c7243d60946e035e38216f26cf234428bb Mon Sep 17 00:00:00 2001 From: andriikushch Date: Fri, 13 Dec 2024 14:17:52 +0100 Subject: [PATCH 14/15] fix json field name --- internal/common/cloudproviderapi/client.go | 2 +- internal/resources/cloudprovider/azure_credential_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/common/cloudproviderapi/client.go b/internal/common/cloudproviderapi/client.go index 339f2469d..3427d58ea 100644 --- a/internal/common/cloudproviderapi/client.go +++ b/internal/common/cloudproviderapi/client.go @@ -213,7 +213,7 @@ type AzureCredential struct { StackID string `json:"stack_id"` // ResourceTagFilters is the list of Azure resource tag filters. - ResourceTagFilters []TagFilter `json:"resource_discovery_tag_filters"` + ResourceTagFilters []TagFilter `json:"resource_tag_filters"` } type TagFilter struct { diff --git a/internal/resources/cloudprovider/azure_credential_test.go b/internal/resources/cloudprovider/azure_credential_test.go index e1598e9e6..bec29d03c 100644 --- a/internal/resources/cloudprovider/azure_credential_test.go +++ b/internal/resources/cloudprovider/azure_credential_test.go @@ -27,7 +27,7 @@ func TestAcc_AzureCredential(t *testing.T) { "client_id": "my_client_id", "client_secret": "", "stack_id":"1", - "resource_discovery_tag_filters": [ + "resource_tag_filters": [ { "key": "key-1", "value": "value-1" @@ -56,7 +56,7 @@ func TestAcc_AzureCredential(t *testing.T) { "client_id": "my-client-id", "client_secret": "", "stack_id":"1", - "resource_discovery_tag_filters": [ + "resource_tag_filters": [ { "key": "key-1", "value": "value-1" @@ -80,7 +80,7 @@ func TestAcc_AzureCredential(t *testing.T) { "client_id": "my-client-id", "client_secret": "", "stack_id":"1", - "resource_discovery_tag_filters": [ + "resource_tag_filters": [ { "key": "key-1", "value": "value-1" From 9550f5164cbf6347fcef823a526c8e3aa267cdfa Mon Sep 17 00:00:00 2001 From: andriikushch Date: Wed, 18 Dec 2024 13:39:32 +0100 Subject: [PATCH 15/15] apply comments from the PR review --- .../cloud_provider_azure_credential.md | 17 +-------- .../data-source.tf | 10 ----- .../data_source_azure_credential.go | 37 ++++++++++++++++++- .../resource_azure_credential.go | 2 - 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/docs/data-sources/cloud_provider_azure_credential.md b/docs/data-sources/cloud_provider_azure_credential.md index 31957a481..4fc9f88ac 100644 --- a/docs/data-sources/cloud_provider_azure_credential.md +++ b/docs/data-sources/cloud_provider_azure_credential.md @@ -34,16 +34,6 @@ resource "grafana_cloud_provider_azure_credential" "test" { data "grafana_cloud_provider_azure_credential" "test" { stack_id = grafana_cloud_provider_azure_credential.test.stack_id resource_id = grafana_cloud_provider_azure_credential.test.resource_id - - resource_discovery_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].key - value = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].value - } - - resource_discovery_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[1].key - value = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[1].value - } } ``` @@ -55,22 +45,19 @@ data "grafana_cloud_provider_azure_credential" "test" { - `resource_id` (String) The ID given by the Grafana Cloud Provider API to this Azure Credential resource. - `stack_id` (String) The StackID of the Grafana Cloud instance. Part of the Terraform Resource ID. -### Optional - -- `resource_discovery_tag_filter` (Block List) The list of tag filters to apply to resources. (see [below for nested schema](#nestedblock--resource_discovery_tag_filter)) - ### Read-Only - `client_id` (String) The client ID of the Azure Credential. - `client_secret` (String, Sensitive) The client secret of the Azure Credential. - `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ resource_id }}". - `name` (String) The name of the Azure Credential. +- `resource_discovery_tag_filter` (Block List) The list of tag filters to apply to resources. (see [below for nested schema](#nestedblock--resource_discovery_tag_filter)) - `tenant_id` (String) The tenant ID of the Azure Credential. ### Nested Schema for `resource_discovery_tag_filter` -Required: +Read-Only: - `key` (String) The key of the tag filter. - `value` (String) The value of the tag filter. diff --git a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf index ca38f0a7d..6bbaabb7f 100644 --- a/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf +++ b/examples/data-sources/grafana_cloud_provider_azure_credential/data-source.tf @@ -19,14 +19,4 @@ resource "grafana_cloud_provider_azure_credential" "test" { data "grafana_cloud_provider_azure_credential" "test" { stack_id = grafana_cloud_provider_azure_credential.test.stack_id resource_id = grafana_cloud_provider_azure_credential.test.resource_id - - resource_discovery_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].key - value = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[0].value - } - - resource_discovery_tag_filter { - key = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[1].key - value = grafana_cloud_provider_azure_credential.test.resource_discovery_tag_filter[1].value - } } \ No newline at end of file diff --git a/internal/resources/cloudprovider/data_source_azure_credential.go b/internal/resources/cloudprovider/data_source_azure_credential.go index c8c3ac16a..97fd6d170 100644 --- a/internal/resources/cloudprovider/data_source_azure_credential.go +++ b/internal/resources/cloudprovider/data_source_azure_credential.go @@ -3,6 +3,8 @@ package cloudprovider import ( "context" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/grafana/terraform-provider-grafana/v3/internal/common" "github.com/grafana/terraform-provider-grafana/v3/internal/common/cloudproviderapi" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -81,11 +83,11 @@ func (r *datasourceAzureCredential) Schema(ctx context.Context, req datasource.S Attributes: map[string]schema.Attribute{ "key": schema.StringAttribute{ Description: "The key of the tag filter.", - Required: true, + Computed: true, }, "value": schema.StringAttribute{ Description: "The value of the tag filter.", - Required: true, + Computed: true, }, }, }, @@ -141,4 +143,35 @@ func (r *datasourceAzureCredential) Read(ctx context.Context, req datasource.Rea if resp.Diagnostics.HasError() { return } + + convertedTagFilters, diags := r.convertTagFilters(ctx, credential.ResourceTagFilters) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + diags = resp.State.SetAttribute(ctx, path.Root("resource_discovery_tag_filter"), convertedTagFilters) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *datasourceAzureCredential) convertTagFilters(ctx context.Context, apiTagFilters []cloudproviderapi.TagFilter) (types.List, diag.Diagnostics) { + tagFiltersTF := make([]TagFilter, len(apiTagFilters)) + conversionDiags := diag.Diagnostics{} + tagFilterListObjType := types.ObjectType{AttrTypes: TagFilter{}.attrTypes()} + + for i, apiTagFilter := range apiTagFilters { + tagFiltersTF[i] = TagFilter{ + Key: types.StringValue(apiTagFilter.Key), + Value: types.StringValue(apiTagFilter.Value), + } + } + + tagFiltersTFList, diags := types.ListValueFrom(ctx, tagFilterListObjType, tagFiltersTF) + conversionDiags.Append(diags...) + if conversionDiags.HasError() { + return types.ListNull(tagFilterListObjType), conversionDiags + } + return tagFiltersTFList, conversionDiags } diff --git a/internal/resources/cloudprovider/resource_azure_credential.go b/internal/resources/cloudprovider/resource_azure_credential.go index c360c54ef..802a2145d 100644 --- a/internal/resources/cloudprovider/resource_azure_credential.go +++ b/internal/resources/cloudprovider/resource_azure_credential.go @@ -370,8 +370,6 @@ func (r *resourceAzureCredential) Update(ctx context.Context, req resource.Updat return } diags = resp.State.SetAttribute(ctx, path.Root("resource_discovery_tag_filter"), convertedTagFilters) - resp.Diagnostics.Append(diags...) - resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return