diff --git a/docs/data-sources/vpn_list_policy_object.md b/docs/data-sources/vpn_list_policy_object.md index ae0ed185..ee33678f 100644 --- a/docs/data-sources/vpn_list_policy_object.md +++ b/docs/data-sources/vpn_list_policy_object.md @@ -3,12 +3,12 @@ page_title: "sdwan_vpn_list_policy_object Data Source - terraform-provider-sdwan" subcategory: "Policy Objects" description: |- - This data source can read the VPN List policy object. + This data source can read the VPN List Policy Object . --- # sdwan_vpn_list_policy_object (Data Source) -This data source can read the VPN List policy object. +This data source can read the VPN List Policy Object . ## Example Usage @@ -23,13 +23,13 @@ data "sdwan_vpn_list_policy_object" "example" { ### Required -- `id` (String) The id of the policy object +- `id` (String) The id of the object ### Read-Only - `entries` (Attributes List) List of entries (see [below for nested schema](#nestedatt--entries)) - `name` (String) The name of the policy object -- `version` (Number) The version of the policy object +- `version` (Number) The version of the object ### Nested Schema for `entries` diff --git a/docs/resources/vpn_list_policy_object.md b/docs/resources/vpn_list_policy_object.md index bec58520..c577eff9 100644 --- a/docs/resources/vpn_list_policy_object.md +++ b/docs/resources/vpn_list_policy_object.md @@ -3,12 +3,12 @@ page_title: "sdwan_vpn_list_policy_object Resource - terraform-provider-sdwan" subcategory: "Policy Objects" description: |- - This resource can manage a VPN List policy object. + This resource can manage a VPN List Policy Object . --- # sdwan_vpn_list_policy_object (Resource) -This resource can manage a VPN List policy object. +This resource can manage a VPN List Policy Object . ## Example Usage @@ -33,8 +33,8 @@ resource "sdwan_vpn_list_policy_object" "example" { ### Read-Only -- `id` (String) The id of the policy object -- `version` (Number) The version of the feature template +- `id` (String) The id of the object +- `version` (Number) The version of the object ### Nested Schema for `entries` diff --git a/gen/definitions/policy_objects/vpn_list.yaml b/gen/definitions/generic/vpn_list_policy_object.yaml similarity index 50% rename from gen/definitions/policy_objects/vpn_list.yaml rename to gen/definitions/generic/vpn_list_policy_object.yaml index 27d449d5..55645ff6 100644 --- a/gen/definitions/policy_objects/vpn_list.yaml +++ b/gen/definitions/generic/vpn_list_policy_object.yaml @@ -1,7 +1,18 @@ --- -name: VPN List -type: vpn +name: VPN List Policy Object +rest_endpoint: /template/policy/list/vpn/ +has_version: true +id_attribute: listId +doc_category: Policy Objects attributes: + - model_name: type + value: vpn + - model_name: name + tf_name: name + type: String + mandatory: true + description: The name of the policy object + example: Example - model_name: entries type: List mandatory: true diff --git a/internal/provider/data_source_sdwan_vpn_list_policy_object.go b/internal/provider/data_source_sdwan_vpn_list_policy_object.go index df17b519..03824265 100644 --- a/internal/provider/data_source_sdwan_vpn_list_policy_object.go +++ b/internal/provider/data_source_sdwan_vpn_list_policy_object.go @@ -50,15 +50,15 @@ func (d *VPNListPolicyObjectDataSource) Metadata(_ context.Context, req datasour func (d *VPNListPolicyObjectDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ // This description is used by the documentation generator and the language server. - MarkdownDescription: "This data source can read the VPN List policy object.", + MarkdownDescription: "This data source can read the VPN List Policy Object .", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - MarkdownDescription: "The id of the policy object", + MarkdownDescription: "The id of the object", Required: true, }, "version": schema.Int64Attribute{ - MarkdownDescription: "The version of the policy object", + MarkdownDescription: "The version of the object", Computed: true, }, "name": schema.StringAttribute{ @@ -90,7 +90,7 @@ func (d *VPNListPolicyObjectDataSource) Configure(_ context.Context, req datasou } func (d *VPNListPolicyObjectDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var config VPNList + var config VPNListPolicyObject // Read config diags := req.Config.Get(ctx, &config) diff --git a/internal/provider/data_source_sdwan_vpn_list_policy_object_test.go b/internal/provider/data_source_sdwan_vpn_list_policy_object_test.go index 757048ff..178bbe7c 100644 --- a/internal/provider/data_source_sdwan_vpn_list_policy_object_test.go +++ b/internal/provider/data_source_sdwan_vpn_list_policy_object_test.go @@ -33,6 +33,7 @@ func TestAccDataSourceSdwanVPNListPolicyObject(t *testing.T) { { Config: testAccDataSourceSdwanVPNListPolicyObjectConfig, Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.sdwan_vpn_list_policy_object.test", "name", "Example"), resource.TestCheckResourceAttr("data.sdwan_vpn_list_policy_object.test", "entries.0.vpn_id", "100-200"), ), }, @@ -42,8 +43,9 @@ func TestAccDataSourceSdwanVPNListPolicyObject(t *testing.T) { const testAccDataSourceSdwanVPNListPolicyObjectConfig = ` + resource "sdwan_vpn_list_policy_object" "test" { - name = "TF_TEST_MIN" + name = "Example" entries = [{ vpn_id = "100-200" }] diff --git a/internal/provider/model_sdwan_vpn_list_policy_object.go b/internal/provider/model_sdwan_vpn_list_policy_object.go index 5158050b..c609a2b9 100644 --- a/internal/provider/model_sdwan_vpn_list_policy_object.go +++ b/internal/provider/model_sdwan_vpn_list_policy_object.go @@ -27,25 +27,23 @@ import ( "github.com/tidwall/sjson" ) -type VPNList struct { - Id types.String `tfsdk:"id"` - Version types.Int64 `tfsdk:"version"` - Name types.String `tfsdk:"name"` - Entries []VPNListEntries `tfsdk:"entries"` +type VPNListPolicyObject struct { + Id types.String `tfsdk:"id"` + Version types.Int64 `tfsdk:"version"` + Name types.String `tfsdk:"name"` + Entries []VPNListPolicyObjectEntries `tfsdk:"entries"` } -type VPNListEntries struct { +type VPNListPolicyObjectEntries struct { VpnId types.String `tfsdk:"vpn_id"` } -func (data VPNList) getType() string { - return "vpn" -} - -func (data VPNList) toBody(ctx context.Context) string { - body, _ := sjson.Set("", "description", "Desc Not Required") - body, _ = sjson.Set(body, "name", data.Name.ValueString()) +func (data VPNListPolicyObject) toBody(ctx context.Context) string { + body := "" body, _ = sjson.Set(body, "type", "vpn") + if !data.Name.IsNull() { + body, _ = sjson.Set(body, "name", data.Name.ValueString()) + } if len(data.Entries) > 0 { body, _ = sjson.Set(body, "entries", []interface{}{}) for _, item := range data.Entries { @@ -59,16 +57,16 @@ func (data VPNList) toBody(ctx context.Context) string { return body } -func (data *VPNList) fromBody(ctx context.Context, res gjson.Result) { +func (data *VPNListPolicyObject) fromBody(ctx context.Context, res gjson.Result) { if value := res.Get("name"); value.Exists() { data.Name = types.StringValue(value.String()) } else { data.Name = types.StringNull() } if value := res.Get("entries"); value.Exists() { - data.Entries = make([]VPNListEntries, 0) + data.Entries = make([]VPNListPolicyObjectEntries, 0) value.ForEach(func(k, v gjson.Result) bool { - item := VPNListEntries{} + item := VPNListPolicyObjectEntries{} if cValue := v.Get("vpn"); cValue.Exists() { item.VpnId = types.StringValue(cValue.String()) } else { @@ -78,4 +76,22 @@ func (data *VPNList) fromBody(ctx context.Context, res gjson.Result) { return true }) } + +} + +func (data *VPNListPolicyObject) hasChanges(ctx context.Context, state *VPNListPolicyObject) bool { + hasChanges := false + if !data.Name.Equal(state.Name) { + hasChanges = true + } + if len(data.Entries) != len(state.Entries) { + hasChanges = true + } else { + for i := range data.Entries { + if !data.Entries[i].VpnId.Equal(state.Entries[i].VpnId) { + hasChanges = true + } + } + } + return hasChanges } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ee8b785f..8b8f069b 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -261,7 +261,6 @@ func (p *SdwanProvider) Resources(ctx context.Context) []func() resource.Resourc NewCiscoVPNInterfaceFeatureTemplateResource, NewCiscoVPNInterfaceIPSecFeatureTemplateResource, NewCLITemplateFeatureTemplateResource, - NewVPNListPolicyObjectResource, NewACLPolicyDefinitionResource, NewAppProbeClassPolicyObjectResource, NewApplicationAwareRoutingPolicyDefinitionResource, @@ -292,6 +291,7 @@ func (p *SdwanProvider) Resources(ctx context.Context) []func() resource.Resourc NewStandardCommunityListPolicyObjectResource, NewTLOCListPolicyObjectResource, NewTrafficDataPolicyDefinitionResource, + NewVPNListPolicyObjectResource, NewVPNMembershipPolicyDefinitionResource, NewCLIDeviceTemplateResource, NewFeatureDeviceTemplateResource, @@ -322,7 +322,6 @@ func (p *SdwanProvider) DataSources(ctx context.Context) []func() datasource.Dat NewCiscoVPNInterfaceFeatureTemplateDataSource, NewCiscoVPNInterfaceIPSecFeatureTemplateDataSource, NewCLITemplateFeatureTemplateDataSource, - NewVPNListPolicyObjectDataSource, NewACLPolicyDefinitionDataSource, NewAppProbeClassPolicyObjectDataSource, NewApplicationAwareRoutingPolicyDefinitionDataSource, @@ -353,6 +352,7 @@ func (p *SdwanProvider) DataSources(ctx context.Context) []func() datasource.Dat NewStandardCommunityListPolicyObjectDataSource, NewTLOCListPolicyObjectDataSource, NewTrafficDataPolicyDefinitionDataSource, + NewVPNListPolicyObjectDataSource, NewVPNMembershipPolicyDefinitionDataSource, NewCLIDeviceTemplateDataSource, NewFeatureDeviceTemplateDataSource, diff --git a/internal/provider/resource_sdwan_vpn_list_policy_object.go b/internal/provider/resource_sdwan_vpn_list_policy_object.go index 477af3c5..2d655bbf 100644 --- a/internal/provider/resource_sdwan_vpn_list_policy_object.go +++ b/internal/provider/resource_sdwan_vpn_list_policy_object.go @@ -22,6 +22,7 @@ package provider import ( "context" "fmt" + "strings" "sync" "github.com/CiscoDevNet/terraform-provider-sdwan/internal/provider/helpers" @@ -57,22 +58,22 @@ func (r *VPNListPolicyObjectResource) Metadata(ctx context.Context, req resource func (r *VPNListPolicyObjectResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ // This description is used by the documentation generator and the language server. - MarkdownDescription: helpers.NewAttributeDescription("This resource can manage a VPN List policy object.").String, + MarkdownDescription: helpers.NewAttributeDescription("This resource can manage a VPN List Policy Object .").String, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - MarkdownDescription: "The id of the policy object", + MarkdownDescription: "The id of the object", Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "version": schema.Int64Attribute{ - MarkdownDescription: "The version of the feature template", + MarkdownDescription: "The version of the object", Computed: true, }, "name": schema.StringAttribute{ - MarkdownDescription: "The name of the policy object", + MarkdownDescription: helpers.NewAttributeDescription("The name of the policy object").String, Required: true, }, "entries": schema.ListNestedAttribute{ @@ -104,7 +105,7 @@ func (r *VPNListPolicyObjectResource) Configure(_ context.Context, req resource. } func (r *VPNListPolicyObjectResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan VPNList + var plan VPNListPolicyObject // Read plan diags := req.Plan.Get(ctx, &plan) @@ -118,7 +119,7 @@ func (r *VPNListPolicyObjectResource) Create(ctx context.Context, req resource.C // Create object body := plan.toBody(ctx) - res, err := r.client.Post("/template/policy/list/vpn", body) + res, err := r.client.Post("/template/policy/list/vpn/", body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST), got error: %s, %s", err, res.String())) return @@ -134,7 +135,7 @@ func (r *VPNListPolicyObjectResource) Create(ctx context.Context, req resource.C } func (r *VPNListPolicyObjectResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var state VPNList + var state VPNListPolicyObject // Read state diags := req.State.Get(ctx, &state) @@ -163,7 +164,7 @@ func (r *VPNListPolicyObjectResource) Read(ctx context.Context, req resource.Rea } func (r *VPNListPolicyObjectResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var plan, state VPNList + var plan, state VPNListPolicyObject // Read plan diags := req.Plan.Get(ctx, &plan) @@ -180,19 +181,22 @@ func (r *VPNListPolicyObjectResource) Update(ctx context.Context, req resource.U tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Name.ValueString())) - body := plan.toBody(ctx) - r.updateMutex.Lock() - res, err := r.client.Put("/template/policy/list/vpn/"+plan.Id.ValueString(), body) - r.updateMutex.Unlock() - if err != nil { - if res.Get("error.message").String() == "Failed to acquire lock, template or policy locked in edit mode." { - resp.Diagnostics.AddWarning("Client Warning", "Failed to modify policy due to policy being locked by another change. Policy changes will not be applied. Re-run 'terraform apply' to try again.") - } else { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) - return + if plan.hasChanges(ctx, &state) { + body := plan.toBody(ctx) + r.updateMutex.Lock() + res, err := r.client.Put("/template/policy/list/vpn/"+plan.Id.ValueString(), body) + r.updateMutex.Unlock() + if err != nil { + if strings.Contains(res.Get("error.message").String(), "Failed to acquire lock") { + resp.Diagnostics.AddWarning("Client Warning", "Failed to modify policy due to policy being locked by another change. Policy changes will not be applied. Re-run 'terraform apply' to try again.") + } else { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } } + } else { + tflog.Debug(ctx, fmt.Sprintf("%s: No changes detected", plan.Name.ValueString())) } - plan.Version = types.Int64Value(state.Version.ValueInt64() + 1) tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Name.ValueString())) @@ -202,7 +206,7 @@ func (r *VPNListPolicyObjectResource) Update(ctx context.Context, req resource.U } func (r *VPNListPolicyObjectResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state VPNList + var state VPNListPolicyObject // Read state diags := req.State.Get(ctx, &state) diff --git a/internal/provider/resource_sdwan_vpn_list_policy_object_test.go b/internal/provider/resource_sdwan_vpn_list_policy_object_test.go index f6937633..022a3092 100644 --- a/internal/provider/resource_sdwan_vpn_list_policy_object_test.go +++ b/internal/provider/resource_sdwan_vpn_list_policy_object_test.go @@ -31,8 +31,9 @@ func TestAccSdwanVPNListPolicyObject(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccSdwanVPNListPolicyObjectConfig_all(), + Config: testAccSdwanVPNListPolicyObjectConfig, Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("sdwan_vpn_list_policy_object.test", "name", "Example"), resource.TestCheckResourceAttr("sdwan_vpn_list_policy_object.test", "entries.0.vpn_id", "100-200"), ), }, @@ -40,13 +41,13 @@ func TestAccSdwanVPNListPolicyObject(t *testing.T) { }) } -func testAccSdwanVPNListPolicyObjectConfig_all() string { - return ` - resource "sdwan_vpn_list_policy_object" "test" { - name = "TF_TEST_ALL" - entries = [{ - vpn_id = "100-200" - }] - } - ` +const testAccSdwanVPNListPolicyObjectConfig = ` + + +resource "sdwan_vpn_list_policy_object" "test" { + name = "Example" + entries = [{ + vpn_id = "100-200" + }] } +`