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

Implement terraform Resource methods on resourceMetricsEndpointScrapeJob #1812

Open
wants to merge 11 commits into
base: metrics-endpoint
Choose a base branch
from
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ resource "grafana_oncall_escalation" "example_notify_step" {
- `ca_cert` (String) Certificate CA bundle (file path or literal value) to use to verify the Grafana server's certificate. May alternatively be set via the `GRAFANA_CA_CERT` environment variable.
- `cloud_access_policy_token` (String, Sensitive) Access Policy Token for Grafana Cloud. May alternatively be set via the `GRAFANA_CLOUD_ACCESS_POLICY_TOKEN` environment variable.
- `cloud_api_url` (String) Grafana Cloud's API URL. May alternatively be set via the `GRAFANA_CLOUD_API_URL` environment variable.
- `connections_access_token` (String, Sensitive) A Grafana Connections API access token. May alternatively be set via the `GRAFANA_CONNECTIONS_ACCESS_TOKEN` environment variable.
- `connections_url` (String) A Grafana Connections API backend address. May alternatively be set via the `GRAFANA_CONNECTIONS_URL` environment variable.
- `http_headers` (Map of String, Sensitive) Optional. HTTP headers mapping keys to values used for accessing the Grafana and Grafana Cloud APIs. May alternatively be set via the `GRAFANA_HTTP_HEADERS` environment variable in JSON format.
- `insecure_skip_verify` (Boolean) Skip TLS certificate verification. May alternatively be set via the `GRAFANA_INSECURE_SKIP_VERIFY` environment variable.
- `oncall_access_token` (String, Sensitive) A Grafana OnCall access token. May alternatively be set via the `GRAFANA_ONCALL_ACCESS_TOKEN` environment variable.
Expand Down
54 changes: 54 additions & 0 deletions docs/resources/connections_metrics_endpoint_scrape_job.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "grafana_connections_metrics_endpoint_scrape_job Resource - terraform-provider-grafana"
subcategory: "Connections"
description: |-

---

# grafana_connections_metrics_endpoint_scrape_job (Resource)



## Example Usage

```terraform
resource "grafana_connections_metrics_endpoint_scrape_job" "test" {
stack_id = "1"
name = "scrape-job-name"
authentication_method = "basic"
authentication_basic_username = "my-username"
authentication_basic_password = "my-password"
url = "https://dev.my-metrics-endpoint-url.com:9000/metrics"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `authentication_method` (String) Method to pass authentication credentials: basic or bearer.
- `name` (String) The name of the metrics endpoint scrape job. Part of the Terraform Resource ID.
- `stack_id` (String) The Stack ID of the Grafana Cloud instance. Part of the Terraform Resource ID.
- `url` (String) The url to scrape metrics; a valid HTTPs URL is required.

### Optional

- `authentication_basic_password` (String, Sensitive) Password for basic authentication.
- `authentication_basic_username` (String) Username for basic authentication.
- `authentication_bearer_token` (String, Sensitive) Token for authentication bearer.
- `enabled` (Boolean) Whether the metrics endpoint scrape job is enabled or not.
- `scrape_interval_seconds` (Number) Frequency for scraping the metrics endpoint: 30, 60, or 120 seconds.

### Read-Only

- `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ name }}".

## Import

Import is supported using the following syntax:

