From f3f8899bf7f9849e9d9e5d7046d7c05c68f025a1 Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Thu, 23 Jan 2025 12:02:15 +0100 Subject: [PATCH 1/2] feat(speacs): Initial panos_application_group codegen spec --- specs/objects/application-group.yaml | 137 +++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 specs/objects/application-group.yaml diff --git a/specs/objects/application-group.yaml b/specs/objects/application-group.yaml new file mode 100644 index 00000000..abdd7021 --- /dev/null +++ b/specs/objects/application-group.yaml @@ -0,0 +1,137 @@ +name: application +terraform_provider_config: + description: Application Group + skip_resource: false + skip_datasource: false + resource_type: entry + resource_variants: [] + suffix: application_group + plural_suffix: '' + plural_name: '' + plural_description: '' +go_sdk_config: + skip: false + package: + - objects + - application + - group +xpath_suffix: +- application-group +locations: +- name: shared + xpath: + path: + - config + - shared + vars: [] + description: Location in Shared Panorama + devices: + - panorama + - ngfw + validators: [] + required: false + read_only: false +- name: vsys + xpath: + path: + - config + - devices + - $ngfw_device + - vsys + - $vsys + vars: + - name: ngfw_device + description: The NGFW device name + required: false + default: localhost.localdomain + validators: [] + type: entry + - name: vsys + description: The Virtual System name + required: false + default: vsys1 + validators: + - type: not-values + spec: + values: + - value: shared + error: The vsys name cannot be "shared". Use the "shared" location instead + type: entry + description: Located in a specific Virtual System + devices: + - ngfw + - panorama + validators: [] + required: false + read_only: false +- name: device-group + xpath: + path: + - config + - devices + - $panorama_device + - device-group + - $device_group + vars: + - name: panorama_device + description: Panorama device name + required: false + default: localhost.localdomain + validators: [] + type: entry + - name: device_group + description: Device Group name + required: true + validators: + - type: not-values + spec: + values: + - value: shared + error: The device group name cannot be "shared". Use the "shared" location + instead + type: entry + description: Located in a specific Device Group + devices: + - panorama + validators: [] + required: false + read_only: false +entries: +- name: name + description: '' + validators: [] +imports: [] +spec: + params: + - name: disable-override + type: enum + profiles: + - xpath: + - disable-override + validators: + - type: values + spec: + values: + - 'yes' + - 'no' + spec: + default: 'no' + values: + - value: 'yes' + - value: 'no' + description: disable object override in child device groups + required: false + - name: members + type: list + profiles: + - xpath: + - members + type: member + validators: [] + spec: + type: string + items: + type: string + description: '' + required: false + variants: [] From 3055910765d637d1b12943cefbd2d274740f7271 Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Fri, 24 Jan 2025 09:56:52 +0100 Subject: [PATCH 2/2] feat(tests): Initial panos_application_group terraform tests --- .../test/resource_application_group_test.go | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 assets/terraform/test/resource_application_group_test.go diff --git a/assets/terraform/test/resource_application_group_test.go b/assets/terraform/test/resource_application_group_test.go new file mode 100644 index 00000000..30bb3586 --- /dev/null +++ b/assets/terraform/test/resource_application_group_test.go @@ -0,0 +1,164 @@ +package provider_test + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + sdkerrors "github.com/PaloAltoNetworks/pango/errors" + "github.com/PaloAltoNetworks/pango/objects/application/group" + + // "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +type applicationGroupExpectNoEntriesInLocation struct { + prefix string +} + +func ApplicationGroupExpectNoEntriesInLocation(prefix string) *applicationGroupExpectNoEntriesInLocation { + return &applicationGroupExpectNoEntriesInLocation{ + prefix: prefix, + } +} + +func (o *applicationGroupExpectNoEntriesInLocation) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) { + service := group.NewService(sdkClient) + location := group.NewDeviceGroupLocation() + location.DeviceGroup.DeviceGroup = fmt.Sprintf("%s-dg1", o.prefix) + objects, err := service.List(ctx, *location, "get", "", "") + if err != nil && !sdkerrors.IsObjectNotFound(err) { + resp.Error = fmt.Errorf("failed to query server for entries: %w", err) + return + } + + var dangling []string + for _, elt := range objects { + if strings.HasPrefix(elt.Name, o.prefix) { + dangling = append(dangling, elt.Name) + } + } + + if len(dangling) > 0 { + resp.Error = fmt.Errorf("delete of the resource didn't remove it from the server") + } +} + +func TestAccPanosApplicationGroup(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckPanosApplicationGroupDestroy(prefix), + Steps: []resource.TestStep{ + { + Config: applicationGroupTmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_application_group.group", + tfjsonpath.New("name"), + knownvalue.StringExact(fmt.Sprintf("%s-appgroup", prefix)), + ), + statecheck.ExpectKnownValue( + "panos_application_group.group", + tfjsonpath.New("disable_override"), + knownvalue.StringExact("yes"), + ), + statecheck.ExpectKnownValue( + "panos_application_group.group", + tfjsonpath.New("members"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("amazon-echo"), + knownvalue.StringExact("amazon-alexa"), + }), + ), + }, + }, + { + Config: applicationGroupCleanupTmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + }, + ConfigStateChecks: []statecheck.StateCheck{ + ApplicationGroupExpectNoEntriesInLocation(prefix), + }, + }, + }, + }) +} + +const applicationGroupTmpl = ` +variable "prefix" { type = string } + +resource "panos_device_group" "dg" { + location = { panorama = {} } + + name = format("%s-dg1", var.prefix) +} + +resource "panos_application_group" "group" { + location = { device_group = { name = panos_device_group.dg.name }} + + name = format("%s-appgroup", var.prefix) + + disable_override = "yes" + members = ["amazon-echo", "amazon-alexa"] +} +` + +const applicationGroupCleanupTmpl = ` +variable "prefix" { type = string } + +resource "panos_device_group" "dg" { + location = { panorama = {} } + + name = format("%s-dg1", var.prefix) +} +` + +func testAccCheckPanosApplicationGroupDestroy(prefix string) func(s *terraform.State) error { + return func(s *terraform.State) error { + api := group.NewService(sdkClient) + location := group.NewDeviceGroupLocation() + location.DeviceGroup.DeviceGroup = fmt.Sprintf("%s-dg1", prefix) + + ctx := context.TODO() + existing, err := api.List(ctx, *location, "get", "", "") + if err != nil && !sdkerrors.IsObjectNotFound(err) { + return err + } + + var dangling []string + for _, elt := range existing { + if strings.HasPrefix(elt.Name, prefix) { + dangling = append(dangling, elt.Name) + } + } + + if len(dangling) > 0 { + err = fmt.Errorf("Some entries were left after terraform teardown") + deleteErr := api.Delete(ctx, *location, dangling...) + if deleteErr != nil { + err = errors.Join(err, deleteErr) + } + return err + } + + return nil + } +}