-
Notifications
You must be signed in to change notification settings - Fork 237
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for metrics endpoint scrape jobs (#1862)
This PR adds resources to manage "metrics endpoint scrape jobs" . Metrics Endpoint enables customers to configure Grafana Cloud managed agents to scrape their publicly addressable metrics endpoint
- Loading branch information
1 parent
63fbba4
commit ad25130
Showing
24 changed files
with
1,706 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
docs/data-sources/connections_metrics_endpoint_scrape_job.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "grafana_connections_metrics_endpoint_scrape_job Data Source - terraform-provider-grafana" | ||
subcategory: "Connections" | ||
description: |- | ||
--- | ||
|
||
# grafana_connections_metrics_endpoint_scrape_job (Data Source) | ||
|
||
|
||
|
||
## Example Usage | ||
|
||
```terraform | ||
data "grafana_connections_metrics_endpoint_scrape_job" "ds_test" { | ||
stack_id = "1" | ||
name = "my-scrape-job" | ||
} | ||
``` | ||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `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. | ||
|
||
### Read-Only | ||
|
||
- `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. | ||
- `authentication_method` (String) Method to pass authentication credentials: basic or bearer. | ||
- `enabled` (Boolean) Whether the metrics endpoint scrape job is enabled or not. | ||
- `id` (String) The Terraform Resource ID. This has the format "{{ stack_id }}:{{ name }}". | ||
- `scrape_interval_seconds` (Number) Frequency for scraping the metrics endpoint: 30, 60, or 120 seconds. | ||
- `url` (String) The url to scrape metrics. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
--- | ||
# 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 = "my-scrape-job" | ||
enabled = true | ||
authentication_method = "basic" | ||
authentication_basic_username = "my-username" | ||
authentication_basic_password = "my-password" | ||
url = "https://grafana.com/metrics" | ||
scrape_interval_seconds = 120 | ||
} | ||
``` | ||
|
||
<!-- 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 from; a valid HTTPs URL is required. | ||
|
||
### Optional | ||
|
||
- `authentication_basic_password` (String, Sensitive) Password for basic authentication, use if scrape job is using basic authentication method | ||
- `authentication_basic_username` (String) Username for basic authentication, use if scrape job is using basic authentication method | ||
- `authentication_bearer_token` (String, Sensitive) Bearer token used for authentication, use if scrape job is using bearer authentication method | ||
- `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 }}" | ||
``` |
4 changes: 4 additions & 0 deletions
4
examples/data-sources/grafana_connections_metrics_endpoint_scrape_job/data-source.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
data "grafana_connections_metrics_endpoint_scrape_job" "ds_test" { | ||
stack_id = "1" | ||
name = "my-scrape-job" | ||
} |
1 change: 1 addition & 0 deletions
1
examples/data-sources/grafana_connections_metrics_endpoint_scrape_job/import.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }}" |
1 change: 1 addition & 0 deletions
1
examples/resources/grafana_connections_metrics_endpoint_scrape_job/import.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }}" |
10 changes: 10 additions & 0 deletions
10
examples/resources/grafana_connections_metrics_endpoint_scrape_job/resource.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
resource "grafana_connections_metrics_endpoint_scrape_job" "test" { | ||
stack_id = "1" | ||
name = "my-scrape-job" | ||
enabled = true | ||
authentication_method = "basic" | ||
authentication_basic_username = "my-username" | ||
authentication_basic_password = "my-password" | ||
url = "https://grafana.com/metrics" | ||
scrape_interval_seconds = 120 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package connectionsapi | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/hashicorp/go-retryablehttp" | ||
) | ||
|
||
type Client struct { | ||
authToken string | ||
apiURL url.URL | ||
client *http.Client | ||
userAgent string | ||
defaultHeaders map[string]string | ||
} | ||
|
||
const ( | ||
defaultRetries = 3 | ||
defaultTimeout = 90 * time.Second | ||
pathPrefix = "/api/v1/stacks" | ||
) | ||
|
||
func NewClient(authToken string, rawURL string, client *http.Client, userAgent string, defaultHeaders map[string]string) (*Client, error) { | ||
parsedURL, err := url.Parse(rawURL) | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse connections API url: %w", err) | ||
} | ||
|
||
if client == nil { | ||
retryClient := retryablehttp.NewClient() | ||
retryClient.RetryMax = defaultRetries | ||
client = retryClient.StandardClient() | ||
client.Timeout = defaultTimeout | ||
} | ||
|
||
return &Client{ | ||
authToken: authToken, | ||
apiURL: *parsedURL, | ||
client: client, | ||
userAgent: userAgent, | ||
defaultHeaders: defaultHeaders, | ||
}, nil | ||
} | ||
|
||
type apiResponseWrapper[T any] struct { | ||
Data T `json:"data"` | ||
} | ||
|
||
type MetricsEndpointScrapeJob struct { | ||
Enabled bool `json:"enabled"` | ||
AuthenticationMethod string `json:"authentication_method"` | ||
AuthenticationBearerToken string `json:"bearer_token,omitempty"` | ||
AuthenticationBasicUsername string `json:"basic_username,omitempty"` | ||
AuthenticationBasicPassword string `json:"basic_password,omitempty"` | ||
URL string `json:"url"` | ||
ScrapeIntervalSeconds int64 `json:"scrape_interval_seconds"` | ||
} | ||
|
||
func (c *Client) CreateMetricsEndpointScrapeJob(ctx context.Context, stackID, jobName string, jobData MetricsEndpointScrapeJob) (MetricsEndpointScrapeJob, error) { | ||
path := fmt.Sprintf("%s/%s/metrics-endpoint/jobs/%s", pathPrefix, stackID, jobName) | ||
respData := apiResponseWrapper[MetricsEndpointScrapeJob]{} | ||
err := c.doAPIRequest(ctx, http.MethodPost, path, &jobData, &respData) | ||
if err != nil { | ||
return MetricsEndpointScrapeJob{}, fmt.Errorf("failed to create metrics endpoint scrape job %q: %w", jobName, err) | ||
} | ||
return respData.Data, nil | ||
} | ||
|
||
func (c *Client) GetMetricsEndpointScrapeJob(ctx context.Context, stackID, jobName string) (MetricsEndpointScrapeJob, error) { | ||
path := fmt.Sprintf("%s/%s/metrics-endpoint/jobs/%s", pathPrefix, stackID, jobName) | ||
respData := apiResponseWrapper[MetricsEndpointScrapeJob]{} | ||
err := c.doAPIRequest(ctx, http.MethodGet, path, nil, &respData) | ||
if err != nil { | ||
return MetricsEndpointScrapeJob{}, fmt.Errorf("failed to get metrics endpoint scrape job %q: %w", jobName, err) | ||
} | ||
return respData.Data, nil | ||
} | ||
|
||
func (c *Client) UpdateMetricsEndpointScrapeJob(ctx context.Context, stackID, jobName string, jobData MetricsEndpointScrapeJob) (MetricsEndpointScrapeJob, error) { | ||
path := fmt.Sprintf("%s/%s/metrics-endpoint/jobs/%s", pathPrefix, stackID, jobName) | ||
respData := apiResponseWrapper[MetricsEndpointScrapeJob]{} | ||
err := c.doAPIRequest(ctx, http.MethodPut, path, &jobData, &respData) | ||
if err != nil { | ||
return MetricsEndpointScrapeJob{}, fmt.Errorf("failed to update metrics endpoint scrape job %q: %w", jobName, err) | ||
} | ||
return respData.Data, nil | ||
} | ||
|
||
func (c *Client) DeleteMetricsEndpointScrapeJob(ctx context.Context, stackID, jobName string) error { | ||
path := fmt.Sprintf("%s/%s/metrics-endpoint/jobs/%s", pathPrefix, stackID, jobName) | ||
err := c.doAPIRequest(ctx, http.MethodDelete, path, nil, nil) | ||
if err != nil { | ||
return fmt.Errorf("failed to delete metrics endpoint scrape job %q: %w", jobName, err) | ||
} | ||
return nil | ||
} | ||
|
||
var ( | ||
ErrNotFound = fmt.Errorf("not found") | ||
ErrUnauthorized = fmt.Errorf("request not authorized for stack") | ||
) | ||
|
||
func (c *Client) doAPIRequest(ctx context.Context, method string, path string, body any, responseData any) error { | ||
var reqBodyBytes io.Reader | ||
if body != nil { | ||
bs, err := json.Marshal(body) | ||
if err != nil { | ||
return fmt.Errorf("failed to marshal request body: %w", err) | ||
} | ||
reqBodyBytes = bytes.NewReader(bs) | ||
} | ||
|
||
req, err := http.NewRequestWithContext(ctx, method, c.apiURL.String()+path, reqBodyBytes) | ||
if err != nil { | ||
return fmt.Errorf("failed to create request: %w", err) | ||
} | ||
|
||
for k, v := range c.defaultHeaders { | ||
req.Header.Add(k, v) | ||
} | ||
|
||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.authToken)) | ||
req.Header.Add("Content-Type", "application/json") | ||
req.Header.Add("User-Agent", c.userAgent) | ||
|
||
resp, err := c.client.Do(req) | ||
if err != nil { | ||
return fmt.Errorf("failed to do request: %w", err) | ||
} | ||
|
||
bodyContents, err := io.ReadAll(resp.Body) | ||
resp.Body.Close() | ||
if err != nil { | ||
return fmt.Errorf("failed to read response body: %w", err) | ||
} | ||
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) { | ||
if resp.StatusCode == 404 { | ||
return ErrNotFound | ||
} | ||
if resp.StatusCode == 401 { | ||
return ErrUnauthorized | ||
} | ||
return fmt.Errorf("status: %d", resp.StatusCode) | ||
} | ||
if responseData != nil && resp.StatusCode != http.StatusNoContent { | ||
err = json.Unmarshal(bodyContents, &responseData) | ||
if err != nil { | ||
return fmt.Errorf("failed to unmarshal response body: %w", err) | ||
} | ||
} | ||
return nil | ||
} |
Oops, something went wrong.