```shell
terraform import grafana_connections_metrics_endpoint_scrape_job.name "{{ stack_id }}:{{ name }}"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import grafana_connections_metrics_endpoint_scrape_job.name "{{ stack_id }}:{{ name }}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "grafana_connections_metrics_endpoint_scrape_job" "test" {
stack_id = "1"
name = "scrape-job-name"
authentication_method = "basic"
authentication_basic_username = "my-username"
authentication_basic_password = "my-password"
url = "https://dev.my-metrics-endpoint-url.com:9000/metrics"
}
3 changes: 0 additions & 3 deletions internal/common/connectionsapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ const (

func NewClient(authToken string, rawURL string, client *http.Client) (*Client, error) {
parsedURL, err := url.Parse(rawURL)
if parsedURL.Scheme != "https" {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this check in order to mock with normal httptest server in acceptance tests.

return nil, fmt.Errorf("https URL scheme expected, provided %q", parsedURL.Scheme)
}

if err != nil {
return nil, fmt.Errorf("failed to parse connections API url: %w", err)
Expand Down
66 changes: 66 additions & 0 deletions internal/resources/connections/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package connections

import (
"github.com/grafana/terraform-provider-grafana/v3/internal/common/connectionsapi"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type metricsEndpointScrapeJobTFModel struct {
ID types.String `tfsdk:"id"`
StackID types.String `tfsdk:"stack_id"`
Name types.String `tfsdk:"name"`
Enabled types.Bool `tfsdk:"enabled"`
AuthenticationMethod types.String `tfsdk:"authentication_method"`
AuthenticationBearerToken types.String `tfsdk:"authentication_bearer_token"`
AuthenticationBasicUsername types.String `tfsdk:"authentication_basic_username"`
AuthenticationBasicPassword types.String `tfsdk:"authentication_basic_password"`
URL types.String `tfsdk:"url"`
ScrapeIntervalSeconds types.Int64 `tfsdk:"scrape_interval_seconds"`
}

// convertJobTFModelToClientModel converts a metricsEndpointScrapeJobTFModel instance to a connectionsapi.MetricsEndpointScrapeJob instance.
// A special converter is needed because the TFModel uses special Terraform types that build upon their underlying Go types for
// supporting Terraform's state management/dependency analysis of the resource and its data.
func convertJobTFModelToClientModel(tfData metricsEndpointScrapeJobTFModel) connectionsapi.MetricsEndpointScrapeJob {
return connectionsapi.MetricsEndpointScrapeJob{
Name: tfData.Name.ValueString(),
Enabled: tfData.Enabled.ValueBool(),
AuthenticationMethod: tfData.AuthenticationMethod.ValueString(),
AuthenticationBearerToken: tfData.AuthenticationBearerToken.ValueString(),
AuthenticationBasicUsername: tfData.AuthenticationBasicUsername.ValueString(),
AuthenticationBasicPassword: tfData.AuthenticationBasicPassword.ValueString(),
URL: tfData.URL.ValueString(),
ScrapeIntervalSeconds: tfData.ScrapeIntervalSeconds.ValueInt64(),
}
}

// convertClientModelToTFModel converts a connectionsapi.MetricsEndpointScrapeJob instance to a metricsEndpointScrapeJobTFModel instance.
// A special converter is needed because the TFModel uses special Terraform types that build upon their underlying Go types for
// supporting Terraform's state management/dependency analysis of the resource and its data.
func convertClientModelToTFModel(stackID string, scrapeJobData connectionsapi.MetricsEndpointScrapeJob) metricsEndpointScrapeJobTFModel {
resp := metricsEndpointScrapeJobTFModel{
ID: types.StringValue(resourceMetricsEndpointScrapeJobTerraformID.Make(stackID, scrapeJobData.Name)),
StackID: types.StringValue(stackID),
Name: types.StringValue(scrapeJobData.Name),
Enabled: types.BoolValue(scrapeJobData.Enabled),
AuthenticationMethod: types.StringValue(scrapeJobData.AuthenticationMethod),
URL: types.StringValue(scrapeJobData.URL),
ScrapeIntervalSeconds: types.Int64Value(scrapeJobData.ScrapeIntervalSeconds),
}

resp.fillOptionalFieldsIfNotEmpty(scrapeJobData)

return resp
}

func (m *metricsEndpointScrapeJobTFModel) fillOptionalFieldsIfNotEmpty(scrapeJobData connectionsapi.MetricsEndpointScrapeJob) {
if scrapeJobData.AuthenticationBearerToken != "" {
m.AuthenticationBearerToken = types.StringValue(scrapeJobData.AuthenticationBearerToken)
}
if scrapeJobData.AuthenticationBasicUsername != "" {
m.AuthenticationBasicUsername = types.StringValue(scrapeJobData.AuthenticationBasicUsername)
}
if scrapeJobData.AuthenticationBasicPassword != "" {
m.AuthenticationBasicPassword = types.StringValue(scrapeJobData.AuthenticationBasicPassword)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/grafana/terraform-provider-grafana/v3/internal/common/connectionsapi"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
Expand Down Expand Up @@ -66,7 +67,7 @@ func withClientForResource(req resource.ConfigureRequest, resp *resource.Configu

if client.ConnectionsAPIClient == nil {
resp.Diagnostics.AddError(
"The Grafana Provider is missing a configuration for the Metrics Endpoint API.",
"The Grafana Provider is missing a configuration for the Connections API.",
"Please ensure that connections_url and connections_access_token are set in the provider configuration.",
)

Expand Down Expand Up @@ -100,14 +101,14 @@ func (r *resourceMetricsEndpointScrapeJob) Schema(ctx context.Context, req resou
},
},
"name": schema.StringAttribute{
Description: "The name of the Metrics Endpoint Scrape Job. Part of the Terraform Resource ID.",
Description: "The name of the metrics endpoint scrape job. Part of the Terraform Resource ID.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"enabled": schema.BoolAttribute{
Description: "Whether the Metrics Endpoint Scrape Job is enabled or not.",
Description: "Whether the metrics endpoint scrape job is enabled or not.",
Optional: true,
Computed: true,
Default: booldefault.StaticBool(true),
Expand Down Expand Up @@ -150,23 +151,89 @@ func (r *resourceMetricsEndpointScrapeJob) Schema(ctx context.Context, req resou
}

func (r *resourceMetricsEndpointScrapeJob) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// TODO implement me
panic("implement me")
var dataTF metricsEndpointScrapeJobTFModel
diags := req.Plan.Get(ctx, &dataTF)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

jobClientModel, err := r.client.CreateMetricsEndpointScrapeJob(ctx, dataTF.StackID.ValueString(),
convertJobTFModelToClientModel(dataTF))
if err != nil {
resp.Diagnostics.AddError("Failed to create metrics endpoint scrape job", err.Error())
return
}

resp.State.Set(ctx, convertClientModelToTFModel(dataTF.StackID.ValueString(), jobClientModel))
}

func (r *resourceMetricsEndpointScrapeJob) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
// TODO implement me
panic("implement me")
var dataTF metricsEndpointScrapeJobTFModel
diags := req.State.Get(ctx, &dataTF)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

jobClientModel, err := r.client.GetMetricsEndpointScrapeJob(
ctx,
dataTF.StackID.ValueString(),
dataTF.Name.ValueString(),
)
if err != nil {
resp.Diagnostics.AddError("Failed to get metrics endpoint scrape job", err.Error())
return
}

jobTF := convertClientModelToTFModel(dataTF.StackID.ValueString(), jobClientModel)

// Set only non-sensitive attributes
resp.State.SetAttribute(ctx, path.Root("stack_id"), jobTF.StackID)
resp.State.SetAttribute(ctx, path.Root("name"), jobTF.Name)
resp.State.SetAttribute(ctx, path.Root("enabled"), jobTF.Enabled)
resp.State.SetAttribute(ctx, path.Root("authentication_method"), jobTF.AuthenticationMethod)
resp.State.SetAttribute(ctx, path.Root("url"), jobTF.URL)
resp.State.SetAttribute(ctx, path.Root("scrape_interval_seconds"), jobTF.ScrapeIntervalSeconds)
}

func (r *resourceMetricsEndpointScrapeJob) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// TODO implement me
panic("implement me")
var dataTF metricsEndpointScrapeJobTFModel
diags := req.Plan.Get(ctx, &dataTF)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

jobClientModel, err := r.client.UpdateMetricsEndpointScrapeJob(ctx, dataTF.StackID.ValueString(), dataTF.Name.ValueString(),
convertJobTFModelToClientModel(dataTF))
if err != nil {
resp.Diagnostics.AddError("Failed to update metrics endpoint scrape job", err.Error())
return
}

resp.State.Set(ctx, convertClientModelToTFModel(dataTF.StackID.ValueString(), jobClientModel))
}

func (r *resourceMetricsEndpointScrapeJob) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
// TODO implement me
panic("implement me")
var dataTF metricsEndpointScrapeJobTFModel
diags := req.State.Get(ctx, &dataTF)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

err := r.client.DeleteMetricsEndpointScrapeJob(
ctx,
dataTF.StackID.ValueString(),
dataTF.Name.ValueString(),
)
if err != nil {
resp.Diagnostics.AddError("Failed to delete metrics endpoint scrape job", err.Error())
return
}

resp.State.Set(ctx, nil)
}

type HTTPSURLValidator struct{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,86 @@ package connections_test

import (
"context"
"net/http"
"net/http/httptest"
"os"
"testing"

"github.com/grafana/terraform-provider-grafana/v3/internal/resources/connections"
"github.com/grafana/terraform-provider-grafana/v3/internal/testutils"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAcc_MetricsEndpointScrapeJob(t *testing.T) {
// Mock the Connections API response for Create, Get, and Delete
mux := http.NewServeMux()
mux.HandleFunc("/api/v1/metrics-endpoint/stacks/1/jobs/scrape-job-name", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(`
{
"data":{
"name":"scrape-job-name",
"authentication_method":"basic",
"basic_username":"my-username",
"basic_password":"my-password",
"url":"https://dev.my-metrics-endpoint-url.com:9000/metrics",
"scrape_interval_seconds":60,
"flavor":"default",
"enabled":true
}
}`))
case http.MethodGet:
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`
{
"data":{
"name":"scrape-job-name",
"authentication_method":"basic",
"url":"https://dev.my-metrics-endpoint-url.com:9000/metrics",
"scrape_interval_seconds":60,
"flavor":"default",
"enabled":true
}
}`))
case http.MethodDelete:
w.WriteHeader(http.StatusNoContent)
}
})

server := httptest.NewServer(mux)
defer server.Close()

require.NoError(t, os.Setenv("GRAFANA_CONNECTIONS_ACCESS_TOKEN", "some token"))
require.NoError(t, os.Setenv("GRAFANA_CONNECTIONS_URL", server.URL))

resource.ParallelTest(t, resource.TestCase{
ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: testutils.TestAccExample(t, "resources/grafana_connections_metrics_endpoint_scrape_job/resource.tf"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("grafana_connections_metrics_endpoint_scrape_job.test", "stack_id", "1"),
resource.TestCheckResourceAttr("grafana_connections_metrics_endpoint_scrape_job.test", "name", "scrape-job-name"),
resource.TestCheckResourceAttr("grafana_connections_metrics_endpoint_scrape_job.test", "enabled", "true"),
resource.TestCheckResourceAttr("grafana_connections_metrics_endpoint_scrape_job.test", "authentication_method", "basic"),
resource.TestCheckResourceAttr("grafana_connections_metrics_endpoint_scrape_job.test", "authentication_basic_username", "my-username"),
resource.TestCheckResourceAttr("grafana_connections_metrics_endpoint_scrape_job.test", "authentication_basic_password", "my-password"),
resource.TestCheckResourceAttr("grafana_connections_metrics_endpoint_scrape_job.test", "url", "https://dev.my-metrics-endpoint-url.com:9000/metrics"),
resource.TestCheckResourceAttr("grafana_connections_metrics_endpoint_scrape_job.test", "scrape_interval_seconds", "60"),
),
},
},
})
}

func Test_httpsURLValidator(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
Expand Down
6 changes: 6 additions & 0 deletions internal/resources/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ func TestAccExamples(t *testing.T) {
testutils.CheckCloudInstanceTestsEnabled(t)
},
},
{
category: "Connections",
testCheck: func(t *testing.T, filename string) {
t.Skip() // TODO: Make all examples work
},
},
} {
// Get all the filenames for all resource examples for this category
filenames := []string{}
Expand Down
Loading