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:
parent
8eb14a1136
commit
c2389be47b
7 changed files with 379 additions and 22 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
|
|
@ -17,6 +18,7 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"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/wait"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
|
|
@ -58,6 +60,7 @@ type Model struct {
|
|||
JaegerUIURL types.String `tfsdk:"jaeger_ui_url"`
|
||||
OtlpTracesURL types.String `tfsdk:"otlp_traces_url"`
|
||||
ZipkinSpansURL types.String `tfsdk:"zipkin_spans_url"`
|
||||
ACL types.Set `tfsdk:"acl"`
|
||||
}
|
||||
|
||||
// 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{
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
|
||||
|
|
@ -273,12 +295,12 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
|
|||
return
|
||||
}
|
||||
// Generate API request body from model
|
||||
payload, err := toCreatePayload(&model)
|
||||
createPayload, err := toCreatePayload(&model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
}
|
||||
createResp, err := r.client.CreateInstance(ctx, projectId).CreateInstancePayload(*payload).Execute()
|
||||
createResp, err := r.client.CreateInstance(ctx, projectId).CreateInstancePayload(*createPayload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -292,11 +314,38 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
|
|||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, waitResp, &model)
|
||||
err = mapFields(ctx, waitResp, nil, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
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
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -325,19 +374,27 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
|
|||
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
|
||||
err = mapFields(ctx, instanceResp, &model)
|
||||
err = mapFields(ctx, instanceResp, aclList, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
// Set refreshed model
|
||||
|
||||
// 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")
|
||||
|
||||
tflog.Info(ctx, "Argus instance read")
|
||||
}
|
||||
|
||||
// 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()
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
err = mapFields(ctx, waitResp, &model)
|
||||
err = mapFields(ctx, waitResp, nil, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -386,6 +452,33 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
if resp.Diagnostics.HasError() {
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
@ -435,7 +528,7 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS
|
|||
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 {
|
||||
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.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
|
||||
}
|
||||
|
||||
|
|
@ -519,6 +643,19 @@ func toCreatePayload(model *Model) (*argus.CreateInstancePayload, error) {
|
|||
}, 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) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue