github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/nsxt_nat_rule.go (about)

     1  /*
     2   * Copyright 2021 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     3   */
     4  
     5  package govcd
     6  
     7  import (
     8  	"fmt"
     9  	"net/url"
    10  
    11  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    12  	"github.com/vmware/go-vcloud-director/v2/util"
    13  )
    14  
    15  // NsxtNatRule describes a single NAT rule of 5 different Rule Types - DNAT`, `NO_DNAT`, `SNAT`, `NO_SNAT`, 'REFLEXIVE'
    16  // 'REFLEXIVE' is only supported in API 35.2 (VCD 10.2.2+)
    17  //
    18  // A SNAT or a DNAT rule on an Edge Gateway in the VMware Cloud Director environment is always configured from the
    19  // perspective of your organization VDC.
    20  // DNAT and NO_DNAT - outside traffic going inside
    21  // SNAT and NO_SNAT - inside traffic going outside
    22  // More docs in https://docs.vmware.com/en/VMware-Cloud-Director/10.2/VMware-Cloud-Director-Tenant-Portal-Guide/GUID-9E43E3DC-C028-47B3-B7CA-59F0ED40E0A6.html
    23  //
    24  // Note. This structure and all its API calls will require at least API version 34.0, but will elevate it to 35.2 if
    25  // possible because API 35.2 introduces support for 2 new fields FirewallMatch and Priority.
    26  type NsxtNatRule struct {
    27  	NsxtNatRule *types.NsxtNatRule
    28  	client      *Client
    29  	// edgeGatewayId is stored here so that pointer receiver functions can embed edge gateway ID into path
    30  	edgeGatewayId string
    31  }
    32  
    33  // GetAllNatRules retrieves all NAT rules with an optional queryParameters filter.
    34  func (egw *NsxtEdgeGateway) GetAllNatRules(queryParameters url.Values) ([]*NsxtNatRule, error) {
    35  	client := egw.client
    36  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtNatRules
    37  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	typeResponses := []*types.NsxtNatRule{{}}
    48  	err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParameters, &typeResponses, nil)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	// Wrap all typeResponses into NsxtNatRule types with client
    54  	wrappedResponses := make([]*NsxtNatRule, len(typeResponses))
    55  	for sliceIndex := range typeResponses {
    56  		wrappedResponses[sliceIndex] = &NsxtNatRule{
    57  			NsxtNatRule:   typeResponses[sliceIndex],
    58  			client:        client,
    59  			edgeGatewayId: egw.EdgeGateway.ID,
    60  		}
    61  	}
    62  
    63  	return wrappedResponses, nil
    64  }
    65  
    66  // GetNatRuleByName finds a NAT rule by Name and returns it
    67  //
    68  // Note. API does not enforce name uniqueness therefore an error will be thrown if two rules with the same name exist
    69  func (egw *NsxtEdgeGateway) GetNatRuleByName(name string) (*NsxtNatRule, error) {
    70  	// Ideally this function would use OpenAPI filters to perform server side filtering, but this endpoint does not
    71  	// support any filters - even ID. Therefore one must retrieve all items and look if there is an item with the same ID
    72  	allNatRules, err := egw.GetAllNatRules(nil)
    73  	if err != nil {
    74  		return nil, fmt.Errorf("error retriving all NSX-T NAT rules: %s", err)
    75  	}
    76  
    77  	var allResults []*NsxtNatRule
    78  
    79  	for _, natRule := range allNatRules {
    80  		if natRule.NsxtNatRule.Name == name {
    81  			allResults = append(allResults, natRule)
    82  		}
    83  	}
    84  
    85  	if len(allResults) > 1 {
    86  		return nil, fmt.Errorf("error - found %d NSX-T NAT rules with name '%s'. Expected 1", len(allResults), name)
    87  	}
    88  
    89  	if len(allResults) == 0 {
    90  		return nil, ErrorEntityNotFound
    91  	}
    92  
    93  	return allResults[0], nil
    94  }
    95  
    96  // GetNatRuleById finds a NAT rule by ID and returns it
    97  func (egw *NsxtEdgeGateway) GetNatRuleById(id string) (*NsxtNatRule, error) {
    98  	// Ideally this function would use OpenAPI filters to perform server side filtering, but this endpoint does not
    99  	// support any filters - even ID. Therefore one must retrieve all items and look if there is an item with the same ID
   100  	allNatRules, err := egw.GetAllNatRules(nil)
   101  	if err != nil {
   102  		return nil, fmt.Errorf("error retriving all NSX-T NAT rules: %s", err)
   103  	}
   104  
   105  	for _, natRule := range allNatRules {
   106  		if natRule.NsxtNatRule.ID == id {
   107  			return natRule, nil
   108  		}
   109  	}
   110  
   111  	return nil, ErrorEntityNotFound
   112  }
   113  
   114  // CreateNatRule creates a NAT rule and returns it.
   115  //
   116  // Note. API has a limitation, that it does not return ID for created rule. To work around it this function creates
   117  // a NAT rule, fetches all rules and finds a rule with exactly the same field values and returns it (including ID)
   118  // There is still a slight risk to retrieve wrong ID if exactly the same rule already exists.
   119  func (egw *NsxtEdgeGateway) CreateNatRule(natRuleConfig *types.NsxtNatRule) (*NsxtNatRule, error) {
   120  	client := egw.client
   121  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtNatRules
   122  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	// Insert Edge Gateway ID into endpoint path edgeGateways/%s/nat/rules
   128  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	// Creating NAT rule must follow different way than usual OpenAPI one because this item has an API bug and
   134  	// NAT rule ID is not returned after this object is created. The only way to find its ID afterwards is to GET all
   135  	// items, and manually match it based on rule name, etc.
   136  	task, err := client.OpenApiPostItemAsync(apiVersion, urlRef, nil, natRuleConfig)
   137  	if err != nil {
   138  		return nil, fmt.Errorf("error creating NSX-T NAT rule: %s", err)
   139  	}
   140  
   141  	err = task.WaitTaskCompletion()
   142  	if err != nil {
   143  		return nil, fmt.Errorf("task failed while creating NSX-T NAT rule: %s", err)
   144  	}
   145  
   146  	// queryParameters (API side filtering) are not used because pretty much nothing is accepted as filter (such fields as
   147  	// name, description, ruleType and even ID are not allowed
   148  	allNatRules, err := egw.GetAllNatRules(nil)
   149  	if err != nil {
   150  		return nil, fmt.Errorf("error fetching all NAT rules: %s", err)
   151  	}
   152  
   153  	for index, singleRule := range allNatRules {
   154  		// Look for a matching rule
   155  		if singleRule.IsEqualTo(natRuleConfig) {
   156  			return allNatRules[index], nil
   157  
   158  		}
   159  	}
   160  	return nil, fmt.Errorf("rule '%s' of type '%s' not found after creation", natRuleConfig.Name, natRuleConfig.RuleType)
   161  }
   162  
   163  // Update allows users to update NSX-T NAT rule
   164  func (nsxtNat *NsxtNatRule) Update(natRuleConfig *types.NsxtNatRule) (*NsxtNatRule, error) {
   165  	client := nsxtNat.client
   166  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtNatRules
   167  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	if nsxtNat.NsxtNatRule.ID == "" {
   173  		return nil, fmt.Errorf("cannot update NSX-T NAT Rule without ID")
   174  	}
   175  
   176  	urlRef, err := nsxtNat.client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, nsxtNat.edgeGatewayId), nsxtNat.NsxtNatRule.ID)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	returnObject := &NsxtNatRule{
   182  		NsxtNatRule:   &types.NsxtNatRule{},
   183  		client:        client,
   184  		edgeGatewayId: nsxtNat.edgeGatewayId,
   185  	}
   186  
   187  	err = client.OpenApiPutItem(apiVersion, urlRef, nil, natRuleConfig, returnObject.NsxtNatRule, nil)
   188  	if err != nil {
   189  		return nil, fmt.Errorf("error updating NSX-T NAT Rule: %s", err)
   190  	}
   191  
   192  	return returnObject, nil
   193  }
   194  
   195  // Delete deletes NSX-T NAT rule
   196  func (nsxtNat *NsxtNatRule) Delete() error {
   197  	client := nsxtNat.client
   198  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtNatRules
   199  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	if nsxtNat.NsxtNatRule.ID == "" {
   205  		return fmt.Errorf("cannot delete NSX-T NAT rule without ID")
   206  	}
   207  
   208  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, nsxtNat.edgeGatewayId), nsxtNat.NsxtNatRule.ID)
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	err = client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil)
   214  
   215  	if err != nil {
   216  		return fmt.Errorf("error deleting NSX-T NAT Rule: %s", err)
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  // IsEqualTo allows to check if a rule has exactly the same fields (except ID) to the supplied rule
   223  // This validation is very tricky because minor version changes impact how fields are return.
   224  // This function relies on most common and stable fields:
   225  // * Name
   226  // * Enabled
   227  // * Description
   228  // * ExternalAddresses
   229  // * InternalAddresses
   230  // * ApplicationPortProfile.ID
   231  func (nsxtNat *NsxtNatRule) IsEqualTo(rule *types.NsxtNatRule) bool {
   232  	return natRulesEqual(nsxtNat.NsxtNatRule, rule)
   233  }
   234  
   235  // natRulesEqual is a helper to check if first and second supplied rules are exactly the same (except ID)
   236  func natRulesEqual(first, second *types.NsxtNatRule) bool {
   237  	util.Logger.Println("comparing NAT rule:")
   238  	util.Logger.Printf("%+v\n", first)
   239  	util.Logger.Println("against:")
   240  	util.Logger.Printf("%+v\n", second)
   241  
   242  	// Being an org user always returns logging as false - therefore cannot compare it.
   243  	// first.Logging == second.Logging
   244  
   245  	// These fields are returned or not returned depending on version and it is impossible to be 100% sure a minor
   246  	// patch does not break such comparison
   247  	// DnatExternalPort
   248  	// SnatDestinationAddresses
   249  	// RuleType - would work up to 35.2+, but then there is another field Type
   250  	// Type only available since 35.2+. Must be explicitly used for REFLEXIVE type in API v36.0+
   251  	// FirewallMatch - it exists only since API 35.2+ and has a default starting this version
   252  	// InternalPort - is deprecated since API V35.0+ and is replaced by DnatExternalPort
   253  	// Priority - is available only in API V35.2+
   254  	// Version - it is something that is automatically handled by API. When creating - you must specify none, but it sets
   255  	// version to 0. When updating one must specify the last version read, and again it will automatically increment this
   256  	// value after update. (probably it is meant to avoid concurrent updates)
   257  	if first.Name == second.Name &&
   258  		first.Enabled == second.Enabled &&
   259  		first.Description == second.Description &&
   260  		first.ExternalAddresses == second.ExternalAddresses &&
   261  		first.InternalAddresses == second.InternalAddresses &&
   262  
   263  		// Match both application profiles being nil (types cannot be equal as they are pointers, not values)
   264  		((first.ApplicationPortProfile == nil && second.ApplicationPortProfile == nil) ||
   265  			// Or both being not nil and having the same IDs
   266  			(first.ApplicationPortProfile != nil && second.ApplicationPortProfile != nil && first.ApplicationPortProfile.ID == second.ApplicationPortProfile.ID) ||
   267  			// Or first Application profile is nil and second is not nil, but has empty ID
   268  			(first.ApplicationPortProfile == nil && second.ApplicationPortProfile != nil && second.ApplicationPortProfile.ID == "") ||
   269  			// Or first Application Profile  is not nil, but has empty ID, while second application port profile is nil
   270  			(first.ApplicationPortProfile != nil && first.ApplicationPortProfile.ID == "" && second.ApplicationPortProfile == nil)) {
   271  
   272  		return true
   273  	}
   274  
   275  	return false
   276  }