diff --git a/examples/pbr_v2/main.tf b/examples/pbr_v2/main.tf new file mode 100644 index 000000000..5b20a3012 --- /dev/null +++ b/examples/pbr_v2/main.tf @@ -0,0 +1,118 @@ +terraform{ + required_providers { + nutanix = { + source = "nutanix/nutanix" + version = "1.6.0" + } + } +} + +#defining nutanix configuration +provider "nutanix"{ + username = var.nutanix_username + password = var.nutanix_password + endpoint = var.nutanix_endpoint + port = 9440 + insecure = true +} + +# create PBR with vpc name with any source or destination or protocol with permit action + +resource "nutanix_pbr_v2" "pbr1" { + name = "%[1]s" + description = "%[2]s" + vpc_ext_id = var.vpc_reference_uuid + priority = 14 + policies{ + policy_match{ + source{ + address_type = "ANY" + } + destination{ + address_type = "ANY" + } + protocol_type = "UDP" + } + policy_action{ + action_type = "PERMIT" + } + } +} + + + +# create PBR with vpc uuid with source external + +resource "nutanix_pbr_v2" "pbr2" { + name = "%[1]s" + description = "%[2]s" + vpc_ext_id = var.vpc_reference_uuid + priority = 11 + policies{ + policy_match{ + source{ + address_type = "EXTERNAL" + } + destination{ + address_type = "SUBNET" + subnet_prefix{ + ipv4{ + ip{ + value= "10.10.10.0" + prefix_length = 24 + } + } + } + } + protocol_type = "ANY" + } + policy_action{ + action_type = "FORWARD" + nexthop_ip_address{ + ipv4{ + value = "10.10.10.10" + } + } + } + } +} + + +#create PBR with vpc name with source Any and destination external +resource "nutanix_pbr_v2" "pbr3" { + name = "%[1]s" + description = "%[2]s" + vpc_ext_id = var.vpc_reference_uuid + priority = 14 + policies{ + policy_match{ + source{ + address_type = "ALL" + } + destination{ + address_type = "INTERNET" + } + protocol_type = "UDP" + } + policy_action{ + action_type = "PERMIT" + } + } +} + +# list pbr + +data "nutanix_pbrs_v2" "pbr4" { +} + + + +# get an entity with pbr uuid + +data "nutanix_pbr_v2" "pbr5" { + ext_id = resource.nutanix_pbr_v2.rtest.ext_id + depends_on = [ + resource.nutanix_pbr_v2.rtest + ] +} + diff --git a/examples/pbr_v2/terraform.tfvars b/examples/pbr_v2/terraform.tfvars new file mode 100644 index 000000000..f75110175 --- /dev/null +++ b/examples/pbr_v2/terraform.tfvars @@ -0,0 +1,6 @@ +#define values to the variables to be used in terraform file +nutanix_username = "admin" +nutanix_password = "password" +nutanix_endpoint = "10.xx.xx.xx" +nutanix_port = 9440 +vpc_reference_uuid = "" diff --git a/examples/pbr_v2/variables.tf b/examples/pbr_v2/variables.tf new file mode 100644 index 000000000..f48bb1554 --- /dev/null +++ b/examples/pbr_v2/variables.tf @@ -0,0 +1,17 @@ +#define the type of variables to be used in terraform file +variable "nutanix_username" { + type = string +} +variable "nutanix_password" { + type = string +} +variable "nutanix_endpoint" { + type = string +} +variable "nutanix_port" { + type = string +} + +variable "vpc_reference_uuid" { + type = string +} diff --git a/nutanix/provider/provider.go b/nutanix/provider/provider.go index 5fa21eb2d..e82254899 100644 --- a/nutanix/provider/provider.go +++ b/nutanix/provider/provider.go @@ -225,6 +225,8 @@ func Provider() *schema.Provider { "nutanix_vpcs_v2": networkingv2.DataSourceNutanixVPCsv2(), "nutanix_floating_ip_v2": networkingv2.DatasourceNutanixFloatingIPV2(), "nutanix_floating_ips_v2": networkingv2.DatasourceNutanixFloatingIPsV2(), + "nutanix_pbr_v2": networkingv2.DatasourceNutanixPbrV2(), + "nutanix_pbrs_v2": networkingv2.DatasourceNutanixPbrsV2(), }, ResourcesMap: map[string]*schema.Resource{ "nutanix_virtual_machine": prism.ResourceNutanixVirtualMachine(), @@ -279,6 +281,7 @@ func Provider() *schema.Provider { "nutanix_subnet_v2": networkingv2.ResourceNutanixSubnetV2(), "nutanix_floating_ip_v2": networkingv2.ResourceNutanixFloatingIPv2(), "nutanix_vpc_v2": networkingv2.ResourceNutanixVPCsV2(), + "nutanix_pbr_v2": networkingv2.ResourceNutanixPbrsV2(), }, ConfigureContextFunc: providerConfigure, } diff --git a/nutanix/sdks/v4/networking/networking.go b/nutanix/sdks/v4/networking/networking.go index 11ddc841f..12b531d2c 100644 --- a/nutanix/sdks/v4/networking/networking.go +++ b/nutanix/sdks/v4/networking/networking.go @@ -7,6 +7,7 @@ import ( ) type Client struct { + RoutingPolicy *api.RoutingPoliciesApi SubnetAPIInstance *api.SubnetsApi VpcAPIInstance *api.VpcsApi FloatingIPAPIInstance *api.FloatingIpsApi @@ -29,6 +30,7 @@ func NewNetworkingClient(credentials client.Credentials) (*Client, error) { } f := &Client{ + RoutingPolicy: api.NewRoutingPoliciesApi(baseClient), SubnetAPIInstance: api.NewSubnetsApi(baseClient), VpcAPIInstance: api.NewVpcsApi(baseClient), FloatingIPAPIInstance: api.NewFloatingIpsApi(baseClient), diff --git a/nutanix/services/v2/networkingv2/data_source_nutanix_floating_ip_v2_test.go b/nutanix/services/v2/networkingv2/data_source_nutanix_floating_ip_v2_test.go index 611794db9..3c8afbfb8 100644 --- a/nutanix/services/v2/networkingv2/data_source_nutanix_floating_ip_v2_test.go +++ b/nutanix/services/v2/networkingv2/data_source_nutanix_floating_ip_v2_test.go @@ -40,7 +40,7 @@ func testAccFipDataSourceConfig(name, desc string) string { data "nutanix_clusters" "clusters" {} locals { - cluster0 = data.nutanix_clusters.clusters.entities[0].metadata.uuid + cluster0 = data.nutanix_clusters.clusters.entities[0].metadata.uuid } resource "nutanix_subnet_v2" "test" { diff --git a/nutanix/services/v2/networkingv2/data_source_nutanix_pbr_v2.go b/nutanix/services/v2/networkingv2/data_source_nutanix_pbr_v2.go new file mode 100644 index 000000000..621c8edf1 --- /dev/null +++ b/nutanix/services/v2/networkingv2/data_source_nutanix_pbr_v2.go @@ -0,0 +1,641 @@ +package networkingv2 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + import1 "github.com/nutanix/ntnx-api-golang-clients/networking-go-client/v4/models/networking/v4/config" + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func DatasourceNutanixPbrV2() *schema.Resource { + return &schema.Resource{ + ReadContext: DatasourceNutanixPbrV2Read, + Schema: map[string]*schema.Schema{ + "ext_id": { + Type: schema.TypeString, + Required: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + "links": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "metadata": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: DatasourceMetadataSchemaV2(), + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "priority": { + Type: schema.TypeInt, + Computed: true, + }, + "policies": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "policy_match": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address_type": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_prefix": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "ipv6": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "destination": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address_type": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_prefix": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "ipv6": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "protocol_type": { + Type: schema.TypeString, + Computed: true, + }, + "protocol_parameters": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "layer_four_protocol_object": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source_port_ranges": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start_port": { + Type: schema.TypeInt, + Computed: true, + }, + "end_port": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "destination_port_ranges": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start_port": { + Type: schema.TypeInt, + Computed: true, + }, + "end_port": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "icmp_object": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "icmp_type": { + Type: schema.TypeInt, + Computed: true, + }, + "icmp_code": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "protocol_number_object": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "protocol_number": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "policy_action": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action_type": { + Type: schema.TypeString, + Computed: true, + }, + "reroute_params": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_ip": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + "reroute_fallback_action": { + Type: schema.TypeString, + Computed: true, + }, + "ingress_service_ip": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + "egress_service_ip": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + }, + }, + }, + "nexthop_ip_address": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + }, + }, + }, + "is_bidirectional": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + "vpc_ext_id": { + Type: schema.TypeString, + Computed: true, + }, + "vpc": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixPbrV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).NetworkingAPI + + extID := d.Get("ext_id") + resp, err := conn.RoutingPolicy.GetRoutingPolicyById(utils.StringPtr(extID.(string))) + if err != nil { + return diag.Errorf("error while fetching routing policy : %v", err) + } + + getResp := resp.Data.GetValue().(import1.RoutingPolicy) + + if err := d.Set("name", getResp.Name); err != nil { + return diag.FromErr(err) + } + if err := d.Set("links", flattenLinks(getResp.Links)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("description", getResp.Description); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("tenant_id", getResp.TenantId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("metadata", flattenMetadata(getResp.Metadata)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("priority", getResp.Priority); err != nil { + return diag.FromErr(err) + } + if err := d.Set("vpc_ext_id", getResp.VpcExtId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("policies", flattenPolicies(getResp.Policies)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("vpc", flattenVpcName(getResp.Vpc)); err != nil { + return diag.FromErr(err) + } + + d.SetId(*getResp.ExtId) + return nil +} + +func flattenPolicies(pr []import1.RoutingPolicyRule) []interface{} { + if len(pr) > 0 { + policies := make([]interface{}, len(pr)) + + for k, v := range pr { + policy := make(map[string]interface{}) + + policy["policy_match"] = flattenPolicyMatch(v.PolicyMatch) + policy["policy_action"] = flattenRoutingPolicyAction(v.PolicyAction) + policy["is_bidirectional"] = v.IsBidirectional + + policies[k] = policy + } + return policies + } + return nil +} + +func flattenPolicyMatch(pr *import1.RoutingPolicyMatchCondition) []map[string]interface{} { + if pr != nil { + policyMatches := make([]map[string]interface{}, 0) + match := make(map[string]interface{}) + + match["source"] = flattenAddressTypeObject(pr.Source) + match["destination"] = flattenAddressTypeObject(pr.Destination) + match["protocol_type"] = flattenProtocolType(pr.ProtocolType) + match["protocol_parameters"] = flattenOneOfRoutingPolicyMatchConditionProtocolParameters(pr.ProtocolParameters) + + policyMatches = append(policyMatches, match) + return policyMatches + } + return nil +} + +func flattenAddressTypeObject(pr *import1.AddressTypeObject) []map[string]interface{} { + if pr != nil { + addressType := make([]map[string]interface{}, 0) + + address := make(map[string]interface{}) + + address["address_type"] = flattenAddressType(pr.AddressType) + address["subnet_prefix"] = flattenIPSubnet(pr.SubnetPrefix) + + addressType = append(addressType, address) + return addressType + } + return nil +} + +func flattenIPSubnet(pr *import1.IPSubnet) []map[string]interface{} { + if pr != nil { + ipsubnets := make([]map[string]interface{}, 0) + subnet := make(map[string]interface{}) + + subnet["ipv4"] = flattenIPv4Subnet(pr.Ipv4) + subnet["ipv6"] = flattenIPv6Subnet(pr.Ipv6) + + ipsubnets = append(ipsubnets, subnet) + return ipsubnets + } + return nil +} + +func flattenOneOfRoutingPolicyMatchConditionProtocolParameters(pr *import1.OneOfRoutingPolicyMatchConditionProtocolParameters) []map[string]interface{} { + if pr != nil { + layerFour := make(map[string]interface{}) + layerFourList := make([]map[string]interface{}, 0) + icmp := make(map[string]interface{}) + icmpList := make([]map[string]interface{}, 0) + protoNum := make(map[string]interface{}) + protoNumList := make([]map[string]interface{}, 0) + + if *pr.ObjectType_ == "networking.v4.config.LayerFourProtocolObject" { + layer := make(map[string]interface{}) + layerList := make([]map[string]interface{}, 0) + + ip := pr.GetValue().(import1.LayerFourProtocolObject) + + layer["source_port_ranges"] = flattenPortRange(ip.SourcePortRanges) + layer["destination_port_ranges"] = flattenPortRange(ip.DestinationPortRanges) + + layerList = append(layerList, layer) + + layerFour["layer_four_protocol_object"] = layerList + + layerFourList = append(layerFourList, layerFour) + + return layerFourList + } + + if *pr.ObjectType_ == "networking.v4.config.ICMPObject" { + obj := make(map[string]interface{}) + objList := make([]map[string]interface{}, 0) + + ip := pr.GetValue().(import1.ICMPObject) + + obj["icmp_code"] = ip.IcmpCode + obj["icmp_type"] = ip.IcmpType + + objList = append(objList, obj) + + icmp["icmp_object"] = objList + + icmpList = append(icmpList, icmp) + + return icmpList + } + proto := make(map[string]interface{}) + protoList := make([]map[string]interface{}, 0) + + vm := pr.GetValue().(import1.ProtocolNumberObject) + + proto["protocol_number"] = vm.ProtocolNumber + + protoList = append(protoList, proto) + + protoNum["protocol_number_object"] = protoList + + protoNumList = append(protoNumList, protoNum) + + return protoNumList + } + return nil +} + +func flattenRoutingPolicyAction(pr *import1.RoutingPolicyAction) []map[string]interface{} { + if pr != nil { + policyAction := make([]map[string]interface{}, 0) + policy := make(map[string]interface{}) + + policy["action_type"] = flattenRoutingPolicyActionType(pr.ActionType) + policy["reroute_params"] = flattenRerouteParam(pr.RerouteParams) + policy["nexthop_ip_address"] = flattenIPAddress(pr.NexthopIpAddress) + + policyAction = append(policyAction, policy) + return policyAction + } + return nil +} + +func flattenRerouteParam(pr []import1.RerouteParam) []interface{} { + if len(pr) > 0 { + routeParams := make([]interface{}, len(pr)) + + for k, v := range pr { + param := make(map[string]interface{}) + + param["service_ip"] = flattenNodeIPAddress(v.ServiceIp) + param["reroute_fallback_action"] = flattenRerouteFallbackAction(v.RerouteFallbackAction) + + routeParams[k] = param + } + return routeParams + } + return nil +} + +func flattenPortRange(pr []import1.PortRange) []interface{} { + if len(pr) > 0 { + ports := make([]interface{}, len(pr)) + + for k, v := range pr { + port := make(map[string]interface{}) + + port["start_port"] = v.StartPort + port["end_port"] = v.EndPort + + ports[k] = port + } + return ports + } + return nil +} + +func flattenAddressType(pr *import1.AddressType) string { + if pr != nil { + const two, three, four = 2, 3, 4 + if *pr == import1.AddressType(two) { + return "ANY" + } + if *pr == import1.AddressType(three) { + return "EXTERNAL" + } + if *pr == import1.AddressType(four) { + return "SUBNET" + } + } + return "UNKNOWN" +} + +func flattenRoutingPolicyActionType(pr *import1.RoutingPolicyActionType) string { + if pr != nil { + const two, three, four, five = 2, 3, 4, 5 + if *pr == import1.RoutingPolicyActionType(two) { + return "PERMIT" + } + if *pr == import1.RoutingPolicyActionType(three) { + return "DENY" + } + if *pr == import1.RoutingPolicyActionType(four) { + return "REROUTE" + } + if *pr == import1.RoutingPolicyActionType(five) { + return "FORWARD" + } + } + return "UNKNOWN" +} + +func flattenProtocolType(pr *import1.ProtocolType) string { + if pr != nil { + const two, three, four, five, six = 2, 3, 4, 5, 6 + if *pr == import1.ProtocolType(two) { + return "ANY" + } + if *pr == import1.ProtocolType(three) { + return "ICMP" + } + if *pr == import1.ProtocolType(four) { + return "TCP" + } + if *pr == import1.ProtocolType(five) { + return "UDP" + } + if *pr == import1.ProtocolType(six) { + return "PROTOCOL_NUMBER" + } + } + return "UNKNOWN" +} + +func flattenVpcName(pr *import1.VpcName) []map[string]interface{} { + if pr != nil { + vpcs := make([]map[string]interface{}, 0) + vpc := make(map[string]interface{}) + + vpc["name"] = pr.Name + + vpcs = append(vpcs, vpc) + return vpcs + } + return nil +} + +func flattenRerouteFallbackAction(pr *import1.RerouteFallbackAction) string { + if pr != nil { + const two, three, four, five = 2, 3, 4, 5 + if *pr == import1.RerouteFallbackAction(two) { + return "ALLOW" + } + if *pr == import1.RerouteFallbackAction(three) { + return "DROP" + } + if *pr == import1.RerouteFallbackAction(four) { + return "PASSTHROUGH" + } + if *pr == import1.RerouteFallbackAction(five) { + return "NO_ACTION" + } + } + return "UNKNOWN" +} diff --git a/nutanix/services/v2/networkingv2/data_source_nutanix_pbr_v2_test.go b/nutanix/services/v2/networkingv2/data_source_nutanix_pbr_v2_test.go new file mode 100644 index 000000000..c2cb88ab7 --- /dev/null +++ b/nutanix/services/v2/networkingv2/data_source_nutanix_pbr_v2_test.go @@ -0,0 +1,112 @@ +package networkingv2_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const datasourceNamePbr = "data.nutanix_pbr_v2.test" + +func TestAccNutanixPbrDataSourceV2_basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-fip-%d", r) + desc := "test fip description" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccPbrDataSourceConfig(name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(datasourceNamePbr, "name", name), + resource.TestCheckResourceAttr(datasourceNamePbr, "description", desc), + resource.TestCheckResourceAttrSet(datasourceNamePbr, "metadata.#"), + resource.TestCheckResourceAttr(datasourceNamePbr, "policies.0.is_bidirectional", "false"), + resource.TestCheckResourceAttr(datasourceNamePbr, "policies.0.policy_match.0.protocol_type", "UDP"), + resource.TestCheckResourceAttr(datasourceNamePbr, "priority", "14"), + ), + }, + }, + }) +} + +func testAccPbrDataSourceConfig(name, desc string) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster0 = data.nutanix_clusters.clusters.entities[0].metadata.uuid + } + + resource "nutanix_subnet_v2" "test" { + name = "terraform-test-subnet-vpc_%[1]s" + description = "test subnet description" + cluster_reference = local.cluster0 + subnet_type = "VLAN" + network_id = 112 + is_external = true + ip_config { + ipv4 { + ip_subnet { + ip { + value = "192.168.0.0" + } + prefix_length = 24 + } + default_gateway_ip { + value = "192.168.0.1" + } + pool_list{ + start_ip { + value = "192.168.0.20" + } + end_ip { + value = "192.168.0.30" + } + } + } + } + depends_on = [data.nutanix_clusters.clusters] + } + resource "nutanix_vpc_v2" "test" { + name = "pbr_vpc_%[1]s" + description = "pbr_vpc_ %[2]s" + external_subnets{ + subnet_reference = nutanix_subnet_v2.test.id + } + depends_on = [nutanix_subnet_v2.test] + } + + resource "nutanix_pbr_v2" "rtest" { + name = "%[1]s" + description = "%[2]s" + vpc_ext_id = nutanix_vpc_v2.test.ext_id + priority = 14 + policies{ + policy_match{ + source{ + address_type = "ANY" + } + destination{ + address_type = "ANY" + } + protocol_type = "UDP" + } + policy_action{ + action_type = "PERMIT" + } + } + } + + data "nutanix_pbr_v2" "test" { + ext_id = resource.nutanix_pbr_v2.rtest.ext_id + depends_on = [ + resource.nutanix_pbr_v2.rtest + ] + } + `, name, desc) +} diff --git a/nutanix/services/v2/networkingv2/data_source_nutanix_pbrs_v2.go b/nutanix/services/v2/networkingv2/data_source_nutanix_pbrs_v2.go new file mode 100644 index 000000000..d2a31478a --- /dev/null +++ b/nutanix/services/v2/networkingv2/data_source_nutanix_pbrs_v2.go @@ -0,0 +1,430 @@ +package networkingv2 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + import1 "github.com/nutanix/ntnx-api-golang-clients/networking-go-client/v4/models/networking/v4/config" + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func DatasourceNutanixPbrsV2() *schema.Resource { + return &schema.Resource{ + ReadContext: DatasourceNutanixPbrsV2Read, + Schema: map[string]*schema.Schema{ + "page": { + Type: schema.TypeInt, + Optional: true, + }, + "limit": { + Type: schema.TypeInt, + Optional: true, + }, + "filter": { + Type: schema.TypeString, + Optional: true, + }, + "order_by": { + Type: schema.TypeString, + Optional: true, + }, + "routing_policies": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ext_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + "links": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "metadata": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: DatasourceMetadataSchemaV2(), + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "priority": { + Type: schema.TypeInt, + Computed: true, + }, + "policies": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "policy_match": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address_type": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_prefix": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "ipv6": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "destination": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address_type": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_prefix": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "ipv6": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "protocol_type": { + Type: schema.TypeString, + Computed: true, + }, + "protocol_parameters": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "layer_four_protocol_object": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source_port_ranges": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start_port": { + Type: schema.TypeInt, + Computed: true, + }, + "end_port": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "destination_port_ranges": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start_port": { + Type: schema.TypeInt, + Computed: true, + }, + "end_port": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "icmp_object": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "icmp_type": { + Type: schema.TypeInt, + Computed: true, + }, + "icmp_code": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "protocol_number_object": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "protocol_number": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "policy_action": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action_type": { + Type: schema.TypeString, + Computed: true, + }, + "reroute_params": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_ip": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + "reroute_fallback_action": { + Type: schema.TypeString, + Computed: true, + }, + "ingress_service_ip": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + "egress_service_ip": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + }, + }, + }, + "nexthop_ip_address": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + }, + }, + }, + "is_bidirectional": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + "vpc_ext_id": { + Type: schema.TypeString, + Computed: true, + }, + "vpc": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixPbrsV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).NetworkingAPI + + // initialize query params + var filter, orderBy *string + var page, limit *int + + if pagef, ok := d.GetOk("page"); ok { + page = utils.IntPtr(pagef.(int)) + } else { + page = nil + } + if limitf, ok := d.GetOk("limit"); ok { + limit = utils.IntPtr(limitf.(int)) + } else { + limit = nil + } + if filterf, ok := d.GetOk("filter"); ok { + filter = utils.StringPtr(filterf.(string)) + } else { + filter = nil + } + if order, ok := d.GetOk("order_by"); ok { + orderBy = utils.StringPtr(order.(string)) + } else { + orderBy = nil + } + + resp, err := conn.RoutingPolicy.ListRoutingPolicies(page, limit, filter, orderBy, nil, nil) + if err != nil { + return diag.Errorf("error while fetching routing policies : %v", err) + } + + getResp := resp.Data + + if getResp != nil { + tmp := resp.Data.GetValue().([]import1.RoutingPolicy) + if err := d.Set("routing_policies", flattenRoutingEntities(tmp)); err != nil { + return diag.FromErr(err) + } + } + + d.SetId(resource.UniqueId()) + return nil +} + +func flattenRoutingEntities(pr []import1.RoutingPolicy) []interface{} { + if len(pr) > 0 { + routingEnts := make([]interface{}, len(pr)) + + for k, v := range pr { + entity := make(map[string]interface{}) + + entity["ext_id"] = v.ExtId + entity["tenant_id"] = v.TenantId + entity["links"] = flattenLinks(v.Links) + entity["metadata"] = flattenMetadata(v.Metadata) + entity["name"] = v.Name + entity["description"] = v.Description + entity["priority"] = v.Priority + entity["policies"] = flattenPolicies(v.Policies) + entity["vpc_ext_id"] = v.VpcExtId + entity["vpc"] = flattenVpcName(v.Vpc) + + routingEnts[k] = entity + } + return routingEnts + } + return nil +} diff --git a/nutanix/services/v2/networkingv2/data_source_nutanix_pbrs_v2_test.go b/nutanix/services/v2/networkingv2/data_source_nutanix_pbrs_v2_test.go new file mode 100644 index 000000000..70ffcf867 --- /dev/null +++ b/nutanix/services/v2/networkingv2/data_source_nutanix_pbrs_v2_test.go @@ -0,0 +1,114 @@ +package networkingv2_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const datasourceNamePbrs = "data.nutanix_pbrs_v2.test" + +func TestAccNutanixPbrsDataSourceV2_basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-fip-%d", r) + desc := "test fip description" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccPbrsDataSourceConfig(name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(datasourceNamePbrs, "routing_policies.#"), + resource.TestCheckResourceAttr(datasourceNamePbrs, "routing_policies.0.name", name), + resource.TestCheckResourceAttr(datasourceNamePbrs, "routing_policies.0.description", desc), + resource.TestCheckResourceAttrSet(datasourceNamePbrs, "routing_policies.0.metadata.#"), + resource.TestCheckResourceAttr(datasourceNamePbrs, "routing_policies.0.policies.0.is_bidirectional", "false"), + resource.TestCheckResourceAttr(datasourceNamePbrs, "routing_policies.0.policies.0.policy_match.0.protocol_type", "UDP"), + resource.TestCheckResourceAttr(datasourceNamePbrs, "routing_policies.0.priority", "14"), + ), + }, + }, + }) +} + +func testAccPbrsDataSourceConfig(name, desc string) string { + return fmt.Sprintf(` + + data "nutanix_clusters" "clusters" {} + + locals { + cluster0 = data.nutanix_clusters.clusters.entities[0].metadata.uuid + } + + resource "nutanix_subnet_v2" "test" { + name = "terraform-test-subnet-vpc_%[1]s" + description = "test subnet description" + cluster_reference = local.cluster0 + subnet_type = "VLAN" + network_id = 112 + is_external = true + ip_config { + ipv4 { + ip_subnet { + ip { + value = "192.168.0.0" + } + prefix_length = 24 + } + default_gateway_ip { + value = "192.168.0.1" + } + pool_list{ + start_ip { + value = "192.168.0.20" + } + end_ip { + value = "192.168.0.30" + } + } + } + } + depends_on = [data.nutanix_clusters.clusters] + } + resource "nutanix_vpc_v2" "test" { + name = "pbr_vpc_%[1]s" + description = "pbr_vpc_ %[2]s" + external_subnets{ + subnet_reference = nutanix_subnet_v2.test.id + } + depends_on = [nutanix_subnet_v2.test] + } + + resource "nutanix_pbr_v2" "test" { + name = "%[1]s" + description = "%[2]s" + vpc_ext_id = nutanix_vpc_v2.test.ext_id + priority = 14 + policies{ + policy_match{ + source{ + address_type = "ANY" + } + destination{ + address_type = "ANY" + } + protocol_type = "UDP" + } + policy_action{ + action_type = "PERMIT" + } + } + } + + data "nutanix_pbrs_v2" "test" { + filter = "name eq '%[1]s'" + depends_on = [ + resource.nutanix_pbr_v2.test + ] + } + `, name, desc) +} diff --git a/nutanix/services/v2/networkingv2/resource_nutanix_floating_ip_v2.go b/nutanix/services/v2/networkingv2/resource_nutanix_floating_ip_v2.go index a61056373..6a5f72a00 100644 --- a/nutanix/services/v2/networkingv2/resource_nutanix_floating_ip_v2.go +++ b/nutanix/services/v2/networkingv2/resource_nutanix_floating_ip_v2.go @@ -231,7 +231,7 @@ func ResourceNutanixFloatingIPv2Create(ctx context.Context, d *schema.ResourceDa // calling group API to poll for completion of task taskconn := meta.(*conns.Client).PrismAPI - // Wait for the FileServer to be available + // Wait for the Routing Policy to be available stateConf := &resource.StateChangeConf{ Pending: []string{"QUEUED", "RUNNING"}, Target: []string{"SUCCEEDED"}, @@ -490,9 +490,10 @@ func expandSubnet(pr interface{}) *import1.Subnet { sub.Description = utils.StringPtr(desc.(string)) } if subType, ok := val["subnet_type"]; ok { + const two, three = 2, 3 subMap := map[string]interface{}{ - "OVERLAY": "2", - "VLAN": "3", + "OVERLAY": two, + "VLAN": three, } pInt := subMap[subType.(string)] p := import1.SubnetType(pInt.(int)) diff --git a/nutanix/services/v2/networkingv2/resource_nutanix_pbrs_v2.go b/nutanix/services/v2/networkingv2/resource_nutanix_pbrs_v2.go new file mode 100644 index 000000000..aabb5c5f2 --- /dev/null +++ b/nutanix/services/v2/networkingv2/resource_nutanix_pbrs_v2.go @@ -0,0 +1,808 @@ +package networkingv2 + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + config "github.com/nutanix/ntnx-api-golang-clients/networking-go-client/v4/models/common/v1/config" + import1 "github.com/nutanix/ntnx-api-golang-clients/networking-go-client/v4/models/networking/v4/config" + import4 "github.com/nutanix/ntnx-api-golang-clients/networking-go-client/v4/models/prism/v4/config" + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func ResourceNutanixPbrsV2() *schema.Resource { + return &schema.Resource{ + CreateContext: ResourceNutanixPbrsV2Create, + ReadContext: ResourceNutanixPbrsV2Read, + UpdateContext: ResourceNutanixPbrsV2Update, + DeleteContext: ResourceNutanixPbrsV2Delete, + Schema: map[string]*schema.Schema{ + "ext_id": { + Optional: true, + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + "links": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": { + Type: schema.TypeString, + Computed: true, + }, + "rel": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "metadata": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: DatasourceMetadataSchemaV2(), + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "priority": { + Type: schema.TypeInt, + Required: true, + }, + "policies": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "policy_match": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"SUBNET", "EXTERNAL", "ANY"}, false), + }, + "subnet_prefix": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "ipv6": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "destination": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"SUBNET", "EXTERNAL", "ANY"}, false), + }, + "subnet_prefix": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "ipv6": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": SchemaForValuePrefixLength(), + "prefix_length": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "protocol_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"TCP", "UDP", "PROTOCOL_NUMBER", + "ANY", "ICMP"}, false), + }, + "protocol_parameters": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "layer_four_protocol_object": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source_port_ranges": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start_port": { + Type: schema.TypeInt, + Required: true, + }, + "end_port": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + "destination_port_ranges": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start_port": { + Type: schema.TypeInt, + Required: true, + }, + "end_port": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "icmp_object": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "icmp_type": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "icmp_code": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "protocol_number_object": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "protocol_number": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "policy_action": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"REROUTE", "DENY", "FORWARD", "PERMIT"}, false), + }, + "reroute_params": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_ip": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + "reroute_fallback_action": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{"PASSTHROUGH", "NO_ACTION", "ALLOW", "DENY"}, false), + }, + "ingress_service_ip": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + "egress_service_ip": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + }, + }, + }, + "nexthop_ip_address": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4": SchemaForValuePrefixLength(), + "ipv6": SchemaForValuePrefixLength(), + }, + }, + }, + }, + }, + }, + "is_bidirectional": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + "vpc_ext_id": { + Type: schema.TypeString, + Required: true, + }, + "vpc": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func ResourceNutanixPbrsV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).NetworkingAPI + + inputSpec := import1.RoutingPolicy{} + vpcRef := "" + pbrPriority := 0 + pbrName := "" + + if name, ok := d.GetOk("name"); ok { + inputSpec.Name = utils.StringPtr(name.(string)) + pbrName = name.(string) + } + if description, ok := d.GetOk("description"); ok { + inputSpec.Description = utils.StringPtr(description.(string)) + } + if priority, ok := d.GetOk("priority"); ok { + inputSpec.Priority = utils.IntPtr(priority.(int)) + pbrPriority = priority.(int) + } + if policies, ok := d.GetOk("policies"); ok { + inputSpec.Policies = expandRoutingPolicyRule(policies.([]interface{})) + } + if vpcExtID, ok := d.GetOk("vpc_ext_id"); ok { + inputSpec.VpcExtId = utils.StringPtr(vpcExtID.(string)) + vpcRef = vpcExtID.(string) + } + + resp, err := conn.RoutingPolicy.CreateRoutingPolicy(&inputSpec) + if err != nil { + return diag.Errorf("error while deleting routing policy : %v", err) + } + + TaskRef := resp.Data.GetValue().(import4.TaskReference) + taskUUID := TaskRef.ExtId + + // calling group API to poll for completion of task + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the Routing Policy to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"QUEUED", "RUNNING"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for routing policy (%s) to create: %s", utils.StringValue(taskUUID), errWaitTask) + } + + // Get UUID from List Routing Policy API as Currently task entities does not return uuid + filter := fmt.Sprintf("vpcExtId eq '%s'", vpcRef) + readResp, err := conn.RoutingPolicy.ListRoutingPolicies(nil, nil, &filter, nil, nil, nil) + if err != nil { + return diag.Errorf("error while fetching routing policies : %v", err) + } + + for _, v := range readResp.Data.GetValue().([]import1.RoutingPolicy) { + if utils.StringValue(v.Name) == pbrName && utils.IntValue(v.Priority) == pbrPriority { + d.SetId(*v.ExtId) + d.Set("ext_id", *v.ExtId) + break + } + } + return ResourceNutanixPbrsV2Read(ctx, d, meta) +} + +func ResourceNutanixPbrsV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).NetworkingAPI + + resp, err := conn.RoutingPolicy.GetRoutingPolicyById(utils.StringPtr(d.Id())) + if err != nil { + return diag.Errorf("error while fetching routing policy : %v", err) + } + + getResp := resp.Data.GetValue().(import1.RoutingPolicy) + + if err := d.Set("name", getResp.Name); err != nil { + return diag.FromErr(err) + } + if err := d.Set("links", flattenLinks(getResp.Links)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("description", getResp.Description); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("tenant_id", getResp.TenantId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("metadata", flattenMetadata(getResp.Metadata)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("priority", getResp.Priority); err != nil { + return diag.FromErr(err) + } + if err := d.Set("vpc_ext_id", getResp.VpcExtId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("policies", flattenPolicies(getResp.Policies)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("vpc", flattenVpcName(getResp.Vpc)); err != nil { + return diag.FromErr(err) + } + return nil +} + +func ResourceNutanixPbrsV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).NetworkingAPI + + resp, err := conn.RoutingPolicy.GetRoutingPolicyById(utils.StringPtr(d.Id())) + if err != nil { + return diag.Errorf("error while fetching routing policy : %v", err) + } + + respVpc := resp.Data.GetValue().(import1.RoutingPolicy) + + updateSpec := respVpc + + if d.HasChange("name") { + updateSpec.Name = utils.StringPtr(d.Get("name").(string)) + } + if d.HasChange("description") { + updateSpec.Description = utils.StringPtr(d.Get("description").(string)) + } + if d.HasChange("priority") { + updateSpec.Priority = utils.IntPtr(d.Get("priority").(int)) + } + if d.HasChange("policies") { + updateSpec.Policies = expandRoutingPolicyRule(d.Get("policies").([]interface{})) + } + if d.HasChange("vpc_ext_id") { + updateSpec.VpcExtId = utils.StringPtr(d.Get("vpc_ext_id").(string)) + } + + updateResp, err := conn.RoutingPolicy.UpdateRoutingPolicyById(utils.StringPtr(d.Id()), &updateSpec) + if err != nil { + return diag.Errorf("error while updating routing policy : %v", err) + } + TaskRef := updateResp.Data.GetValue().(import4.TaskReference) + taskUUID := TaskRef.ExtId + + // calling group API to poll for completion of task + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the Routing Policy to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"QUEUED", "RUNNING"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for routing policy (%s) to update: %s", utils.StringValue(taskUUID), errWaitTask) + } + return ResourceNutanixPbrsV2Read(ctx, d, meta) +} + +func ResourceNutanixPbrsV2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).NetworkingAPI + + resp, err := conn.RoutingPolicy.DeleteRoutingPolicyById(utils.StringPtr(d.Id())) + if err != nil { + return diag.Errorf("error while deleting routing policy : %v", err) + } + TaskRef := resp.Data.GetValue().(import4.TaskReference) + taskUUID := TaskRef.ExtId + + // calling group API to poll for completion of task + + taskconn := meta.(*conns.Client).PrismAPI + // Wait for the Subnet to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"QUEUED", "RUNNING"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshPrismTaskGroupFunc(ctx, taskconn, utils.StringValue(taskUUID)), + Timeout: d.Timeout(schema.TimeoutCreate), + } + + if _, errWaitTask := stateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for routing policy (%s) to delete: %s", utils.StringValue(taskUUID), errWaitTask) + } + return nil +} + +func expandRoutingPolicyRule(pr []interface{}) []import1.RoutingPolicyRule { + if len(pr) > 0 { + rules := make([]import1.RoutingPolicyRule, len(pr)) + for k, v := range pr { + val := v.(map[string]interface{}) + rule := import1.RoutingPolicyRule{} + if policyMatch, ok := val["policy_match"]; ok && len(policyMatch.([]interface{})) > 0 { + rule.PolicyMatch = expandRoutingPolicyMatchCondition(policyMatch) + } + if policyAction, ok := val["policy_action"]; ok && len(policyAction.([]interface{})) > 0 { + rule.PolicyAction = expandRoutingPolicyAction(policyAction) + } + if isBidirectional, ok := val["is_bidirectional"]; ok { + rule.IsBidirectional = utils.BoolPtr(isBidirectional.(bool)) + } + rules[k] = rule + } + return rules + } + return nil +} + +func expandRoutingPolicyMatchCondition(pr interface{}) *import1.RoutingPolicyMatchCondition { + if pr != nil { + match := import1.RoutingPolicyMatchCondition{} + prI := pr.([]interface{}) + val := prI[0].(map[string]interface{}) + + if source, ok := val["source"]; ok && len(source.([]interface{})) > 0 { + match.Source = expandAddressTypeObject(source) + } + if destination, ok := val["destination"]; ok && len(destination.([]interface{})) > 0 { + match.Destination = expandAddressTypeObject(destination) + } + if protocolType, ok := val["protocol_type"]; ok { + const two, three, four, five, six = 2, 3, 4, 5, 6 + protoMap := map[string]interface{}{ + "ANY": two, + "ICMP": three, + "TCP": four, + "UDP": five, + "PROTOCOL_NUMBER": six, + } + + pInt := protoMap[protocolType.(string)] + p := import1.ProtocolType(pInt.(int)) + match.ProtocolType = &p + } + if protocolParameters, ok := val["protocol_parameters"]; ok && len(protocolParameters.([]interface{})) > 0 { + match.ProtocolParameters = expandOneOfRoutingPolicyMatchConditionProtocolParameters(protocolParameters) + } + return &match + } + return nil +} + +func expandAddressTypeObject(pr interface{}) *import1.AddressTypeObject { + if pr != nil { + address := import1.AddressTypeObject{} + prI := pr.([]interface{}) + val := prI[0].(map[string]interface{}) + + if addressType, ok := val["address_type"]; ok { + const two, three, four = 2, 3, 4 + addMap := map[string]interface{}{ + "ANY": two, + "EXTERNAL": three, + "SUBNET": four, + } + pInt := addMap[addressType.(string)] + p := import1.AddressType(pInt.(int)) + address.AddressType = &p + } + if subnetPrefix, ok := val["subnet_prefix"]; ok && len(subnetPrefix.([]interface{})) > 0 { + address.SubnetPrefix = expandIPSubnetObject(subnetPrefix) + } + return &address + } + return nil +} + +func expandIPSubnetObject(pr interface{}) *import1.IPSubnet { + if pr != nil { + subnet := import1.IPSubnet{} + prI := pr.([]interface{}) + val := prI[0].(map[string]interface{}) + + if ipv4, ok := val["ipv4"]; ok && len(ipv4.([]interface{})) > 0 { + subnet.Ipv4 = expandIPv4Subnet(ipv4) + } + if ipv6, ok := val["ipv6"]; ok && len(ipv6.([]interface{})) > 0 { + subnet.Ipv6 = expandIPv6Subnet(ipv6) + } + return &subnet + } + return nil +} + +func expandOneOfRoutingPolicyMatchConditionProtocolParameters(pr interface{}) *import1.OneOfRoutingPolicyMatchConditionProtocolParameters { + if pr != nil { + protoParams := import1.OneOfRoutingPolicyMatchConditionProtocolParameters{} + prI := pr.([]interface{}) + val := prI[0].(map[string]interface{}) + + if layerFourProtocolObject, ok := val["layer_four_protocol_object"]; ok && len(layerFourProtocolObject.([]interface{})) > 0 { + layerFourObj := import1.NewLayerFourProtocolObject() + layerI := layerFourProtocolObject.([]interface{}) + layerVal := layerI[0].(map[string]interface{}) + + if sourcePortRanges, ok := layerVal["source_port_ranges"]; ok && len(sourcePortRanges.([]interface{})) > 0 { + layerFourObj.SourcePortRanges = expandPortRange(sourcePortRanges.([]interface{})) + } + if destinationPortRanges, ok := layerVal["destination_port_ranges"]; ok && len(destinationPortRanges.([]interface{})) > 0 { + layerFourObj.DestinationPortRanges = expandPortRange(destinationPortRanges.([]interface{})) + } + + protoParams.SetValue(*layerFourObj) + } + + if icmpObject, ok := val["icmp_object"]; ok && len(icmpObject.([]interface{})) > 0 { + icmpObj := import1.NewICMPObject() + icmpI := icmpObject.([]interface{}) + icmpVal := icmpI[0].(map[string]interface{}) + if icmpType, ok := icmpVal["icmp_type"]; ok { + icmpObj.IcmpType = utils.IntPtr(icmpType.(int)) + } + if icmpCode, ok := icmpVal["icmp_code"]; ok { + icmpObj.IcmpCode = utils.IntPtr(icmpCode.(int)) + } + protoParams.SetValue(*icmpObj) + } + + if protoNum, ok := val["protocol_number_object"]; ok && len(protoNum.([]interface{})) > 0 { + protoNumObj := import1.NewProtocolNumberObject() + protoI := protoNum.([]interface{}) + protoVal := protoI[0].(map[string]interface{}) + if protocolNumber, ok := protoVal["protocol_number"]; ok { + protoNumObj.ProtocolNumber = utils.IntPtr(protocolNumber.(int)) + } + protoParams.SetValue(*protoNumObj) + } + return &protoParams + } + return nil +} + +func expandPortRange(pr []interface{}) []import1.PortRange { + if len(pr) > 0 { + portRanges := make([]import1.PortRange, len(pr)) + for k, v := range pr { + val := v.(map[string]interface{}) + portRange := import1.PortRange{} + if startPort, ok := val["start_port"]; ok { + portRange.StartPort = utils.IntPtr(startPort.(int)) + } + if endPort, ok := val["end_port"]; ok { + portRange.EndPort = utils.IntPtr(endPort.(int)) + } + portRanges[k] = portRange + } + return portRanges + } + return nil +} + +func expandRoutingPolicyAction(pr interface{}) *import1.RoutingPolicyAction { + if pr != nil { + action := import1.RoutingPolicyAction{} + prI := pr.([]interface{}) + val := prI[0].(map[string]interface{}) + + if actionType, ok := val["action_type"]; ok { + const two, three, four, five = 2, 3, 4, 5 + actMap := map[string]interface{}{ + "PERMIT": two, + "DENY": three, + "REROUTE": four, + "FORWARD": five, + } + pInt := actMap[actionType.(string)] + p := import1.RoutingPolicyActionType(pInt.(int)) + action.ActionType = &p + } + if rerouteParams, ok := val["reroute_params"]; ok && len(rerouteParams.([]interface{})) > 0 { + action.RerouteParams = expandRerouteParams(rerouteParams.([]interface{})) + } + if nexthopIPAddress, ok := val["nexthop_ip_address"]; ok && len(nexthopIPAddress.([]interface{})) > 0 { + action.NexthopIpAddress = expandIPAddressObject(nexthopIPAddress) + } + return &action + } + return nil +} + +func expandRerouteParams(pr []interface{}) []import1.RerouteParam { + if len(pr) > 0 { + reroutes := make([]import1.RerouteParam, len(pr)) + for k, v := range pr { + val := v.(map[string]interface{}) + reroute := import1.RerouteParam{} + if serviceIP, ok := val["service_ip"]; ok && len(serviceIP.([]interface{})) > 0 { + reroute.ServiceIp = expandIPAddressObject(serviceIP) + } + if rerouteFallbackAction, ok := val["reroute_fallback_action"]; ok { + const two, three, four, five = 2, 3, 4, 5 + actMap := map[string]interface{}{ + "ALLOW": two, + "DROP": three, + "PASSTHROUGH": four, + "NO_ACTION": five, + } + + pInt := actMap[rerouteFallbackAction.(string)] + p := import1.RerouteFallbackAction(pInt.(int)) + reroute.RerouteFallbackAction = &p + } + if ingressServiceIP, ok := val["ingress_service_ip"]; ok && len(ingressServiceIP.([]interface{})) > 0 { + reroute.IngressServiceIp = expandIPAddressObject(ingressServiceIP) + } + if egressServiceIP, ok := val["egress_service_ip"]; ok && len(egressServiceIP.([]interface{})) > 0 { + reroute.EgressServiceIp = expandIPAddressObject(egressServiceIP) + } + reroutes[k] = reroute + } + return reroutes + } + return nil +} + +func expandIPAddressObject(pr interface{}) *config.IPAddress { + if pr != nil { + ip := config.IPAddress{} + prI := pr.([]interface{}) + val := prI[0].(map[string]interface{}) + + if ipv4, ok := val["ipv4"]; ok && len(ipv4.([]interface{})) > 0 { + ip.Ipv4 = expandIPv4Address(ipv4) + } + if ipv6, ok := val["ipv6"]; ok && len(ipv6.([]interface{})) > 0 { + ip.Ipv6 = expandIPv6Address(ipv6) + } + return &ip + } + return nil +} diff --git a/nutanix/services/v2/networkingv2/resource_nutanix_pbrs_v2_test.go b/nutanix/services/v2/networkingv2/resource_nutanix_pbrs_v2_test.go new file mode 100644 index 000000000..cd543f162 --- /dev/null +++ b/nutanix/services/v2/networkingv2/resource_nutanix_pbrs_v2_test.go @@ -0,0 +1,301 @@ +package networkingv2_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const resourceNamePbr = "nutanix_pbr_v2.test" + +func TestAccNutanixPbrV2_Basic(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-pbr-%d", r) + desc := "test pbr description" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testPbrConfig(name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceNamePbr, "name", name), + resource.TestCheckResourceAttr(resourceNamePbr, "description", desc), + resource.TestCheckResourceAttrSet(resourceNamePbr, "metadata.#"), + resource.TestCheckResourceAttr(resourceNamePbr, "policies.0.is_bidirectional", "false"), + resource.TestCheckResourceAttr(resourceNamePbr, "policies.0.policy_match.0.protocol_type", "UDP"), + resource.TestCheckResourceAttr(resourceNamePbr, "priority", "14"), + ), + }, + }, + }) +} + +func TestAccNutanixPbrV2_WithSourceDest(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-pbr-%d", r) + desc := "test pbr description" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testPbrConfigWithSrcDstn(name, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceNamePbr, "name", name), + resource.TestCheckResourceAttr(resourceNamePbr, "description", desc), + resource.TestCheckResourceAttrSet(resourceNamePbr, "metadata.#"), + resource.TestCheckResourceAttr(resourceNamePbr, "policies.0.is_bidirectional", "false"), + resource.TestCheckResourceAttr(resourceNamePbr, "policies.0.policy_match.0.protocol_type", "ANY"), + resource.TestCheckResourceAttr(resourceNamePbr, "priority", "11"), + ), + }, + }, + }) +} + +func TestAccNutanixPbrV2_ErrorWithPrioirty(t *testing.T) { + r := acctest.RandInt() + name := fmt.Sprintf("test-pbr-%d", r) + desc := "test pbr description" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testPbrConfigWithDefaultPriority(name, desc), + ExpectError: regexp.MustCompile("Modification of default routing policy with priority less than 10 is not allowed"), + }, + }, + }) +} + +func testPbrConfig(name, desc string) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster0 = data.nutanix_clusters.clusters.entities[0].metadata.uuid + } + + resource "nutanix_subnet_v2" "test" { + name = "terraform-test-subnet-vpc_%[1]s" + description = "test subnet description" + cluster_reference = local.cluster0 + subnet_type = "VLAN" + network_id = 112 + is_external = true + ip_config { + ipv4 { + ip_subnet { + ip { + value = "192.168.0.0" + } + prefix_length = 24 + } + default_gateway_ip { + value = "192.168.0.1" + } + pool_list{ + start_ip { + value = "192.168.0.20" + } + end_ip { + value = "192.168.0.30" + } + } + } + } + depends_on = [data.nutanix_clusters.clusters] + } + + resource "nutanix_vpc_v2" "test" { + name = "pbr_vpc_%[1]s" + description = "pbr_vpc_ %[2]s" + external_subnets{ + subnet_reference = nutanix_subnet_v2.test.id + } + depends_on = [nutanix_subnet_v2.test] + } + + resource "nutanix_pbr_v2" "test" { + name = "%[1]s" + description = "%[2]s" + vpc_ext_id = nutanix_vpc_v2.test.ext_id + priority = 14 + policies{ + policy_match{ + source{ + address_type = "ANY" + } + destination{ + address_type = "ANY" + } + protocol_type = "UDP" + } + policy_action{ + action_type = "PERMIT" + } + } + } +`, name, desc) +} + +func testPbrConfigWithSrcDstn(name, desc string) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster0 = data.nutanix_clusters.clusters.entities[0].metadata.uuid + } + + resource "nutanix_subnet_v2" "test" { + name = "terraform-test-subnet-vpc_%[1]s" + description = "test subnet description" + cluster_reference = local.cluster0 + subnet_type = "VLAN" + network_id = 112 + is_external = true + ip_config { + ipv4 { + ip_subnet { + ip { + value = "192.168.0.0" + } + prefix_length = 24 + } + default_gateway_ip { + value = "192.168.0.1" + } + pool_list{ + start_ip { + value = "192.168.0.20" + } + end_ip { + value = "192.168.0.30" + } + } + } + } + depends_on = [data.nutanix_clusters.clusters] + } + + resource "nutanix_vpc_v2" "test" { + name = "pbr_vpc_%[1]s" + description = "pbr_vpc_ %[2]s" + external_subnets{ + subnet_reference = nutanix_subnet_v2.test.id + } + depends_on = [nutanix_subnet_v2.test] + } + + resource "nutanix_pbr_v2" "test" { + name = "%[1]s" + description = "%[2]s" + vpc_ext_id = nutanix_vpc_v2.test.ext_id + priority = 11 + policies{ + policy_match{ + source{ + address_type = "EXTERNAL" + } + destination{ + address_type = "SUBNET" + subnet_prefix{ + ipv4{ + ip{ + value= "10.10.10.0" + prefix_length = 24 + } + } + } + } + protocol_type = "ANY" + } + policy_action{ + action_type = "FORWARD" + nexthop_ip_address{ + ipv4{ + value = "10.10.10.10" + } + } + } + } + } +`, name, desc) +} + +func testPbrConfigWithDefaultPriority(name, desc string) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster0 = data.nutanix_clusters.clusters.entities[0].metadata.uuid + } + + resource "nutanix_subnet_v2" "test" { + name = "terraform-test-subnet-vpc_%[1]s" + description = "test subnet description" + cluster_reference = local.cluster0 + subnet_type = "VLAN" + network_id = 112 + is_external = true + ip_config { + ipv4 { + ip_subnet { + ip { + value = "192.168.0.0" + } + prefix_length = 24 + } + default_gateway_ip { + value = "192.168.0.1" + } + pool_list{ + start_ip { + value = "192.168.0.20" + } + end_ip { + value = "192.168.0.30" + } + } + } + } + depends_on = [data.nutanix_clusters.clusters] + } + + resource "nutanix_vpc_v2" "test" { + name = "pbr_vpc_%[1]s" + description = "pbr_vpc_ %[2]s" + external_subnets{ + subnet_reference = nutanix_subnet_v2.test.id + } + depends_on = [nutanix_subnet_v2.test] + } + + resource "nutanix_pbr_v2" "test" { + name = "%[1]s" + description = "%[2]s" + vpc_ext_id = nutanix_vpc_v2.test.ext_id + priority = 1 + policies{ + policy_match{ + source{ + address_type = "ANY" + } + destination{ + address_type = "ANY" + } + protocol_type = "UDP" + } + policy_action{ + action_type = "PERMIT" + } + } + } +`, name, desc) +} diff --git a/nutanix/services/v2/networkingv2/resource_nutanix_subnets_v2.go b/nutanix/services/v2/networkingv2/resource_nutanix_subnets_v2.go index 318420232..1cda9d9e8 100644 --- a/nutanix/services/v2/networkingv2/resource_nutanix_subnets_v2.go +++ b/nutanix/services/v2/networkingv2/resource_nutanix_subnets_v2.go @@ -930,11 +930,12 @@ func expandVirtualSwitch(pr interface{}) *import1.VirtualSwitch { vSwitch.Mtu = utils.Int64Ptr(mtu.(int64)) } if bondMode, ok := val["bond_mode"]; ok { + const two, three, four, five = 2, 3, 4, 5 bondMap := map[string]interface{}{ - "ACTIVE_BACKUP": "2", - "BALANCE_SLB": "3", - "BALANCE_TCP": "4", - "NONE": "5", + "ACTIVE_BACKUP": two, + "BALANCE_SLB": three, + "BALANCE_TCP": four, + "NONE": five, } pInt := bondMap[bondMode.(string)] p := import1.BondModeType(pInt.(int)) diff --git a/nutanix/services/v2/networkingv2/resource_nutanix_vpcs_v2.go b/nutanix/services/v2/networkingv2/resource_nutanix_vpcs_v2.go index c7520660d..f3fe6c039 100644 --- a/nutanix/services/v2/networkingv2/resource_nutanix_vpcs_v2.go +++ b/nutanix/services/v2/networkingv2/resource_nutanix_vpcs_v2.go @@ -253,7 +253,7 @@ func ResourceNutanixVPCsV2Create(ctx context.Context, d *schema.ResourceData, me // calling group API to poll for completion of task taskconn := meta.(*conns.Client).PrismAPI - // Wait for the FileServer to be available + // Wait for the VPC to be available stateConf := &resource.StateChangeConf{ Pending: []string{"QUEUED", "RUNNING"}, Target: []string{"SUCCEEDED"}, diff --git a/website/docs/d/pbr_v2.html.markdown b/website/docs/d/pbr_v2.html.markdown new file mode 100644 index 000000000..f08af6297 --- /dev/null +++ b/website/docs/d/pbr_v2.html.markdown @@ -0,0 +1,99 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_pbr_v2" +sidebar_current: "docs-nutanix-datasource-pbr-v4" +description: |- + Provides a datasource to get a single Routing Policy corresponding to the extId. +--- + +# nutanix_pbr_v2 + +Get a single Routing Policy corresponding to the extId. + +## Example Usage + +```hcl + data "nutanix_pbr_v2" "test"{ + ext_id = + } +``` + +## Argument Reference + +The following arguments are supported: + +* `pbr_uuid`: (Required) pbr UUID + + +## Attribute Reference + +The following attributes are exported: + +* `tenant_id`: A globally unique identifier that represents the tenant that owns this entity +* `links`: A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `metadata`: Metadata associated with this resource. +* `name`: Name of the routing policy. +* `description`: A description of the routing policy. +* `priority`: Priority of the routing policy. +* `policies`: Routing Policies +* `vpc_ext_id`: ExtId of the VPC extId to which the routing policy belongs. +* `vpc`: VPC name for projections + + +### policies + +* `policy_match`: Match condition for the traffic that is entering the VPC. +* `policy_action`: The action to be taken on the traffic matching the routing policy. +* `is_bidirectional`: If True, policies in the reverse direction will be installed with the same action but source and destination will be swapped. + + +### policy_match +* `source`: Address Type like "EXTERNAL" or "ANY". +* `destination`: Address Type like "EXTERNAL" or "ANY". +* `protocol_type`: Routing Policy IP protocol type. +* `protocol_parameters`: Protocol Params Object. + +### policy_match.source, policy_match.destination +* `address_type`: Address Type like "EXTERNAL" or "ANY". +* `subnet_prefix`: Subnet Prefix + +### subnet_prefix +* `ip`: IP of address +* `prefix_length`: The prefix length of the network to which this host IPv4/IPv6 address belongs. + + +### protocol_parameters +* `layer_four_protocol_object`: Layer Four Protocol Object. +* `icmp_object`: ICMP object +* `protocol_number_object`: Protocol Number Object. + +### layer_four_protocol_object +* `source_port_ranges`: Start and end port ranges object. +* `destination_port_ranges`: Start and end port ranges object. + +### icmp_object +* `icmp_type`: icmp type +* `icmp_code`: icmp code + +### protocol_number_object +* `protocol_number`: protocol number + + +### policy_action +* `action_type`: Routing policy action type. +* `reroute_params`: Routing policy Reroute params. +* `nexthop_ip_address`: An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. + +### reroute_params +* `service_ip`: An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. +* `reroute_fallback_action`: Type of fallback action in reroute case when service VM is down. +* `ingress_service_ip`: An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. +* `egress_service_ip`: An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. + + +### ipv4,ipv6 Configuration format +* `value`: ip value +* `prefix_length`: The prefix length of the network to which this host IPv4/IPv6 address belongs. + + +See detailed information in [Nutanix Routing Policy v4](https://developers.nutanix.com/api-reference?namespace=networking&version=v4.0.b1). \ No newline at end of file diff --git a/website/docs/d/pbrs_v2.html.markdown b/website/docs/d/pbrs_v2.html.markdown new file mode 100644 index 000000000..895fe5818 --- /dev/null +++ b/website/docs/d/pbrs_v2.html.markdown @@ -0,0 +1,105 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_pbrs_v2" +sidebar_current: "docs-nutanix-datasource-pbrs-v4" +description: |- + Provides a datasource to get the list of Routing Policies. +--- + +# nutanix_pbrs_v2 + +Get a list of Routing Policies. + +## Example Usage + +```hcl + data "nutanix_pbrs_v2" "test"{ + } +``` + +## Argument Reference + +The following arguments are supported: + +* `page`: (Optional) A URL query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. Any number out of this range might lead to no results. +* `limit`: (Optional) A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set. +* `filter`: (Optional) A URL query parameter that allows clients to filter a collection of resources. +* `order_by`: (Optional) A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default +* `expand`: (Optional) A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. +* `select`: (Optional) A URL query parameter that allows clients to request a specific set of properties for each entity or complex type. + +* `routing_policies`: List all of routing policies. + +## routing_policies + +The following attributes are exported: + +* `ext_id`: A globally unique identifier of an instance that is suitable for external consumption. +* `tenant_id`: A globally unique identifier that represents the tenant that owns this entity +* `links`: A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `metadata`: Metadata associated with this resource. +* `name`: Name of the routing policy. +* `description`: A description of the routing policy. +* `priority`: Priority of the routing policy. +* `policies`: Routing Policies +* `vpc_ext_id`: ExtId of the VPC extId to which the routing policy belongs. +* `vpc`: VPC name for projections + + +### policies + +* `policy_match`: Match condition for the traffic that is entering the VPC. +* `policy_action`: The action to be taken on the traffic matching the routing policy. +* `is_bidirectional`: If True, policies in the reverse direction will be installed with the same action but source and destination will be swapped. + + +### policy_match +* `source`: Address Type like "EXTERNAL" or "ANY". +* `destination`: Address Type like "EXTERNAL" or "ANY". +* `protocol_type`: Routing Policy IP protocol type. +* `protocol_parameters`: Protocol Params Object. + +### policy_match.source, policy_match.destination +* `address_type`: Address Type like "EXTERNAL" or "ANY". +* `subnet_prefix`: Subnet Prefix + +### subnet_prefix +* `ip`: IP of address +* `prefix_length`: The prefix length of the network to which this host IPv4/IPv6 address belongs. + + +### protocol_parameters +* `layer_four_protocol_object`: Layer Four Protocol Object. +* `icmp_object`: ICMP object +* `protocol_number_object`: Protocol Number Object. + +### layer_four_protocol_object +* `source_port_ranges`: Start and end port ranges object. +* `destination_port_ranges`: Start and end port ranges object. + +### icmp_object +* `icmp_type`: icmp type +* `icmp_code`: icmp code + +### protocol_number_object +* `protocol_number`: protocol number + + +### policy_action +* `action_type`: Routing policy action type. +* `reroute_params`: Routing policy Reroute params. +* `nexthop_ip_address`: An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. + +### reroute_params +* `service_ip`: An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. +* `reroute_fallback_action`: Type of fallback action in reroute case when service VM is down. +* `ingress_service_ip`: An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. +* `egress_service_ip`: An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. + + +### ipv4,ipv6 Configuration format +* `value`: ip value +* `prefix_length`: The prefix length of the network to which this host IPv4/IPv6 address belongs. + + +See detailed information in [Nutanix Routing Policies v4](https://developers.nutanix.com/api-reference?namespace=networking&version=v4.0.b1). \ No newline at end of file diff --git a/website/docs/r/pbrs_v2.html.markdown b/website/docs/r/pbrs_v2.html.markdown new file mode 100644 index 000000000..0b29c366a --- /dev/null +++ b/website/docs/r/pbrs_v2.html.markdown @@ -0,0 +1,162 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_pbr_v2" +sidebar_current: "docs-nutanix-resource-pbr-v4" +description: |- + Create Routing Policy within VPCs . +--- + +# nutanix_pbr_v2 + +Create a Routing Policy. + + +## Example + +```hcl + resource "nutanix_pbr_v2" "test" { + name = "{{ name }}" + description = "{{ desc }}" + vpc_ext_id = "{{ vpc uuid }}" + priority = 14 + policies{ + policy_match{ + source{ + address_type = "ANY" + } + destination{ + address_type = "ANY" + } + protocol_type = "UDP" + } + policy_action{ + action_type = "PERMIT" + } + } + } + + + resource "nutanix_pbr_v2" "test" { + name = "{{ name }}" + description = "{{ desc }}" + vpc_ext_id = "{{ vpc uuid }}" + priority = 11 + policies{ + policy_match{ + source{ + address_type = "EXTERNAL" + } + destination{ + address_type = "SUBNET" + subnet_prefix{ + ipv4{ + ip{ + value= "10.10.10.0" + prefix_length = 24 + } + } + } + } + protocol_type = "ANY" + } + policy_action{ + action_type = "FORWARD" + nexthop_ip_address{ + ipv4{ + value = "10.10.10.10" + } + } + } + } + } +``` + +## Argument Reference + +The following arguments are supported: + +* `name`: (Required) Name of the routing policy. +* `description`: A description of the routing policy. +* `priority`: (Required) Priority of the routing policy. +* `policies`: (Required) Routing Policies. +* `vpc_ext_id`: (Required) ExtId of the VPC extId to which the routing policy belongs. + + +### policies + +* `policy_match`: (Required) Match condition for the traffic that is entering the VPC. +* `policy_action`: (Required) The action to be taken on the traffic matching the routing policy. +* `is_bidirectional`: (Optional) If True, policies in the reverse direction will be installed with the same action but source and destination will be swapped. + + +### policy_match +* `source`: (Required) Address Type like "EXTERNAL" or "ANY". +* `destination`: (Required) Address Type like "EXTERNAL" or "ANY". +* `protocol_type`: (Required) Routing Policy IP protocol type. Acceptables values are "TCP", "UDP", "PROTOCOL_NUMBER", "ANY", "ICMP" . +* `protocol_parameters`: (Optional) Protocol Params Object. + +### policy_match.source, policy_match.destination +* `address_type`: (Required) Address Type. Acceptables values are "SUBNET", "EXTERNAL", "ANY" . +* `subnet_prefix`: (Optional) Subnet Prefix + +### subnet_prefix +* `ipv4`: (Optional) IPv4 Object. +* `ipv6`: (Optional) IPv6 Object. + +### subnet_prefix.ipv4. subnet_prefix.ipv6 +* `ip`: (Required) IP of address +* `prefix_length`: (Optional) The prefix length of the network to which this host IPv4/IPv6 address belongs. + + +### protocol_parameters +* `layer_four_protocol_object`: (Optional) Layer Four Protocol Object. +* `icmp_object`: (Optional) ICMP object +* `protocol_number_object`: (Optional) Protocol Number Object. + +### layer_four_protocol_object +* `source_port_ranges`: (Optional) Start and end port ranges object. +* `destination_port_ranges`: (Optional) Start and end port ranges object. + +### source_port_ranges, destination_port_ranges +* `start_port`: (Required) Start Port. +* `end_port`: (Required) End Port. + + +### icmp_object +* `icmp_type`: (Optional) icmp type +* `icmp_code`: (Optional) icmp code + +### protocol_number_object +* `protocol_number`: (Required) protocol number + + +### policy_action +* `action_type`: (Required) Routing policy action type. +* `reroute_params`: (Optional) Routing policy Reroute params. +* `nexthop_ip_address`: (Optional) An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. + +### reroute_params +* `service_ip`: (Optional) An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. +* `reroute_fallback_action`: (Optional) Type of fallback action in reroute case when service VM is down. Acceptables values are "PASSTHROUGH", "NO_ACTION", "ALLOW", "DENY". +* `ingress_service_ip`: (Optional) An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. +* `egress_service_ip`: (Optional) An unique address that identifies a device on the internet or a local network in IPv4 or IPv6 format. + + +### ipv4,ipv6 Configuration format +* `value`: (Required) ip value +* `prefix_length`: (Optional) The prefix length of the network to which this host IPv4/IPv6 address belongs. + + + +## Attributes Reference + +The following attributes are exported: + +* `ext_id`: A globally unique identifier of an instance that is suitable for external consumption. +* `tenant_id`: A globally unique identifier that represents the tenant that owns this entity +* `links`: A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. +* `metadata`: Metadata associated with this resource. +* `vpc`: VPC name for projections + + +See detailed information in [Nutanix Routing Policy v4](https://developers.nutanix.com/api-reference?namespace=networking&version=v4.0.b1). \ No newline at end of file