Onboard Argus ACL (#304)

* resource create and schema/model

* consider empty value in resource creation

* Address issue in mapfields that came up in testing

* Unit testing the mapFields func

* extend update

* extend read

* extend datasource.go

* update example

* extended acceptance tests and generated docs

* update description and comments

* improve messages and var names, fix update acceptance test

* extend acceptance tests, improve error messages
This commit is contained in:
Diogo Ferrão 2024-03-22 17:35:10 +00:00 committed by GitHub
parent 8eb14a1136
commit c2389be47b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 379 additions and 22 deletions

View file

@ -29,6 +29,7 @@ data "stackit_argus_instance" "example" {
### Read-Only ### Read-Only
- `acl` (Set of String) The access control list for this instance. Each entry is an IP or IP range that is permitted to access, in CIDR notation.
- `alerting_url` (String) Specifies Alerting URL. - `alerting_url` (String) Specifies Alerting URL.
- `dashboard_url` (String) Specifies Argus instance dashboard URL. - `dashboard_url` (String) Specifies Argus instance dashboard URL.
- `grafana_initial_admin_password` (String, Sensitive) Specifies an initial Grafana admin password. - `grafana_initial_admin_password` (String, Sensitive) Specifies an initial Grafana admin password.

View file

@ -17,6 +17,7 @@ resource "stackit_argus_instance" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "example-instance" name = "example-instance"
plan_name = "Monitoring-Medium-EU01" plan_name = "Monitoring-Medium-EU01"
acl = ["1.1.1.1/32", "2.2.2.2/32"]
} }
``` ```
@ -31,6 +32,7 @@ resource "stackit_argus_instance" "example" {
### Optional ### Optional
- `acl` (Set of String) The access control list for this instance. Each entry is an IP or IP range that is permitted to access, in CIDR notation.
- `parameters` (Map of String) Additional parameters. - `parameters` (Map of String) Additional parameters.
### Read-Only ### Read-Only

View file

@ -2,4 +2,5 @@ resource "stackit_argus_instance" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "example-instance" name = "example-instance"
plan_name = "Monitoring-Medium-EU01" plan_name = "Monitoring-Medium-EU01"
acl = ["1.1.1.1/32", "2.2.2.2/32"]
} }

View file

