diff --git a/nsxt/resource_nsxt_policy_service.go b/nsxt/resource_nsxt_policy_service.go index 6b2b47036..746bc9f61 100644 --- a/nsxt/resource_nsxt_policy_service.go +++ b/nsxt/resource_nsxt_policy_service.go @@ -184,6 +184,19 @@ func resourceNsxtPolicyService() *schema.Resource { }, }, }, + + "nested_service_entry": { + Type: schema.TypeSet, + Description: "Nested service service entry", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "display_name": getOptionalDisplayNameSchema(false), + "description": getDescriptionSchema(), + "nested_service_path": getPolicyPathSchema(true, false, "Nested Service Path"), + }, + }, + }, }, } } @@ -377,6 +390,34 @@ func resourceNsxtPolicyServiceGetEntriesFromSchema(d *schema.ResourceData) ([]*d serviceEntries = append(serviceEntries, entryStruct) } + // Nested Service service entries + nestedEntries := d.Get("nested_service_entry").(*schema.Set).List() + for _, nestedEntry := range nestedEntries { + entryData := nestedEntry.(map[string]interface{}) + displayName := entryData["display_name"].(string) + description := entryData["description"].(string) + nestedServicePath := entryData["nested_service_path"].(string) + + // Use a different random Id each time + id := newUUID() + + serviceEntry := model.NestedServiceServiceEntry{ + Id: &id, + DisplayName: &displayName, + Description: &description, + NestedServicePath: &nestedServicePath, + ResourceType: model.ServiceEntry_RESOURCE_TYPE_NESTEDSERVICESERVICEENTRY, + } + + dataValue, errs := converter.ConvertToVapi(serviceEntry, model.NestedServiceServiceEntryBindingType()) + if errs != nil { + return serviceEntries, errs[0] + } + entryStruct := dataValue.(*data.StructValue) + serviceEntries = append(serviceEntries, entryStruct) + + } + return serviceEntries, nil } @@ -500,6 +541,7 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error var etherEntriesList []map[string]interface{} var ipProtEntriesList []map[string]interface{} var algEntriesList []map[string]interface{} + var nestedServiceEntriesList []map[string]interface{} for _, entry := range obj.ServiceEntries { elem := make(map[string]interface{}) @@ -588,6 +630,18 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error elem["display_name"] = filterServiceEntryDisplayName(*serviceEntry.DisplayName, *serviceEntry.Id) elem["description"] = serviceEntry.Description igmpEntriesList = append(igmpEntriesList, elem) + } else if resourceType == model.ServiceEntry_RESOURCE_TYPE_NESTEDSERVICESERVICEENTRY { + nestedEntry, errs := converter.ConvertToGolang(entry, model.NestedServiceServiceEntryBindingType()) + if errs != nil { + return errs[0] + } + + serviceEntry := nestedEntry.(model.NestedServiceServiceEntry) + elem["display_name"] = filterServiceEntryDisplayName(*serviceEntry.DisplayName, *serviceEntry.Id) + elem["description"] = serviceEntry.Description + elem["nested_service_path"] = serviceEntry.NestedServicePath + nestedServiceEntriesList = append(nestedServiceEntriesList, elem) + } else { return fmt.Errorf("Unrecognized Service Entry Type %s", resourceType) } @@ -623,6 +677,11 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error return err } + err = d.Set("nested_service_entry", nestedServiceEntriesList) + if err != nil { + return err + } + return nil } diff --git a/nsxt/resource_nsxt_policy_service_test.go b/nsxt/resource_nsxt_policy_service_test.go index e93ef3666..b6a9e58bc 100644 --- a/nsxt/resource_nsxt_policy_service_test.go +++ b/nsxt/resource_nsxt_policy_service_test.go @@ -5,6 +5,7 @@ package nsxt import ( "fmt" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -575,6 +576,129 @@ func TestAccResourceNsxtPolicyService_algType(t *testing.T) { }) } +func TestAccResourceNsxtPolicyService_nestedServiceType(t *testing.T) { + name := getAccTestResourceName() + updateName := getAccTestResourceName() + testResourceName := "nsxt_policy_service.test" + testNestedService1Name := "HTTP" + testNestedService2Name := "HTTPS" + regexpService1Name, err := regexp.Compile("/.*/" + testNestedService1Name) + + if err != nil { + fmt.Printf("Error while compiling regexp: %v", err) + } + + regexpService2Name, err := regexp.Compile("/.*/" + testNestedService2Name) + + if err != nil { + fmt.Printf("Error while compiling regexp: %v", err) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyServiceCheckDestroy(state, updateName) + }, + Steps: []resource.TestStep{ + { + // Step 0: Create a nested service + Config: testAccNsxtPolicyNestedServiceCreateTemplate(name, testNestedService1Name), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyServiceExists(testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", name), + resource.TestCheckResourceAttr(testResourceName, "description", "Nested service"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "algorithm_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "l4_port_set_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "icmp_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "igmp_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "ether_type_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "ip_protocol_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.0.display_name", testNestedService1Name), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.0.description", "Entry-1"), + resource.TestMatchResourceAttr(testResourceName, "nested_service_entry.0.nested_service_path", regexpService1Name), + ), + }, + { + // Step 1: Add another nested service + Config: testAccNsxtPolicyNestedServiceUpdateTemplate(name, testNestedService1Name, testNestedService2Name), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyServiceExists(testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", name), + resource.TestCheckResourceAttr(testResourceName, "description", "Nested service"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "algorithm_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "l4_port_set_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "icmp_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "igmp_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "ether_type_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "ip_protocol_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.#", "2"), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.0.display_name", testNestedService1Name), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.0.description", "Entry-1"), + resource.TestMatchResourceAttr(testResourceName, "nested_service_entry.0.nested_service_path", regexpService1Name), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.1.display_name", testNestedService2Name), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.1.description", "Entry-2"), + resource.TestMatchResourceAttr(testResourceName, "nested_service_entry.1.nested_service_path", regexpService2Name), + ), + }, + { + // Step 2: Remove nested service 2 + Config: testAccNsxtPolicyNestedServiceCreateTemplate(name, testNestedService1Name), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyServiceExists(testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", name), + resource.TestCheckResourceAttr(testResourceName, "description", "Nested service"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "algorithm_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "l4_port_set_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "icmp_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "igmp_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "ether_type_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "ip_protocol_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.0.display_name", testNestedService1Name), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.0.description", "Entry-1"), + resource.TestMatchResourceAttr(testResourceName, "nested_service_entry.0.nested_service_path", regexpService1Name), + ), + }, + { + // Step 3: Mix with other service types + Config: testAccNsxtPolicyNestedServiceMixedTemplate(name, testNestedService1Name), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyServiceExists(testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", name), + resource.TestCheckResourceAttr(testResourceName, "description", "Nested service"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "algorithm_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "l4_port_set_entry.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "l4_port_set_entry.0.display_name", "entry-2"), + resource.TestCheckResourceAttr(testResourceName, "l4_port_set_entry.0.description", "Entry-2"), + resource.TestCheckResourceAttr(testResourceName, "l4_port_set_entry.0.protocol", "TCP"), + resource.TestCheckResourceAttr(testResourceName, "icmp_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "igmp_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "ether_type_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "ip_protocol_entry.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.0.display_name", testNestedService1Name), + resource.TestCheckResourceAttr(testResourceName, "nested_service_entry.0.description", "Entry-1"), + resource.TestMatchResourceAttr(testResourceName, "nested_service_entry.0.nested_service_path", regexpService1Name), + ), + }, + }, + }) +} + func TestAccResourceNsxtPolicyService_importBasic(t *testing.T) { name := getAccTestResourceName() testResourceName := "nsxt_policy_service.test" @@ -921,3 +1045,72 @@ resource "nsxt_policy_service" "test" { } }`, serviceName, serviceName, alg, sourcePorts, destPort) } + +func testAccNsxtPolicyNestedServiceCreateTemplate(serviceName string, nestedServiceEntryName string) string { + return fmt.Sprintf(` +resource "nsxt_policy_service" "test" { + description = "Nested service" + display_name = "%s" + + nested_service_entry { + display_name = "%s" + description = "Entry-1" + nested_service_path = "%s" + } + + tag { + scope = "scope1" + tag = "tag1" + } +}`, serviceName, nestedServiceEntryName, testAccAdjustPolicyInfraConfig("/infra/services/"+nestedServiceEntryName)) +} + +func testAccNsxtPolicyNestedServiceUpdateTemplate(serviceName string, nestedServiceEntry1Name string, nestedServiceEntry2Name string) string { + return fmt.Sprintf(`resource "nsxt_policy_service" "test" { + description = "Nested service" + display_name = "%s" + + nested_service_entry { + display_name = "%s" + description = "Entry-1" + nested_service_path = "%s" + } + + nested_service_entry { + display_name = "%s" + description = "Entry-2" + nested_service_path = "%s" + } + + tag { + scope = "scope1" + tag = "tag1" + } +}`, serviceName, nestedServiceEntry1Name, testAccAdjustPolicyInfraConfig("/infra/services/"+nestedServiceEntry1Name), nestedServiceEntry2Name, testAccAdjustPolicyInfraConfig("/infra/services/"+nestedServiceEntry2Name)) + +} + +func testAccNsxtPolicyNestedServiceMixedTemplate(serviceName string, nestedServiceEntryName string) string { + return fmt.Sprintf(`resource "nsxt_policy_service" "test" { + description = "Nested service" + display_name = "%s" + + nested_service_entry { + display_name = "%s" + description = "Entry-1" + nested_service_path = "%s" + } + + l4_port_set_entry { + display_name = "entry-2" + description = "Entry-2" + protocol = "TCP" + destination_ports = [ "443" ] + } + + tag { + scope = "scope1" + tag = "tag1" + } +}`, serviceName, nestedServiceEntryName, testAccAdjustPolicyInfraConfig("/infra/services/"+nestedServiceEntryName)) +} diff --git a/website/docs/r/policy_service.html.markdown b/website/docs/r/policy_service.html.markdown index e2d594e00..e69c1445d 100644 --- a/website/docs/r/policy_service.html.markdown +++ b/website/docs/r/policy_service.html.markdown @@ -90,6 +90,11 @@ The service must contain at least 1 entry (of at least one of the types), and po * `destination_port` - (Required) a single destination port. * `source_ports` - (Optional) Set of source ports/ranges. * `algorithm` - (Required) Algorithm, one of `ORACLE_TNS`, `FTP`, `SUN_RPC_TCP`, `SUN_RPC_UDP`, `MS_RPC_TCP`, `MS_RPC_UDP`, `NBNS_BROADCAST`(Deprecated), `NBDG_BROADCAST`(Deprecated), `TFTP`. +* `nested_service_entry` - (Optional) Feature introduced since at least NSX-T 3.1, set of Nested service entries. Each with the following attributes: + * `display_name` - (Optional) Display name of the service entry. + * `description` - (Optional) Description of the service entry. + * `nested_service_path` - (Required) Path of the nested service. + ## Attributes Reference