From f941e53b15dc12c90881b10d7745c1aadc02a940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 6 Jun 2024 16:54:03 +0100 Subject: [PATCH] Allow users to set Argus metrics storage retention policy (#393) * implement crud commands * resource testing * acceptance tests * move function to utils, cleanup tests * fix linting * use conversion pkg for int conversion, extend testing * address PR comments * address PR comments --- docs/resources/argus_instance.md | 6 +- .../internal/services/argus/argus_acc_test.go | 181 ++++++----- .../services/argus/instance/datasource.go | 7 +- .../services/argus/instance/resource.go | 185 ++++++++++- .../services/argus/instance/resource_test.go | 290 ++++++++++++++++-- 5 files changed, 553 insertions(+), 116 deletions(-) diff --git a/docs/resources/argus_instance.md b/docs/resources/argus_instance.md index 8e09024e..63675240 100644 --- a/docs/resources/argus_instance.md +++ b/docs/resources/argus_instance.md @@ -33,6 +33,9 @@ resource "stackit_argus_instance" "example" { ### Optional - `acl` (Set of String) The access control list for this instance. Each entry is an IP address range that is permitted to access, in CIDR notation. +- `metrics_retention_days` (Number) Specifies for how many days the raw metrics are kept. +- `metrics_retention_days_1h_downsampling` (Number) Specifies for how many days the 1h downsampled metrics are kept. must be less than the value of the 5m downsampling retention. Default is set to `0` (disabled). +- `metrics_retention_days_5m_downsampling` (Number) Specifies for how many days the 5m downsampled metrics are kept. must be less than the value of the general retention. Default is set to `0` (disabled). - `parameters` (Map of String) Additional parameters. ### Read-Only @@ -51,9 +54,6 @@ resource "stackit_argus_instance" "example" { - `logs_push_url` (String) Specifies URL for pushing logs. - `logs_url` (String) Specifies Logs URL. - `metrics_push_url` (String) Specifies URL for pushing metrics. -- `metrics_retention_days` (Number) Specifies for how many days the raw metrics are kept. -- `metrics_retention_days_1h_downsampling` (Number) Specifies for how many days the 1h downsampled metrics are kept. must be less than the value of the 5m downsampling retention. Default is set to `0` (disabled). -- `metrics_retention_days_5m_downsampling` (Number) Specifies for how many days the 5m downsampled metrics are kept. must be less than the value of the general retention. Default is set to `0` (disabled). - `metrics_url` (String) Specifies metrics URL. - `otlp_traces_url` (String) - `plan_id` (String) The Argus plan ID. diff --git a/stackit/internal/services/argus/argus_acc_test.go b/stackit/internal/services/argus/argus_acc_test.go index 08d28870..4c5839da 100644 --- a/stackit/internal/services/argus/argus_acc_test.go +++ b/stackit/internal/services/argus/argus_acc_test.go @@ -18,13 +18,16 @@ import ( ) var instanceResource = map[string]string{ - "project_id": testutil.ProjectId, - "name": testutil.ResourceNameWithDateTime("argus"), - "plan_name": "Monitoring-Basic-EU01", - "new_plan_name": "Monitoring-Medium-EU01", - "acl-0": "1.2.3.4/32", - "acl-1": "111.222.111.222/32", - "acl-1-updated": "111.222.111.125/32", + "project_id": testutil.ProjectId, + "name": testutil.ResourceNameWithDateTime("argus"), + "plan_name": "Monitoring-Basic-EU01", + "new_plan_name": "Monitoring-Medium-EU01", + "acl-0": "1.2.3.4/32", + "acl-1": "111.222.111.222/32", + "acl-1-updated": "111.222.111.125/32", + "metrics_retention_days": "60", + "metrics_retention_days_5m_downsampling": "30", + "metrics_retention_days_1h_downsampling": "15", } var scrapeConfigResource = map[string]string{ @@ -42,59 +45,48 @@ var credentialResource = map[string]string{ "project_id": testutil.ProjectId, } -func resourceConfig(acl *string, instanceName, planName, target, saml2EnableUrlParameters string) string { - if acl == nil { - return fmt.Sprintf(` - %s +func instanceResourceConfig(acl, metricsRetentionDays, metricsRetentionDays1hDownsampling, metricsRetentionDays5mDownsampling *string, instanceName, planName string) string { + var aclStr string + var metricsRetentionDaysStr string + var metricsRetentionDays1hDownsamplingStr string + var metricsRetentionDays5mDownsamplingStr string - resource "stackit_argus_instance" "instance" { - project_id = "%s" - name = "%s" - plan_name = "%s" - } - - resource "stackit_argus_scrapeconfig" "scrapeconfig" { - project_id = stackit_argus_instance.instance.project_id - instance_id = stackit_argus_instance.instance.instance_id - name = "%s" - metrics_path = "%s" - targets = [%s] - scrape_interval = "%s" - sample_limit = %s - saml2 = { - enable_url_parameters = %s - } - } - - resource "stackit_argus_credential" "credential" { - project_id = stackit_argus_instance.instance.project_id - instance_id = stackit_argus_instance.instance.instance_id - } - - `, - testutil.ArgusProviderConfig(), - instanceResource["project_id"], - instanceName, - planName, - scrapeConfigResource["name"], - scrapeConfigResource["metrics_path"], - target, - scrapeConfigResource["scrape_interval"], - scrapeConfigResource["sample_limit"], - saml2EnableUrlParameters, - ) + if acl != nil { + aclStr = fmt.Sprintf("acl = %s", *acl) } + + if metricsRetentionDays != nil { + metricsRetentionDaysStr = fmt.Sprintf("metrics_retention_days = %s", *metricsRetentionDays) + } + + if metricsRetentionDays1hDownsampling != nil { + metricsRetentionDays1hDownsamplingStr = fmt.Sprintf("metrics_retention_days_1h_downsampling = %s", *metricsRetentionDays1hDownsampling) + } + + if metricsRetentionDays5mDownsampling != nil { + metricsRetentionDays5mDownsamplingStr = fmt.Sprintf("metrics_retention_days_5m_downsampling = %s", *metricsRetentionDays5mDownsampling) + } + + optionalsStr := strings.Join([]string{aclStr, metricsRetentionDaysStr, metricsRetentionDays1hDownsamplingStr, metricsRetentionDays5mDownsamplingStr}, "\n") + return fmt.Sprintf(` - %s + resource "stackit_argus_instance" "instance" { + project_id = "%s" + name = "%s" + plan_name = "%s" + %s + } + `, + instanceResource["project_id"], + instanceName, + planName, + optionalsStr, + ) +} - resource "stackit_argus_instance" "instance" { - project_id = "%s" - name = "%s" - plan_name = "%s" - acl = %s - } - - resource "stackit_argus_scrapeconfig" "scrapeconfig" { +func scrapeConfigResourceConfig(target, saml2EnableUrlParameters string) string { + return fmt.Sprintf( + `resource "stackit_argus_scrapeconfig" "scrapeconfig" { project_id = stackit_argus_instance.instance.project_id instance_id = stackit_argus_instance.instance.instance_id name = "%s" @@ -105,19 +97,7 @@ func resourceConfig(acl *string, instanceName, planName, target, saml2EnableUrlP saml2 = { enable_url_parameters = %s } - } - - resource "stackit_argus_credential" "credential" { - project_id = stackit_argus_instance.instance.project_id - instance_id = stackit_argus_instance.instance.instance_id - } - - `, - testutil.ArgusProviderConfig(), - instanceResource["project_id"], - instanceName, - planName, - *acl, + }`, scrapeConfigResource["name"], scrapeConfigResource["metrics_path"], target, @@ -127,6 +107,22 @@ func resourceConfig(acl *string, instanceName, planName, target, saml2EnableUrlP ) } +func credentialResourceConfig() string { + return `resource "stackit_argus_credential" "credential" { + project_id = stackit_argus_instance.instance.project_id + instance_id = stackit_argus_instance.instance.instance_id + }` +} + +func resourceConfig(acl, metricsRetentionDays, metricsRetentionDays1hDownsampling, metricsRetentionDays5mDownsampling *string, instanceName, planName, target, saml2EnableUrlParameters string) string { + return fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s", + testutil.ArgusProviderConfig(), + instanceResourceConfig(acl, metricsRetentionDays, metricsRetentionDays1hDownsampling, metricsRetentionDays5mDownsampling, instanceName, planName), + scrapeConfigResourceConfig(target, saml2EnableUrlParameters), + credentialResourceConfig(), + ) +} + func TestAccResource(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, @@ -141,6 +137,9 @@ func TestAccResource(t *testing.T) { instanceResource["acl-1"], instanceResource["acl-1"], )), + utils.Ptr(instanceResource["metrics_retention_days"]), + utils.Ptr(instanceResource["metrics_retention_days_1h_downsampling"]), + utils.Ptr(instanceResource["metrics_retention_days_5m_downsampling"]), instanceResource["name"], instanceResource["plan_name"], scrapeConfigResource["urls"], @@ -158,9 +157,9 @@ func TestAccResource(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "grafana_url"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "grafana_initial_admin_user"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "grafana_initial_admin_password"), - resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_retention_days"), - resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_retention_days_5m_downsampling"), - resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_retention_days_1h_downsampling"), + resource.TestCheckResourceAttr("stackit_argus_instance.instance", "metrics_retention_days", instanceResource["metrics_retention_days"]), + resource.TestCheckResourceAttr("stackit_argus_instance.instance", "metrics_retention_days_5m_downsampling", instanceResource["metrics_retention_days_5m_downsampling"]), + resource.TestCheckResourceAttr("stackit_argus_instance.instance", "metrics_retention_days_1h_downsampling", instanceResource["metrics_retention_days_1h_downsampling"]), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_url"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_push_url"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "targets_url"), @@ -204,9 +203,12 @@ func TestAccResource(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_argus_credential.credential", "password"), ), }, - // Creation without ACL + // Creation without ACL and partial metrics retention days { Config: resourceConfig( + nil, + nil, + utils.Ptr(instanceResource["metrics_retention_days_1h_downsampling"]), nil, instanceResource["name"], instanceResource["plan_name"], @@ -227,7 +229,7 @@ func TestAccResource(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "grafana_initial_admin_password"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_retention_days"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_retention_days_5m_downsampling"), - resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_retention_days_1h_downsampling"), + resource.TestCheckResourceAttr("stackit_argus_instance.instance", "metrics_retention_days_1h_downsampling", instanceResource["metrics_retention_days_1h_downsampling"]), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_url"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_push_url"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "targets_url"), @@ -273,6 +275,9 @@ func TestAccResource(t *testing.T) { { Config: resourceConfig( utils.Ptr("[]"), + nil, + nil, + nil, instanceResource["name"], instanceResource["plan_name"], scrapeConfigResource["urls"], @@ -350,11 +355,15 @@ func TestAccResource(t *testing.T) { name = stackit_argus_scrapeconfig.scrapeconfig.name } `, - resourceConfig(utils.Ptr(fmt.Sprintf( - "[%q, %q]", - instanceResource["acl-0"], - instanceResource["acl-1"], - )), + resourceConfig( + utils.Ptr(fmt.Sprintf( + "[%q, %q]", + instanceResource["acl-0"], + instanceResource["acl-1"], + )), + utils.Ptr(instanceResource["metrics_retention_days"]), + utils.Ptr(instanceResource["metrics_retention_days_1h_downsampling"]), + utils.Ptr(instanceResource["metrics_retention_days_5m_downsampling"]), instanceResource["name"], instanceResource["plan_name"], scrapeConfigResource["urls"], @@ -441,11 +450,15 @@ func TestAccResource(t *testing.T) { }, // Update { - Config: resourceConfig(utils.Ptr(fmt.Sprintf( - "[%q, %q]", - instanceResource["acl-0"], - instanceResource["acl-1-updated"], - )), + Config: resourceConfig( + utils.Ptr(fmt.Sprintf( + "[%q, %q]", + instanceResource["acl-0"], + instanceResource["acl-1-updated"], + )), + utils.Ptr(instanceResource["metrics_retention_days"]), + utils.Ptr(instanceResource["metrics_retention_days_1h_downsampling"]), + utils.Ptr(instanceResource["metrics_retention_days_5m_downsampling"]), fmt.Sprintf("%s-new", instanceResource["name"]), instanceResource["new_plan_name"], "", diff --git a/stackit/internal/services/argus/instance/datasource.go b/stackit/internal/services/argus/instance/datasource.go index 0f134d3e..5143b05a 100644 --- a/stackit/internal/services/argus/instance/datasource.go +++ b/stackit/internal/services/argus/instance/datasource.go @@ -233,7 +233,7 @@ func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques return } - aclList, err := d.client.ListACL(ctx, instanceId, projectId).Execute() + aclListResp, err := d.client.ListACL(ctx, instanceId, projectId).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Calling API to list ACL data: %v", err)) return @@ -245,11 +245,12 @@ func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) return } - err = mapACLField(aclList, &model) + err = mapACLField(aclListResp, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API response for the ACL: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API response for the ACL: %v", err)) return } + diags = resp.State.Set(ctx, model) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/stackit/internal/services/argus/instance/resource.go b/stackit/internal/services/argus/instance/resource.go index 7641a3a8..445a1455 100644 --- a/stackit/internal/services/argus/instance/resource.go +++ b/stackit/internal/services/argus/instance/resource.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "strconv" "strings" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" @@ -209,14 +210,17 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r }, "metrics_retention_days": schema.Int64Attribute{ Description: "Specifies for how many days the raw metrics are kept.", + Optional: true, Computed: true, }, "metrics_retention_days_5m_downsampling": schema.Int64Attribute{ Description: "Specifies for how many days the 5m downsampled metrics are kept. must be less than the value of the general retention. Default is set to `0` (disabled).", + Optional: true, Computed: true, }, "metrics_retention_days_1h_downsampling": schema.Int64Attribute{ Description: "Specifies for how many days the 1h downsampled metrics are kept. must be less than the value of the 5m downsampling retention. Default is set to `0` (disabled).", + Optional: true, Computed: true, }, "metrics_url": schema.StringAttribute{ @@ -288,6 +292,10 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques } } + metricsRetentionDays := conversion.Int64ValueToPointer(model.MetricsRetentionDays) + metricsRetentionDays5mDownsampling := conversion.Int64ValueToPointer(model.MetricsRetentionDays5mDownsampling) + metricsRetentionDays1hDownsampling := conversion.Int64ValueToPointer(model.MetricsRetentionDays1hDownsampling) + projectId := model.ProjectId.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) @@ -354,6 +362,49 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques if resp.Diagnostics.HasError() { return } + + // If any of the metrics retention days are set, set the metrics retention policy + if metricsRetentionDays != nil || metricsRetentionDays5mDownsampling != nil || metricsRetentionDays1hDownsampling != nil { + // Need to get the metrics retention policy because update endpoint is a PUT and we need to send all fields + metricsResp, err := r.client.GetMetricsStorageRetentionExecute(ctx, *instanceId, projectId) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Getting metrics retention policy: %v", err)) + return + } + + metricsRetentionPayload, err := toUpdateMetricsStorageRetentionPayload(metricsRetentionDays, metricsRetentionDays5mDownsampling, metricsRetentionDays1hDownsampling, metricsResp) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Building metrics retention policy payload: %v", err)) + return + } + + _, err = r.client.UpdateMetricsStorageRetention(ctx, *instanceId, projectId).UpdateMetricsStorageRetentionPayload(*metricsRetentionPayload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Setting metrics retention policy: %v", err)) + return + } + } + + // Get metrics retention policy after update + metricsResp, err := r.client.GetMetricsStorageRetentionExecute(ctx, *instanceId, projectId) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Getting metrics retention policy: %v", err)) + return + } + // Map response body to schema + err = mapMetricsRetentionField(metricsResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API response for the metrics retention: %v", err)) + return + } + + // Set state to fully populated data + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Argus instance created") } @@ -385,12 +436,18 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r return } - aclList, err := r.client.ListACL(ctx, instanceId, projectId).Execute() + aclListResp, err := r.client.ListACL(ctx, instanceId, projectId).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Calling API for ACL data: %v", err)) return } + metricsRetentionResp, err := r.client.GetMetricsStorageRetention(ctx, instanceId, projectId).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Calling API to get metrics retention: %v", err)) + return + } + // Map response body to schema err = mapFields(ctx, instanceResp, &model) if err != nil { @@ -399,9 +456,16 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r } // Map response body to schema - err = mapACLField(aclList, &model) + err = mapACLField(aclListResp, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API response for the ACL: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API response for the ACL: %v", err)) + return + } + + // Map response body to schema + err = mapMetricsRetentionField(metricsRetentionResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API response for the metrics retention: %v", err)) return } @@ -436,6 +500,10 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques } } + metricsRetentionDays := conversion.Int64ValueToPointer(model.MetricsRetentionDays) + metricsRetentionDays5mDownsampling := conversion.Int64ValueToPointer(model.MetricsRetentionDays5mDownsampling) + metricsRetentionDays1hDownsampling := conversion.Int64ValueToPointer(model.MetricsRetentionDays1hDownsampling) + err := r.loadPlanId(ctx, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Loading service plan: %v", err)) @@ -497,6 +565,47 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques return } + // If any of the metrics retention days are set, set the metrics retention policy + if metricsRetentionDays != nil || metricsRetentionDays5mDownsampling != nil || metricsRetentionDays1hDownsampling != nil { + // Need to get the metrics retention policy because update endpoint is a PUT and we need to send all fields + metricsResp, err := r.client.GetMetricsStorageRetentionExecute(ctx, instanceId, projectId) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Getting metrics retention policy: %v", err)) + return + } + + metricsRetentionPayload, err := toUpdateMetricsStorageRetentionPayload(metricsRetentionDays, metricsRetentionDays5mDownsampling, metricsRetentionDays1hDownsampling, metricsResp) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Building metrics retention policy payload: %v", err)) + return + } + _, err = r.client.UpdateMetricsStorageRetention(ctx, instanceId, projectId).UpdateMetricsStorageRetentionPayload(*metricsRetentionPayload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Setting metrics retention policy: %v", err)) + return + } + } + + // Get metrics retention policy after update + metricsResp, err := r.client.GetMetricsStorageRetentionExecute(ctx, instanceId, projectId) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Getting metrics retention policy: %v", err)) + return + } + + // Map response body to schema + err = mapMetricsRetentionField(metricsResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API response for the metrics retention %v", err)) + return + } + // Set state to fully populated data + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Argus instance updated") } @@ -639,6 +748,42 @@ func mapACLField(aclList *argus.ListACLResponse, model *Model) error { return nil } +func mapMetricsRetentionField(r *argus.GetMetricsStorageRetentionResponse, model *Model) error { + if r == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + if r.MetricsRetentionTimeRaw == nil || r.MetricsRetentionTime5m == nil || r.MetricsRetentionTime1h == nil { + return fmt.Errorf("metrics retention time is nil") + } + + stripedMetricsRetentionDays := strings.TrimSuffix(*r.MetricsRetentionTimeRaw, "d") + metricsRetentionDays, err := strconv.ParseInt(stripedMetricsRetentionDays, 10, 64) + if err != nil { + return fmt.Errorf("parsing metrics retention days: %w", err) + } + model.MetricsRetentionDays = types.Int64Value(metricsRetentionDays) + + stripedMetricsRetentionDays5m := strings.TrimSuffix(*r.MetricsRetentionTime5m, "d") + metricsRetentionDays5m, err := strconv.ParseInt(stripedMetricsRetentionDays5m, 10, 64) + if err != nil { + return fmt.Errorf("parsing metrics retention days 5m: %w", err) + } + model.MetricsRetentionDays5mDownsampling = types.Int64Value(metricsRetentionDays5m) + + stripedMetricsRetentionDays1h := strings.TrimSuffix(*r.MetricsRetentionTime1h, "d") + metricsRetentionDays1h, err := strconv.ParseInt(stripedMetricsRetentionDays1h, 10, 64) + if err != nil { + return fmt.Errorf("parsing metrics retention days 1h: %w", err) + } + model.MetricsRetentionDays1hDownsampling = types.Int64Value(metricsRetentionDays1h) + + return nil +} + func toCreatePayload(model *Model) (*argus.CreateInstancePayload, error) { if model == nil { return nil, fmt.Errorf("nil model") @@ -655,6 +800,40 @@ func toCreatePayload(model *Model) (*argus.CreateInstancePayload, error) { }, nil } +func toUpdateMetricsStorageRetentionPayload(retentionDaysRaw, retentionDays5m, retentionDays1h *int64, resp *argus.GetMetricsStorageRetentionResponse) (*argus.UpdateMetricsStorageRetentionPayload, error) { + var retentionTimeRaw string + var retentionTime5m string + var retentionTime1h string + + if resp == nil || resp.MetricsRetentionTimeRaw == nil || resp.MetricsRetentionTime5m == nil || resp.MetricsRetentionTime1h == nil { + return nil, fmt.Errorf("nil response") + } + + if retentionDaysRaw == nil { + retentionTimeRaw = *resp.MetricsRetentionTimeRaw + } else { + retentionTimeRaw = fmt.Sprintf("%dd", *retentionDaysRaw) + } + + if retentionDays5m == nil { + retentionTime5m = *resp.MetricsRetentionTime5m + } else { + retentionTime5m = fmt.Sprintf("%dd", *retentionDays5m) + } + + if retentionDays1h == nil { + retentionTime1h = *resp.MetricsRetentionTime1h + } else { + retentionTime1h = fmt.Sprintf("%dd", *retentionDays1h) + } + + return &argus.UpdateMetricsStorageRetentionPayload{ + MetricsRetentionTimeRaw: &retentionTimeRaw, + MetricsRetentionTime5m: &retentionTime5m, + MetricsRetentionTime1h: &retentionTime1h, + }, nil +} + func updateACL(ctx context.Context, projectId, instanceId string, acl []string, client *argus.APIClient) error { payload := argus.UpdateACLPayload{ Acl: utils.Ptr(acl), diff --git a/stackit/internal/services/argus/instance/resource_test.go b/stackit/internal/services/argus/instance/resource_test.go index 2959a83a..62c223ae 100644 --- a/stackit/internal/services/argus/instance/resource_test.go +++ b/stackit/internal/services/argus/instance/resource_test.go @@ -14,11 +14,12 @@ import ( func TestMapFields(t *testing.T) { tests := []struct { - description string - instanceResp *argus.GetInstanceResponse - listACLResp *argus.ListACLResponse - expected Model - isValid bool + description string + instanceResp *argus.GetInstanceResponse + listACLResp *argus.ListACLResponse + getMetricsRetentionResp *argus.GetMetricsStorageRetentionResponse + expected Model + isValid bool }{ { "default_ok", @@ -26,15 +27,23 @@ func TestMapFields(t *testing.T) { Id: utils.Ptr("iid"), }, &argus.ListACLResponse{}, + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, Model{ - Id: types.StringValue("pid,iid"), - ProjectId: types.StringValue("pid"), - InstanceId: types.StringValue("iid"), - PlanId: types.StringNull(), - PlanName: types.StringNull(), - Name: types.StringNull(), - Parameters: types.MapNull(types.StringType), - ACL: types.SetNull(types.StringType), + Id: types.StringValue("pid,iid"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue("iid"), + PlanId: types.StringNull(), + PlanName: types.StringNull(), + Name: types.StringNull(), + Parameters: types.MapNull(types.StringType), + ACL: types.SetNull(types.StringType), + MetricsRetentionDays: types.Int64Value(60), + MetricsRetentionDays1hDownsampling: types.Int64Value(30), + MetricsRetentionDays5mDownsampling: types.Int64Value(7), }, true, }, @@ -46,6 +55,11 @@ func TestMapFields(t *testing.T) { PlanName: utils.Ptr("plan1"), PlanId: utils.Ptr("planId"), Parameters: &map[string]string{"key": "value"}, + Instance: &argus.InstanceSensitiveData{ + MetricsRetentionTimeRaw: utils.Ptr(int64(60)), + MetricsRetentionTime1h: utils.Ptr(int64(30)), + MetricsRetentionTime5m: utils.Ptr(int64(7)), + }, }, &argus.ListACLResponse{ Acl: &[]string{ @@ -53,6 +67,11 @@ func TestMapFields(t *testing.T) { }, Message: utils.Ptr("message"), }, + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, Model{ Id: types.StringValue("pid,iid"), ProjectId: types.StringValue("pid"), @@ -64,6 +83,9 @@ func TestMapFields(t *testing.T) { ACL: types.SetValueMust(types.StringType, []attr.Value{ types.StringValue("1.1.1.1/32"), }), + MetricsRetentionDays: types.Int64Value(60), + MetricsRetentionDays1hDownsampling: types.Int64Value(30), + MetricsRetentionDays5mDownsampling: types.Int64Value(7), }, true, }, @@ -83,6 +105,11 @@ func TestMapFields(t *testing.T) { }, Message: utils.Ptr("message"), }, + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, Model{ Id: types.StringValue("pid,iid"), ProjectId: types.StringValue("pid"), @@ -95,6 +122,9 @@ func TestMapFields(t *testing.T) { types.StringValue("1.1.1.1/32"), types.StringValue("8.8.8.8/32"), }), + MetricsRetentionDays: types.Int64Value(60), + MetricsRetentionDays1hDownsampling: types.Int64Value(30), + MetricsRetentionDays5mDownsampling: types.Int64Value(7), }, true, }, @@ -108,15 +138,23 @@ func TestMapFields(t *testing.T) { Acl: &[]string{}, Message: nil, }, + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, Model{ - Id: types.StringValue("pid,iid"), - ProjectId: types.StringValue("pid"), - InstanceId: types.StringValue("iid"), - PlanId: types.StringNull(), - PlanName: types.StringNull(), - Name: types.StringNull(), - Parameters: types.MapNull(types.StringType), - ACL: types.SetNull(types.StringType), + Id: types.StringValue("pid,iid"), + ProjectId: types.StringValue("pid"), + InstanceId: types.StringValue("iid"), + PlanId: types.StringNull(), + PlanName: types.StringNull(), + Name: types.StringNull(), + Parameters: types.MapNull(types.StringType), + ACL: types.SetNull(types.StringType), + MetricsRetentionDays: types.Int64Value(60), + MetricsRetentionDays1hDownsampling: types.Int64Value(30), + MetricsRetentionDays5mDownsampling: types.Int64Value(7), }, true, }, @@ -124,6 +162,7 @@ func TestMapFields(t *testing.T) { "response_nil_fail", nil, nil, + nil, Model{}, false, }, @@ -131,9 +170,80 @@ func TestMapFields(t *testing.T) { "no_resource_id", &argus.GetInstanceResponse{}, nil, + nil, Model{}, false, }, + { + "empty metrics retention", + &argus.GetInstanceResponse{ + Id: utils.Ptr("iid"), + Name: nil, + }, + &argus.ListACLResponse{ + Acl: &[]string{}, + Message: nil, + }, + &argus.GetMetricsStorageRetentionResponse{}, + Model{}, + false, + }, + { + "nil metrics retention", + &argus.GetInstanceResponse{ + Id: utils.Ptr("iid"), + Name: nil, + }, + &argus.ListACLResponse{ + Acl: &[]string{}, + Message: nil, + }, + nil, + Model{}, + false, + }, + { + "update metrics retention", + &argus.GetInstanceResponse{ + Id: utils.Ptr("iid"), + Name: utils.Ptr("name"), + PlanName: utils.Ptr("plan1"), + PlanId: utils.Ptr("planId"), + Parameters: &map[string]string{"key": "value"}, + Instance: &argus.InstanceSensitiveData{ + MetricsRetentionTimeRaw: utils.Ptr(int64(30)), + MetricsRetentionTime1h: utils.Ptr(int64(15)), + MetricsRetentionTime5m: utils.Ptr(int64(10)), + }, + }, + &argus.ListACLResponse{ + Acl: &[]string{ + "1.1.1.1/32", + }, + Message: utils.Ptr("message"), + }, + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, + Model{ + Id: types.StringValue("pid,iid"), + ProjectId: types.StringValue("pid"), + Name: types.StringValue("name"), + InstanceId: types.StringValue("iid"), + PlanId: types.StringValue("planId"), + PlanName: types.StringValue("plan1"), + Parameters: toTerraformStringMapMust(context.Background(), map[string]string{"key": "value"}), + ACL: types.SetValueMust(types.StringType, []attr.Value{ + types.StringValue("1.1.1.1/32"), + }), + MetricsRetentionDays: types.Int64Value(60), + MetricsRetentionDays1hDownsampling: types.Int64Value(30), + MetricsRetentionDays5mDownsampling: types.Int64Value(7), + }, + true, + }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { @@ -143,10 +253,11 @@ func TestMapFields(t *testing.T) { } err := mapFields(context.Background(), tt.instanceResp, state) aclErr := mapACLField(tt.listACLResp, state) - if !tt.isValid && err == nil && aclErr == nil { + metricsErr := mapMetricsRetentionField(tt.getMetricsRetentionResp, state) + if !tt.isValid && err == nil && aclErr == nil && metricsErr == nil { t.Fatalf("Should have failed") } - if tt.isValid && (err != nil || aclErr != nil) { + if tt.isValid && (err != nil || aclErr != nil || metricsErr != nil) { t.Fatalf("Should not have failed: %v", err) } @@ -278,6 +389,139 @@ func TestToPayloadUpdate(t *testing.T) { } } +func TestToUpdateMetricsStorageRetentionPayload(t *testing.T) { + tests := []struct { + description string + retentionDaysRaw *int64 + retentionDays1h *int64 + retentionDays5m *int64 + getMetricsResp *argus.GetMetricsStorageRetentionResponse + expected *argus.UpdateMetricsStorageRetentionPayload + isValid bool + }{ + { + "basic_ok", + utils.Ptr(int64(120)), + utils.Ptr(int64(60)), + utils.Ptr(int64(14)), + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, + &argus.UpdateMetricsStorageRetentionPayload{ + MetricsRetentionTimeRaw: utils.Ptr("120d"), + MetricsRetentionTime1h: utils.Ptr("60d"), + MetricsRetentionTime5m: utils.Ptr("14d"), + }, + true, + }, + { + "only_raw_given", + utils.Ptr(int64(120)), + nil, + nil, + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, + &argus.UpdateMetricsStorageRetentionPayload{ + MetricsRetentionTimeRaw: utils.Ptr("120d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, + true, + }, + { + "only_1h_given", + nil, + utils.Ptr(int64(60)), + nil, + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, + &argus.UpdateMetricsStorageRetentionPayload{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("60d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, + true, + }, + { + "only_5m_given", + nil, + nil, + utils.Ptr(int64(14)), + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, + &argus.UpdateMetricsStorageRetentionPayload{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("14d"), + }, + true, + }, + { + "none_given", + nil, + nil, + nil, + &argus.GetMetricsStorageRetentionResponse{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, + &argus.UpdateMetricsStorageRetentionPayload{ + MetricsRetentionTimeRaw: utils.Ptr("60d"), + MetricsRetentionTime1h: utils.Ptr("30d"), + MetricsRetentionTime5m: utils.Ptr("7d"), + }, + true, + }, + { + "nil_response", + nil, + nil, + nil, + nil, + nil, + false, + }, + { + "empty_response", + nil, + nil, + nil, + &argus.GetMetricsStorageRetentionResponse{}, + nil, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output, err := toUpdateMetricsStorageRetentionPayload(tt.retentionDaysRaw, tt.retentionDays5m, tt.retentionDays1h, tt.getMetricsResp) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + func makeTestMap(t *testing.T) basetypes.MapValue { p := make(map[string]attr.Value, 1) p["key"] = types.StringValue("value")