Interim commit of PFW rules implementation. This is very preliminary!

rc-1.30
Sergey Shubin svs1370 3 years ago
parent 8058b1c08f
commit 4c3e2c1363

@ -1,7 +1,7 @@
# terraform-provider-decort # terraform-provider-decort
Terraform provider for Digital Energy Cloud Orchestration Technology (DECORT) platform Terraform provider for Digital Energy Cloud Orchestration Technology (DECORT) platform
NOTE: provider rc-1.25 is designed for DECORT API 3.7.x. For older API versions please use: NOTE: provider rc-1.30 is designed for DECORT API 3.7.x. For older API versions please use:
- DECORT API 3.6.x versions - provider version rc-1.10 - DECORT API 3.6.x versions - provider version rc-1.10
- DECORT API versions prior to 3.6.0 - Terraform DECS provider (https://github.com/rudecs/terraform-provider-decs) - DECORT API versions prior to 3.6.0 - Terraform DECS provider (https://github.com/rudecs/terraform-provider-decs)

@ -28,6 +28,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
// "net/url" // "net/url"
// "strconv"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -151,7 +152,7 @@ func parseBootDiskId(disks []DiskRecord) uint {
// Parse the list of interfaces from compute/get response into a list of networks // Parse the list of interfaces from compute/get response into a list of networks
// attached to this compute // attached to this compute
func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []interface{} { func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord, pfwVinsID int, pfwRules []map[string]interface{}) []interface{} {
// return value will be used to d.Set("network") item of dataSourceCompute schema // return value will be used to d.Set("network") item of dataSourceCompute schema
length := len(ifaces) length := len(ifaces)
log.Debugf("parseComputeInterfacesToNetworks: called for %d ifaces", length) log.Debugf("parseComputeInterfacesToNetworks: called for %d ifaces", length)
@ -167,6 +168,14 @@ func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []interface{} {
elem["ip_address"] = value.IPAddress elem["ip_address"] = value.IPAddress
elem["mac"] = value.MAC elem["mac"] = value.MAC
if value.NetType == "VINS" && len(pfwRules) > 0 && pfwVinsID == value.NetID {
// we have non-empty port forward rules that seem to be relevant to the current
// network segment - set "pfw_rule" element accordingly
log.Debugf("parseComputeInterfacesToNetworks: setting pfw_rule attributes on network block for ViNS ID %d",
value.NetID)
elem["pfw_rule"] = pfwRules
}
// log.Debugf(" element %d: net_id=%d, net_type=%s", i, value.NetID, value.NetType) // log.Debugf(" element %d: net_id=%d, net_type=%s", i, value.NetID, value.NetType)
result = append(result, elem) result = append(result, elem)
@ -240,7 +249,7 @@ func parseComputeInterfaces(ifaces []InterfaceRecord) []map[string]interface{} {
return result return result
} }
func flattenCompute(d *schema.ResourceData, compFacts string) error { func flattenCompute(d *schema.ResourceData, compFacts string, pfwVinsID int, pfwRules []map[string]interface{}) error {
// This function expects that compFacts string contains response from API compute/get, // This function expects that compFacts string contains response from API compute/get,
// i.e. detailed information about compute instance. // i.e. detailed information about compute instance.
// //
@ -283,7 +292,7 @@ func flattenCompute(d *schema.ResourceData, compFacts string) error {
if len(model.Interfaces) > 0 { if len(model.Interfaces) > 0 {
log.Debugf("flattenCompute: calling parseComputeInterfacesToNetworks for %d interfaces", len(model.Interfaces)) log.Debugf("flattenCompute: calling parseComputeInterfacesToNetworks for %d interfaces", len(model.Interfaces))
if err = d.Set("network", parseComputeInterfacesToNetworks(model.Interfaces)); err != nil { if err = d.Set("network", parseComputeInterfacesToNetworks(model.Interfaces, pfwVinsID, pfwRules)); err != nil {
return err return err
} }
} }
@ -299,15 +308,23 @@ func flattenCompute(d *schema.ResourceData, compFacts string) error {
} }
func dataSourceComputeRead(d *schema.ResourceData, m interface{}) error { func dataSourceComputeRead(d *schema.ResourceData, m interface{}) error {
compFacts, err := utilityComputeCheckPresence(d, m) compID, compFacts, err := utilityComputeCheckPresence(d, m)
if compFacts == "" { if compFacts == "" {
// if empty string is returned from utilityComputeCheckPresence then there is no // if empty compFacts is returned from utilityComputeCheckPresence and err=nil
// such Compute and err tells so - just return it to the calling party // it means that there is no such Compute;
// In any other case non-nil error will be reported.
d.SetId("") // ensure ID is empty d.SetId("") // ensure ID is empty
return err return err
} }
return flattenCompute(d, compFacts) vinsID, pfwRules, err := utilityComputePfwGet(compID, m)
if err != nil {
log.Errorf("dataSourceComputeRead: there was error calling utilityComputePfwGet for compute ID %s: %s",
d.Id(), err)
return err
}
return flattenCompute(d, compFacts, vinsID, pfwRules)
} }
func dataSourceCompute() *schema.Resource { func dataSourceCompute() *schema.Resource {

@ -444,9 +444,17 @@ const AccountsListAPI = "/restmachine/cloudapi/account/list" // returns list of
type AccountsListResp []AccountRecord type AccountsListResp []AccountRecord
// //
// structures related to /cloudapi/portforwarding/list API // structures related to /cloudapi/compute/pfwlLst API
// //
type PfwRecord struct {
// Note that if there are port forwarding rules for compute, then compute/pfwList response
// will contain a list which starts with prefix (see PfwPrefixRecord) and then contains
// one or more rule records (see PfwRuleRecord)
type PfwPrefixRecord struct {
VinsID int `json:"vinsId"`
VinsName string `json:"vinsName"`
}
type PfwRuleRecord struct {
ID int `json:"id"` ID int `json:"id"`
LocalIP string `json:"localIp"` LocalIP string `json:"localIp"`
LocalPort int `json:"localPort"` LocalPort int `json:"localPort"`
@ -458,8 +466,6 @@ type PfwRecord struct {
const ComputePfwListAPI = "/restmachine/cloudapi/compute/pfwList" const ComputePfwListAPI = "/restmachine/cloudapi/compute/pfwList"
type ComputePfwListResp []PfwRecord
const ComputePfwAddAPI = "/restmachine/cloudapi/compute/pfwAdd" const ComputePfwAddAPI = "/restmachine/cloudapi/compute/pfwAdd"
const ComputePfwDelAPI = "/restmachine/cloudapi/compute/pfwDel" const ComputePfwDelAPI = "/restmachine/cloudapi/compute/pfwDel"
@ -538,6 +544,8 @@ type VnfRecord struct {
AccountID int `json:"accountId"` AccountID int `json:"accountId"`
Type string `json:"type"` // "DHCP", "NAT", "GW" etc Type string `json:"type"` // "DHCP", "NAT", "GW" etc
Config map[string]interface{} `json:"config"` // NOTE: VNF specs vary by VNF type Config map[string]interface{} `json:"config"` // NOTE: VNF specs vary by VNF type
Status string `json:"status"`
TechStatus string `json:"techStatus"`
} }
type VnfGwConfigRecord struct { // describes GW VNF config structure inside ViNS, as returned by API vins/get type VnfGwConfigRecord struct { // describes GW VNF config structure inside ViNS, as returned by API vins/get
@ -546,6 +554,14 @@ type VnfGwConfigRecord struct { // describes GW VNF config structure inside ViNS
ExtNetMask int `json:"ext_net_mask"` ExtNetMask int `json:"ext_net_mask"`
DefaultGW string `json:"default_gw"` DefaultGW string `json:"default_gw"`
} }
type NatRuleRecord struct { // describes one NAT rule, a list of such rules is maintained inside VNF NAT Config
}
type VnfNatConfigRecord struct { // describes NAT VNF config structure inside ViNS, as returned by API vins/get
Netmask int `json:"netmask"`
Network string `json:"network"` // just network address, no mask, e.g. "192.168.1.0"
Rules []NatRuleRecord `json:"rules"`
}
type VinsRecord struct { // represents part of the response from API vins/get type VinsRecord struct { // represents part of the response from API vins/get
ID int `json:"id"` ID int `json:"id"`
Name string `json:"name"` Name string `json:"name"`

@ -129,7 +129,7 @@ func networkSubresourceSchemaMake() map[string]*schema.Schema {
Optional: true, Optional: true,
Computed: true, Computed: true,
DiffSuppressFunc: networkSubresIPAddreDiffSupperss, DiffSuppressFunc: networkSubresIPAddreDiffSupperss,
Description: "Optional IP address to assign to this connection. This IP should belong to the selected network and free for use.", Description: "Optional IP address to assign to this connection. This IP should belong to the selected network and available for use.",
}, },
"mac": { "mac": {
@ -138,6 +138,15 @@ func networkSubresourceSchemaMake() map[string]*schema.Schema {
Description: "MAC address associated with this connection. MAC address is assigned automatically.", Description: "MAC address associated with this connection. MAC address is assigned automatically.",
}, },
"pfw_rule": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: pfwSubresourceSchemaMake(),
},
Description: "Port forwarding rule to setup for this connection. You may specify several such blocks, one for each rule.",
},
} }
return rets return rets
} }

@ -0,0 +1,70 @@
/*
Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package decort
import (
// "encoding/json"
// "fmt"
// "bytes"
// log "github.com/sirupsen/logrus"
// "net/url"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)
// This is subresource of network subresource of compute resource used
// when creating/managing port forwarding rules for a compute connected
// to the corresponding network
// It only applies to a ViNS connection AND to a ViNS with external network connection
func pfwSubresourceSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
"pub_port_start": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 65535),
Description: "Port number on the external interface. For a ranged rule it set the starting port number.",
},
"pub_port_end": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 65535),
Description: "End port number on the external interface for a ranged rule. Set it equal to start port for a single port rule.",
},
"local_port": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 65535),
Description: "Port number on the local interface.",
},
"proto": {
Type: schema.TypeString,
Required: true,
StateFunc: stateFuncToLower,
ValidateFunc: validation.StringInSlice([]string{"tcp", "udp"}, false),
Description: "Protocol for this rule. Could be either tcp or udp.",
},
}
return rets
}

@ -103,7 +103,7 @@ func Provider() *schema.Provider {
"decort_kvmvm": resourceCompute(), "decort_kvmvm": resourceCompute(),
"decort_disk": resourceDisk(), "decort_disk": resourceDisk(),
"decort_vins": resourceVins(), "decort_vins": resourceVins(),
// "decort_pfw": resourcePfw(), // "decort_k8s": resourceK8s(),
}, },
DataSourcesMap: map[string]*schema.Resource{ DataSourcesMap: map[string]*schema.Resource{
@ -113,7 +113,8 @@ func Provider() *schema.Provider {
"decort_image": dataSourceImage(), "decort_image": dataSourceImage(),
"decort_disk": dataSourceDisk(), "decort_disk": dataSourceDisk(),
"decort_vins": dataSourceVins(), "decort_vins": dataSourceVins(),
// "decort_pfw": dataSourcePfw(), // "decort_k8ci": dataSourceK8ci(),
// "decort_k8s": dataSourceK8s(),
}, },
ConfigureFunc: providerConfigure, ConfigureFunc: providerConfigure,

@ -184,7 +184,7 @@ func resourceComputeRead(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceComputeRead: called for Compute name %s, RG ID %d", log.Debugf("resourceComputeRead: called for Compute name %s, RG ID %d",
d.Get("name").(string), d.Get("rg_id").(int)) d.Get("name").(string), d.Get("rg_id").(int))
compFacts, err := utilityComputeCheckPresence(d, m) compID, compFacts, err := utilityComputeCheckPresence(d, m)
if compFacts == "" { if compFacts == "" {
if err != nil { if err != nil {
return err return err
@ -193,7 +193,14 @@ func resourceComputeRead(d *schema.ResourceData, m interface{}) error {
return nil return nil
} }
if err = flattenCompute(d, compFacts); err != nil { vinsID, pfwRules, err := utilityComputePfwGet(compID, m)
if err != nil {
log.Errorf("resourceComputeRead: there was error calling utilityComputePfwGet for compute ID %s: %s",
d.Id(), err)
return err
}
if err = flattenCompute(d, compFacts, vinsID, pfwRules); err != nil {
return err return err
} }
@ -299,7 +306,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceComputeDelete: called for Compute name %s, RG ID %d", log.Debugf("resourceComputeDelete: called for Compute name %s, RG ID %d",
d.Get("name").(string), d.Get("rg_id").(int)) d.Get("name").(string), d.Get("rg_id").(int))
compFacts, err := utilityComputeCheckPresence(d, m) _, compFacts, err := utilityComputeCheckPresence(d, m)
if compFacts == "" { if compFacts == "" {
// the target Compute does not exist - in this case according to Terraform best practice // the target Compute does not exist - in this case according to Terraform best practice
// we exit from Destroy method without error // we exit from Destroy method without error
@ -352,7 +359,7 @@ func resourceComputeExists(d *schema.ResourceData, m interface{}) (bool, error)
log.Debugf("resourceComputeExist: called for Compute name %s, RG ID %d", log.Debugf("resourceComputeExist: called for Compute name %s, RG ID %d",
d.Get("name").(string), d.Get("rg_id").(int)) d.Get("name").(string), d.Get("rg_id").(int))
compFacts, err := utilityComputeCheckPresence(d, m) _, compFacts, err := utilityComputeCheckPresence(d, m)
if compFacts == "" { if compFacts == "" {
if err != nil { if err != nil {
return false, err return false, err

@ -29,6 +29,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"strconv" "strconv"
"strings"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -145,11 +146,48 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat
if ipSet { if ipSet {
urlValues.Add("ipAddr", ipaddr.(string)) urlValues.Add("ipAddr", ipaddr.(string))
} }
log.Debugf("utilityComputeNetworksConfigure: ready to add network type %s ID %d for Compute ID %s",
net_data["net_type"].(string), net_data["net_id"].(int), d.Id())
_, err := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues) _, err := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues)
if err != nil { if err != nil {
// failed to attach network - partial resource update // failed to attach network - partial resource update
apiErrCount++ apiErrCount++
lastSavedError = err lastSavedError = err
continue
}
if pfw_rules, ok := net_data["pfw_rule"]; ok {
// fool-proof - port forwarding is applicable to VINS type networks only! And only to
// those ViNSes that have active GW VNF, but here we check for VINS type only, the rest
// will be validated by the cloud platform
if net_data["net_type"].(string) != "VINS" {
log.Errorf("utilityComputeNetworksConfigure: encountered port forward rules specs in network block of type %s for Compute ID %s",
net_data["net_type"].(string), d.Id())
apiErrCount++
lastSavedError = err
continue
}
log.Debugf("utilityComputeNetworksConfigure: found port forward rules specs in network block ID %d for Compute ID %s",
net_data["net_id"].(int), d.Id())
for _, rule_runner := range pfw_rules.(*schema.Set).List() {
pfwValues := &url.Values{}
rule := rule_runner.(map[string]interface{})
pfwValues.Add("computeId", d.Id())
pfwValues.Add("publicPortStart", fmt.Sprintf("%d", rule["pub_port_start"].(int)))
pfwValues.Add("publicPortEnd", fmt.Sprintf("%d", rule["pub_port_end"].(int)))
pfwValues.Add("localBasePort", fmt.Sprintf("%d", rule["local_port"].(int)))
pfwValues.Add("proto", rule["proto"].(string))
log.Debugf("utilityComputeNetworksConfigure: ready to add pfw rule %d:%d -> %d proto %s for Compute ID %s",
rule["pub_port_start"].(int), rule["pub_port_end"].(int),
rule["proto"].(string), d.Id())
_, err := ctrl.decortAPICall("POST", ComputePfwAddAPI, pfwValues)
if err != nil {
// failed to add port forward rule - partial resource update
apiErrCount++
lastSavedError = err
}
}
} }
} }
@ -209,7 +247,11 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat
return nil return nil
} }
func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (string, error) {
//func (ctrl *ControllerCfg) utilityComputePfwConfigure(d *schema.ResourceData, do_delta bool) error {
//}
func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (int, string, error) {
// This function tries to locate Compute by one of the following approaches: // This function tries to locate Compute by one of the following approaches:
// - if compute_id is specified - locate by compute ID // - if compute_id is specified - locate by compute ID
// - if compute_name is specified - locate by a combination of compute name and resource // - if compute_name is specified - locate by a combination of compute name and resource
@ -246,27 +288,27 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (string,
urlValues.Add("computeId", fmt.Sprintf("%d", theId)) urlValues.Add("computeId", fmt.Sprintf("%d", theId))
computeFacts, err := controller.decortAPICall("POST", ComputeGetAPI, urlValues) computeFacts, err := controller.decortAPICall("POST", ComputeGetAPI, urlValues)
if err != nil { if err != nil {
return "", err return 0, "", err
} }
return computeFacts, nil return theId, computeFacts, nil
} }
// ID was not set in the schema upon entering this function - work through Compute name // ID was not set in the schema upon entering this function - work through Compute name
// and RG ID // and RG ID
computeName, argSet := d.GetOk("name") computeName, argSet := d.GetOk("name")
if !argSet { if !argSet {
return "", fmt.Errorf("Cannot locate compute instance if name is empty and no compute ID specified") return 0, "", fmt.Errorf("Cannot locate compute instance if name is empty and no compute ID specified")
} }
rgId, argSet := d.GetOk("rg_id") rgId, argSet := d.GetOk("rg_id")
if !argSet { if !argSet {
return "", fmt.Errorf("Cannot locate compute by name %s if no resource group ID is set", computeName.(string)) return 0, "", fmt.Errorf("Cannot locate compute by name %s if no resource group ID is set", computeName.(string))
} }
urlValues.Add("rgId", fmt.Sprintf("%d", rgId)) urlValues.Add("rgId", fmt.Sprintf("%d", rgId))
apiResp, err := controller.decortAPICall("POST", RgListComputesAPI, urlValues) apiResp, err := controller.decortAPICall("POST", RgListComputesAPI, urlValues)
if err != nil { if err != nil {
return "", err return 0, "", err
} }
log.Debugf("utilityComputeCheckPresence: ready to unmarshal string %s", apiResp) log.Debugf("utilityComputeCheckPresence: ready to unmarshal string %s", apiResp)
@ -274,7 +316,7 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (string,
computeList := RgListComputesResp{} computeList := RgListComputesResp{}
err = json.Unmarshal([]byte(apiResp), &computeList) err = json.Unmarshal([]byte(apiResp), &computeList)
if err != nil { if err != nil {
return "", err return 0, "", err
} }
// log.Printf("%#v", computeList) // log.Printf("%#v", computeList)
@ -288,11 +330,83 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (string,
cgetValues.Add("computeId", fmt.Sprintf("%d", item.ID)) cgetValues.Add("computeId", fmt.Sprintf("%d", item.ID))
apiResp, err = controller.decortAPICall("POST", ComputeGetAPI, cgetValues) apiResp, err = controller.decortAPICall("POST", ComputeGetAPI, cgetValues)
if err != nil { if err != nil {
return "", err return 0, "", err
} }
return apiResp, nil // NOTE: compute ID is unsigned int in the platform. Here we convert it to int, which may have
// unwanted side effects when the number of compute instances grows
return int(item.ID), apiResp, nil
}
}
return 0, "", nil // there should be no error if Compute does not exist
}
// This function reads port forwards from a specified compute and returns them (if any) in a
// form of a list of maps of interfaces suitable to be used for d.Set("pfw_rule") on the
// network block, corresponding to the ViNS these rules belong to. To simlify this network
// block identification among multiple blocks of the same compute this function also
// returns the ID of the ViNS associated with listed rules.
func utilityComputePfwGet(compId int, m interface{}) (int, []map[string]interface{}, error) {
// If there is an error either reading portforward rules from the cloud or parsing them, error is
// returned.
// In case there are no portforwarding rules for this compute, err = nil and rule record list is empty.
// Otherwise, both prefix record and rule record list contain meaningful data.
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
pfwPrefix := PfwPrefixRecord{}
pfwRules := []PfwRuleRecord{}
pfwRulesList := []map[string]interface{}{}
urlValues.Add("computeId", fmt.Sprintf("%d", compId))
apiResp, err := controller.decortAPICall("POST", ComputePfwListAPI, urlValues)
if err != nil {
return 0, pfwRulesList, err
}
if apiResp == "" {
// No port forward rules defined for this compute
return 0, pfwRulesList, nil
}
log.Debugf("utilityComputePfwGet: ready to split API response string %s", apiResp)
twoParts := strings.SplitN(apiResp, "},", 2)
if len(twoParts) != 2 {
log.Errorf("utilityComputePfwGet: non-empty pfwList response for compute ID %d failed to split into 2 fragments (got %d)", compId, len(twoParts))
return 0, pfwRulesList, fmt.Errorf("Non-empty pfwList response failed to split into 2 fragments")
}
prefixResp := strings.TrimSuffix(strings.TrimPrefix(twoParts[0], "["), ",") + "}"
log.Debugf("utilityComputePfwGet: ready to unmarshal prefix part %s", prefixResp)
err = json.Unmarshal([]byte(prefixResp), &pfwPrefix)
if err != nil {
log.Errorf("utilityComputePfwGet: failed to unmarshal prefix part of API response: %s", err)
return 0, pfwRulesList, err
}
rulesResp := "[" + twoParts[1]
log.Debugf("utilityComputePfwGet: ready to unmarshal rules part %s", rulesResp)
err = json.Unmarshal([]byte(rulesResp), &pfwRules)
if err != nil {
log.Errorf("utilityComputePfwGet: failed to unmarshal rules part of API response: %s", err)
return 0, pfwRulesList, err
}
log.Debugf("utilityComputePfwGet: successfully read %d port forward rules for Compute ID %d, ViNS ID %d",
len(pfwRules), compId, pfwPrefix.VinsID)
for _, runner := range pfwRules {
rule := map[string]interface{}{
"pub_port_start": runner.PublicPortStart,
"pub_port_end": runner.PublicPortEnd,
"local_port": runner.LocalPort,
"proto": runner.Protocol,
} }
pfwRulesList = append(pfwRulesList, rule)
} }
return "", nil // there should be no error if Compute does not exist return pfwPrefix.VinsID, pfwRulesList, nil
} }

Loading…
Cancel
Save