This commit is contained in:
KasimBaybikov
2023-05-04 10:08:25 +03:00
parent 9bad8a6947
commit 8ca233dd32
288 changed files with 6645 additions and 11464 deletions

View File

@@ -33,11 +33,12 @@ package vins
import (
"context"
"encoding/json"
"fmt"
"reflect"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/constants"
log "github.com/sirupsen/logrus"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vins"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/constants"
// "net/url"
@@ -46,44 +47,32 @@ import (
// "github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)
// vins_facts is a response string from API vins/get
func flattenVins(d *schema.ResourceData, vins_facts string) diag.Diagnostics {
// NOTE: this function modifies ResourceData argument - as such it should never be called
// from resourceVinsExists(...) method
// log.Debugf("flattenVins: ready to decode response body from API %s", vins_facts)
vinsRecord := VinsRecord{}
err := json.Unmarshal([]byte(vins_facts), &vinsRecord)
if err != nil {
return diag.FromErr(err)
}
func flattenVins(d *schema.ResourceData, vinsRecord *vins.RecordVINS) diag.Diagnostics {
log.Debugf("flattenVins: decoded ViNS name:ID %s:%d, account ID %d, RG ID %d",
vinsRecord.Name, vinsRecord.ID, vinsRecord.AccountID, vinsRecord.RgID)
vinsRecord.Name, vinsRecord.ID, vinsRecord.AccountID, vinsRecord.RGID)
d.SetId(fmt.Sprintf("%d", vinsRecord.ID))
d.Set("name", vinsRecord.Name)
d.Set("account_id", vinsRecord.AccountID)
d.Set("account_name", vinsRecord.AccountName)
d.Set("rg_id", vinsRecord.RgID)
d.Set("description", vinsRecord.Desc)
d.Set("ipcidr", vinsRecord.IPCidr)
// d.Set("account_name", vinsRecord.AccountName)
d.Set("rg_id", vinsRecord.RGID)
d.Set("description", vinsRecord.Description)
d.Set("ipcidr", vinsRecord.Network)
noExtNetConnection := true
for _, value := range vinsRecord.VNFs {
if value.Type == "GW" {
log.Debugf("flattenVins: discovered GW VNF ID %d in ViNS ID %d", value.ID, vinsRecord.ID)
extNetID, idOk := value.Config["ext_net_id"] // NOTE: unknown numbers are unmarshalled to float64. This is by design!
extNetIP, ipOk := value.Config["ext_net_ip"]
if idOk && ipOk {
log.Debugf("flattenVins: ViNS ext_net_id=%d, ext_net_ip=%s", int(extNetID.(float64)), extNetIP.(string))
d.Set("ext_ip_addr", extNetIP.(string))
d.Set("ext_net_id", int(extNetID.(float64)))
} else {
return diag.Errorf("Failed to unmarshal VNF GW Config - structure is invalid.")
}
noExtNetConnection = false
break
gw := vinsRecord.VNFs.GW
if !reflect.ValueOf(gw).IsZero() {
log.Debugf("flattenVins: discovered GW VNF ID %d in ViNS ID %d", gw.ID, vinsRecord.ID)
extNetID := gw.Config.ExtNetID
extNetIP := gw.Config.ExtNetIP
if extNetID != 0 && extNetIP != "" {
log.Debugf("flattenVins: ViNS ext_net_id=%d, ext_net_ip=%s", extNetID, extNetIP)
d.Set("ext_ip_addr", extNetIP)
d.Set("ext_net_id", extNetID)
} else {
return diag.Errorf("Failed to unmarshal VNF GW Config - structure is invalid.")
}
noExtNetConnection = false
}
if noExtNetConnection {
@@ -98,10 +87,8 @@ func flattenVins(d *schema.ResourceData, vins_facts string) diag.Diagnostics {
func dataSourceVinsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
vinsFacts, err := utilityVinsCheckPresence(ctx, d, m)
if vinsFacts == "" {
// if empty string is returned from utilityVinsCheckPresence then there is no
// such ViNS and err tells so - just return it to the calling party
d.SetId("") // ensure ID is empty in this case
if vinsFacts == nil {
d.SetId("")
return diag.FromErr(err)
}

View File

@@ -37,14 +37,15 @@ import (
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vins"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/constants"
)
func flattenVinsList(vl VinsList) []map[string]interface{} {
func flattenVinsList(vl vins.ListVINS) []map[string]interface{} {
res := make([]map[string]interface{}, 0)
for _, v := range vl {
temp := map[string]interface{}{
"account_id": v.AccountId,
"account_id": v.AccountID,
"account_name": v.AccountName,
"created_by": v.CreatedBy,
"created_time": v.CreatedTime,
@@ -59,7 +60,7 @@ func flattenVinsList(vl VinsList) []map[string]interface{} {
"status": v.Status,
"updated_by": v.UpdatedBy,
"updated_time": v.UpdatedTime,
"vxlan_id": v.VXLanID,
"vxlan_id": v.VXLANID,
}
res = append(res, temp)
}

View File

@@ -1,94 +0,0 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
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.
*/
/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.
Source code: https://repository.basistech.ru/BASIS/terraform-provider-decort
Please see README.md to learn where to place source code so that it
builds seamlessly.
Documentation: https://repository.basistech.ru/BASIS/terraform-provider-decort/wiki
*/
package vins
type Vins struct {
AccountId int `json:"accountId"`
AccountName string `json:"accountName"`
CreatedBy string `json:"createdBy"`
CreatedTime int `json:"createdTime"`
DeletedBy string `json:"deletedBy"`
DeletedTime int `json:"deletedTime"`
ExternalIP string `json:"externalIP"`
ID int `json:"id"`
Name string `json:"name"`
Network string `json:"network"`
RGID int `json:"rgId"`
RGName string `json:"rgName"`
Status string `json:"status"`
UpdatedBy string `json:"updatedBy"`
UpdatedTime int `json:"updatedTime"`
VXLanID int `json:"vxlanId"`
}
type VinsList []Vins
type VinsSearchResp []VinsSearchRecord
type VnfRecord struct {
ID int `json:"id"`
AccountID int `json:"accountId"`
Type string `json:"type"` // "DHCP", "NAT", "GW" etc
Config map[string]interface{} `json:"config"` // NOTE: VNF specs vary by VNF type
}
type VnfGwConfigRecord struct { // describes GW VNF config structure inside ViNS, as returned by API vins/get
ExtNetID int `json:"ext_net_id"`
ExtNetIP string `json:"ext_net_ip"`
ExtNetMask int `json:"ext_net_mask"`
DefaultGW string `json:"default_gw"`
}
type VinsRecord struct { // represents part of the response from API vins/get
ID int `json:"id"`
Name string `json:"name"`
IPCidr string `json:"network"`
VxLanID int `json:"vxlanId"`
ExternalIP string `json:"externalIP"`
AccountID int `json:"accountId"`
AccountName string `json:"accountName"`
RgID int `json:"rgid"`
RgName string `json:"rgName"`
VNFs map[string]VnfRecord `json:"vnfs"`
Desc string `json:"desc"`
}
type VinsSearchRecord struct {
ID int `json:"id"`
Name string `json:"name"`
IPCidr string `json:"network"`
VxLanID int `json:"vxlanId"`
ExternalIP string `json:"externalIP"`
AccountID int `json:"accountId"`
AccountName string `json:"accountName"`
RgID int `json:"rgId"`
RgName string `json:"rgName"`
}

View File

@@ -33,13 +33,12 @@ package vins
import (
"context"
"fmt"
"net/url"
"strconv"
log "github.com/sirupsen/logrus"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vins"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/constants"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
log "github.com/sirupsen/logrus"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -62,82 +61,78 @@ func resourceVinsCreate(ctx context.Context, d *schema.ResourceData, m interface
log.Debugf("resourceVinsCreate: called for ViNS name %s, Account ID %d, RG ID %d",
d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int))
apiToCall := VinsCreateInAccountAPI
createInAcc := true
c := m.(*controller.ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("name", d.Get("name").(string))
inAccReq := vins.CreateInAccountRequest{
Name: d.Get("name").(string),
}
inRGReq := vins.CreateInRGRequest{
Name: d.Get("name").(string),
}
argVal, argSet := d.GetOk("rg_id")
if argSet && argVal.(int) > 0 {
apiToCall = VinsCreateInRgAPI
urlValues.Add("rgId", fmt.Sprintf("%d", argVal.(int)))
createInAcc = false
inRGReq.RGID = uint64(argVal.(int))
} else {
// RG ID either not set at all or set to 0 - user may want ViNS at account level
argVal, argSet = d.GetOk("account_id")
if !argSet || argVal.(int) <= 0 {
// No valid Account ID (and no RG ID either) - cannot create ViNS
return diag.Errorf("resourceVinsCreate: ViNS name %s - no valid account and/or resource group ID specified", d.Id())
}
urlValues.Add("accountId", fmt.Sprintf("%d", argVal.(int)))
inAccReq.AccountID = uint64(argVal.(int))
}
argVal, argSet = d.GetOk("ext_net_id") // NB: even if ext_net_id value is explicitly set to 0, argSet = false anyway
if argSet {
if argVal.(int) > 0 {
// connect to specific external network
urlValues.Add("extNetId", fmt.Sprintf("%d", argVal.(int)))
/*
Commented out, as we've made "ext_net_ip" parameter non-configurable via Terraform!
// in case of specific ext net connection user may also want a particular IP address
argVal, argSet = d.GetOk("ext_net_ip")
if argSet && argVal.(string) != "" {
urlValues.Add("extIp", argVal.(string))
}
*/
inRGReq.ExtNetID = uint64(argVal.(int))
} else {
// ext_net_id is set to a negative value - connect to default external network
// no particular IP address selection in this case
urlValues.Add("extNetId", "0")
inRGReq.ExtNetID = 0
}
}
argVal, argSet = d.GetOk("ipcidr")
if argSet && argVal.(string) != "" {
log.Debugf("resourceVinsCreate: ipcidr is set to %s", argVal.(string))
urlValues.Add("ipcidr", argVal.(string))
inAccReq.IPCIDR = argVal.(string)
inRGReq.IPCIDR = argVal.(string)
}
argVal, argSet = d.GetOk("description")
if argSet {
urlValues.Add("desc", argVal.(string))
inAccReq.Description = argVal.(string)
inRGReq.Description = argVal.(string)
}
apiResp, err := c.DecortAPICall(ctx, "POST", apiToCall, urlValues)
if err != nil {
return diag.FromErr(err)
var vinsID uint64
if createInAcc {
apiResp, err := c.CloudBroker().VINS().CreateInAccount(ctx, inAccReq)
if err != nil {
return diag.FromErr(err)
}
vinsID = apiResp
} else {
apiResp, err := c.CloudBroker().VINS().CreateInRG(ctx, inRGReq)
if err != nil {
return diag.FromErr(err)
}
vinsID = apiResp
}
d.SetId(apiResp) // update ID of the resource to tell Terraform that the ViNS resource exists
vinsId, _ := strconv.Atoi(apiResp)
d.SetId(strconv.FormatUint(vinsID, 10))
log.Debugf("resourceVinsCreate: new ViNS ID / name %d / %s creation sequence complete", vinsId, d.Get("name").(string))
log.Debugf("resourceVinsCreate: new ViNS ID / name %d / %s creation sequence complete", vinsID, d.Get("name").(string))
// We may reuse dataSourceVinsRead here as we maintain similarity
// between ViNS resource and ViNS data source schemas
// ViNS resource read function will also update resource ID on success, so that Terraform
// will know the resource exists (however, we already did it a few lines before)
return dataSourceVinsRead(ctx, d, m)
}
func resourceVinsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
vinsFacts, err := utilityVinsCheckPresence(ctx, d, m)
if vinsFacts == "" {
// if empty string is returned from utilityVinsCheckPresence then there is no
// such ViNS and err tells so - just return it to the calling party
d.SetId("") // ensure ID is empty
if vinsFacts == nil {
d.SetId("")
return diag.FromErr(err)
}
@@ -145,40 +140,39 @@ func resourceVinsRead(ctx context.Context, d *schema.ResourceData, m interface{}
}
func resourceVinsUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
log.Debugf("resourceVinsUpdate: called for ViNS ID / name %s / %s, Account ID %d, RG ID %d",
d.Id(), d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int))
c := m.(*controller.ControllerCfg)
vinsID, _ := strconv.ParseUint(d.Id(), 10, 64)
// 1. Handle external network connection change
oldExtNetId, newExtNedId := d.GetChange("ext_net_id")
if oldExtNetId.(int) != newExtNedId.(int) {
log.Debugf("resourceVinsUpdate: changing ViNS ID %s - ext_net_id %d -> %d", d.Id(), oldExtNetId.(int), newExtNedId.(int))
extnetParams := &url.Values{}
extnetParams.Add("vinsId", d.Id())
if oldExtNetId.(int) > 0 {
// there was preexisting external net connection - disconnect ViNS
_, err := c.DecortAPICall(ctx, "POST", VinsExtNetDisconnectAPI, extnetParams)
req := vins.ExtNetDisconnectRequest{VINSID: vinsID}
_, err := c.CloudBroker().VINS().ExtNetDisconnect(ctx, req)
if err != nil {
return diag.FromErr(err)
}
}
if newExtNedId.(int) > 0 {
// new external network connection requested - connect ViNS
extnetParams.Add("netId", fmt.Sprintf("%d", newExtNedId.(int)))
_, err := c.DecortAPICall(ctx, "POST", VinsExtNetConnectAPI, extnetParams)
req := vins.ExtNetConnectRequest{
VINSID: vinsID,
NetID: uint64(newExtNedId.(int)),
}
_, err := c.CloudBroker().VINS().ExtNetConnect(ctx, req)
if err != nil {
return diag.FromErr(err)
}
}
}
// we may reuse dataSourceVinsRead here as we maintain similarity
// between Compute resource and Compute data source schemas
return dataSourceVinsRead(ctx, d, m)
}
@@ -187,7 +181,7 @@ func resourceVinsDelete(ctx context.Context, d *schema.ResourceData, m interface
d.Id(), d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int))
vinsFacts, err := utilityVinsCheckPresence(ctx, d, m)
if vinsFacts == "" {
if vinsFacts == nil {
if err != nil {
return diag.FromErr(err)
}
@@ -196,13 +190,14 @@ func resourceVinsDelete(ctx context.Context, d *schema.ResourceData, m interface
return nil
}
params := &url.Values{}
params.Add("vinsId", d.Id())
params.Add("force", "1") // disconnect all computes before deleting ViNS
params.Add("permanently", "1") // delete ViNS immediately bypassing recycle bin
c := m.(*controller.ControllerCfg)
_, err = c.DecortAPICall(ctx, "POST", VinsDeleteAPI, params)
req := vins.DeleteRequest{
VINSID: vinsFacts.ID,
Force: true,
Permanently: true,
}
_, err = c.CloudBroker().VINS().Delete(ctx, req)
if err != nil {
return diag.FromErr(err)
}

View File

@@ -33,44 +33,25 @@ package vins
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
log "github.com/sirupsen/logrus"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vins"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// On success this function returns a string, as returned by API vins/get, which could be unmarshalled
// into VinsGetResp structure
func utilityVinsCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (string, error) {
// This function tries to locate ViNS by one of the following algorithms depending
// on the parameters passed:
// - if resource group ID is specified -> it looks for a ViNS at the RG level
// - if account ID is specifeid -> it looks for a ViNS at the account level
//
// If succeeded, it returns non empty string that contains JSON formatted facts about the
// ViNS as returned by vins/get API call.
// Otherwise it returns empty string and a meaningful error.
//
// This function does not modify its ResourceData argument, so it is safe to use it as core
// method for the Terraform resource Exists method.
//
func utilityVinsCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*vins.RecordVINS, error) {
c := m.(*controller.ControllerCfg)
urlValues := &url.Values{}
// make it possible to use "read" & "check presence" functions with ViNS ID set so
// that Import of ViNS resource is possible
idSet := false
theId, err := strconv.Atoi(d.Id())
if err != nil || theId <= 0 {
vinsID, err := strconv.ParseUint(d.Id(), 10, 64)
if err != nil || vinsID <= 0 {
vinsId, argSet := d.GetOk("vins_id") // NB: vins_id is NOT present in vinsResource schema!
if argSet {
theId = vinsId.(int)
vinsID = uint64(vinsId.(int))
idSet = true
}
} else {
@@ -78,76 +59,65 @@ func utilityVinsCheckPresence(ctx context.Context, d *schema.ResourceData, m int
}
if idSet {
// ViNS ID is specified, try to get compute instance straight by this ID
log.Debugf("utilityVinsCheckPresence: locating ViNS by its ID %d", theId)
urlValues.Add("vinsId", fmt.Sprintf("%d", theId))
vinsFacts, err := c.DecortAPICall(ctx, "POST", VinsGetAPI, urlValues)
log.Debugf("utilityVinsCheckPresence: locating ViNS by its ID %d", vinsID)
req := vins.GetRequest{VINSID: vinsID}
vinsFacts, err := c.CloudBroker().VINS().Get(ctx, req)
if err != nil {
return "", err
return nil, err
}
return vinsFacts, nil
}
// ID was not set in the schema upon entering this function - work through ViNS name
// and Account / RG ID
vinsName, argSet := d.GetOk("name")
if !argSet {
// if ViNS name is not set. then we cannot locate ViNS
return "", fmt.Errorf("Cannot check ViNS presence if ViNS name is empty")
return nil, fmt.Errorf("Cannot check ViNS presence if ViNS name is empty")
}
req := vins.SearchRequest{
Name: vinsName.(string),
ShowAll: false,
}
urlValues.Add("name", vinsName.(string))
urlValues.Add("show_all", "false")
log.Debugf("utilityVinsCheckPresence: preparing to locate ViNS name %s", vinsName.(string))
rgId, rgSet := d.GetOk("rg_id")
if rgSet {
log.Debugf("utilityVinsCheckPresence: limiting ViNS search to RG ID %d", rgId.(int))
urlValues.Add("rgId", fmt.Sprintf("%d", rgId.(int)))
req.RGID = uint64(rgId.(int))
}
accountId, accountSet := d.GetOk("account_id")
if accountSet {
log.Debugf("utilityVinsCheckPresence: limiting ViNS search to Account ID %d", accountId.(int))
urlValues.Add("accountId", fmt.Sprintf("%d", accountId.(int)))
req.AccountID = uint64(accountId.(int))
}
apiResp, err := c.DecortAPICall(ctx, "POST", VinsSearchAPI, urlValues)
vinsList, err := c.CloudBroker().VINS().Search(ctx, req)
if err != nil {
return "", err
return nil, err
}
// log.Debugf("%s", apiResp)
// log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %s", VinsSearchAPI)
model := VinsSearchResp{}
err = json.Unmarshal([]byte(apiResp), &model)
if err != nil {
return "", err
}
log.Debugf("utilityVinsCheckPresence: traversing decoded Json of length %d", len(model))
for index, item := range model {
log.Debugf("utilityVinsCheckPresence: traversing decoded Json of length %d", len(vinsList))
for index, item := range vinsList {
if item.Name == vinsName.(string) {
if (accountSet && item.AccountID != accountId.(int)) ||
(rgSet && item.RgID != rgId.(int)) {
// double check that account ID and Rg ID match, if set in the schema
if (accountSet && item.AccountID != uint64(accountId.(int))) ||
(rgSet && item.RGID != uint64(rgId.(int))) {
continue
}
log.Debugf("utilityVinsCheckPresence: match ViNS name %s / ID %d, account ID %d, RG ID %d at index %d",
item.Name, item.ID, item.AccountID, item.RgID, index)
item.Name, item.ID, item.AccountID, item.RGID, index)
// element returned by API vins/search does not contain all information we may need to
// manage ViNS, so we have to get detailed info by calling API vins/get
rqValues := &url.Values{}
rqValues.Add("vinsId", fmt.Sprintf("%d", item.ID))
vinsGetResp, err := c.DecortAPICall(ctx, "POST", VinsGetAPI, rqValues)
req := vins.GetRequest{VINSID: item.ID}
vinsGetResp, err := c.CloudBroker().VINS().Get(ctx, req)
if err != nil {
return "", err
return nil, err
}
return vinsGetResp, nil
}
}
return "", fmt.Errorf("Cannot find ViNS name %s. Check name and/or RG ID & Account ID and your access rights", vinsName.(string))
return nil, fmt.Errorf("Cannot find ViNS name %s. Check name and/or RG ID & Account ID and your access rights", vinsName.(string))
}

View File

@@ -33,38 +33,27 @@ package vins
import (
"context"
"encoding/json"
"net/url"
"strconv"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
log "github.com/sirupsen/logrus"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vins"
"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func utilityVinsListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (VinsList, error) {
vinsList := VinsList{}
func utilityVinsListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (vins.ListVINS, error) {
c := m.(*controller.ControllerCfg)
urlValues := &url.Values{}
req := vins.ListRequest{}
if includeDeleted, ok := d.GetOk("include_deleted"); ok {
urlValues.Add("includeDeleted", strconv.FormatBool(includeDeleted.(bool)))
}
if page, ok := d.GetOk("page"); ok {
urlValues.Add("page", strconv.Itoa(page.(int)))
req.Page = uint64(page.(int))
}
if size, ok := d.GetOk("size"); ok {
urlValues.Add("size", strconv.Itoa(size.(int)))
req.Size = uint64(size.(int))
}
log.Debugf("utilityVinsListCheckPresence")
vinsListRaw, err := c.DecortAPICall(ctx, "POST", VinsListAPI, urlValues)
if err != nil {
return nil, err
}
err = json.Unmarshal([]byte(vinsListRaw), &vinsList)
vinsList, err := c.CloudBroker().VINS().List(ctx, req)
if err != nil {
return nil, err
}