diff --git a/assets/terraform/examples/resources/panos_nat_policy/resource.tf b/assets/terraform/examples/resources/panos_nat_policy/resource.tf new file mode 100644 index 00000000..aec859c8 --- /dev/null +++ b/assets/terraform/examples/resources/panos_nat_policy/resource.tf @@ -0,0 +1,24 @@ +# Manages the entire NAT policy +resource "panos_nat_policy" "name" { + location = { + device_group = { + name = panos_device_group.example.name + } + } + + rules = { + name = "rule-1" + + source_zones = ["any"] # from + source_addresses = ["10.0.0.0/24"] # source + destination_zone = ["any"] # to + destination_addresses = ["172.16.0.0/16"] # destination + services = ["any"] + + source_translation = { + static_ip = { + translated_address = ["192.168.0.1/24"] + } + } + } +} diff --git a/assets/terraform/go.mod b/assets/terraform/go.mod index 6586a6cb..33495eb2 100644 --- a/assets/terraform/go.mod +++ b/assets/terraform/go.mod @@ -4,12 +4,14 @@ go 1.22.5 require ( github.com/PaloAltoNetworks/pango v0.10.3-0.20240408115758-216d8509e7cf - github.com/hashicorp/terraform-plugin-framework v1.8.0 - github.com/hashicorp/terraform-plugin-go v0.23.0 + github.com/hashicorp/terraform-plugin-framework v1.12.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.14.0 + github.com/hashicorp/terraform-plugin-go v0.24.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 + golang.org/x/sync v0.8.0 ) require ( @@ -29,7 +31,7 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-plugin v1.6.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect @@ -57,15 +59,14 @@ require ( github.com/zclconf/go-cty v1.15.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sync v0.8.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/sys v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.34.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect + google.golang.org/grpc v1.66.2 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/assets/terraform/go.sum b/assets/terraform/go.sum index 9b6b6dfd..9b2c301e 100644 --- a/assets/terraform/go.sum +++ b/assets/terraform/go.sum @@ -62,8 +62,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= -github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= +github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= +github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -81,10 +81,12 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-framework v1.8.0 h1:P07qy8RKLcoBkCrY2RHJer5AEvJnDuXomBgou6fD8kI= -github.com/hashicorp/terraform-plugin-framework v1.8.0/go.mod h1:/CpTukO88PcL/62noU7cuyaSJ4Rsim+A/pa+3rUVufY= -github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= -github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= +github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= +github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE= +github.com/hashicorp/terraform-plugin-framework-validators v0.14.0 h1:3PCn9iyzdVOgHYOBmncpSSOxjQhCTYmc+PGvbdlqSaI= +github.com/hashicorp/terraform-plugin-framework-validators v0.14.0/go.mod h1:LwDKNdzxrDY/mHBrlC6aYfE2fQ3Dk3gaJD64vNiXvo4= +github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U= +github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= @@ -172,8 +174,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -214,14 +216,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= -google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/assets/terraform/test/panos_sweeper_test.go b/assets/terraform/test/panos_sweeper_test.go new file mode 100644 index 00000000..d62dd0d0 --- /dev/null +++ b/assets/terraform/test/panos_sweeper_test.go @@ -0,0 +1,11 @@ +package provider_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestMain(m *testing.M) { + resource.TestMain(m) +} diff --git a/assets/terraform/test/resource_address_group_test.go b/assets/terraform/test/resource_address_group_test.go index 5044a140..dc7bf4a9 100644 --- a/assets/terraform/test/resource_address_group_test.go +++ b/assets/terraform/test/resource_address_group_test.go @@ -101,7 +101,7 @@ func makeAddressGroupConfig(label string) string { default = false } - resource "panos_address_objects" "google_dns_servers" { + resource "panos_addresses" "google_dns_servers" { location = { shared = true } @@ -120,7 +120,7 @@ func makeAddressGroupConfig(label string) string { } name = "${var.address_group_name}-base-${var.name_suffix}" - static = [for name, data in resource.panos_address_objects.google_dns_servers.addresses : name] + static = [for name, data in resource.panos_addresses.google_dns_servers.addresses : name] } resource "panos_address_group" "%s" { @@ -133,7 +133,7 @@ func makeAddressGroupConfig(label string) string { static = var.from_address_group ? ( [panos_address_group.%s_base[0].name] ) : ( - [for name, data in resource.panos_address_objects.google_dns_servers.addresses : name] + [for name, data in resource.panos_addresses.google_dns_servers.addresses : name] ) } ` diff --git a/assets/terraform/test/resource_ethernet_interface_test.go b/assets/terraform/test/resource_ethernet_interface_test.go index 2755b045..a0cf3092 100644 --- a/assets/terraform/test/resource_ethernet_interface_test.go +++ b/assets/terraform/test/resource_ethernet_interface_test.go @@ -70,7 +70,7 @@ func makePanosEthernetInterface_Layer3(label string) string { variable "ip_addr_netmask" { type = string } variable "template_name" { type = string } - resource "panos_panorama_template" "acc_codegen_template" { + resource "panos_template" "acc_codegen_template" { name = "${var.template_name}-${var.name_suffix}" location = { @@ -84,7 +84,7 @@ func makePanosEthernetInterface_Layer3(label string) string { location = { template = { vsys = "vsys1" - name = panos_panorama_template.acc_codegen_template.name + name = panos_template.acc_codegen_template.name } } @@ -141,4 +141,3 @@ func testAccCheckPanosEthernetInterfaceDestroy(entryName, templateName string) f return nil } } - diff --git a/assets/terraform/test/resource_nat_policy_test.go b/assets/terraform/test/resource_nat_policy_test.go new file mode 100644 index 00000000..7c1a7907 --- /dev/null +++ b/assets/terraform/test/resource_nat_policy_test.go @@ -0,0 +1,412 @@ +package provider_test + +import ( + "bytes" + "context" + "errors" + "fmt" + "strings" + "testing" + "text/template" + + sdkerrors "github.com/PaloAltoNetworks/pango/errors" + "github.com/PaloAltoNetworks/pango/policies/rules/nat" + + "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/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +type deviceType int + +const ( + devicePanorama deviceType = iota + deviceFirewall +) + +var ( + UnexpectedRulesError = errors.New("exhaustive resource didn't delete existing rules") + DanglingObjectsError = errors.New("some objects were not deleted by the provider") +) + +type expectServerRulesOrder struct { + Location nat.Location + Prefix string + RuleNames []string +} + +func ExpectServerRulesOrder(prefix string, location nat.Location, ruleNames []string) *expectServerRulesOrder { + return &expectServerRulesOrder{ + Location: location, + Prefix: prefix, + RuleNames: ruleNames, + } +} + +func (o *expectServerRulesOrder) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) { + service := nat.NewService(sdkClient) + + objects, err := service.List(ctx, o.Location, "get", "", "") + if err != nil { + resp.Error = fmt.Errorf("failed to query server for rules: %w", err) + return + } + + type ruleWithState struct { + Idx int + State int + } + + rulesWithIdx := make(map[string]ruleWithState) + for idx, elt := range o.RuleNames { + rulesWithIdx[fmt.Sprintf("%s-%s", o.Prefix, elt)] = ruleWithState{ + Idx: idx, + State: 0, + } + } + + var prevActualIdx = -1 + for actualIdx, elt := range objects { + if state, ok := rulesWithIdx[elt.Name]; !ok { + continue + } else { + state.State = 1 + rulesWithIdx[elt.Name] = state + + if state.Idx == 0 { + prevActualIdx = actualIdx + continue + } else if prevActualIdx == -1 { + resp.Error = fmt.Errorf("rules missing from the server") + return + } else if actualIdx-prevActualIdx > 1 { + resp.Error = fmt.Errorf("invalid rules order on the server") + return + } + prevActualIdx = actualIdx + } + } + + var missing []string + for name, elt := range rulesWithIdx { + if elt.State != 1 { + missing = append(missing, name) + } + } + + if len(missing) > 0 { + resp.Error = fmt.Errorf("not all rules are present on the server: %s", strings.Join(missing, ", ")) + return + } +} + +type expectServerRulesCount struct { + Prefix string + Location nat.Location + Count int +} + +func ExpectServerRulesCount(prefix string, location nat.Location, count int) *expectServerRulesCount { + return &expectServerRulesCount{ + Prefix: prefix, + Location: location, + Count: count, + } +} + +func (o *expectServerRulesCount) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) { + service := nat.NewService(sdkClient) + + objects, err := service.List(ctx, o.Location, "get", "", "") + if err != nil { + resp.Error = fmt.Errorf("failed to query server for rules: %w", err) + return + } + + var count int + for _, elt := range objects { + if strings.HasPrefix(elt.Name, o.Prefix) { + count += 1 + } + } + + if count != o.Count { + resp.Error = UnexpectedRulesError + return + } +} + +func TestAccPanosNatPolicy(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + rulesInitial := []string{"rule-1", "rule-2", "rule-3"} + rulesReordered := []string{"rule-2", "rule-1", "rule-3"} + + prefixed := func(name string) string { + return fmt.Sprintf("%s-%s", prefix, name) + } + + withPrefix := func(rules []string) []config.Variable { + var result []config.Variable + for _, elt := range rules { + result = append(result, config.StringVariable(prefixed(elt))) + } + + return result + } + + device := devicePanorama + + sdkLocation, cfgLocation := natPolicyLocationByDeviceType(device) + + stateExpectedRuleName := func(idx int, value string) statecheck.StateCheck { + return statecheck.ExpectKnownValue( + fmt.Sprintf("panos_nat_policy.%s", prefix), + tfjsonpath.New("rules").AtSliceIndex(idx).AtMapKey("name"), + knownvalue.StringExact(prefixed(value)), + ) + } + + planExpectedRuleName := func(idx int, value string) plancheck.PlanCheck { + return plancheck.ExpectKnownValue( + fmt.Sprintf("panos_nat_policy.%s", prefix), + tfjsonpath.New("rules").AtSliceIndex(idx).AtMapKey("name"), + knownvalue.StringExact(prefixed(value)), + ) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + natPolicyPreCheck(prefix, sdkLocation) + + }, + ProtoV6ProviderFactories: testAccProviders, + CheckDestroy: natPolicyCheckDestroy(prefix, sdkLocation), + Steps: []resource.TestStep{ + { + Config: makeNatPolicyConfig(prefix), + ConfigVariables: map[string]config.Variable{ + "rule_names": config.ListVariable(withPrefix(rulesInitial)...), + "location": cfgLocation, + }, + ConfigStateChecks: []statecheck.StateCheck{ + stateExpectedRuleName(0, "rule-1"), + stateExpectedRuleName(1, "rule-2"), + stateExpectedRuleName(2, "rule-3"), + ExpectServerRulesCount(prefix, sdkLocation, len(rulesInitial)), + ExpectServerRulesOrder(prefix, sdkLocation, rulesInitial), + }, + }, + { + Config: makeNatPolicyConfig(prefix), + ConfigVariables: map[string]config.Variable{ + "rule_names": config.ListVariable(withPrefix(rulesInitial)...), + "location": cfgLocation, + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + { + Config: makeNatPolicyConfig(prefix), + ConfigVariables: map[string]config.Variable{ + "rule_names": config.ListVariable(withPrefix(rulesReordered)...), + "location": cfgLocation, + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + planExpectedRuleName(0, "rule-2"), + planExpectedRuleName(1, "rule-1"), + planExpectedRuleName(2, "rule-3"), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + stateExpectedRuleName(0, "rule-2"), + stateExpectedRuleName(1, "rule-1"), + stateExpectedRuleName(2, "rule-3"), + ExpectServerRulesOrder(prefix, sdkLocation, rulesReordered), + }, + }, + }, + }) +} + +const configTmpl = ` +variable "rule_names" { type = list(string) } +variable "location" { type = map } + +resource "panos_nat_policy" "{{ .ResourceName }}" { + location = var.location + + rules = [ + for index, name in var.rule_names: { + name = name + source_zones = ["any"] + source_addresses = ["any"] + destination_zone = ["external"] + destination_addresses = ["any"] + + destination_translation = { + translated_address = format("172.16.0.%s", index) + } + } + ] +} +` + +func makeNatPolicyConfig(prefix string) string { + var buf bytes.Buffer + tmpl := template.Must(template.New("").Parse(configTmpl)) + + context := struct { + ResourceName string + }{ + ResourceName: prefix, + } + + err := tmpl.Execute(&buf, context) + if err != nil { + panic(err) + } + + return buf.String() +} + +func natPolicyLocationByDeviceType(typ deviceType) (nat.Location, config.Variable) { + var sdkLocation nat.Location + var cfgLocation config.Variable + switch typ { + case devicePanorama: + sdkLocation = nat.Location{ + Shared: &nat.SharedLocation{ + Rulebase: "pre-rulebase", + }, + } + cfgLocation = config.ObjectVariable(map[string]config.Variable{ + "shared": config.ObjectVariable(map[string]config.Variable{ + "rulebase": config.StringVariable("pre-rulebase"), + }), + }) + case deviceFirewall: + sdkLocation = nat.Location{ + Vsys: &nat.VsysLocation{ + NgfwDevice: "localhost.localdomain", + Vsys: "vsys1", + }, + } + cfgLocation = config.ObjectVariable(map[string]config.Variable{ + "vsys": config.ObjectVariable(map[string]config.Variable{ + "name": config.StringVariable("vsys1"), + }), + }) + } + + return sdkLocation, cfgLocation +} + +func natPolicyPreCheck(prefix string, location nat.Location) { + service := nat.NewService(sdkClient) + ctx := context.TODO() + + stringPointer := func(value string) *string { return &value } + + rules := []nat.Entry{ + { + Name: fmt.Sprintf("%s-rule0", prefix), + Description: stringPointer("Rule 0"), + From: []string{"any"}, + To: []string{"external"}, + Source: []string{"any"}, + Destination: []string{"any"}, + }, + { + Name: fmt.Sprintf("%s-rule99", prefix), + Description: stringPointer("Rule 99"), + From: []string{"any"}, + To: []string{"external"}, + Source: []string{"any"}, + Destination: []string{"any"}, + }, + } + + for _, elt := range rules { + _, err := service.Create(ctx, location, &elt) + if err != nil { + panic(fmt.Sprintf("natPolicyPreCheck failed: %s", err)) + } + + } +} + +func natPolicyCheckDestroy(prefix string, location nat.Location) func(s *terraform.State) error { + return func(s *terraform.State) error { + service := nat.NewService(sdkClient) + ctx := context.TODO() + + rules, err := service.List(ctx, location, "get", "", "") + if err != nil && !sdkerrors.IsObjectNotFound(err) { + return err + } + + for _, elt := range rules { + if strings.HasPrefix(elt.Name, prefix) { + return DanglingObjectsError + } + } + + return nil + } +} + +func init() { + resource.AddTestSweepers("pango_nat_policy", &resource.Sweeper{ + Name: "pango_nat_policy", + F: func(typ string) error { + service := nat.NewService(sdkClient) + + var deviceTyp deviceType + switch typ { + case "panorama": + deviceTyp = devicePanorama + case "firewall": + deviceTyp = deviceFirewall + default: + panic("invalid device type") + } + + location, _ := natPolicyLocationByDeviceType(deviceTyp) + ctx := context.TODO() + objects, err := service.List(ctx, location, "get", "", "") + if err != nil && !sdkerrors.IsObjectNotFound(err) { + return fmt.Errorf("Failed to list NAT rules during sweep: %w", err) + } + + var names []string + for _, elt := range objects { + if strings.HasPrefix(elt.Name, "test-acc") { + names = append(names, elt.Name) + } + } + + if len(names) > 0 { + err = service.Delete(ctx, location, names...) + if err != nil { + return fmt.Errorf("Failed to delete NAT rules during sweep: %w", err) + } + } + + return nil + }, + }) +} diff --git a/assets/terraform/test/resource_panorama_template_test.go b/assets/terraform/test/resource_panorama_template_test.go index 38f43f38..635cd3f6 100644 --- a/assets/terraform/test/resource_panorama_template_test.go +++ b/assets/terraform/test/resource_panorama_template_test.go @@ -36,7 +36,7 @@ func TestAccPanosTemplate_RequiredInputs(t *testing.T) { }, ConfigStateChecks: []statecheck.StateCheck{ statecheck.ExpectKnownValue( - "panos_panorama_template."+resourceName, + "panos_template."+resourceName, tfjsonpath.New("name"), knownvalue.StringExact(templateName), ), @@ -50,7 +50,7 @@ func makePanosTemplateConfig(label string) string { configTpl := ` variable "template_name" { type = string } - resource "panos_panorama_template" "%s" { + resource "panos_template" "%s" { name = var.template_name location = { diff --git a/assets/terraform/test/resource_panorama_template_variable_test.go b/assets/terraform/test/resource_panorama_template_variable_test.go index 6f568fa3..9e5dbd4f 100644 --- a/assets/terraform/test/resource_panorama_template_variable_test.go +++ b/assets/terraform/test/resource_panorama_template_variable_test.go @@ -56,17 +56,17 @@ func TestAccPanosPanoramaTemplateVariable(t *testing.T) { }, ConfigStateChecks: []statecheck.StateCheck{ statecheck.ExpectKnownValue( - "panos_panorama_template_variable."+resourceName, + "panos_template_variable."+resourceName, tfjsonpath.New("type").AtMapKey(testEntry.variableType), knownvalue.StringExact(testEntry.value), ), statecheck.ExpectKnownValue( - "panos_panorama_template_variable."+resourceName, + "panos_template_variable."+resourceName, tfjsonpath.New("name"), knownvalue.StringExact("$tempvar-"+nameSuffix), ), compareValuesDiffer.AddStateValue( - "panos_panorama_template_variable."+resourceName, + "panos_template_variable."+resourceName, tfjsonpath.New("type"), ), }, @@ -92,7 +92,7 @@ func makePanoramaTemplateVariableConfig(label string) string { variable "templ_var_value" { type = string } variable "templ_name" { type = string } - resource "panos_panorama_template" "%s" { + resource "panos_template" "%s" { name = "${var.templ_name}_${var.name_suffix}" location = { @@ -102,10 +102,10 @@ func makePanoramaTemplateVariableConfig(label string) string { } } - resource "panos_panorama_template_variable" "%s" { + resource "panos_template_variable" "%s" { location = { template = { - name = panos_panorama_template.%s.name + name = panos_template.%s.name } } @@ -142,4 +142,3 @@ func testAccPanosPanoramaTemplateVariableDestroy(entryName, templateName string) return nil } } - diff --git a/assets/terraform/test/resource_virtual_router_test.go b/assets/terraform/test/resource_virtual_router_test.go index 2c4d2502..09f447d3 100644 --- a/assets/terraform/test/resource_virtual_router_test.go +++ b/assets/terraform/test/resource_virtual_router_test.go @@ -118,7 +118,7 @@ func makePanosVirtualRouterConfig(label string) string { default = "ethernet1/40" } - resource "panos_panorama_template" "template" { + resource "panos_template" "template" { name = "${var.template_name}-${var.name_suffix}" location = { @@ -133,7 +133,7 @@ func makePanosVirtualRouterConfig(label string) string { location = { template = { vsys = "vsys1" - name = panos_panorama_template.template.name + name = panos_template.template.name } } @@ -166,7 +166,7 @@ func makePanosVirtualRouterConfig(label string) string { resource "panos_virtual_router" "%s" { location = { template = { - name = panos_panorama_template.template.name + name = panos_template.template.name } } diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index ea177daa..e1a118d9 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -135,7 +135,7 @@ func (c *Creator) processTemplate(templateName, filePath string) error { var formattedCode []byte formattedCode, err = format.Source(data.Bytes()) if err != nil { - log.Printf("Failed to format source code: %s", err.Error()) + log.Printf("Failed to format source code: %s, %s", filePath, err.Error()) formattedCode = data.Bytes() } formattedBuf := bytes.NewBuffer(formattedCode) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 911d4168..8e536360 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -158,6 +158,7 @@ type SpecParam struct { Name *NameVariant Description string `json:"description" yaml:"description"` TerraformProviderConfig *SpecParamTerraformProviderConfig `json:"terraform_provider_config" yaml:"terraform_provider_config"` + IsNil bool `json:"-" yaml:"-"` Type string `json:"type" yaml:"type"` Default string `json:"default" yaml:"default"` Required bool `json:"required" yaml:"required"` @@ -172,9 +173,11 @@ type SpecParam struct { } type SpecParamTerraformProviderConfig struct { - Private bool `json:"ignored" yaml:"private"` - Sensitive bool `json:"sensitive" yaml:"sensitive"` - Computed bool `json:"computed" yaml:"computed"` + Name string `json:"name" yaml:"name"` + Type string `json:"type" yaml:"type"` + Private bool `json:"ignored" yaml:"private"` + Sensitive bool `json:"sensitive" yaml:"sensitive"` + Computed bool `json:"computed" yaml:"computed"` } type SpecParamLength struct { @@ -209,6 +212,14 @@ type SpecParamProfile struct { FromVersion string `json:"from_version" yaml:"from_version"` } +func (o *SpecParam) NameVariant() *NameVariant { + if o.TerraformProviderConfig != nil && o.TerraformProviderConfig.Name != "" { + return NewNameVariant(o.TerraformProviderConfig.Name) + } + + return o.Name +} + func hasChildEncryptedResources(param *SpecParam) bool { if param.Hashing != nil { return true @@ -346,6 +357,7 @@ func schemaParameterToSpecParameter(schemaSpec *parameter.Parameter) (*SpecParam var defaultVal string + var paramTypeIsNil bool var innerSpec *Spec var itemsSpec SpecParamItems @@ -408,7 +420,8 @@ func schemaParameterToSpecParameter(schemaSpec *parameter.Parameter) (*SpecParam case *parameter.EnumSpec: defaultVal = spec.Default case *parameter.NilSpec: - specType = "string" + paramTypeIsNil = true + specType = "" case *parameter.SimpleSpec: if typed, ok := spec.Default.(string); ok { defaultVal = typed @@ -445,6 +458,8 @@ func schemaParameterToSpecParameter(schemaSpec *parameter.Parameter) (*SpecParam if schemaSpec.CodegenOverrides != nil { sensitive = schemaSpec.CodegenOverrides.Terraform.Sensitive terraformProviderConfig = &SpecParamTerraformProviderConfig{ + Name: schemaSpec.CodegenOverrides.Terraform.Name, + Type: schemaSpec.CodegenOverrides.Terraform.Type, Private: schemaSpec.CodegenOverrides.Terraform.Private, Sensitive: schemaSpec.CodegenOverrides.Terraform.Sensitive, Computed: schemaSpec.CodegenOverrides.Terraform.Computed, @@ -453,6 +468,7 @@ func schemaParameterToSpecParameter(schemaSpec *parameter.Parameter) (*SpecParam specParameter := &SpecParam{ Description: schemaSpec.Description, Type: specType, + IsNil: paramTypeIsNil, Default: defaultVal, Required: schemaSpec.Required, Sensitive: sensitive, diff --git a/pkg/schema/parameter/parameter.go b/pkg/schema/parameter/parameter.go index 860ff0a7..bd7f8b89 100644 --- a/pkg/schema/parameter/parameter.go +++ b/pkg/schema/parameter/parameter.go @@ -43,9 +43,11 @@ type EnumSpecValue struct { } type CodegenOverridesTerraform struct { - Private bool `yaml:"private"` - Sensitive bool `yaml:"sensitive"` - Computed bool `yaml:"computed"` + Name string `yaml:"name"` + Type string `yaml:"type"` + Private bool `yaml:"private"` + Sensitive bool `yaml:"sensitive"` + Computed bool `yaml:"computed"` } type CodegenOverrides struct { diff --git a/pkg/translate/terraform_provider/funcs.go b/pkg/translate/terraform_provider/funcs.go index f191a646..1bd43aef 100644 --- a/pkg/translate/terraform_provider/funcs.go +++ b/pkg/translate/terraform_provider/funcs.go @@ -30,11 +30,12 @@ type parameterEncryptionSpec struct { } type parameterSpec struct { - Name *properties.NameVariant - Type string - Required bool - ItemsType string - Encryption *parameterEncryptionSpec + PangoName *properties.NameVariant + TerraformName *properties.NameVariant + Type string + Required bool + ItemsType string + Encryption *parameterEncryptionSpec } type spec struct { @@ -70,10 +71,11 @@ func renderSpecsForParams(params map[string]*properties.SpecParam, parentNames [ } specs = append(specs, parameterSpec{ - Name: elt.Name, - Type: elt.Type, - ItemsType: itemsType, - Encryption: encryptionSpec, + PangoName: elt.Name, + TerraformName: elt.NameVariant(), + Type: elt.Type, + ItemsType: itemsType, + Encryption: encryptionSpec, }) } @@ -90,7 +92,7 @@ func generateFromTerraformToPangoSpec(pangoTypePrefix string, terraformPrefix st pangoType := fmt.Sprintf("%s%s", pangoTypePrefix, paramSpec.Name.CamelCase) pangoReturnType := fmt.Sprintf("%s%s", pangoTypePrefix, paramSpec.Name.CamelCase) - terraformType := fmt.Sprintf("%s%s", terraformPrefix, paramSpec.Name.CamelCase) + terraformType := fmt.Sprintf("%s%s", terraformPrefix, paramSpec.NameVariant().CamelCase) parentNames = append(parentNames, paramSpec.Name.Underscore) @@ -197,17 +199,17 @@ const copyToPangoTmpl = ` {{- define "terraformNestedElementsAssign" }} {{- with .Parameter }} - {{- $result := .Name.LowerCamelCase }} - {{- $diag := .Name.LowerCamelCase | printf "%s_diags" }} - var {{ $result }}_entry *{{ $.Spec.PangoType }}{{ .Name.CamelCase }} - if o.{{ .Name.CamelCase }} != nil { - if *obj != nil && (*obj).{{ .Name.CamelCase }} != nil { - {{ $result }}_entry = (*obj).{{ .Name.CamelCase }} + {{- $result := .TerraformName.LowerCamelCase }} + {{- $diag := .TerraformName.LowerCamelCase | printf "%s_diags" }} + var {{ $result }}_entry *{{ $.Spec.PangoType }}{{ .PangoName.CamelCase }} + if o.{{ .TerraformName.CamelCase }} != nil { + if *obj != nil && (*obj).{{ .PangoName.CamelCase }} != nil { + {{ $result }}_entry = (*obj).{{ .PangoName.CamelCase }} } else { - {{ $result }}_entry = new({{ $.Spec.PangoType }}{{ .Name.CamelCase }}) + {{ $result }}_entry = new({{ $.Spec.PangoType }}{{ .PangoName.CamelCase }}) } - diags.Append(o.{{ .Name.CamelCase }}.CopyToPango(ctx, &{{ $result }}_entry, encrypted)...) + diags.Append(o.{{ .TerraformName.CamelCase }}.CopyToPango(ctx, &{{ $result }}_entry, encrypted)...) if diags.HasError() { return diags } @@ -218,15 +220,15 @@ const copyToPangoTmpl = ` {{- define "terraformListElementsAs" }} {{- with .Parameter }} - {{- $pangoType := printf "%s%s" $.Spec.PangoType .Name.CamelCase }} - {{- $terraformType := printf "%s%sObject" $.Spec.TerraformType .Name.CamelCase }} - {{- $pangoEntries := printf "%s_pango_entries" .Name.LowerCamelCase }} - {{- $tfEntries := printf "%s_tf_entries" .Name.LowerCamelCase }} + {{- $pangoType := printf "%s%s" $.Spec.PangoType .TerraformName.CamelCase }} + {{- $terraformType := printf "%s%sObject" $.Spec.TerraformType .TerraformName.CamelCase }} + {{- $pangoEntries := printf "%s_pango_entries" .TerraformName.LowerCamelCase }} + {{- $tfEntries := printf "%s_tf_entries" .TerraformName.LowerCamelCase }} {{- if eq .ItemsType "entry" }} var {{ $tfEntries }} []{{ $terraformType }} var {{ $pangoEntries }} []{{ $pangoType }} { - d := o.{{ .Name.CamelCase }}.ElementsAs(ctx, &{{ $tfEntries }}, false) + d := o.{{ .TerraformName.CamelCase }}.ElementsAs(ctx, &{{ $tfEntries }}, false) diags.Append(d...) if diags.HasError() { return diags @@ -242,7 +244,7 @@ const copyToPangoTmpl = ` } {{- else }} {{ $pangoEntries }} := make([]{{ .ItemsType }}, 0) - diags.Append(o.{{ .Name.CamelCase }}.ElementsAs(ctx, &{{ $pangoEntries }}, false)...) + diags.Append(o.{{ .TerraformName.CamelCase }}.ElementsAs(ctx, &{{ $pangoEntries }}, false)...) if diags.HasError() { return diags } @@ -252,9 +254,9 @@ const copyToPangoTmpl = ` {{- define "renderSimpleAssignment" }} {{- if .Encryption }} - (*encrypted)["{{ .Encryption.PlaintextPath }}"] = o.{{ .Name.CamelCase }} + (*encrypted)["{{ .Encryption.PlaintextPath }}"] = o.{{ .TerraformName.CamelCase }} {{- end }} - {{ .Name.LowerCamelCase }}_value := o.{{ .Name.CamelCase }}.Value{{ CamelCaseType .Type }}Pointer() + {{ .TerraformName.LowerCamelCase }}_value := o.{{ .TerraformName.CamelCase }}.Value{{ CamelCaseType .Type }}Pointer() {{- end }} {{- range .Specs }} @@ -262,12 +264,12 @@ const copyToPangoTmpl = ` func (o *{{ .TerraformType }}{{ .ModelOrObject }}) CopyToPango(ctx context.Context, obj **{{ .PangoReturnType }}, encrypted *map[string]types.String) diag.Diagnostics { var diags diag.Diagnostics {{- range .Params }} - {{- $terraformType := printf "%s%s" $spec.TerraformType .Name.CamelCase }} + {{- $terraformType := printf "%s%s" $spec.TerraformType .TerraformName.CamelCase }} {{- if eq .Type "" }} {{- $pangoType := printf "%sObject" $spec.PangoType }} {{- template "terraformNestedElementsAssign" Map "Parameter" . "Spec" $spec }} {{- else if eq .Type "list" }} - {{- $pangoType := printf "%s%s" $spec.PangoType .Name.CamelCase }} + {{- $pangoType := printf "%s%s" $spec.PangoType .TerraformName.CamelCase }} {{- template "terraformListElementsAs" Map "Parameter" . "Spec" $spec }} {{- else }} {{- template "renderSimpleAssignment" . }} @@ -293,21 +295,21 @@ func (o *{{ .TerraformType }}{{ .ModelOrObject }}) CopyToPango(ctx context.Conte {{- end }} {{- range .Params }} {{- if eq .Type "" }} - (*obj).{{ .Name.CamelCase }} = {{ .Name.LowerCamelCase }}_entry + (*obj).{{ .PangoName.CamelCase }} = {{ .TerraformName.LowerCamelCase }}_entry {{- else if eq .Type "list" }} - (*obj).{{ .Name.CamelCase }} = {{ .Name.LowerCamelCase }}_pango_entries + (*obj).{{ .PangoName.CamelCase }} = {{ .TerraformName.LowerCamelCase }}_pango_entries {{- else }} - (*obj).{{ .Name.CamelCase }} = {{ .Name.LowerCamelCase }}_value + (*obj).{{ .PangoName.CamelCase }} = {{ .TerraformName.LowerCamelCase }}_value {{- end }} {{- end }} {{- range .OneOf }} {{- if eq .Type "" }} - (*obj).{{ .Name.CamelCase }} = {{ .Name.LowerCamelCase }}_entry + (*obj).{{ .PangoName.CamelCase }} = {{ .TerraformName.LowerCamelCase }}_entry {{- else if eq .Type "list" }} - (*obj).{{ .Name.CamelCase }} = {{ .Name.LowerCamelCase }}_pango_entries + (*obj).{{ .PangoName.CamelCase }} = {{ .TerraformName.LowerCamelCase }}_pango_entries {{- else }} - (*obj).{{ .Name.CamelCase }} = {{ .Name.LowerCamelCase }}_value + (*obj).{{ .PangoName.CamelCase }} = {{ .TerraformName.LowerCamelCase }}_value {{- end }} {{- end }} @@ -321,37 +323,37 @@ const copyFromPangoTmpl = ` {{- if eq .Type "" }} // TODO: Missing implementation {{- else if eq .Type "list" }} - {{ .Name.CamelCase }}: {{ .Name.LowerCamelCase }}_list, + {{ .TerraformName.CamelCase }}: {{ .TerraformName.LowerCamelCase }}_list, {{- end }} {{- end }} {{- define "renderListValueSimple" }} -var {{ .Name.LowerCamelCase }}_list types.List +var {{ .TerraformName.LowerCamelCase }}_list types.List { schema := rsschema.{{ .Type | PascalCase }}Attribute{} - {{ .Name.LowerCamelCase }}_list, {{ .Name.LowerCamelCase }}_diags := types.ListValueFrom(ctx, obj.{{ .Name.CamelCase }}, schema.GetType()) - diags.Append({{ .Name.LowerCamelCase }}_diags...) + {{ .TerraformName.LowerCamelCase }}_list, {{ .TerraformName.LowerCamelCase }}_diags := types.ListValueFrom(ctx, obj.{{ .PangoName.CamelCase }}, schema.GetType()) + diags.Append({{ .TerraformName.LowerCamelCase }}_diags...) } {{- end }} {{- define "renderNestedValues" }} {{- range .Spec.Params }} - {{- $terraformType := printf "%s%s" $.TerraformType (.Name.CamelCase) }} + {{- $terraformType := printf "%s%s" $.TerraformType (.TerraformName.CamelCase) }} {{- if eq .Type "" }} - // TODO {{ .Name.CamelCase }} {{ .Type }} + // TODO {{ .TerraformName.CamelCase }} {{ .Type }} {{- else if (and (eq .Type "list") (eq .ItemsType "entry")) }} - {{- template "renderListValueEntry" Map "Name" .Name "Type" $terraformType }} + {{- template "renderListValueEntry" Map "Name" .TerraformName "Type" $terraformType }} {{- else if (and (eq .Type "list") (eq .ItemsType "member")) }} - // TODO: {{ .Name.CamelCase }} {{ .ItemsType }} + // TODO: {{ .TerraformName.CamelCase }} {{ .ItemsType }} {{- else if (eq .Type "list") }} - {{- template "renderListValueSimple" Map "Name" .Name "Type" .ItemsType }} + {{- template "renderListValueSimple" Map "Name" .TerraformName "Type" .ItemsType }} {{- else }} - // TODO: {{ .Name.CamelCase }} {{ .Type }} + // TODO: {{ .TerraformName.CamelCase }} {{ .Type }} {{- end }} {{- end }} {{- range .Spec.OneOf }} - // TODO: .Spec.OneOf {{ .Name.CamelCase }} + // TODO: .Spec.OneOf {{ .TerraformName.CamelCase }} {{- end }} {{- end }} @@ -369,31 +371,31 @@ var {{ .Name.LowerCamelCase }}_list types.List {{- define "terraformListElementsAsParam" }} {{- with .Parameter }} - {{- $pangoType := printf "%s%s" $.Spec.PangoType .Name.CamelCase }} - {{- $terraformType := printf "%s%sObject" $.Spec.TerraformType .Name.CamelCase }} - {{- $terraformList := printf "%s_list" .Name.LowerCamelCase }} - {{- $pangoEntries := printf "%s_pango_entries" .Name.LowerCamelCase }} - {{- $tfEntries := printf "%s_tf_entries" .Name.LowerCamelCase }} + {{- $pangoType := printf "%s%s" $.Spec.PangoType .TerraformName.CamelCase }} + {{- $terraformType := printf "%s%sObject" $.Spec.TerraformType .TerraformName.CamelCase }} + {{- $terraformList := printf "%s_list" .TerraformName.LowerCamelCase }} + {{- $pangoEntries := printf "%s_pango_entries" .TerraformName.LowerCamelCase }} + {{- $tfEntries := printf "%s_tf_entries" .TerraformName.LowerCamelCase }} {{- if eq .ItemsType "entry" }} var {{ $terraformList }} types.List { var {{ $tfEntries }} []{{ $terraformType }} - for _, elt := range obj.{{ .Name.CamelCase }} { + for _, elt := range obj.{{ .TerraformName.CamelCase }} { var entry {{ $terraformType }} entry_diags := entry.CopyFromPango(ctx, &elt, encrypted) diags.Append(entry_diags...) {{ $tfEntries }} = append({{ $tfEntries }}, entry) } var list_diags diag.Diagnostics - schemaType := o.getTypeFor("{{ .Name.Underscore }}") + schemaType := o.getTypeFor("{{ .TerraformName.Underscore }}") {{ $terraformList }}, list_diags = types.ListValueFrom(ctx, schemaType, {{ $tfEntries }}) diags.Append(list_diags...) } {{- else }} - var {{ .Name.LowerCamelCase }}_list types.List + var {{ .TerraformName.LowerCamelCase }}_list types.List { var list_diags diag.Diagnostics - {{ .Name.LowerCamelCase }}_list, list_diags = types.ListValueFrom(ctx, types.{{ .ItemsType | PascalCase }}Type, obj.{{ .Name.CamelCase }}) + {{ .TerraformName.LowerCamelCase }}_list, list_diags = types.ListValueFrom(ctx, types.{{ .ItemsType | PascalCase }}Type, obj.{{ .PangoName.CamelCase }}) diags.Append(list_diags...) } {{- end }} @@ -416,13 +418,13 @@ var {{ .Name.LowerCamelCase }}_list types.List {{- define "terraformCreateEntryAssignmentForParam" }} {{- with .Parameter }} - {{- $result := .Name.LowerCamelCase }} - {{- $diag := .Name.LowerCamelCase | printf "%s_diags" }} - var {{ $result }}_object *{{ $.Spec.TerraformType }}{{ .Name.CamelCase }}Object - if obj.{{ .Name.CamelCase }} != nil { - {{ $result }}_object = new({{ $.Spec.TerraformType }}{{ .Name.CamelCase }}Object) + {{- $result := .TerraformName.LowerCamelCase }} + {{- $diag := .TerraformName.LowerCamelCase | printf "%s_diags" }} + var {{ $result }}_object *{{ $.Spec.TerraformType }}{{ .TerraformName.CamelCase }}Object + if obj.{{ .PangoName.CamelCase }} != nil { + {{ $result }}_object = new({{ $.Spec.TerraformType }}{{ .TerraformName.CamelCase }}Object) - diags.Append({{ $result }}_object.CopyFromPango(ctx, obj.{{ .Name.CamelCase }}, encrypted)...) + diags.Append({{ $result }}_object.CopyFromPango(ctx, obj.{{ .PangoName.CamelCase }}, encrypted)...) if diags.HasError() { return diags } @@ -448,17 +450,17 @@ var {{ .Name.LowerCamelCase }}_list types.List {{- range .Params }} {{- $terraformType := printf "types.%s" (.Type | PascalCase) }} {{- if (not (or (eq .Type "") (eq .Type "list"))) }} - var {{ .Name.LowerCamelCase }}_value {{ $terraformType }} - if obj.{{ .Name.CamelCase }} != nil { + var {{ .TerraformName.LowerCamelCase }}_value {{ $terraformType }} + if obj.{{ .PangoName.CamelCase }} != nil { {{- if .Encryption }} - (*encrypted)["{{ .Encryption.EncryptedPath }}"] = types.StringValue(*obj.{{ .Name.CamelCase }}) + (*encrypted)["{{ .Encryption.EncryptedPath }}"] = types.StringValue(*obj.{{ .TerraformName.CamelCase }}) if value, ok := (*encrypted)["{{ .Encryption.PlaintextPath }}"]; ok { - {{ .Name.LowerCamelCase }}_value = value + {{ .TerraformName.LowerCamelCase }}_value = value } else { panic("{{ .Encryption.PlaintextPath }}") } {{- else }} - {{ .Name.LowerCamelCase }}_value = types.{{ .Type | PascalCase }}Value(*obj.{{ .Name.CamelCase }}) + {{ .TerraformName.LowerCamelCase }}_value = types.{{ .Type | PascalCase }}Value(*obj.{{ .PangoName.CamelCase }}) {{- end }} } {{- end }} @@ -467,9 +469,9 @@ var {{ .Name.LowerCamelCase }}_list types.List {{- range .OneOf }} {{- $terraformType := printf "types.%s" (.Type | PascalCase) }} {{- if (not (or (eq .Type "") (eq .Type "list"))) }} - var {{ .Name.LowerCamelCase }}_value {{ $terraformType }} - if obj.{{ .Name.CamelCase }} != nil { - {{ .Name.LowerCamelCase }}_value = types.{{ .Type | PascalCase }}Value(*obj.{{ .Name.CamelCase }}) + var {{ .TerraformName.LowerCamelCase }}_value {{ $terraformType }} + if obj.{{ .PangoName.CamelCase }} != nil { + {{ .TerraformName.LowerCamelCase }}_value = types.{{ .Type | PascalCase }}Value(*obj.{{ .PangoName.CamelCase }}) } {{- end }} {{- end }} @@ -478,11 +480,11 @@ var {{ .Name.LowerCamelCase }}_list types.List {{- define "assignFromPangoToTerraform" }} {{- with .Parameter }} {{- if eq .Type "" }} - o.{{ .Name.CamelCase }} = {{ .Name.LowerCamelCase }}_object + o.{{ .TerraformName.CamelCase }} = {{ .TerraformName.LowerCamelCase }}_object {{- else if eq .Type "list" }} - o.{{ .Name.CamelCase }} = {{ .Name.LowerCamelCase }}_list + o.{{ .TerraformName.CamelCase }} = {{ .TerraformName.LowerCamelCase }}_list {{- else }} - o.{{ .Name.CamelCase }} = {{ .Name.LowerCamelCase }}_value + o.{{ .TerraformName.CamelCase }} = {{ .TerraformName.LowerCamelCase }}_value {{- end }} {{- end }} {{- end }} @@ -939,7 +941,7 @@ func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager } } - structName := fmt.Sprintf("%s%s", structPrefix, param.Name.CamelCase) + structName := fmt.Sprintf("%s%s", structPrefix, param.NameVariant().CamelCase) var attributes []attributeCtx if param.HasEntryName() { @@ -957,11 +959,17 @@ func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager }) } + validatorFn := "ExactlyOneOf" var expressions []string for _, elt := range param.Spec.OneOf { if elt.IsPrivateParameter() { continue } + + if elt.IsNil { + validatorFn = "ConflictsWith" + } + expressions = append(expressions, fmt.Sprintf(`path.MatchRelative().AtParent().AtName("%s")`, elt.Name.Underscore)) } @@ -973,7 +981,7 @@ func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager } functions := []validatorFunctionCtx{{ - Function: "ExactlyOneOf", + Function: validatorFn, Expressions: expressions, }} @@ -1003,11 +1011,13 @@ func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager isResource = true } - var computed bool + var computed, required bool switch schemaTyp { case schemaDataSource: computed = true + required = false case schemaResource: + required = param.Required if param.TerraformProviderConfig != nil { computed = param.TerraformProviderConfig.Computed } @@ -1020,7 +1030,7 @@ func createSchemaSpecForParameter(schemaTyp schemaType, manager *imports.Manager StructName: structName, ReturnType: returnType, Description: "", - Required: param.Required, + Required: required, Optional: !param.Required, Computed: computed, Sensitive: param.Sensitive, @@ -1093,11 +1103,13 @@ func createSchemaAttributeForParameter(schemaTyp schemaType, manager *imports.Ma } } - var computed bool + var computed, required bool switch schemaTyp { case schemaDataSource: + required = false computed = true case schemaResource: + required = param.Required if param.TerraformProviderConfig != nil { computed = param.TerraformProviderConfig.Computed } else if param.Default != "" { @@ -1107,12 +1119,12 @@ func createSchemaAttributeForParameter(schemaTyp schemaType, manager *imports.Ma return attributeCtx{ Package: packageName, - Name: param.Name, + Name: param.NameVariant(), SchemaType: schemaType, ElementType: elementType, Description: param.Description, - Required: param.Required, - Optional: !param.Required, + Required: required, + Optional: !required, Sensitive: param.Sensitive, Default: defaultValue, Computed: computed, @@ -1414,16 +1426,21 @@ func createSchemaSpecForNormalization(resourceTyp properties.ResourceType, schem schemas = append(schemas, createSchemaSpecForParameter(schemaTyp, manager, structName, packageName, elt, nil)...) } + validatorFn := "ExactlyOneOf" var expressions []string for _, elt := range spec.Spec.OneOf { if elt.IsPrivateParameter() { continue } + + if elt.IsNil { + validatorFn = "ConflictsWith" + } expressions = append(expressions, fmt.Sprintf(`path.MatchRelative().AtParent().AtName("%s")`, elt.Name.Underscore)) } functions := []validatorFunctionCtx{{ - Function: "ExactlyOneOf", + Function: validatorFn, Expressions: expressions, }} @@ -1896,7 +1913,7 @@ type datasourceStructSpec struct { func terraformTypeForProperty(structPrefix string, prop *properties.SpecParam) string { if prop.Type == "" { - return fmt.Sprintf("*%s%sObject", structPrefix, prop.Name.CamelCase) + return fmt.Sprintf("*%s%sObject", structPrefix, prop.NameVariant().CamelCase) } if prop.Type == "list" && prop.Items.Type == "entry" { @@ -1911,9 +1928,9 @@ func terraformTypeForProperty(structPrefix string, prop *properties.SpecParam) s } func structFieldSpec(param *properties.SpecParam, structPrefix string) datasourceStructFieldSpec { - tfTag := fmt.Sprintf("`tfsdk:\"%s\"`", param.Name.Underscore) + tfTag := fmt.Sprintf("`tfsdk:\"%s\"`", param.NameVariant().Underscore) return datasourceStructFieldSpec{ - Name: param.Name.CamelCase, + Name: param.NameVariant().CamelCase, Type: terraformTypeForProperty(structPrefix, param), Tags: []string{tfTag}, } @@ -1922,7 +1939,7 @@ func structFieldSpec(param *properties.SpecParam, structPrefix string) datasourc func dataSourceStructContextForParam(structPrefix string, param *properties.SpecParam) []datasourceStructSpec { var structs []datasourceStructSpec - structName := fmt.Sprintf("%s%s", structPrefix, param.Name.CamelCase) + structName := fmt.Sprintf("%s%s", structPrefix, param.NameVariant().CamelCase) var fields []datasourceStructFieldSpec diff --git a/specs/policies/network-address-translation.yaml b/specs/policies/network-address-translation.yaml new file mode 100644 index 00000000..86f113e9 --- /dev/null +++ b/specs/policies/network-address-translation.yaml @@ -0,0 +1,559 @@ +name: "Network address translation policy" +terraform_provider_config: + resource_type: uuid + resource_variants: + - singular + - plural + suffix: "nat_policy" + plural_suffix: "nat_policy_rules" + plural_name: "rules" +go_sdk_config: + package: + - "policies" + - "rules" + - "nat" +xpath_suffix: + - "nat" + - "rules" +locations: + - name: "shared" + description: "Located in shared." + devices: + - panorama + - ngfw + xpath: + path: + - "config" + - "shared" + - "$rulebase" + vars: + - name: "rulebase" + type: object + description: "The rulebase." + default: "pre-rulebase" + validators: + - type: values + spec: + values: ["post-rulebase", "pre-rulebase"] + - name: "vsys" + description: "Located in a specific vsys." + devices: + - panorama + - ngfw + xpath: + path: + - "config" + - "devices" + - "$ngfw_device" + - "vsys" + - "$vsys" + - "rulebase" + vars: + - name: "ngfw_device" + description: "The NGFW device." + default: "localhost.localdomain" + - name: "vsys" + description: "The vsys." + default: "vsys1" + validators: + - type: not-values + spec: + values: + - value: "shared" + error: 'The vsys cannot be "shared". Use the "shared" path instead.' + - name: "from_panorama_vsys" + description: "Located in a specific vsys in the config pushed from Panorama." + read_only: true + devices: + - ngfw + xpath: + path: + - "config" + - "panorama" + - "vsys" + - "$vsys" + - "rulebase" + vars: + - name: "vsys" + description: "The vsys." + default: "vsys1" + validators: + - type: not-values + spec: + values: + - value: "shared" + error: 'The vsys cannot be "shared". Use the "shared" path instead.' + - name: "device_group" + description: "Located in a specific device group." + devices: + - panorama + xpath: + path: + - "config" + - "devices" + - "$panorama_device" + - "device-group" + - "$device_group" + - "$rulebase" + vars: + - name: "panorama_device" + description: "The panorama device." + default: "localhost.localdomain" + - name: "device_group" + description: "The device group." + required: true + validators: + - type: not-values + spec: + values: + - value: "shared" + error: 'The device group cannot be "shared". Use the "shared" path instead.' + - name: "rulebase" + type: object + description: "The rulebase." + default: "pre-rulebase" + validators: + - type: values + spec: + values: ["post-rulebase", "pre-rulebase"] +entries: + - name: name + description: "The name of the NAT rule." + validators: + - type: length + spec: + min: 1 + max: 63 +version: 11.2.0 +spec: + params: + - name: uuid + type: string + description: "The UUID value." + codegen_overrides: + terraform: + computed: true + validators: + - type: regexp + spec: + expr: "[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}" + - type: length + spec: + min: 3 + max: 36 + profiles: + - xpath: ["uuid"] + - name: from + description: Source Zone(s) + codegen_overrides: + terraform: + name: source_zones + type: list + profiles: + - type: member + xpath: [from] + spec: + items: + type: string + validators: + - type: length + spec: + max: 31 + - name: to + description: Destination Zone(s) + codegen_overrides: + terraform: + name: destination_zone + type: string + type: list + profiles: + - type: member + xpath: [to] + spec: + items: + type: string + validators: + - type: length + spec: + max: 31 + - name: source + description: Source Address(es) + required: true + type: list + codegen_overrides: + terraform: + name: source_addresses + profiles: + - type: member + xpath: [source] + spec: + items: + type: string + validators: + - type: length + spec: + max: 63 + - name: destination + description: Destination Address(es) + required: true + type: list + codegen_overrides: + terraform: + name: destination_addresses + profiles: + - type: member + xpath: [destination] + spec: + items: + type: string + validators: + - type: length + spec: + max: 63 + - name: service + type: string + profiles: + - xpath: [service] + spec: + default: any + - name: nat-type + type: enum + profiles: + - xpath: [nat-type] + validators: + - type: values + spec: + values: [ipv4, nat64, nptv6] + spec: + values: + - { value: ipv4 } + - { value: nat64 } + - { value: nptv6 } + - name: to-interface + description: Destination Interface + type: string + codegen_overrides: + terraform: + name: destination-interface + profiles: + - xpath: [to-interface] + - name: source-translation + description: Source Address Translation + type: object + profiles: + - xpath: [source-translation] + spec: + variants: + - name: dynamic-ip-and-port + description: Dynamic IP and Port + type: object + profiles: + - xpath: [dynamic-ip-and-port] + spec: + variants: + - name: translated-address + description: Translated Address + type: list + profiles: + - type: member + xpath: [translated-address] + spec: + items: + type: string + - name: interface-address + description: Interface Address + type: object + profiles: + - xpath: [interface-address] + spec: + params: + - name: interface + type: list + profiles: + - type: member + xpath: [interface] + validators: + - type: count + spec: + min: 1 + max: 1 + spec: + items: + type: string + variants: + - name: ip + description: IP + type: string + profiles: + - xpath: [ip] + - name: floating-ip + description: Floating IP + type: string + profiles: + - xpath: [floating-ip] + - name: interface-address + description: Interface Address + type: object + spec: + params: + - name: interface + description: Interface + type: string + profiles: + - xpath: [interface-address] + variants: + - name: ip + description: IP + type: string + profiles: + - xpath: [ip] + - name: floating-ip + description: Floating IP + type: string + profiles: + - xpath: [floating-ip] + - name: dynamic-ip + type: object + profiles: + - xpath: [dynamic-ip] + spec: + params: + - name: translated-address + type: list + profiles: + - type: member + xpath: [translated-address] + spec: + items: + type: string + - name: fallback + description: Dynamic IP/Port Fallback + type: object + profiles: + - xpath: [fallback] + spec: + variants: + - name: translated-address + description: Translated Address + type: list + profiles: + - type: member + xpath: [translated-address] + spec: + items: + type: string + - name: interface-address + description: Interface Address + type: object + profiles: + - xpath: [interface-address] + spec: + params: + - name: interface + description: Interface + type: string + profiles: + - xpath: [interface] + validators: + - type: length + spec: + max: 31 + variants: + - name: ip + description: IP + type: string + profiles: + - xpath: [ip] + - name: floating-ip + description: Floating IP + type: string + profiles: + - xpath: [floating-ip] + - name: static-ip + type: object + profiles: + - xpath: [static-ip] + spec: + params: + - name: translated-address + type: string + profiles: + - xpath: [translated-address] + validators: + - type: length + spec: + max: 63 + - name: bi-directional + type: enum + profiles: + - xpath: [bi-directional] + validators: + - type: values + spec: + values: [yes, no] + spec: + values: + - { value: yes } + - { value: no } + - name: active-active-device-binding + type: enum + profiles: + - xpath: [active-active-device-binding] + validators: + - type: values + spec: + values: [primary, both, "0", "1"] + spec: + values: + - { value: primary } + - { value: secondary } + - { value: "0" } + - { value: "1" } + - name: tags + type: list + profiles: + - type: member + xpath: [tag] + spec: + items: + type: string + - name: target + type: object + profiles: + - xpath: [target] + spec: + params: + - name: devices + type: list + profiles: + - type: entry + xpath: [devices] + spec: + items: + type: string + - name: tags + type: list + profiles: + - type: member + xpath: [tags] + spec: + items: + type: string + - name: negate + type: bool + profiles: + - xpath: [negate] + - name: disabled + type: bool + profiles: + - xpath: [disabled] + spec: + default: false + - name: description + type: string + profiles: + - xpath: [description] + validators: + - type: length + spec: + min: 0 + max: 1024 + - name: group-tag + type: string + profiles: + - xpath: [group-tag] + validators: + - type: length + spec: + max: 127 + variants: + - name: none + type: nil + profiles: + - xpath: [] + - name: destination-translation + type: object + profiles: + - xpath: [destination-translation] + spec: + params: + - name: translated-address + type: string + profiles: + - xpath: [translated-address] + validators: + - type: length + spec: + max: 63 + - name: translated-port + type: int64 + profiles: + - xpath: [translated-port] + validators: + - type: range + spec: + min: 1 + max: 65535 + - name: dns-rewrite + type: object + profiles: + - xpath: [dns-rewrite] + spec: + params: + - name: direction + type: enum + profiles: + - xpath: [direction] + validators: + - type: values + spec: + values: [reverse, forward] + spec: + values: + - { value: reverse } + - { value: forward } + - name: dynamic-destination-translation + type: object + profiles: + - xpath: [dynamic-destination-translation] + spec: + params: + - name: translated-address + type: string + profiles: + - xpath: [translated-address] + validators: + - type: length + spec: + max: 63 + - name: translated-port + type: int64 + profiles: + - xpath: [translated-port] + validators: + - type: range + spec: + min: 1 + max: 65535 + - name: distribution + type: enum + profiles: + - xpath: [distribution] + validators: + - type: values + spec: + values: + [ + round-robin, + source-ip-hash, + ip-modulo, + ip-hash, + least-sessions, + ] + spec: + values: + - { value: round-robin } + - { value: source-ip-hash } + - { value: ip-modulo } + - { value: ip-hash } + - { value: least-sessions } diff --git a/specs/schema/spec.schema.json b/specs/schema/spec.schema.json index 8fb5fe25..16ebda43 100644 --- a/specs/schema/spec.schema.json +++ b/specs/schema/spec.schema.json @@ -43,6 +43,7 @@ } }, "type": { "$ref": "#/$defs/type" }, + "required": { "type": "boolean", "default": "false" }, "profiles": { "type": "array", "items": { "$ref": "profile.schema.json" }