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 }