From 1a45e7fc4144b095c8272950df65f9dea401819f Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Wed, 17 Jan 2024 11:25:36 +0100 Subject: [PATCH 1/6] Initial commits for host resource Signed-off-by: Alina Buzachis --- Makefile | 2 +- config.tfrc | 7 + examples/resources/aap_host/main.tf | 29 +++ internal/provider/helper.go | 7 + internal/provider/host_resource.go | 355 ++++++++++++++++++++++++++++ 5 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 config.tfrc create mode 100644 examples/resources/aap_host/main.tf create mode 100644 internal/provider/helper.go create mode 100644 internal/provider/host_resource.go diff --git a/Makefile b/Makefile index f1029a1..d2b1091 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ default: build -build: +build: @echo "==> Building package..." go build ./... diff --git a/config.tfrc b/config.tfrc new file mode 100644 index 0000000..0efac4d --- /dev/null +++ b/config.tfrc @@ -0,0 +1,7 @@ +provider_installation { + dev_overrides { + "ansible/aap" = "/Users/alinabuzachis/dev/ansible/terraform-provider-aap" + } + + direct {} +} diff --git a/examples/resources/aap_host/main.tf b/examples/resources/aap_host/main.tf new file mode 100644 index 0000000..b4ae033 --- /dev/null +++ b/examples/resources/aap_host/main.tf @@ -0,0 +1,29 @@ +terraform { + required_providers { + aap = { + source = "ansible/aap" + } + } +} + +provider "aap" { + host = "https://localhost:8043" + username = "test" + password = "test" + insecure_skip_verify = true +} + +resource "aap_host" "sample" { + inventory_id = 1 + name = "tf_host" + variables = jsonencode( + { + "foo": "bar" + } + ) +} + + +output "host" { + value = aap_host.sample +} \ No newline at end of file diff --git a/internal/provider/helper.go b/internal/provider/helper.go new file mode 100644 index 0000000..55202c4 --- /dev/null +++ b/internal/provider/helper.go @@ -0,0 +1,7 @@ +package provider + +import "github.com/hashicorp/terraform-plugin-framework/attr" + +func IsValueProvided(value attr.Value) bool { + return !value.IsNull() && !value.IsUnknown() +} \ No newline at end of file diff --git a/internal/provider/host_resource.go b/internal/provider/host_resource.go new file mode 100644 index 0000000..656f67d --- /dev/null +++ b/internal/provider/host_resource.go @@ -0,0 +1,355 @@ +package provider + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &HostResource{} + _ resource.ResourceWithConfigure = &HostResource{} +) + +// NewHostResource is a helper function to simplify the provider implementation +func NewHostResource() resource.Resource { + return &HostResource{} +} + +type HostResourceModelInterface interface { + ParseHttpResponse(body []byte) error + CreateRequestBody() ([]byte, diag.Diagnostics) + GetURL() string +} + +// HostResource is the resource implementation. +type HostResource struct { + client ProviderHTTPClient +} + +// Metadata returns the resource type name. +func (r *HostResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_host" +} + +// Schema defines the schema for the host resource +func (r *HostResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + + "inventory_id": schema.Int64Attribute{ + Required: true, + }, + "instance_id": schema.StringAttribute{ + Required: true, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "host_url": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "variables": schema.StringAttribute{ + Optional: true, + CustomType: jsontypes.NormalizedType{}, + }, + }, + } +} + +// HostResourceModel maps the resource schema data. +type HostResourceModel struct { + InventoryId types.Int64 `tfsdk:"inventory_id"` + InstanceId types.Int64 `tfsdk:"instance_id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + URL types.String `tfsdk:"host_url"` + Variables jsontypes.Normalized `tfsdk:"variables"` +} + +func (d *HostResourceModel) GetURL() string { + if IsValueProvided(d.URL) { + return d.URL.ValueString() + } + return "" +} + +func (d *HostResourceModel) CreateRequestBody() ([]byte, diag.Diagnostics) { + body := make(map[string]interface{}) + var diags diag.Diagnostics + + // Inventory id + body["inventory"] = d.InventoryId.ValueInt64() + + // Instance id + body["instance_id"] = d.InstanceId.ValueInt64() + + // Name + body["name"] = d.Name.ValueString() + + // Variables + if IsValueProvided(d.Variables) { + // var vars map[string]interface{} + // diags.Append(d.Variables.Unmarshal(&vars)...) + body["variables"] = d.Variables.ValueString() + } + + // URL + if IsValueProvided(d.URL) { + body["url"] = d.URL.ValueString() + } + + // Description + if IsValueProvided(d.Description) { + body["description"] = d.Description.ValueString() + } + + json_raw, err := json.Marshal(body) + if err != nil { + diags.Append(diag.NewErrorDiagnostic("Body JSON Marshal Error", err.Error())) + return nil, diags + } + return json_raw, diags +} + +func (d *HostResourceModel) ParseHttpResponse(body []byte) error { + /* Unmarshal the json string */ + result := make(map[string]interface{}) + + err := json.Unmarshal([]byte(body), &result) + if err != nil { + return err + } + + d.Name = types.StringValue(result["name"].(string)) + d.Description = types.StringValue(result["description"].(string)) + d.URL = types.StringValue(result["url"].(string)) + + return nil +} + +// Configure adds the provider configured client to the resource. +func (d *HostResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*AAPClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *AAPClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + + +func (r HostResource) CreateHost(data HostResourceModelInterface) diag.Diagnostics { + var diags diag.Diagnostics + var req_data io.Reader = nil + req_body, diagCreateReq := data.CreateRequestBody() + diags.Append(diagCreateReq...) + if diags.HasError() { + return diags + } + if req_body != nil { + req_data = bytes.NewReader(req_body) + } + + resp, body, err := r.client.doRequest(http.MethodPost, "/api/v2/hosts/", req_data) + if err != nil { + diags.AddError("Body JSON Marshal Error", err.Error()) + return diags + } + if resp == nil { + diags.AddError("Http response Error", "no http response from server") + return diags + } + if resp.StatusCode != http.StatusCreated { + diags.AddError("Unexpected Http Status code", + fmt.Sprintf("expected (%d) got (%s)", http.StatusCreated, resp.Status)) + return diags + } + err = data.ParseHttpResponse(body) + if err != nil { + diags.AddError("error while parsing the json response: ", err.Error()) + return diags + } + return diags +} + + +func (r HostResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data HostResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(r.CreateHost(&data)...) + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r HostResource) DeleteHost(data HostResourceModelInterface) diag.Diagnostics { + var diags diag.Diagnostics + + resp, _, err := r.client.doRequest(http.MethodDelete, data.GetURL(), nil) + if err != nil { + diags.AddError("Body JSON Marshal Error", err.Error()) + return diags + } + if resp == nil { + diags.AddError("Http response Error", "no http response from server") + return diags + } + if resp.StatusCode != http.StatusNoContent { + diags.AddError("Unexpected Http Status code", + fmt.Sprintf("expected (%d) got (%s)", http.StatusNoContent, resp.Status)) + return diags + } + return diags +} + + +func (r HostResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data HostResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + resp.Diagnostics.Append(r.DeleteHost(&data)...) + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r HostResource) UpdateHost(data HostResourceModelInterface) diag.Diagnostics { + var diags diag.Diagnostics + var req_data io.Reader = nil + req_body, diagCreateReq := data.CreateRequestBody() + diags.Append(diagCreateReq...) + if diags.HasError() { + return diags + } + if req_body != nil { + req_data = bytes.NewReader(req_body) + } + resp, body, err := r.client.doRequest(http.MethodPut, data.GetURL(), req_data) + + if err != nil { + diags.AddError("Body JSON Marshal Error", err.Error()) + return diags + } + if resp == nil { + diags.AddError("Http response Error", "no http response from server") + return diags + } + if resp.StatusCode != http.StatusOK { + diags.AddError("Unexpected Http Status code", + fmt.Sprintf("expected (%d) got (%s)", http.StatusOK, resp.Status)) + return diags + } + err = data.ParseHttpResponse(body) + if err != nil { + diags.AddError("error while parsing the json response: ", err.Error()) + return diags + } + return diags +} + +func (r HostResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data HostResourceModel + var data_with_URL HostResourceModel + + // Read Terraform plan and state data into the model + // The URL is generated once the host is created. To update the correct host, we retrieve the state data and append the URL from the state data to the plan data. + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(req.State.Get(ctx, &data_with_URL)...) + data.URL = data_with_URL.URL + + resp.Diagnostics.Append(r.UpdateHost(&data)...) + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r HostResource) ReadHost(data HostResourceModelInterface) diag.Diagnostics { + var diags diag.Diagnostics + // Read existing Host + host_url := data.GetURL() + resp, body, err := r.client.doRequest(http.MethodGet, host_url, nil) + if err != nil { + diags.AddError("Get Error", err.Error()) + return diags + } + if resp == nil { + diags.AddError("Http response Error", "no http response from server") + return diags + } + if resp.StatusCode != http.StatusOK { + diags.AddError("Unexpected Http Status code", + fmt.Sprintf("expected (%d) got (%s)", http.StatusOK, resp.Status)) + } + + err = data.ParseHttpResponse(body) + if err != nil { + diags.AddError("error while parsing the json response: ", err.Error()) + return diags + } + return diags +} + +func (r HostResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data HostResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(r.ReadHost(&data)...) + if resp.Diagnostics.HasError() { + return + } + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} \ No newline at end of file From 18c9b116e586b4a48f5815d69d0204dc459e966a Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Fri, 19 Jan 2024 15:11:31 +0100 Subject: [PATCH 2/6] Add host resource Signed-off-by: Alina Buzachis --- examples/resources/host/main.tf | 30 ++++ internal/provider/host_resource.go | 203 ++++++++++++++---------- internal/provider/host_resource_test.go | 185 +++++++++++++++++++++ internal/provider/job_resource.go | 25 +-- internal/provider/provider.go | 1 + internal/provider/utils.go | 24 +++ 6 files changed, 365 insertions(+), 103 deletions(-) create mode 100644 examples/resources/host/main.tf create mode 100644 internal/provider/host_resource_test.go diff --git a/examples/resources/host/main.tf b/examples/resources/host/main.tf new file mode 100644 index 0000000..9654638 --- /dev/null +++ b/examples/resources/host/main.tf @@ -0,0 +1,30 @@ +terraform { + required_providers { + aap = { + source = "ansible/aap" + } + } +} + +provider "aap" { + host = "https://localhost:8043" + username = "test" + password = "test" + insecure_skip_verify = true +} + +resource "aap_host" "sample" { + inventory_id = 1 + name = "tf_host" + variables = jsonencode( + { + "foo": "bar" + } + ) + group_id = 2 + disassociate_group = true +} + +output "host" { + value = aap_host.sample +} diff --git a/internal/provider/host_resource.go b/internal/provider/host_resource.go index 656f67d..556f2c4 100644 --- a/internal/provider/host_resource.go +++ b/internal/provider/host_resource.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisfies the expected interfaces. @@ -52,8 +53,8 @@ func (r *HostResource) Schema(_ context.Context, _ resource.SchemaRequest, resp "inventory_id": schema.Int64Attribute{ Required: true, }, - "instance_id": schema.StringAttribute{ - Required: true, + "instance_id": schema.Int64Attribute{ + Optional: true, }, "name": schema.StringAttribute{ Required: true, @@ -72,18 +73,35 @@ func (r *HostResource) Schema(_ context.Context, _ resource.SchemaRequest, resp Optional: true, CustomType: jsontypes.NormalizedType{}, }, + "enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Defaults true.", + }, + "group_id": schema.Int64Attribute{ + Optional: true, + Description: "Set this option to associate an existing group with a host.", + }, + "disassociate_group": schema.BoolAttribute{ + Optional: true, + Description: "Set group_id and and disassociate_group options to remove " + + "the group from a host without deleting the group.", + }, }, } } // HostResourceModel maps the resource schema data. type HostResourceModel struct { - InventoryId types.Int64 `tfsdk:"inventory_id"` - InstanceId types.Int64 `tfsdk:"instance_id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - URL types.String `tfsdk:"host_url"` - Variables jsontypes.Normalized `tfsdk:"variables"` + InventoryId types.Int64 `tfsdk:"inventory_id"` + InstanceId types.Int64 `tfsdk:"instance_id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + URL types.String `tfsdk:"host_url"` + Variables jsontypes.Normalized `tfsdk:"variables"` + Enabled types.Bool `tfsdk:"enabled"` + GroupId types.Int64 `tfsdk:"group_id"` + DisassociateGroup types.Bool `tfsdk:"disassociate_group"` } func (d *HostResourceModel) GetURL() string { @@ -108,11 +126,24 @@ func (d *HostResourceModel) CreateRequestBody() ([]byte, diag.Diagnostics) { // Variables if IsValueProvided(d.Variables) { - // var vars map[string]interface{} - // diags.Append(d.Variables.Unmarshal(&vars)...) body["variables"] = d.Variables.ValueString() } + // Groups + if IsValueProvided(d.GroupId) { + body["id"] = d.GroupId.ValueInt64() + } + + // DisassociateGroup + if IsValueProvided(d.DisassociateGroup) { + // DisassociateGroup value does not really matter + // To remove a group from a host you only need to pass this parameter + // Add it to the body only if set to true + if d.DisassociateGroup.ValueBool() { + body["disassociate_group"] = true + } + } + // URL if IsValueProvided(d.URL) { body["url"] = d.URL.ValueString() @@ -123,11 +154,17 @@ func (d *HostResourceModel) CreateRequestBody() ([]byte, diag.Diagnostics) { body["description"] = d.Description.ValueString() } + // Enabled + if IsValueProvided(d.Enabled) { + body["enabled"] = d.Enabled.ValueBool() + } + json_raw, err := json.Marshal(body) if err != nil { diags.Append(diag.NewErrorDiagnostic("Body JSON Marshal Error", err.Error())) return nil, diags } + return json_raw, diags } @@ -135,20 +172,43 @@ func (d *HostResourceModel) ParseHttpResponse(body []byte) error { /* Unmarshal the json string */ result := make(map[string]interface{}) - err := json.Unmarshal([]byte(body), &result) + err := json.Unmarshal(body, &result) if err != nil { return err } d.Name = types.StringValue(result["name"].(string)) - d.Description = types.StringValue(result["description"].(string)) d.URL = types.StringValue(result["url"].(string)) + if result["description"] != "" { + d.Description = types.StringValue(result["description"].(string)) + } else { + d.Description = types.StringNull() + } + + if result["variables"] != "" { + d.Variables = jsontypes.NewNormalizedValue(result["variables"].(string)) + } else { + d.Variables = jsontypes.NewNormalizedNull() + } + + if r, ok := result["group_id"]; ok { + d.GroupId = basetypes.NewInt64Value(int64(r.(float64))) + } + + if r, ok := result["disassociate_group"]; ok && r != nil { + d.DisassociateGroup = basetypes.NewBoolValue(r.(bool)) + } + + if r, ok := result["enabled"]; ok && r != nil { + d.Enabled = basetypes.NewBoolValue(r.(bool)) + } + return nil } // Configure adds the provider configured client to the resource. -func (d *HostResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (d *HostResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -166,41 +226,45 @@ func (d *HostResource) Configure(ctx context.Context, req resource.ConfigureRequ d.client = client } - -func (r HostResource) CreateHost(data HostResourceModelInterface) diag.Diagnostics { +func MakeReqData(data HostResourceModelInterface) (io.Reader, diag.Diagnostics) { var diags diag.Diagnostics var req_data io.Reader = nil + req_body, diagCreateReq := data.CreateRequestBody() diags.Append(diagCreateReq...) + if diags.HasError() { - return diags + return nil, diags } + if req_body != nil { req_data = bytes.NewReader(req_body) } + return req_data, diags +} + +func (r HostResource) CreateHost(data HostResourceModelInterface) diag.Diagnostics { + req_data, diags := MakeReqData(data) resp, body, err := r.client.doRequest(http.MethodPost, "/api/v2/hosts/", req_data) - if err != nil { - diags.AddError("Body JSON Marshal Error", err.Error()) - return diags - } - if resp == nil { - diags.AddError("Http response Error", "no http response from server") - return diags - } - if resp.StatusCode != http.StatusCreated { - diags.AddError("Unexpected Http Status code", - fmt.Sprintf("expected (%d) got (%s)", http.StatusCreated, resp.Status)) - return diags - } + diags.Append(IsResponseValid(resp, err, http.StatusCreated)...) + err = data.ParseHttpResponse(body) if err != nil { diags.AddError("error while parsing the json response: ", err.Error()) return diags } + return diags } +func (r HostResource) AssociateGroup(data HostResourceModelInterface) diag.Diagnostics { + req_data, diags := MakeReqData(data) + resp, _, err := r.client.doRequest(http.MethodPost, data.GetURL()+"/groups/", req_data) + diags.Append(IsResponseValid(resp, err, http.StatusNoContent)...) + + return diags +} func (r HostResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data HostResourceModel @@ -219,29 +283,26 @@ func (r HostResource) Create(ctx context.Context, req resource.CreateRequest, re // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + if IsValueProvided((&data).GroupId) { + resp.Diagnostics.Append(r.AssociateGroup(&data)...) + if resp.Diagnostics.HasError() { + return + } + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + } } func (r HostResource) DeleteHost(data HostResourceModelInterface) diag.Diagnostics { var diags diag.Diagnostics resp, _, err := r.client.doRequest(http.MethodDelete, data.GetURL(), nil) - if err != nil { - diags.AddError("Body JSON Marshal Error", err.Error()) - return diags - } - if resp == nil { - diags.AddError("Http response Error", "no http response from server") - return diags - } - if resp.StatusCode != http.StatusNoContent { - diags.AddError("Unexpected Http Status code", - fmt.Sprintf("expected (%d) got (%s)", http.StatusNoContent, resp.Status)) - return diags - } + diags.Append(IsResponseValid(resp, err, http.StatusNoContent)...) + return diags } - func (r HostResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var data HostResourceModel @@ -258,36 +319,16 @@ func (r HostResource) Delete(ctx context.Context, req resource.DeleteRequest, re } func (r HostResource) UpdateHost(data HostResourceModelInterface) diag.Diagnostics { - var diags diag.Diagnostics - var req_data io.Reader = nil - req_body, diagCreateReq := data.CreateRequestBody() - diags.Append(diagCreateReq...) - if diags.HasError() { - return diags - } - if req_body != nil { - req_data = bytes.NewReader(req_body) - } + req_data, diags := MakeReqData(data) resp, body, err := r.client.doRequest(http.MethodPut, data.GetURL(), req_data) + diags.Append(IsResponseValid(resp, err, http.StatusOK)...) - if err != nil { - diags.AddError("Body JSON Marshal Error", err.Error()) - return diags - } - if resp == nil { - diags.AddError("Http response Error", "no http response from server") - return diags - } - if resp.StatusCode != http.StatusOK { - diags.AddError("Unexpected Http Status code", - fmt.Sprintf("expected (%d) got (%s)", http.StatusOK, resp.Status)) - return diags - } err = data.ParseHttpResponse(body) if err != nil { diags.AddError("error while parsing the json response: ", err.Error()) return diags } + return diags } @@ -296,7 +337,9 @@ func (r HostResource) Update(ctx context.Context, req resource.UpdateRequest, re var data_with_URL HostResourceModel // Read Terraform plan and state data into the model - // The URL is generated once the host is created. To update the correct host, we retrieve the state data and append the URL from the state data to the plan data. + // The URL is generated once the host is created. + // To update the correct host, we retrieve the state data + // and append the URL from the state data to the plan data. resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) resp.Diagnostics.Append(req.State.Get(ctx, &data_with_URL)...) data.URL = data_with_URL.URL @@ -308,25 +351,23 @@ func (r HostResource) Update(ctx context.Context, req resource.UpdateRequest, re // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + if IsValueProvided((&data).GroupId) { + resp.Diagnostics.Append(r.AssociateGroup(&data)...) + if resp.Diagnostics.HasError() { + return + } + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + } } func (r HostResource) ReadHost(data HostResourceModelInterface) diag.Diagnostics { var diags diag.Diagnostics // Read existing Host - host_url := data.GetURL() + host_url := data.GetURL() resp, body, err := r.client.doRequest(http.MethodGet, host_url, nil) - if err != nil { - diags.AddError("Get Error", err.Error()) - return diags - } - if resp == nil { - diags.AddError("Http response Error", "no http response from server") - return diags - } - if resp.StatusCode != http.StatusOK { - diags.AddError("Unexpected Http Status code", - fmt.Sprintf("expected (%d) got (%s)", http.StatusOK, resp.Status)) - } + diags.Append(IsResponseValid(resp, err, http.StatusOK)...) err = data.ParseHttpResponse(body) if err != nil { @@ -352,4 +393,4 @@ func (r HostResource) Read(ctx context.Context, req resource.ReadRequest, resp * } // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} \ No newline at end of file +} diff --git a/internal/provider/host_resource_test.go b/internal/provider/host_resource_test.go new file mode 100644 index 0000000..a591f68 --- /dev/null +++ b/internal/provider/host_resource_test.go @@ -0,0 +1,185 @@ +package provider + +import ( + "bytes" + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +func TestHostResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaRequest := fwresource.SchemaRequest{} + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the HostResource and call its Schema method + NewHostResource().Schema(ctx, schemaRequest, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} + +func TestHostResourceCreateRequestBody(t *testing.T) { + var testTable = []struct { + name string + input HostResourceModel + expected []byte + }{ + { + name: "test with unknown values", + input: HostResourceModel{ + Name: types.StringValue("test host"), + Description: types.StringUnknown(), + URL: types.StringUnknown(), + Variables: jsontypes.NewNormalizedUnknown(), + GroupId: types.Int64Unknown(), + DisassociateGroup: basetypes.NewBoolValue(false), + Enabled: basetypes.NewBoolValue(false), + InventoryId: types.Int64Unknown(), + InstanceId: types.Int64Unknown(), + }, + expected: []byte(`{"enabled":false,"instance_id":0,"inventory":0,"name":"test host"}`), + }, + { + name: "test with null values", + input: HostResourceModel{ + Name: types.StringValue("test host"), + Description: types.StringNull(), + URL: types.StringNull(), + Variables: jsontypes.NewNormalizedNull(), + GroupId: types.Int64Null(), + DisassociateGroup: basetypes.NewBoolValue(false), + Enabled: basetypes.NewBoolValue(false), + InventoryId: types.Int64Null(), + InstanceId: types.Int64Null(), + }, + expected: []byte(`{"enabled":false,"instance_id":0,"inventory":0,"name":"test host"}`), + }, + { + name: "test with some values", + input: HostResourceModel{ + Name: types.StringValue("host1"), + Description: types.StringNull(), + URL: types.StringValue("/api/v2/hosts/1/"), + Variables: jsontypes.NewNormalizedValue("{\"foo\":\"bar\"}"), + }, + expected: []byte( + `{"instance_id":0,"inventory":0,"name":"host1","url":"/api/v2/hosts/1/",` + + `"variables":"{\"foo\":\"bar\"}"}`, + ), + }, + { + name: "test with group id", + input: HostResourceModel{ + Name: types.StringValue("host1"), + Description: types.StringNull(), + URL: types.StringValue("/api/v2/hosts/1/"), + Variables: jsontypes.NewNormalizedValue("{\"foo\":\"bar\"}"), + GroupId: basetypes.NewInt64Value(2), + }, + expected: []byte( + `{"id":2,"instance_id":0,"inventory":0,"name":"host1","url":"/api/v2/hosts/1/",` + + `"variables":"{\"foo\":\"bar\"}"}`, + ), + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + actual, diags := test.input.CreateRequestBody() + if diags.HasError() { + t.Fatal(diags.Errors()) + } + if !bytes.Equal(test.expected, actual) { + t.Errorf("Expected (%s) not equal to actual (%s)", test.expected, actual) + } + }) + } +} + +// CustomError is a custom error type +type CustomError struct { + Message string +} + +// Implement the error interface for Cu +func (e CustomError) Error() string { + return e.Message +} + +func TestHostResourceParseHttpResponse(t *testing.T) { + customErr := CustomError{ + Message: "invalid character 'N' looking for beginning of value", + } + emptyError := CustomError{} + + var testTable = []struct { + name string + input []byte + expected HostResourceModel + errors error + }{ + { + name: "test with JSON error", + input: []byte("Not valid JSON"), + expected: HostResourceModel{}, + errors: customErr, + }, + { + name: "test with missing values", + input: []byte(`{"name": "host1", "url": "/api/v2/hosts/1/", "description": "", "variables": "", "group_id": 2}`), + expected: HostResourceModel{ + Name: types.StringValue("host1"), + URL: types.StringValue("/api/v2/hosts/1/"), + Description: types.StringNull(), + GroupId: types.Int64Value(2), + Variables: jsontypes.NewNormalizedNull(), + }, + errors: emptyError, + }, + { + name: "test with all values", + input: []byte( + `{"description":"A basic test host","group_id":1,"name":"host1","disassociate_group":false,` + + `"enabled":false,"url":"/api/v2/hosts/1/","variables":"{\"foo\":\"bar\",\"nested\":{\"foobar\":\"baz\"}}"}`, + ), + expected: HostResourceModel{ + Name: types.StringValue("host1"), + URL: types.StringValue("/api/v2/hosts/1/"), + Description: types.StringValue("A basic test host"), + GroupId: types.Int64Value(1), + DisassociateGroup: basetypes.NewBoolValue(false), + Variables: jsontypes.NewNormalizedValue("{\"foo\":\"bar\",\"nested\":{\"foobar\":\"baz\"}}"), + Enabled: basetypes.NewBoolValue(false), + }, + errors: emptyError, + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + resource := HostResourceModel{} + err := resource.ParseHttpResponse(test.input) + if test.errors != nil && err != nil && test.errors.Error() != err.Error() { + t.Errorf("Expected error diagnostics (%s), actual was (%s)", test.errors, err) + } + if test.expected != resource { + t.Errorf("Expected (%s) not equal to actual (%s)", test.expected, resource) + } + }) + } +} diff --git a/internal/provider/job_resource.go b/internal/provider/job_resource.go index 684c3cf..93d8ffc 100644 --- a/internal/provider/job_resource.go +++ b/internal/provider/job_resource.go @@ -203,20 +203,8 @@ func (r JobResource) CreateJob(data JobResourceModelInterface) diag.Diagnostics var postURL = "/api/v2/job_templates/" + data.GetTemplateID() + "/launch/" resp, body, err := r.client.doRequest(http.MethodPost, postURL, reqData) + diags.Append(IsResponseValid(resp, err, http.StatusCreated)...) - if err != nil { - diags.AddError("client request error", err.Error()) - return diags - } - if resp == nil { - diags.AddError("Http response Error", "no http response from server") - return diags - } - if resp.StatusCode != http.StatusCreated { - diags.AddError("Unexpected Http Status code", - fmt.Sprintf("expected (%d) got (%d)", http.StatusCreated, resp.StatusCode)) - return diags - } err = data.ParseHTTPResponse(body) if err != nil { diags.AddError("error while parsing the json response: ", err.Error()) @@ -227,18 +215,11 @@ func (r JobResource) CreateJob(data JobResourceModelInterface) diag.Diagnostics func (r JobResource) ReadJob(data JobResourceModelInterface) error { // Read existing Job + var diags diag.Diagnostics jobURL := data.GetURL() if len(jobURL) > 0 { resp, body, err := r.client.doRequest("GET", jobURL, nil) - if err != nil { - return err - } - if resp == nil { - return fmt.Errorf("the server response is null") - } - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("the server returned status code %d while attempting to Get from URL %s", resp.StatusCode, jobURL) - } + diags.Append(IsResponseValid(resp, err, http.StatusOK)...) err = data.ParseHTTPResponse(body) if err != nil { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b3d4821..b9779b6 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -161,6 +161,7 @@ func (p *aapProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ NewJobResource, NewGroupResource, + NewHostResource, } } diff --git a/internal/provider/utils.go b/internal/provider/utils.go index 4562c9e..45a1ac9 100644 --- a/internal/provider/utils.go +++ b/internal/provider/utils.go @@ -1,9 +1,33 @@ package provider import ( + "fmt" + "net/http" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" ) func IsValueProvided(value attr.Value) bool { return !value.IsNull() && !value.IsUnknown() } + +func IsResponseValid(resp *http.Response, err error, expected_status int) diag.Diagnostics { + var diags diag.Diagnostics + + if err != nil { + diags.AddError("Body JSON Marshal Error", err.Error()) + return diags + } + if resp == nil { + diags.AddError("Http response Error", "no http response from server") + return diags + } + if resp.StatusCode != expected_status { + diags.AddError("Unexpected Http Status code", + fmt.Sprintf("expected (%d) got (%s)", expected_status, resp.Status)) + return diags + } + + return diags +} From 70b8552ec0fd3d7f24eea0552ba5289e0163a08d Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Fri, 19 Jan 2024 15:14:26 +0100 Subject: [PATCH 3/6] Delete internal/provider/helper.go --- internal/provider/helper.go | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 internal/provider/helper.go diff --git a/internal/provider/helper.go b/internal/provider/helper.go deleted file mode 100644 index 55202c4..0000000 --- a/internal/provider/helper.go +++ /dev/null @@ -1,7 +0,0 @@ -package provider - -import "github.com/hashicorp/terraform-plugin-framework/attr" - -func IsValueProvided(value attr.Value) bool { - return !value.IsNull() && !value.IsUnknown() -} \ No newline at end of file From ec55757bc82ea624cd6bdd340cd63ae90fea3e2c Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Fri, 19 Jan 2024 15:30:25 +0100 Subject: [PATCH 4/6] Add tests for IsResponseValid Signed-off-by: Alina Buzachis --- internal/provider/test_utils.go | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/internal/provider/test_utils.go b/internal/provider/test_utils.go index ae33d85..f749920 100644 --- a/internal/provider/test_utils.go +++ b/internal/provider/test_utils.go @@ -2,11 +2,15 @@ package provider import ( "encoding/json" + "fmt" "io" "net/http" "reflect" "slices" "strings" + "testing" + + "github.com/stretchr/testify/assert" ) // DeepEqualJSONByte compares the JSON in two byte slices. @@ -73,3 +77,51 @@ func (c *MockHTTPClient) doRequest(method string, path string, data io.Reader) ( } return &http.Response{StatusCode: c.httpCode}, result, nil } + +const ( + expectedStatusCode = 200 + unexpectedStatusCode = 404 +) + +func TestIsResponseValid(t *testing.T) { + var testTable = []struct { + resp *http.Response + err error + expected int + }{ + { + resp: &http.Response{ + StatusCode: expectedStatusCode, + Status: "OK", + }, + err: nil, + }, + { + resp: nil, + err: fmt.Errorf("sample error message"), + }, + { + resp: nil, + err: nil, + }, + { + resp: &http.Response{ + StatusCode: unexpectedStatusCode, + Status: "Not Found", + }, + err: nil, + }, + } + + for _, test := range testTable { + t.Run(fmt.Sprintf("Status: %d", expectedStatusCode), func(t *testing.T) { + diags := IsResponseValid(test.resp, test.err, expectedStatusCode) + + if test.resp != nil && test.err == nil && test.resp.StatusCode == expectedStatusCode { + assert.Empty(t, diags, "No errors expected for a successful response") + } else { + assert.NotEmpty(t, diags, "Error expected") + } + }) + } +} From 832f0a84caf6155597320a6a59d6539190bb2cce Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Fri, 19 Jan 2024 16:20:22 +0100 Subject: [PATCH 5/6] Fixes Signed-off-by: Alina Buzachis --- config.tfrc | 7 ---- examples/resources/aap_host/main.tf | 29 -------------- internal/provider/host_resource.go | 52 +++++++++++-------------- internal/provider/host_resource_test.go | 42 +++++++++----------- 4 files changed, 42 insertions(+), 88 deletions(-) delete mode 100644 config.tfrc delete mode 100644 examples/resources/aap_host/main.tf diff --git a/config.tfrc b/config.tfrc deleted file mode 100644 index 0efac4d..0000000 --- a/config.tfrc +++ /dev/null @@ -1,7 +0,0 @@ -provider_installation { - dev_overrides { - "ansible/aap" = "/Users/alinabuzachis/dev/ansible/terraform-provider-aap" - } - - direct {} -} diff --git a/examples/resources/aap_host/main.tf b/examples/resources/aap_host/main.tf deleted file mode 100644 index b4ae033..0000000 --- a/examples/resources/aap_host/main.tf +++ /dev/null @@ -1,29 +0,0 @@ -terraform { - required_providers { - aap = { - source = "ansible/aap" - } - } -} - -provider "aap" { - host = "https://localhost:8043" - username = "test" - password = "test" - insecure_skip_verify = true -} - -resource "aap_host" "sample" { - inventory_id = 1 - name = "tf_host" - variables = jsonencode( - { - "foo": "bar" - } - ) -} - - -output "host" { - value = aap_host.sample -} \ No newline at end of file diff --git a/internal/provider/host_resource.go b/internal/provider/host_resource.go index 556f2c4..4d2c5c5 100644 --- a/internal/provider/host_resource.go +++ b/internal/provider/host_resource.go @@ -8,7 +8,6 @@ import ( "io" "net/http" - "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -53,8 +52,9 @@ func (r *HostResource) Schema(_ context.Context, _ resource.SchemaRequest, resp "inventory_id": schema.Int64Attribute{ Required: true, }, - "instance_id": schema.Int64Attribute{ + "instance_id": schema.StringAttribute{ Optional: true, + Computed: true, }, "name": schema.StringAttribute{ Required: true, @@ -70,8 +70,7 @@ func (r *HostResource) Schema(_ context.Context, _ resource.SchemaRequest, resp }, }, "variables": schema.StringAttribute{ - Optional: true, - CustomType: jsontypes.NormalizedType{}, + Optional: true, }, "enabled": schema.BoolAttribute{ Optional: true, @@ -93,15 +92,15 @@ func (r *HostResource) Schema(_ context.Context, _ resource.SchemaRequest, resp // HostResourceModel maps the resource schema data. type HostResourceModel struct { - InventoryId types.Int64 `tfsdk:"inventory_id"` - InstanceId types.Int64 `tfsdk:"instance_id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - URL types.String `tfsdk:"host_url"` - Variables jsontypes.Normalized `tfsdk:"variables"` - Enabled types.Bool `tfsdk:"enabled"` - GroupId types.Int64 `tfsdk:"group_id"` - DisassociateGroup types.Bool `tfsdk:"disassociate_group"` + InventoryId types.Int64 `tfsdk:"inventory_id"` + InstanceId types.String `tfsdk:"instance_id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + URL types.String `tfsdk:"host_url"` + Variables types.String `tfsdk:"variables"` + Enabled types.Bool `tfsdk:"enabled"` + GroupId types.Int64 `tfsdk:"group_id"` + DisassociateGroup types.Bool `tfsdk:"disassociate_group"` } func (d *HostResourceModel) GetURL() string { @@ -119,7 +118,7 @@ func (d *HostResourceModel) CreateRequestBody() ([]byte, diag.Diagnostics) { body["inventory"] = d.InventoryId.ValueInt64() // Instance id - body["instance_id"] = d.InstanceId.ValueInt64() + body["instance_id"] = d.InstanceId.ValueString() // Name body["name"] = d.Name.ValueString() @@ -144,11 +143,6 @@ func (d *HostResourceModel) CreateRequestBody() ([]byte, diag.Diagnostics) { } } - // URL - if IsValueProvided(d.URL) { - body["url"] = d.URL.ValueString() - } - // Description if IsValueProvided(d.Description) { body["description"] = d.Description.ValueString() @@ -180,6 +174,14 @@ func (d *HostResourceModel) ParseHttpResponse(body []byte) error { d.Name = types.StringValue(result["name"].(string)) d.URL = types.StringValue(result["url"].(string)) + if r, ok := result["instance_id"]; ok { + d.InstanceId = types.StringValue(r.(string)) + } + + if r, ok := result["inventory"]; ok { + d.InventoryId = types.Int64Value(int64(r.(float64))) + } + if result["description"] != "" { d.Description = types.StringValue(result["description"].(string)) } else { @@ -187,17 +189,9 @@ func (d *HostResourceModel) ParseHttpResponse(body []byte) error { } if result["variables"] != "" { - d.Variables = jsontypes.NewNormalizedValue(result["variables"].(string)) + d.Variables = types.StringValue(result["variables"].(string)) } else { - d.Variables = jsontypes.NewNormalizedNull() - } - - if r, ok := result["group_id"]; ok { - d.GroupId = basetypes.NewInt64Value(int64(r.(float64))) - } - - if r, ok := result["disassociate_group"]; ok && r != nil { - d.DisassociateGroup = basetypes.NewBoolValue(r.(bool)) + d.Variables = types.StringNull() } if r, ok := result["enabled"]; ok && r != nil { diff --git a/internal/provider/host_resource_test.go b/internal/provider/host_resource_test.go index a591f68..dab82e1 100644 --- a/internal/provider/host_resource_test.go +++ b/internal/provider/host_resource_test.go @@ -5,7 +5,6 @@ import ( "context" "testing" - "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -45,14 +44,14 @@ func TestHostResourceCreateRequestBody(t *testing.T) { Name: types.StringValue("test host"), Description: types.StringUnknown(), URL: types.StringUnknown(), - Variables: jsontypes.NewNormalizedUnknown(), + Variables: types.StringNull(), GroupId: types.Int64Unknown(), DisassociateGroup: basetypes.NewBoolValue(false), Enabled: basetypes.NewBoolValue(false), InventoryId: types.Int64Unknown(), - InstanceId: types.Int64Unknown(), + InstanceId: types.StringNull(), }, - expected: []byte(`{"enabled":false,"instance_id":0,"inventory":0,"name":"test host"}`), + expected: []byte(`{"enabled":false,"instance_id":"","inventory":0,"name":"test host"}`), }, { name: "test with null values", @@ -60,26 +59,26 @@ func TestHostResourceCreateRequestBody(t *testing.T) { Name: types.StringValue("test host"), Description: types.StringNull(), URL: types.StringNull(), - Variables: jsontypes.NewNormalizedNull(), + Variables: types.StringNull(), GroupId: types.Int64Null(), DisassociateGroup: basetypes.NewBoolValue(false), Enabled: basetypes.NewBoolValue(false), InventoryId: types.Int64Null(), - InstanceId: types.Int64Null(), + InstanceId: types.StringNull(), }, - expected: []byte(`{"enabled":false,"instance_id":0,"inventory":0,"name":"test host"}`), + expected: []byte(`{"enabled":false,"instance_id":"","inventory":0,"name":"test host"}`), }, { name: "test with some values", input: HostResourceModel{ + InventoryId: types.Int64Value(1), Name: types.StringValue("host1"), Description: types.StringNull(), URL: types.StringValue("/api/v2/hosts/1/"), - Variables: jsontypes.NewNormalizedValue("{\"foo\":\"bar\"}"), + Variables: types.StringValue("{\"foo\":\"bar\"}"), }, expected: []byte( - `{"instance_id":0,"inventory":0,"name":"host1","url":"/api/v2/hosts/1/",` + - `"variables":"{\"foo\":\"bar\"}"}`, + `{"instance_id":"","inventory":1,"name":"host1","variables":"{\"foo\":\"bar\"}"}`, ), }, { @@ -88,12 +87,11 @@ func TestHostResourceCreateRequestBody(t *testing.T) { Name: types.StringValue("host1"), Description: types.StringNull(), URL: types.StringValue("/api/v2/hosts/1/"), - Variables: jsontypes.NewNormalizedValue("{\"foo\":\"bar\"}"), + Variables: types.StringValue("{\"foo\":\"bar\"}"), GroupId: basetypes.NewInt64Value(2), }, expected: []byte( - `{"id":2,"instance_id":0,"inventory":0,"name":"host1","url":"/api/v2/hosts/1/",` + - `"variables":"{\"foo\":\"bar\"}"}`, + `{"id":2,"instance_id":"","inventory":0,"name":"host1","variables":"{\"foo\":\"bar\"}"}`, ), }, } @@ -141,13 +139,13 @@ func TestHostResourceParseHttpResponse(t *testing.T) { }, { name: "test with missing values", - input: []byte(`{"name": "host1", "url": "/api/v2/hosts/1/", "description": "", "variables": "", "group_id": 2}`), + input: []byte(`{"inventory":1,"name": "host1", "url": "/api/v2/hosts/1/", "description": "", "variables": "", "group_id": 2}`), expected: HostResourceModel{ + InventoryId: types.Int64Value(1), Name: types.StringValue("host1"), URL: types.StringValue("/api/v2/hosts/1/"), Description: types.StringNull(), - GroupId: types.Int64Value(2), - Variables: jsontypes.NewNormalizedNull(), + Variables: types.StringNull(), }, errors: emptyError, }, @@ -158,13 +156,11 @@ func TestHostResourceParseHttpResponse(t *testing.T) { `"enabled":false,"url":"/api/v2/hosts/1/","variables":"{\"foo\":\"bar\",\"nested\":{\"foobar\":\"baz\"}}"}`, ), expected: HostResourceModel{ - Name: types.StringValue("host1"), - URL: types.StringValue("/api/v2/hosts/1/"), - Description: types.StringValue("A basic test host"), - GroupId: types.Int64Value(1), - DisassociateGroup: basetypes.NewBoolValue(false), - Variables: jsontypes.NewNormalizedValue("{\"foo\":\"bar\",\"nested\":{\"foobar\":\"baz\"}}"), - Enabled: basetypes.NewBoolValue(false), + Name: types.StringValue("host1"), + URL: types.StringValue("/api/v2/hosts/1/"), + Description: types.StringValue("A basic test host"), + Variables: types.StringValue("{\"foo\":\"bar\",\"nested\":{\"foobar\":\"baz\"}}"), + Enabled: basetypes.NewBoolValue(false), }, errors: emptyError, }, From f65cdc3438d90314e5148c9e4be20e7a1b0d6d94 Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Fri, 19 Jan 2024 17:46:12 +0100 Subject: [PATCH 6/6] Adapt group_resource.go to use IsResponseValid Signed-off-by: Alina Buzachis --- internal/provider/group_resource.go | 55 +++-------------------------- 1 file changed, 4 insertions(+), 51 deletions(-) diff --git a/internal/provider/group_resource.go b/internal/provider/group_resource.go index c01d404..7b4e966 100644 --- a/internal/provider/group_resource.go +++ b/internal/provider/group_resource.go @@ -174,19 +174,7 @@ func (r GroupResource) CreateGroup(data GroupResourceModelInterface) diag.Diagno } resp, body, err := r.client.doRequest(http.MethodPost, "/api/v2/groups/", req_data) - if err != nil { - diags.AddError("Body JSON Marshal Error", err.Error()) - return diags - } - if resp == nil { - diags.AddError("Http response Error", "no http response from server") - return diags - } - if resp.StatusCode != http.StatusCreated { - diags.AddError("Unexpected Http Status code", - fmt.Sprintf("expected (%d) got (%s)", http.StatusCreated, resp.Status)) - return diags - } + diags.Append(IsResponseValid(resp, err, http.StatusCreated)...) err = data.ParseHttpResponse(body) if err != nil { diags.AddError("error while parsing the json response: ", err.Error()) @@ -218,19 +206,7 @@ func (r GroupResource) DeleteGroup(data GroupResourceModelInterface) diag.Diagno var diags diag.Diagnostics resp, _, err := r.client.doRequest(http.MethodDelete, data.GetURL(), nil) - if err != nil { - diags.AddError("Body JSON Marshal Error", err.Error()) - return diags - } - if resp == nil { - diags.AddError("Http response Error", "no http response from server") - return diags - } - if resp.StatusCode != http.StatusNoContent { - diags.AddError("Unexpected Http Status code", - fmt.Sprintf("expected (%d) got (%s)", http.StatusNoContent, resp.Status)) - return diags - } + diags.Append(IsResponseValid(resp, err, http.StatusNoContent)...) return diags } @@ -261,20 +237,8 @@ func (r GroupResource) UpdateGroup(data GroupResourceModelInterface) diag.Diagno req_data = bytes.NewReader(req_body) } resp, body, err := r.client.doRequest(http.MethodPut, data.GetURL(), req_data) + diags.Append(IsResponseValid(resp, err, http.StatusOK)...) - if err != nil { - diags.AddError("Body JSON Marshal Error", err.Error()) - return diags - } - if resp == nil { - diags.AddError("Http response Error", "no http response from server") - return diags - } - if resp.StatusCode != http.StatusOK { - diags.AddError("Unexpected Http Status code", - fmt.Sprintf("expected (%d) got (%s)", http.StatusOK, resp.Status)) - return diags - } err = data.ParseHttpResponse(body) if err != nil { diags.AddError("error while parsing the json response: ", err.Error()) @@ -301,18 +265,7 @@ func (r GroupResource) ReadGroup(data GroupResourceModelInterface) diag.Diagnost // Read existing Group group_url := data.GetURL() resp, body, err := r.client.doRequest(http.MethodGet, group_url, nil) - if err != nil { - diags.AddError("Get Error", err.Error()) - return diags - } - if resp == nil { - diags.AddError("Http response Error", "no http response from server") - return diags - } - if resp.StatusCode != http.StatusOK { - diags.AddError("Unexpected Http Status code", - fmt.Sprintf("expected (%d) got (%s)", http.StatusOK, resp.Status)) - } + diags.Append(IsResponseValid(resp, err, http.StatusOK)...) err = data.ParseHttpResponse(body) if err != nil {