@ -22,6 +22,9 @@ var instanceResource = map[string]string{
"name": testutil.ResourceNameWithDateTime("argus"), "name": testutil.ResourceNameWithDateTime("argus"),
"plan_name": "Monitoring-Basic-EU01", "plan_name": "Monitoring-Basic-EU01",
"new_plan_name": "Monitoring-Medium-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",
} }
var scrapeConfigResource = map[string]string{ var scrapeConfigResource = map[string]string{
@ -39,8 +42,9 @@ var credentialResource = map[string]string{
"project_id": testutil.ProjectId, "project_id": testutil.ProjectId,
} }
func resourceConfig(instanceName, planName, target, saml2EnableUrlParameters string) string { func resourceConfig(acl *string, instanceName, planName, target, saml2EnableUrlParameters string) string {
return fmt.Sprintf(` if acl == nil {
return fmt.Sprintf(`
%s %s
resource "stackit_argus_instance" "instance" { resource "stackit_argus_instance" "instance" {
@ -68,10 +72,52 @@ func resourceConfig(instanceName, planName, target, saml2EnableUrlParameters str
} }
`, `,
testutil.ArgusProviderConfig(),
instanceResource["project_id"],
instanceName,
planName,
scrapeConfigResource["name"],
scrapeConfigResource["metrics_path"],
target,
scrapeConfigResource["scrape_interval"],
scrapeConfigResource["sample_limit"],
saml2EnableUrlParameters,
)
}
return fmt.Sprintf(`
%s
resource "stackit_argus_instance" "instance" {
project_id = "%s"
name = "%s"
plan_name = "%s"
acl = %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(), testutil.ArgusProviderConfig(),
instanceResource["project_id"], instanceResource["project_id"],
instanceName, instanceName,
planName, planName,
*acl,
scrapeConfigResource["name"], scrapeConfigResource["name"],
scrapeConfigResource["metrics_path"], scrapeConfigResource["metrics_path"],
target, target,
@ -88,7 +134,18 @@ func TestAccResource(t *testing.T) {
Steps: []resource.TestStep{ Steps: []resource.TestStep{
// Creation // Creation
{ {
Config: resourceConfig(instanceResource["name"], instanceResource["plan_name"], scrapeConfigResource["urls"], scrapeConfigResource["saml2_enable_url_parameters"]), Config: resourceConfig(
utils.Ptr(fmt.Sprintf(
"[%q, %q, %q]",
instanceResource["acl-0"],
instanceResource["acl-1"],
instanceResource["acl-1"],
)),
instanceResource["name"],
instanceResource["plan_name"],
scrapeConfigResource["urls"],
scrapeConfigResource["saml2_enable_url_parameters"],
),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Instance data // Instance data
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "project_id", instanceResource["project_id"]), resource.TestCheckResourceAttr("stackit_argus_instance.instance", "project_id", instanceResource["project_id"]),
@ -115,6 +172,11 @@ func TestAccResource(t *testing.T) {
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "otlp_traces_url"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "otlp_traces_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "zipkin_spans_url"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "zipkin_spans_url"),
// ACL
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "acl.#", "2"),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "acl.0", instanceResource["acl-0"]),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "acl.1", instanceResource["acl-1"]),
// scrape config data // scrape config data
resource.TestCheckResourceAttrPair( resource.TestCheckResourceAttrPair(
"stackit_argus_instance.instance", "project_id", "stackit_argus_instance.instance", "project_id",
@ -141,7 +203,73 @@ func TestAccResource(t *testing.T) {
resource.TestCheckResourceAttrSet("stackit_argus_credential.credential", "username"), resource.TestCheckResourceAttrSet("stackit_argus_credential.credential", "username"),
resource.TestCheckResourceAttrSet("stackit_argus_credential.credential", "password"), resource.TestCheckResourceAttrSet("stackit_argus_credential.credential", "password"),
), ),
}, { },
// Creation without ACL
{
Config: resourceConfig(
nil,
instanceResource["name"],
instanceResource["plan_name"],
scrapeConfigResource["urls"],
scrapeConfigResource["saml2_enable_url_parameters"],
),
Check: resource.ComposeAggregateTestCheckFunc(
// Instance data
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "project_id", instanceResource["project_id"]),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "instance_id"),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "name", instanceResource["name"]),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "plan_name", instanceResource["plan_name"]),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "dashboard_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "is_updatable"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "grafana_public_read_access"),
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.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "metrics_push_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "targets_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "alerting_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "logs_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "logs_push_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "jaeger_traces_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "jaeger_ui_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "otlp_traces_url"),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "zipkin_spans_url"),
// ACL
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "acl.#", "0"),
// scrape config data
resource.TestCheckResourceAttrPair(
"stackit_argus_instance.instance", "project_id",
"stackit_argus_scrapeconfig.scrapeconfig", "project_id",
),
resource.TestCheckResourceAttrPair(
"stackit_argus_instance.instance", "instance_id",
"stackit_argus_scrapeconfig.scrapeconfig", "instance_id",
),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "name", scrapeConfigResource["name"]),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "targets.0.urls.#", "2"),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "metrics_path", scrapeConfigResource["metrics_path"]),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "scheme", scrapeConfigResource["scheme"]),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "scrape_interval", scrapeConfigResource["scrape_interval"]),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "sample_limit", scrapeConfigResource["sample_limit"]),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "saml2.enable_url_parameters", scrapeConfigResource["saml2_enable_url_parameters"]),
// credentials
resource.TestCheckResourceAttr("stackit_argus_credential.credential", "project_id", credentialResource["project_id"]),
resource.TestCheckResourceAttrPair(
"stackit_argus_instance.instance", "instance_id",
"stackit_argus_credential.credential", "instance_id",
),
resource.TestCheckResourceAttrSet("stackit_argus_credential.credential", "username"),
resource.TestCheckResourceAttrSet("stackit_argus_credential.credential", "password"),
),
},
{
// Data source // Data source
Config: fmt.Sprintf(` Config: fmt.Sprintf(`
%s %s
@ -157,7 +285,16 @@ func TestAccResource(t *testing.T) {
name = stackit_argus_scrapeconfig.scrapeconfig.name name = stackit_argus_scrapeconfig.scrapeconfig.name
} }
`, `,
resourceConfig(instanceResource["name"], instanceResource["plan_name"], scrapeConfigResource["urls"], scrapeConfigResource["saml2_enable_url_parameters"]), resourceConfig(utils.Ptr(fmt.Sprintf(
"[%q, %q]",
instanceResource["acl-0"],
instanceResource["acl-1"],
)),
instanceResource["name"],
instanceResource["plan_name"],
scrapeConfigResource["urls"],
scrapeConfigResource["saml2_enable_url_parameters"],
),
), ),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Instance data // Instance data
@ -165,6 +302,9 @@ func TestAccResource(t *testing.T) {
resource.TestCheckResourceAttrSet("data.stackit_argus_instance.instance", "instance_id"), resource.TestCheckResourceAttrSet("data.stackit_argus_instance.instance", "instance_id"),
resource.TestCheckResourceAttr("data.stackit_argus_instance.instance", "name", instanceResource["name"]), resource.TestCheckResourceAttr("data.stackit_argus_instance.instance", "name", instanceResource["name"]),
resource.TestCheckResourceAttr("data.stackit_argus_instance.instance", "plan_name", instanceResource["plan_name"]), resource.TestCheckResourceAttr("data.stackit_argus_instance.instance", "plan_name", instanceResource["plan_name"]),
resource.TestCheckResourceAttr("data.stackit_argus_instance.instance", "acl.#", "2"),
resource.TestCheckResourceAttr("data.stackit_argus_instance.instance", "acl.0", instanceResource["acl-0"]),
resource.TestCheckResourceAttr("data.stackit_argus_instance.instance", "acl.1", instanceResource["acl-1"]),
resource.TestCheckResourceAttrPair( resource.TestCheckResourceAttrPair(
"stackit_argus_instance.instance", "project_id", "stackit_argus_instance.instance", "project_id",
"data.stackit_argus_instance.instance", "project_id", "data.stackit_argus_instance.instance", "project_id",
@ -236,13 +376,25 @@ func TestAccResource(t *testing.T) {
}, },
// Update // Update
{ {
Config: resourceConfig(fmt.Sprintf("%s-new", instanceResource["name"]), instanceResource["new_plan_name"], "", "true"), Config: resourceConfig(utils.Ptr(fmt.Sprintf(
"[%q, %q]",
instanceResource["acl-0"],
instanceResource["acl-1-updated"],
)),
fmt.Sprintf("%s-new", instanceResource["name"]),
instanceResource["new_plan_name"],
"",
"true",
),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Instance // Instance
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "project_id", instanceResource["project_id"]), resource.TestCheckResourceAttr("stackit_argus_instance.instance", "project_id", instanceResource["project_id"]),
resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "instance_id"), resource.TestCheckResourceAttrSet("stackit_argus_instance.instance", "instance_id"),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "name", instanceResource["name"]+"-new"), resource.TestCheckResourceAttr("stackit_argus_instance.instance", "name", instanceResource["name"]+"-new"),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "plan_name", instanceResource["new_plan_name"]), resource.TestCheckResourceAttr("stackit_argus_instance.instance", "plan_name", instanceResource["new_plan_name"]),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "acl.#", "2"),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "acl.0", instanceResource["acl-0"]),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "acl.1", instanceResource["acl-1-updated"]),
// Scrape Config // Scrape Config
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "name", scrapeConfigResource["name"]), resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "name", scrapeConfigResource["name"]),
@ -300,6 +452,9 @@ func TestAccResource(t *testing.T) {
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "name", instanceResource["name"]), resource.TestCheckResourceAttr("stackit_argus_instance.instance", "name", instanceResource["name"]),
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "plan_name", instanceResource["new_plan_name"]), resource.TestCheckResourceAttr("stackit_argus_instance.instance", "plan_name", instanceResource["new_plan_name"]),
// ACL
resource.TestCheckResourceAttr("stackit_argus_instance.instance", "acl.#", "0"),
// Scrape Config // Scrape Config
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "name", scrapeConfigResource["name"]), resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "name", scrapeConfigResource["name"]),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "targets.#", "1"), resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "targets.#", "1"),
@ -307,8 +462,8 @@ func TestAccResource(t *testing.T) {
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "scheme", scrapeConfigResource["scheme"]), resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "scheme", scrapeConfigResource["scheme"]),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "scrape_interval", scrapeConfigResource["scrape_interval"]), resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "scrape_interval", scrapeConfigResource["scrape_interval"]),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "sample_limit", scrapeConfigResource["sample_limit"]), resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "sample_limit", scrapeConfigResource["sample_limit"]),
resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "saml2.%", "0"), resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "saml2.%", "1"),
resource.TestCheckNoResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "saml2.enable_url_parameters"), resource.TestCheckResourceAttr("stackit_argus_scrapeconfig.scrapeconfig", "saml2.enable_url_parameters", "false"),
), ),
}, },

View file

@ -196,6 +196,11 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
"zipkin_spans_url": schema.StringAttribute{ "zipkin_spans_url": schema.StringAttribute{
Computed: true, Computed: true,
}, },
"acl": schema.SetAttribute{
Description: "The access control list for this instance. Each entry is a single IP address that is permitted to access, in CIDR notation (/32).",
ElementType: types.StringType,
Computed: true,
},
}, },
} }
} }
@ -216,7 +221,13 @@ func (d *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
return return
} }
err = mapFields(ctx, instanceResponse, &model) aclList, 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
}
err = mapFields(ctx, instanceResponse, aclList, &model)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return return

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/path"
@ -17,6 +18,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/config" "github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/argus" "github.com/stackitcloud/stackit-sdk-go/services/argus"
"github.com/stackitcloud/stackit-sdk-go/services/argus/wait" "github.com/stackitcloud/stackit-sdk-go/services/argus/wait"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
@ -58,6 +60,7 @@ type Model struct {
JaegerUIURL types.String `tfsdk:"jaeger_ui_url"` JaegerUIURL types.String `tfsdk:"jaeger_ui_url"`
OtlpTracesURL types.String `tfsdk:"otlp_traces_url"` OtlpTracesURL types.String `tfsdk:"otlp_traces_url"`
ZipkinSpansURL types.String `tfsdk:"zipkin_spans_url"` ZipkinSpansURL types.String `tfsdk:"zipkin_spans_url"`
ACL types.Set `tfsdk:"acl"`
} }
// NewInstanceResource is a helper function to simplify the provider implementation. // NewInstanceResource is a helper function to simplify the provider implementation.
@ -250,6 +253,16 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
"zipkin_spans_url": schema.StringAttribute{ "zipkin_spans_url": schema.StringAttribute{
Computed: true, Computed: true,
}, },
"acl": schema.SetAttribute{
Description: "The access control list for this instance. Each entry is a single IP address that is permitted to access, in CIDR notation (/32).",
ElementType: types.StringType,
Optional: true,
Validators: []validator.Set{
setvalidator.ValueStringsAre(
validate.CIDR(),
),
},
},
}, },
} }
} }
@ -264,6 +277,15 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
return return
} }
acl := []string{}
if !(model.ACL.IsNull() || model.ACL.IsUnknown()) {
diags = model.ACL.ElementsAs(ctx, &acl, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
@ -273,12 +295,12 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
return return
} }
// Generate API request body from model // Generate API request body from model
payload, err := toCreatePayload(&model) createPayload, err := toCreatePayload(&model)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err))
return return
} }
createResp, err := r.client.CreateInstance(ctx, projectId).CreateInstancePayload(*payload).Execute() createResp, err := r.client.CreateInstance(ctx, projectId).CreateInstancePayload(*createPayload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err))
return return
@ -292,11 +314,38 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
} }
// Map response body to schema // Map response body to schema
err = mapFields(ctx, waitResp, &model) err = mapFields(ctx, waitResp, nil, &model)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
return return
} }
// Set state to instance populated data
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Create ACL
err = updateACL(ctx, projectId, *instanceId, acl, r.client)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating ACL: %v", err))
return
}
aclList, err := r.client.ListACL(ctx, *instanceId, projectId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API to list ACL data: %v", err))
return
}
// Map response body to schema
err = mapFields(ctx, waitResp, aclList, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API response: %v", err))
return
}
// Set state to fully populated data // Set state to fully populated data
diags = resp.State.Set(ctx, model) diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
@ -325,19 +374,27 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
return return
} }
aclList, 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
}
// Map response body to schema // Map response body to schema
err = mapFields(ctx, instanceResp, &model) err = mapFields(ctx, instanceResp, aclList, &model)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return return
} }
// Set refreshed model
// Set state to fully populated data
diags = resp.State.Set(ctx, model) diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
tflog.Info(ctx, "Argus instance created")
tflog.Info(ctx, "Argus instance read")
} }
// Update updates the resource and sets the updated Terraform state on success. // Update updates the resource and sets the updated Terraform state on success.
@ -352,6 +409,15 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
acl := []string{}
if !(model.ACL.IsNull() || model.ACL.IsUnknown()) {
diags = model.ACL.ElementsAs(ctx, &acl, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
err := r.loadPlanId(ctx, &model) err := r.loadPlanId(ctx, &model)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Loading service plan: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Loading service plan: %v", err))
@ -376,7 +442,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
return return
} }
err = mapFields(ctx, waitResp, &model) err = mapFields(ctx, waitResp, nil, &model)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
return return
@ -386,6 +452,33 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
// Update ACL
err = updateACL(ctx, projectId, instanceId, acl, r.client)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Updating ACL: %v", err))
return
}
aclList, err := r.client.ListACL(ctx, instanceId, projectId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Calling API to list ACL data: %v", err))
return
}
// Map response body to schema
err = mapFields(ctx, waitResp, aclList, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing ACL API payload: %v", err))
return
}
// Set state to ACL populated data
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Argus instance updated") tflog.Info(ctx, "Argus instance updated")
} }
@ -435,7 +528,7 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS
tflog.Info(ctx, "Argus instance state imported") tflog.Info(ctx, "Argus instance state imported")
} }
func mapFields(ctx context.Context, r *argus.GetInstanceResponse, model *Model) error { func mapFields(ctx context.Context, r *argus.GetInstanceResponse, acl *argus.ListACLResponse, model *Model) error {
if r == nil { if r == nil {
return fmt.Errorf("response input is nil") return fmt.Errorf("response input is nil")
} }
@ -500,6 +593,37 @@ func mapFields(ctx context.Context, r *argus.GetInstanceResponse, model *Model)
model.OtlpTracesURL = types.StringPointerValue(i.OtlpTracesUrl) model.OtlpTracesURL = types.StringPointerValue(i.OtlpTracesUrl)
model.ZipkinSpansURL = types.StringPointerValue(i.ZipkinSpansUrl) model.ZipkinSpansURL = types.StringPointerValue(i.ZipkinSpansUrl)
} }
err := mapACLField(acl, model)
if err != nil {
return err
}
return nil
}
func mapACLField(aclList *argus.ListACLResponse, model *Model) error {
if aclList == nil {
if model.ACL.IsNull() || model.ACL.IsUnknown() {
model.ACL = types.SetNull(types.StringType)
}
return nil
}
if aclList.Acl == nil || len(*aclList.Acl) == 0 {
model.ACL = types.SetNull(types.StringType)
return nil
}
acl := []attr.Value{}
for _, cidr := range *aclList.Acl {
acl = append(acl, types.StringValue(cidr))
}
aclTF, diags := types.SetValue(types.StringType, acl)
if diags.HasError() {
return fmt.Errorf("mapping ACL: %w", core.DiagsToError(diags))
}
model.ACL = aclTF
return nil return nil
} }
@ -519,6 +643,19 @@ func toCreatePayload(model *Model) (*argus.CreateInstancePayload, error) {
}, nil }, nil
} }
func updateACL(ctx context.Context, projectId, instanceId string, acl []string, client *argus.APIClient) error {
payload := argus.UpdateACLPayload{
Acl: utils.Ptr(acl),
}
_, err := client.UpdateACL(ctx, instanceId, projectId).UpdateACLPayload(payload).Execute()
if err != nil {
return fmt.Errorf("updating ACL: %w", err)
}
return nil
}
func toUpdatePayload(model *Model) (*argus.UpdateInstancePayload, error) { func toUpdatePayload(model *Model) (*argus.UpdateInstancePayload, error) {
if model == nil { if model == nil {
return nil, fmt.Errorf("nil model") return nil, fmt.Errorf("nil model")

View file

@ -14,16 +14,18 @@ import (
func TestMapFields(t *testing.T) { func TestMapFields(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
input *argus.GetInstanceResponse instanceResp *argus.GetInstanceResponse
expected Model listACLResp *argus.ListACLResponse
isValid bool expected Model
isValid bool
}{ }{
{ {
"default_ok", "default_ok",
&argus.GetInstanceResponse{ &argus.GetInstanceResponse{
Id: utils.Ptr("iid"), Id: utils.Ptr("iid"),
}, },
nil,
Model{ Model{
Id: types.StringValue("pid,iid"), Id: types.StringValue("pid,iid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
@ -32,6 +34,7 @@ func TestMapFields(t *testing.T) {
PlanName: types.StringNull(), PlanName: types.StringNull(),
Name: types.StringNull(), Name: types.StringNull(),
Parameters: types.MapNull(types.StringType), Parameters: types.MapNull(types.StringType),
ACL: types.SetNull(types.StringType),
}, },
true, true,
}, },
@ -44,6 +47,12 @@ func TestMapFields(t *testing.T) {
PlanId: utils.Ptr("planId"), PlanId: utils.Ptr("planId"),
Parameters: &map[string]string{"key": "value"}, Parameters: &map[string]string{"key": "value"},
}, },
&argus.ListACLResponse{
Acl: &[]string{
"1.1.1.1/32",
},
Message: utils.Ptr("message"),
},
Model{ Model{
Id: types.StringValue("pid,iid"), Id: types.StringValue("pid,iid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
@ -52,6 +61,40 @@ func TestMapFields(t *testing.T) {
PlanId: types.StringValue("planId"), PlanId: types.StringValue("planId"),
PlanName: types.StringValue("plan1"), PlanName: types.StringValue("plan1"),
Parameters: toTerraformStringMapMust(context.Background(), map[string]string{"key": "value"}), Parameters: toTerraformStringMapMust(context.Background(), map[string]string{"key": "value"}),
ACL: types.SetValueMust(types.StringType, []attr.Value{
types.StringValue("1.1.1.1/32"),
}),
},
true,
},
{
"values_ok_multiple_acls",
&argus.GetInstanceResponse{
Id: utils.Ptr("iid"),
Name: utils.Ptr("name"),
PlanName: utils.Ptr("plan1"),
PlanId: utils.Ptr("planId"),
Parameters: &map[string]string{"key": "value"},
},
&argus.ListACLResponse{
Acl: &[]string{
"1.1.1.1/32",
"8.8.8.8/32",
},
Message: utils.Ptr("message"),
},
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"),
types.StringValue("8.8.8.8/32"),
}),
}, },
true, true,
}, },
@ -61,6 +104,10 @@ func TestMapFields(t *testing.T) {
Id: utils.Ptr("iid"), Id: utils.Ptr("iid"),
Name: nil, Name: nil,
}, },
&argus.ListACLResponse{
Acl: &[]string{},
Message: nil,
},
Model{ Model{
Id: types.StringValue("pid,iid"), Id: types.StringValue("pid,iid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
@ -69,18 +116,21 @@ func TestMapFields(t *testing.T) {
PlanName: types.StringNull(), PlanName: types.StringNull(),
Name: types.StringNull(), Name: types.StringNull(),
Parameters: types.MapNull(types.StringType), Parameters: types.MapNull(types.StringType),
ACL: types.SetNull(types.StringType),
}, },
true, true,
}, },
{ {
"response_nil_fail", "response_nil_fail",
nil, nil,
nil,
Model{}, Model{},
false, false,
}, },
{ {
"no_resource_id", "no_resource_id",
&argus.GetInstanceResponse{}, &argus.GetInstanceResponse{},
nil,
Model{}, Model{},
false, false,
}, },
@ -90,7 +140,7 @@ func TestMapFields(t *testing.T) {
state := &Model{ state := &Model{
ProjectId: tt.expected.ProjectId, ProjectId: tt.expected.ProjectId,
} }
err := mapFields(context.Background(), tt.input, state) err := mapFields(context.Background(), tt.instanceResp, tt.listACLResp, state)
if !tt.isValid && err == nil { if !tt.isValid && err == nil {
t.Fatalf("Should have failed") t.Fatalf("Should have failed")
} }