From 290b70e7b694d5d157c018365fdf5ff83e3d6040 Mon Sep 17 00:00:00 2001 From: Rohit Myali Date: Wed, 24 Apr 2024 13:43:43 +0530 Subject: [PATCH 1/2] Added aclrule resource Signed-off-by: Rohit Myali --- docs/resources/aclrule.md | 43 ++++ .../netscalersdx_aclrule/provider.tf | 13 ++ .../netscalersdx_aclrule/resource.tf | 8 + .../resource_netscalersdx_aclrule_test.go | 88 ++++++++ internal/aclrule/resource_aclrule.go | 189 ++++++++++++++++++ internal/aclrule/resource_schema.go | 107 ++++++++++ internal/provider/provider.go | 2 + 7 files changed, 450 insertions(+) create mode 100644 docs/resources/aclrule.md create mode 100644 examples/resources/netscalersdx_aclrule/provider.tf create mode 100644 examples/resources/netscalersdx_aclrule/resource.tf create mode 100644 internal/acctest/resource_netscalersdx_aclrule_test.go create mode 100644 internal/aclrule/resource_aclrule.go create mode 100644 internal/aclrule/resource_schema.go diff --git a/docs/resources/aclrule.md b/docs/resources/aclrule.md new file mode 100644 index 0000000..06fb377 --- /dev/null +++ b/docs/resources/aclrule.md @@ -0,0 +1,43 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "netscalersdx_aclrule Resource - terraform-provider-netscalersdx" +subcategory: "" +description: |- + Configuration for ACL Rule resource. +--- + +# netscalersdx_aclrule (Resource) + +Configuration for ACL Rule resource. + +## Example Usage + +```terraform +resource "netscalersdx_aclrule" "tf_aclrule" { + name = "tf_aclrule" + priority = 100 + protocol = "TCP" + action = "Allow" + dst_port = 80 + src_ip = "10.10.10.10" +} +``` + + +## Schema + +### Required + +- `action` (String) Action can be [Allow Block]. Minimum length = 4 Maximum length = 5 +- `name` (String) Rule Name. Minimum length = 1 Maximum length = 128 +- `priority` (Number) Priority. Minimum value = 1 Maximum value = +- `protocol` (String) IP Protocol. The allowed values are [TCP UDP ICMP ANY]. Minimum length = 3 Maximum length = 4 +- `src_ip` (String) Source IP Address or Subnet. Minimum length = 3 Maximum length = 128 + +### Optional + +- `dst_port` (String) Enable external authentication. + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/examples/resources/netscalersdx_aclrule/provider.tf b/examples/resources/netscalersdx_aclrule/provider.tf new file mode 100644 index 0000000..ba73215 --- /dev/null +++ b/examples/resources/netscalersdx_aclrule/provider.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + netscalersdx = { + source = "netscaler/netscalersdx" + } + } +} +provider "netscalersdx" { + host = "https://10.10.10.10" # Optionally use NETSCALERSDX_HOST env var + username = "nsroot" # Optionally use NETSCALERSDX_USERNAME env var + password = "secretpassword" # Optionally use NETSCALERSDX_PASSWORD env var + ssl_verify = false # Optionally use NETSCALERSDX_SSL_VERIFY env var +} diff --git a/examples/resources/netscalersdx_aclrule/resource.tf b/examples/resources/netscalersdx_aclrule/resource.tf new file mode 100644 index 0000000..dcec3d1 --- /dev/null +++ b/examples/resources/netscalersdx_aclrule/resource.tf @@ -0,0 +1,8 @@ +resource "netscalersdx_aclrule" "tf_aclrule" { + name = "tf_aclrule" + priority = 100 + protocol = "TCP" + action = "Allow" + dst_port = 80 + src_ip = "10.10.10.10" +} diff --git a/internal/acctest/resource_netscalersdx_aclrule_test.go b/internal/acctest/resource_netscalersdx_aclrule_test.go new file mode 100644 index 0000000..0f3c3bd --- /dev/null +++ b/internal/acctest/resource_netscalersdx_aclrule_test.go @@ -0,0 +1,88 @@ +package acctest + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const ( + testAccAclrulePlaceholder = ` + + resource "netscalersdx_aclrule" "tf_aclrule" { + name = "tf_aclrule" + priority = 100 + protocol = "TCP" + action = "Allow" + dst_port = 80 + src_ip = "10.10.10.10" + } + + ` +) + +func TestAccAclrule_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckAclruleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAclrulePlaceholder, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAclruleExists("netscalersdx_aclrule.tf_aclrule"), + resource.TestCheckResourceAttr("netscalersdx_aclrule.tf_aclrule", "name", "tf_aclrule"), + resource.TestCheckResourceAttr("netscalersdx_aclrule.tf_aclrule", "protocol", "TCP"), + resource.TestCheckResourceAttr("netscalersdx_aclrule.tf_aclrule", "action", "Allow"), + ), + }, + }, + }) +} + +func testAccCheckAclruleExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Aclrule not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Aclrule ID is set") + } + + client, err := testAccApiClient() + if err != nil { + return err + } + + data, err := client.GetResource("aclrule", rs.Primary.ID) + if err != nil { + return err + } + + if data == nil { + return fmt.Errorf("Aclrule not found: %s", n) + } + return nil + } +} + +func testAccCheckAclruleDestroy(s *terraform.State) error { + client, err := testAccApiClient() + if err != nil { + return err + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "netscalersdx_aclrule" { + continue + } + _, err := client.GetResource("aclrule", rs.Primary.ID) + if err == nil { + return fmt.Errorf("Aclrule still exists") + } + } + return nil +} diff --git a/internal/aclrule/resource_aclrule.go b/internal/aclrule/resource_aclrule.go new file mode 100644 index 0000000..e2b534d --- /dev/null +++ b/internal/aclrule/resource_aclrule.go @@ -0,0 +1,189 @@ +package aclrule + +import ( + "context" + "fmt" + + "terraform-provider-netscalersdx/internal/service" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ resource.Resource = (*aclruleResource)(nil) +var _ resource.ResourceWithConfigure = (*aclruleResource)(nil) + +func AclruleResource() resource.Resource { + return &aclruleResource{} +} + +type aclruleResource struct { + client *service.NitroClient +} + +func (r *aclruleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_aclrule" +} + +// Configure configures the client resource. +func (r *aclruleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.client = *req.ProviderData.(**service.NitroClient) +} + +func (r *aclruleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = aclruleResourceSchema() +} + +func (r *aclruleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + tflog.Debug(ctx, "In Create Method of aclrule Resource") + + var data aclruleModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + aclruleReq := aclruleGetThePayloadFromtheConfig(ctx, &data) + + endpoint := "aclrule" + + // Create the request + returnData, err := r.client.AddResource(endpoint, aclruleReq) + + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error creating resource: %s", endpoint), + fmt.Sprintf("Error: %s", err.Error()), + ) + return + } + + resID := returnData[endpoint].([]interface{})[0].(map[string]interface{})["id"].(string) + + // Example data value setting + data.Id = types.StringValue(resID) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + rreq := resource.ReadRequest{ + State: resp.State, + ProviderMeta: req.ProviderMeta, + } + rresp := resource.ReadResponse{ + State: resp.State, + Diagnostics: resp.Diagnostics, + } + + r.Read(ctx, rreq, &rresp) + + *resp = resource.CreateResponse{ + State: rresp.State, + Diagnostics: rresp.Diagnostics, + } + +} + +func (r *aclruleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + + var resId types.String + req.State.GetAttribute(ctx, path.Root("id"), &resId) + tflog.Debug(ctx, fmt.Sprintf("In Read Method of aclrule Resource with Id: %s", resId)) + + var data aclruleModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Read API call logic + endpoint := "aclrule" + + responseData, err := r.client.GetResource(endpoint, data.Id.ValueString()) + if err != nil { + resp.State.RemoveResource(ctx) + tflog.Warn(ctx, fmt.Sprintf("removing resource aclrule: %v from state because it is not present in the remote", data.Id.ValueString())) + return + } + + getResponseData := responseData[endpoint].([]interface{})[0].(map[string]interface{}) + + aclruleSetAttrFromGet(ctx, &data, getResponseData) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *aclruleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + tflog.Debug(ctx, "In Update Method of aclrule Resource") + + var data aclruleModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + var state aclruleModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + resourceId := state.Id.ValueString() + endpoint := "aclrule" + requestPayload := aclruleGetThePayloadFromtheConfig(ctx, &data) + data.Id = state.Id + + _, err := r.client.UpdateResource(endpoint, requestPayload, resourceId) + + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Resource", + fmt.Sprintf("Error updating resource: %s", err.Error()), + ) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *aclruleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Debug(ctx, "In Delete Method of aclrule Resource") + + var data aclruleModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete API call logic + endpoint := "aclrule" + _, err := r.client.DeleteResource(endpoint, data.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error deleting resource: %s", endpoint), + fmt.Sprintf("Error: %s", err.Error()), + ) + return + } +} diff --git a/internal/aclrule/resource_schema.go b/internal/aclrule/resource_schema.go new file mode 100644 index 0000000..f2b4d44 --- /dev/null +++ b/internal/aclrule/resource_schema.go @@ -0,0 +1,107 @@ +package aclrule + +import ( + "context" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func aclruleResourceSchema() schema.Schema { + return schema.Schema{ + Description: "Configuration for ACL Rule resource.", + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Required: true, + Description: "Action can be [Allow Block]. Minimum length = 4 Maximum length = 5", + MarkdownDescription: "Action can be [Allow Block]. Minimum length = 4 Maximum length = 5", + }, + "dst_port": schema.StringAttribute{ + Optional: true, + Description: "Enable external authentication.", + MarkdownDescription: "Enable external authentication.", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Rule Name. Minimum length = 1 Maximum length = 128", + MarkdownDescription: "Rule Name. Minimum length = 1 Maximum length = 128", + }, + "priority": schema.Int64Attribute{ + Required: true, + Description: "Priority. Minimum value = 1 Maximum value = ", + MarkdownDescription: "Priority. Minimum value = 1 Maximum value = ", + }, + "protocol": schema.StringAttribute{ + Required: true, + Description: "IP Protocol. The allowed values are [TCP UDP ICMP ANY]. Minimum length = 3 Maximum length = 4", + MarkdownDescription: "IP Protocol. The allowed values are [TCP UDP ICMP ANY]. Minimum length = 3 Maximum length = 4", + }, + "src_ip": schema.StringAttribute{ + Required: true, + Description: "Source IP Address or Subnet. Minimum length = 3 Maximum length = 128", + MarkdownDescription: "Source IP Address or Subnet. Minimum length = 3 Maximum length = 128", + }, + "id": schema.StringAttribute{ + Computed: true, + Optional: true, + }, + }, + } +} + +type aclruleModel struct { + Action types.String `tfsdk:"action"` + DstPort types.String `tfsdk:"dst_port"` + Name types.String `tfsdk:"name"` + Priority types.Int64 `tfsdk:"priority"` + Protocol types.String `tfsdk:"protocol"` + SrcIp types.String `tfsdk:"src_ip"` + Id types.String `tfsdk:"id"` +} + +func aclruleGetThePayloadFromtheConfig(ctx context.Context, data *aclruleModel) aclruleReq { + tflog.Debug(ctx, "In aclruleGetThePayloadFromtheConfig Function") + aclruleReqPayload := aclruleReq{ + Action: data.Action.ValueString(), + DstPort: data.DstPort.ValueString(), + Name: data.Name.ValueString(), + Priority: data.Priority.ValueInt64Pointer(), + Protocol: data.Protocol.ValueString(), + SrcIp: data.SrcIp.ValueString(), + } + return aclruleReqPayload +} +func aclruleSetAttrFromGet(ctx context.Context, data *aclruleModel, getResponseData map[string]interface{}) *aclruleModel { + tflog.Debug(ctx, "In aclruleSetAttrFromGet Function") + if !data.Action.IsNull() { + data.Action = types.StringValue(getResponseData["action"].(string)) + } + if !data.DstPort.IsNull() { + data.DstPort = types.StringValue(getResponseData["dst_port"].(string)) + } + if !data.Name.IsNull() { + data.Name = types.StringValue(getResponseData["name"].(string)) + } + if !data.Priority.IsNull() { + val, _ := strconv.Atoi(getResponseData["priority"].(string)) + data.Priority = types.Int64Value(int64(val)) + } + if !data.Protocol.IsNull() { + data.Protocol = types.StringValue(getResponseData["protocol"].(string)) + } + if !data.SrcIp.IsNull() { + data.SrcIp = types.StringValue(getResponseData["src_ip"].(string)) + } + return data +} + +type aclruleReq struct { + Action string `json:"action,omitempty"` + DstPort string `json:"dst_port,omitempty"` + Name string `json:"name,omitempty"` + Priority *int64 `json:"priority,omitempty"` + Protocol string `json:"protocol,omitempty"` + SrcIp string `json:"src_ip,omitempty"` +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index bb10e6c..bc6ac3d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -6,6 +6,7 @@ import ( "os" "strconv" + "terraform-provider-netscalersdx/internal/aclrule" "terraform-provider-netscalersdx/internal/blx_device_profile" "terraform-provider-netscalersdx/internal/cipher_group" "terraform-provider-netscalersdx/internal/current_timezone" @@ -137,6 +138,7 @@ func (p *sdxprovider) Resources(_ context.Context) []func() resource.Resource { snmp_manager.SnmpManagerResource, mpsuser.MpsuserResource, mpsgroup.MpsgroupResource, + aclrule.AclruleResource, } } func (p *sdxprovider) DataSources(_ context.Context) []func() datasource.DataSource { From db9ae92f1a9504d5fb9a24ae0679826c1b2bf14c Mon Sep 17 00:00:00 2001 From: Rohit Myali Date: Wed, 24 Apr 2024 13:46:40 +0530 Subject: [PATCH 2/2] Added aclrule resource Signed-off-by: Rohit Myali --- examples/resources/netscalersdx_aclrule/resource.tf | 2 +- internal/acctest/resource_netscalersdx_aclrule_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/resources/netscalersdx_aclrule/resource.tf b/examples/resources/netscalersdx_aclrule/resource.tf index dcec3d1..c54f10e 100644 --- a/examples/resources/netscalersdx_aclrule/resource.tf +++ b/examples/resources/netscalersdx_aclrule/resource.tf @@ -3,6 +3,6 @@ resource "netscalersdx_aclrule" "tf_aclrule" { priority = 100 protocol = "TCP" action = "Allow" - dst_port = 80 + dst_port = "80" src_ip = "10.10.10.10" } diff --git a/internal/acctest/resource_netscalersdx_aclrule_test.go b/internal/acctest/resource_netscalersdx_aclrule_test.go index 0f3c3bd..31ac860 100644 --- a/internal/acctest/resource_netscalersdx_aclrule_test.go +++ b/internal/acctest/resource_netscalersdx_aclrule_test.go @@ -16,7 +16,7 @@ const ( priority = 100 protocol = "TCP" action = "Allow" - dst_port = 80 + dst_port = "80" src_ip = "10.10.10.10" }