diff --git a/README.md b/README.md index 9e6eec6..7e7c7f9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # terraform-provider-decort 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.40 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 versions prior to 3.6.0 - Terraform DECS provider (https://github.com/rudecs/terraform-provider-decs) diff --git a/decort/controller.go b/decort/controller.go index d623e78..7b5ae0b 100644 --- a/decort/controller.go +++ b/decort/controller.go @@ -132,7 +132,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) { } ret_config.auth_mode_code = MODE_LEGACY default: - return nil, fmt.Errorf("Unknown authenticator mode %q provided.", ret_config.auth_mode_txt) + return nil, fmt.Errorf("Unknown authenticator mode %s provided.", ret_config.auth_mode_txt) } if allow_unverified_ssl { @@ -205,7 +205,7 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) { return "", fmt.Errorf("getOAuth2JWT method called for undefined authorization mode.") } if config.auth_mode_code != MODE_OAUTH2 { - return "", fmt.Errorf("getOAuth2JWT method called for incompatible authorization mode %q.", config.auth_mode_txt) + return "", fmt.Errorf("getOAuth2JWT method called for incompatible authorization mode %s.", config.auth_mode_txt) } params := url.Values{} @@ -231,7 +231,7 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) { // fmt.Println("response Status:", resp.Status) // fmt.Println("response Headers:", resp.Header) // fmt.Println("response Headers:", req.URL) - return "", fmt.Errorf("getOauth2JWT: unexpected status code %d when obtaining JWT from %q for APP_ID %q, request Body %q", + return "", fmt.Errorf("getOauth2JWT: unexpected status code %d when obtaining JWT from %s for APP_ID %s, request Body %s", resp.StatusCode, req.URL, config.app_id, params_str) } defer resp.Body.Close() @@ -279,7 +279,7 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) { return false, err } if resp.StatusCode != http.StatusOK { - return false, fmt.Errorf("validateJWT: unexpected status code %d when validating JWT against %q.", + return false, fmt.Errorf("validateJWT: unexpected status code %d when validating JWT against %s.", resp.StatusCode, req.URL) } defer resp.Body.Close() @@ -298,7 +298,7 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) { return false, fmt.Errorf("validateLegacyUser method called for undefined authorization mode.") } if config.auth_mode_code != MODE_LEGACY { - return false, fmt.Errorf("validateLegacyUser method called for incompatible authorization mode %q.", config.auth_mode_txt) + return false, fmt.Errorf("validateLegacyUser method called for incompatible authorization mode %s.", config.auth_mode_txt) } params := url.Values{} @@ -319,7 +319,7 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) { return false, err } if resp.StatusCode != http.StatusOK { - return false, fmt.Errorf("validateLegacyUser: unexpected status code %d when validating legacy user %q against %q.", + return false, fmt.Errorf("validateLegacyUser: unexpected status code %d when validating legacy user %s against %s.", resp.StatusCode, config.legacy_user, config.controller_url) } defer resp.Body.Close() @@ -335,13 +335,15 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) { return true, nil } -func (config *ControllerCfg) decortAPICall(method string, api_name string, url_values *url.Values) (json_resp string, err error) { +func (config *ControllerCfg) decortAPICall(method string, api_name string, url_values *url.Values) (json_resp string, err error, hrc int) { // This is a convenience wrapper around standard HTTP request methods that is aware of the // authorization mode for which the provider was initialized and compiles request accordingly. + hrc = 0 // HTTP Response Code + if config.cc_client == nil { // this should never happen if ClientConfig was properly called prior to decortAPICall - return "", fmt.Errorf("decortAPICall method called with unconfigured DECORT cloud controller HTTP client.") + return "", fmt.Errorf("decortAPICall method called with unconfigured DECORT cloud controller HTTP client."), 0 } // Example: to create api_params, one would generally do the following: @@ -359,7 +361,7 @@ func (config *ControllerCfg) decortAPICall(method string, api_name string, url_v // if config.auth_mode_code == MODE_UNDEF { - return "", fmt.Errorf("decortAPICall method called for unknown authorization mode.") + return "", fmt.Errorf("decortAPICall method called for unknown authorization mode."), 0 } if config.auth_mode_code == MODE_LEGACY { @@ -369,7 +371,7 @@ func (config *ControllerCfg) decortAPICall(method string, api_name string, url_v req, err := http.NewRequest(method, config.controller_url + api_name, strings.NewReader(params_str)) if err != nil { - return "", err + return "", err, 0 } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Length", strconv.Itoa(len(params_str))) @@ -380,29 +382,29 @@ func (config *ControllerCfg) decortAPICall(method string, api_name string, url_v resp, err := config.cc_client.Do(req) if err != nil { - return "", err + return "", err, 0 } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { tmp_body, err := ioutil.ReadAll(resp.Body) if err != nil { - return "", err + return "", err, resp.StatusCode } json_resp := Jo2JSON(string(tmp_body)) log.Debugf("decortAPICall: %s %s\n %s", method, api_name, json_resp) - return json_resp, nil + return json_resp, nil, resp.StatusCode } else { - return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %q with request Body %q", - resp.StatusCode, req.URL, params_str) + return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %s with request Body %s", + resp.StatusCode, req.URL, params_str), resp.StatusCode } /* if resp.StatusCode == StatusServiceUnavailable { - return nil, fmt.Errorf("decortAPICall method called for incompatible authorization mode %q.", config.auth_mode_txt) + return nil, fmt.Errorf("decortAPICall method called for incompatible authorization mode %s.", config.auth_mode_txt), resp.StatusCode } */ - return "", err + return "", err, resp.StatusCode } diff --git a/decort/data_source_compute.go b/decort/data_source_compute.go index 1492200..1d5e53e 100644 --- a/decort/data_source_compute.go +++ b/decort/data_source_compute.go @@ -174,32 +174,6 @@ func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []interface{} { return result } -/* -func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []map[string]interface{} { - // return value will be used to d.Set("network") item of dataSourceCompute schema - length := len(ifaces) - log.Debugf("parseComputeInterfacesToNetworks: called for %d ifaces", length) - - result := make([]map[string]interface{}, length, length) - - for i, value := range ifaces { - elem := make(map[string]interface{}) - // Keys in this map should correspond to the Schema definition - // as returned by networkSubresourceSchemaMake() - elem["net_id"] = value.NetID - elem["net_type"] = value.NetType - elem["ip_address"] = value.IPAddress - elem["mac"] = value.MAC - - // log.Debugf(" element %d: net_id=%d, net_type=%s", i, value.NetID, value.NetType) - - result[i] = elem - } - - return result -} -*/ - // NOTE: this function is retained for historical purposes and actually not used as of rc-1.10 func parseComputeInterfaces(ifaces []InterfaceRecord) []map[string]interface{} { diff --git a/decort/data_source_image.go b/decort/data_source_image.go index c0b6369..ceef23b 100644 --- a/decort/data_source_image.go +++ b/decort/data_source_image.go @@ -45,7 +45,7 @@ func dataSourceImageRead(d *schema.ResourceData, m interface{}) error { if accSet { url_values.Add("accountId", fmt.Sprintf("%d", accId.(int))) } - body_string, err := controller.decortAPICall("POST", ImagesListAPI, url_values) + body_string, err, _ := controller.decortAPICall("POST", ImagesListAPI, url_values) if err != nil { return err } diff --git a/decort/data_source_pfw.go b/decort/data_source_pfw.go new file mode 100644 index 0000000..be810a5 --- /dev/null +++ b/decort/data_source_pfw.go @@ -0,0 +1,125 @@ +/* +Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +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" + // "hash/fnv" + log "github.com/sirupsen/logrus" + // "net/url" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + + +func flattenPfw(d *schema.ResourceData, pfwFacts string) error { + // NOTE: this function modifies ResourceData argument - as such it should never be called + // from resourcePfwExists(...) method + // log.Debugf("flattenPfw: ready to decode response body from API %s", pfwFacts) + pfwRecord := ComputePfwListResp{} + err := json.Unmarshal([]byte(pfwFacts), &pfwRecord) + if err != nil { + return err + } + + log.Debugf("flattenPfw: decoded %d PFW rules for compute ID %s on ViNS ID %d", + len(pfwRecord.Rules), pfwRecord.Header.VinsID, pfwRecord.Header.VinsID) + + /* + combo := fmt.Sprintf("%d:%d", compId.(int), pfwRecord.ViNS.VinsID) + hasher := fnv.New32a() + hasher.Write([]byte(combo)) + d.SetId(fmt.Sprintf("%d", hasher.Sum32())) + */ + // set ID of this PFW rule set as "compute_id:vins_id" + d.SetId(fmt.Sprintf("%d:%d", pfwRecord.Header.ComputeID, pfwRecord.Header.VinsID)) + log.Debugf("flattenPfw: PFW rule set ID %s", d.Id()) + d.Set("compute_id", pfwRecord.Header.ComputeID) + d.Set("vins_id", pfwRecord.Header.VinsID) + + pfwRulesList := []interface{}{} + for _, runner := range pfwRecord.Rules { + rule := map[string]interface{}{ + "pub_port_start": runner.PublicPortStart, + "pub_port_end": runner.PublicPortEnd, + "local_port": runner.LocalPort, + "proto": runner.Protocol, + "rule_id": runner.ID, + } + pfwRulesList = append(pfwRulesList, rule) + } + if err = d.Set("rule", pfwRulesList); err != nil { + return err + } + + return nil +} + +func dataSourcePfwRead(d *schema.ResourceData, m interface{}) error { + pfwFacts, err := utilityPfwCheckPresence(d, m) + if pfwFacts == "" { + // if empty string is returned from dataSourcePfwRead then we got no + // PFW rules. It could also be because there was some error, which + // is indicated by non-nil err value + d.SetId("") // ensure ID is empty in this case anyway + return err + } + + return flattenPfw(d, pfwFacts) +} + +func dataSourcePfw() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + Read: dataSourcePfwRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &Timeout30s, + Default: &Timeout60s, + }, + + Schema: map[string]*schema.Schema{ + "compute_id": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "ID of the compute instance to configure port forwarding rules for.", + }, + + "vins_id": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "ID of the ViNS to configure port forwarding rules on. Compute must be already plugged into this ViNS and ViNS must have external network connection.", + }, + + "rule": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: rulesSubresourceSchemaMake(), + }, + Description: "Port forwarding rule. You may specify several rules, one in each such block.", + }, + }, + } +} diff --git a/decort/models_api.go b/decort/models_api.go index cd44e58..026efd5 100644 --- a/decort/models_api.go +++ b/decort/models_api.go @@ -446,7 +446,20 @@ type AccountsListResp []AccountRecord // // structures related to /cloudapi/portforwarding/list API // -type PfwRecord struct { +// Note the specifics of compute/pfwList response in API 3.7.x (this may be changed in the future): +// 1) if there are no PFW rules and compute is not connected to any PFW-able ViNS +// the response will be empty string +// 2) if there are no PFW rules but compute is connected to a PFW-able ViNS +// the response will contain a list with a single element - prefix (see PfwPrefixRecord) +// 3) if there are port forwarding rules, the response will contain a list which starts +// with prefix (see PfwPrefixRecord) and then followed by one or more rule records +// (see PfwRuleRecord) +type PfwPrefixRecord struct { + VinsID int `json:"vinsId"` + VinsName string `json:"vinsName"` + ComputeID int `json:"computeId"` +} +type PfwRuleRecord struct { ID int `json:"id"` LocalIP string `json:"localIp"` LocalPort int `json:"localPort"` @@ -456,9 +469,12 @@ type PfwRecord struct { ComputeID int `json:"vmId"` } -const ComputePfwListAPI = "/restmachine/cloudapi/compute/pfwList" +type ComputePfwListResp struct { + Header PfwPrefixRecord `json:"header"` + Rules []PfwRuleRecord `json:"rules"` +} -type ComputePfwListResp []PfwRecord +const ComputePfwListAPI = "/restmachine/cloudapi/compute/pfwList" const ComputePfwAddAPI = "/restmachine/cloudapi/compute/pfwAdd" diff --git a/decort/provider.go b/decort/provider.go index 0dc8876..b890a7b 100644 --- a/decort/provider.go +++ b/decort/provider.go @@ -103,7 +103,7 @@ func Provider() *schema.Provider { "decort_kvmvm": resourceCompute(), "decort_disk": resourceDisk(), "decort_vins": resourceVins(), - // "decort_pfw": resourcePfw(), + "decort_pfw": resourcePfw(), }, DataSourcesMap: map[string]*schema.Resource{ @@ -113,7 +113,7 @@ func Provider() *schema.Provider { "decort_image": dataSourceImage(), "decort_disk": dataSourceDisk(), "decort_vins": dataSourceVins(), - // "decort_pfw": dataSourcePfw(), + "decort_pfw": dataSourcePfw(), }, ConfigureFunc: providerConfigure, diff --git a/decort/resource_compute.go b/decort/resource_compute.go index a27f16f..3e8733c 100644 --- a/decort/resource_compute.go +++ b/decort/resource_compute.go @@ -102,7 +102,7 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error { } } - apiResp, err := controller.decortAPICall("POST", computeCreateAPI, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", computeCreateAPI, urlValues) if err != nil { return err } @@ -166,7 +166,7 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error { reqValues := &url.Values{} reqValues.Add("computeId", fmt.Sprintf("%d", compId)) log.Debugf("resourceComputeCreate: starting Compute ID %d after completing its resource configuration", compId) - apiResp, err = controller.decortAPICall("POST", ComputeStartAPI, reqValues) + apiResp, err, _ = controller.decortAPICall("POST", ComputeStartAPI, reqValues) if err != nil { return err } @@ -243,7 +243,7 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error { log.Debugf("resourceComputeUpdate: changing CPU %d -> %d and/or RAM %d -> %d", oldCpu.(int), newCpu.(int), oldRam.(int), newRam.(int)) - _, err := controller.decortAPICall("POST", ComputeResizeAPI, params) + _, err, _ := controller.decortAPICall("POST", ComputeResizeAPI, params) if err != nil { return err } @@ -259,7 +259,7 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error { bdsParams.Add("size", fmt.Sprintf("%d", newSize.(int))) log.Debugf("resourceComputeUpdate: compute ID %s, boot disk ID %d resize %d -> %d", d.Id(), d.Get("boot_disk_id").(int), oldSize.(int), newSize.(int)) - _, err := controller.decortAPICall("POST", DisksResizeAPI, params) + _, err, _ := controller.decortAPICall("POST", DisksResizeAPI, params) if err != nil { return err } @@ -326,7 +326,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error { detachParams.Add("computeId", d.Id()) detachParams.Add("diskId", fmt.Sprintf("%d", diskFacts.ID)) - _, err = controller.decortAPICall("POST", ComputeDiskDetachAPI, detachParams) + _, err, _ = controller.decortAPICall("POST", ComputeDiskDetachAPI, detachParams) if err != nil { // We do not fail compute deletion on data disk detach errors log.Errorf("resourceComputeDelete: error when detaching Disk ID %d: %s", diskFacts.ID, err) @@ -339,7 +339,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error { params.Add("permanently", "1") // TODO: this is for the upcoming API update - params.Add("detachdisks", "1") - _, err = controller.decortAPICall("POST", ComputeDeleteAPI, params) + _, err, _ = controller.decortAPICall("POST", ComputeDeleteAPI, params) if err != nil { return err } diff --git a/decort/resource_disk.go b/decort/resource_disk.go index a19bf08..b54fb9b 100644 --- a/decort/resource_disk.go +++ b/decort/resource_disk.go @@ -55,7 +55,7 @@ func resourceDiskCreate(d *schema.ResourceData, m interface{}) error { urlValues.Add("description", argVal.(string)) } - apiResp, err := controller.decortAPICall("POST", DisksCreateAPI, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", DisksCreateAPI, urlValues) if err != nil { return err } @@ -105,7 +105,7 @@ func resourceDiskUpdate(d *schema.ResourceData, m interface{}) error { sizeParams := &url.Values{} sizeParams.Add("diskId", d.Id()) sizeParams.Add("size", fmt.Sprintf("%d", newSize.(int))) - _, err := controller.decortAPICall("POST", DisksResizeAPI, sizeParams) + _, err, _ := controller.decortAPICall("POST", DisksResizeAPI, sizeParams) if err != nil { return err } @@ -121,7 +121,7 @@ func resourceDiskUpdate(d *schema.ResourceData, m interface{}) error { renameParams := &url.Values{} renameParams.Add("diskId", d.Id()) renameParams.Add("name", newName.(string)) - _, err := controller.decortAPICall("POST", DisksRenameAPI, renameParams) + _, err, _ := controller.decortAPICall("POST", DisksRenameAPI, renameParams) if err != nil { return err } @@ -171,7 +171,7 @@ func resourceDiskDelete(d *schema.ResourceData, m interface{}) error { params.Add("permanently", "1") controller := m.(*ControllerCfg) - _, err = controller.decortAPICall("POST", DisksDeleteAPI, params) + _, err, _ = controller.decortAPICall("POST", DisksDeleteAPI, params) if err != nil { return err } diff --git a/decort/resource_pfw.go b/decort/resource_pfw.go new file mode 100644 index 0000000..5021932 --- /dev/null +++ b/decort/resource_pfw.go @@ -0,0 +1,208 @@ +/* +Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +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" + log "github.com/sirupsen/logrus" + "net/url" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourcePfwCreate(d *schema.ResourceData, m interface{}) error { + compId := d.Get("compute_id") + + rules_set, ok := d.GetOk("rules") + if !ok || rules_set.(*schema.Set).Len() == 0 { + log.Debugf("resourcePfwCreate: empty new PFW rules set requested for compute ID %d - nothing to create", compId.(int)) + return nil + } + + log.Debugf("resourcePfwCreate: ready to setup %d PFW rules for compute ID %d", + rules_set.(*schema.Set).Len(), compId.(int)) + + controller := m.(*ControllerCfg) + apiErrCount := 0 + var lastSavedError error + + for _, runner := range rules_set.(*schema.Set).List() { + rule := runner.(map[string]interface{}) + params := &url.Values{} + params.Add("computeId", fmt.Sprintf("%d", compId.(int))) + params.Add("publicPortStart", fmt.Sprintf("%d", rule["pub_port_start"].(int))) + params.Add("publicPortEnd", fmt.Sprintf("%d", rule["pub_port_end"].(int))) + params.Add("localBasePort", fmt.Sprintf("%d", rule["local_port"].(int))) + params.Add("proto", rule["proto"].(string)) + log.Debugf("resourcePfwCreate: ready to add rule %d:%d -> %d %s for Compute ID %d", + rule["pub_port_start"].(int),rule["pub_port_end"].(int), + rule["local_port"].(int), rule["proto"].(string), + compId.(int)) + _, err, _ := controller.decortAPICall("POST", ComputePfwAddAPI, params) + if err != nil { + log.Errorf("resourcePfwCreate: error adding rule %d:%d -> %d %s for Compute ID %d: %s", + rule["pub_port_start"].(int),rule["pub_port_end"].(int), + rule["local_port"].(int), rule["proto"].(string), + compId.(int), + err) + apiErrCount++ + lastSavedError = err + } + } + + if apiErrCount > 0 { + log.Errorf("resourcePfwCreate: there were %d error(s) adding PFW rules to Compute ID %s. Last error was: %s", + apiErrCount, compId.(int), lastSavedError) + return lastSavedError + } + + return nil +} + +func resourcePfwRead(d *schema.ResourceData, m interface{}) error { + pfwFacts, err := utilityPfwCheckPresence(d, m) + if pfwFacts == "" { + // if empty string is returned from dataSourcePfwRead then we got no + // PFW rules. It could also be because there was some error, which + // is indicated by non-nil err value + d.SetId("") // ensure ID is empty in this case anyway + return err + } + + return flattenPfw(d, pfwFacts) +} + +func resourcePfwUpdate(d *schema.ResourceData, m interface{}) error { + // TODO: update not implemented yet + compId := d.Get("compute_id") + return fmt.Errorf("resourcePfwUpdate: method is not implemented yet (Compute ID %d)", compId.(int)) +} + +func resourcePfwDelete(d *schema.ResourceData, m interface{}) error { + compId := d.Get("compute_id") + + rules_set, ok := d.GetOk("rules") + if !ok || rules_set.(*schema.Set).Len() == 0 { + log.Debugf("resourcePfwCreate: no PFW rules defined for compute ID %d - nothing to delete", compId.(int)) + return nil + } + + log.Debugf("resourcePfwDelete: ready to delete %d PFW rules from compute ID %d", + rules_set.(*schema.Set).Len(), compId.(int)) + + controller := m.(*ControllerCfg) + apiErrCount := 0 + var lastSavedError error + + for _, runner := range rules_set.(*schema.Set).List() { + rule := runner.(map[string]interface{}) + params := &url.Values{} + params.Add("computeId", fmt.Sprintf("%d", compId.(int))) + params.Add("ruleId", fmt.Sprintf("%d", rule["id"].(int))) + log.Debugf("resourcePfwCreate: ready to delete rule ID%s (%d:%d -> %d %s) from Compute ID %d", + rule["id"].(int), + rule["pub_port_start"].(int),rule["pub_port_end"].(int), + rule["local_port"].(int), rule["proto"].(string), + compId.(int)) + _, err, _ := controller.decortAPICall("POST", ComputePfwDelAPI, params) + if err != nil { + log.Errorf("resourcePfwDelete: error deleting rule ID %d (%d:%d -> %d %s) from Compute ID %d: %s", + rule["id"].(int), + rule["pub_port_start"].(int),rule["pub_port_end"].(int), + rule["local_port"].(int), rule["proto"].(string), + compId.(int), + err) + apiErrCount++ + lastSavedError = err + } + } + + if apiErrCount > 0 { + log.Errorf("resourcePfwDelete: there were %d error(s) when deleting PFW rules from Compute ID %s. Last error was: %s", + apiErrCount, compId.(int), lastSavedError) + return lastSavedError + } + + return nil +} + +func resourcePfwExists(d *schema.ResourceData, m interface{}) (bool, error) { + // Reminder: according to Terraform rules, this function should not modify its ResourceData argument + log.Debugf("resourcePfwExists: called for Compute ID %d", d.Get("compute_id").(int)) + + pfwFacts, err := utilityPfwCheckPresence(d, m) + if pfwFacts == "" { + if err != nil { + return false, err + } + return false, nil + } + return true, nil +} + +func resourcePfw() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + Create: resourcePfwCreate, + Read: resourcePfwRead, + Update: resourcePfwUpdate, + Delete: resourcePfwDelete, + Exists: resourcePfwExists, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: &Timeout180s, + Read: &Timeout30s, + Update: &Timeout180s, + Delete: &Timeout60s, + Default: &Timeout60s, + }, + + Schema: map[string]*schema.Schema{ + "compute_id": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "ID of the compute instance to configure port forwarding rules for.", + }, + + "vins_id": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "ID of the ViNS to configure port forwarding rules on. Compute must be already plugged into this ViNS and ViNS must have external network connection.", + }, + + "rule": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: rulesSubresourceSchemaMake(), + }, + Description: "Port forwarding rule. You may specify several rules, one in each such block.", + }, + }, + } +} diff --git a/decort/resource_rg.go b/decort/resource_rg.go index 5935a44..662216d 100644 --- a/decort/resource_rg.go +++ b/decort/resource_rg.go @@ -115,7 +115,7 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error { url_values.Add("extIp", ext_ip.(string)) } - api_resp, err := controller.decortAPICall("POST", ResgroupCreateAPI, url_values) + api_resp, err, _ := controller.decortAPICall("POST", ResgroupCreateAPI, url_values) if err != nil { return err } @@ -233,7 +233,7 @@ func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error { if do_general_update { log.Debugf("resourceResgroupUpdate: detected delta between new and old RG specs - updating the RG") - _, err := controller.decortAPICall("POST", ResgroupUpdateAPI, url_values) + _, err, _ := controller.decortAPICall("POST", ResgroupUpdateAPI, url_values) if err != nil { return err } @@ -264,7 +264,7 @@ func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error { url_values.Add("reason", "Destroyed by DECORT Terraform provider") controller := m.(*ControllerCfg) - _, err = controller.decortAPICall("POST", ResgroupDeleteAPI, url_values) + _, err, _ = controller.decortAPICall("POST", ResgroupDeleteAPI, url_values) if err != nil { return err } diff --git a/decort/resource_vins.go b/decort/resource_vins.go index caa9b58..9c40acf 100644 --- a/decort/resource_vins.go +++ b/decort/resource_vins.go @@ -106,7 +106,7 @@ func resourceVinsCreate(d *schema.ResourceData, m interface{}) error { urlValues.Add("desc", argVal.(string)) } - apiResp, err := controller.decortAPICall("POST", apiToCall, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", apiToCall, urlValues) if err != nil { return err } @@ -154,7 +154,7 @@ func resourceVinsUpdate(d *schema.ResourceData, m interface{}) error { if oldExtNetId.(int) > 0 { // there was preexisting external net connection - disconnect ViNS - _, err := controller.decortAPICall("POST", VinsExtNetDisconnectAPI, extnetParams) + _, err, _ := controller.decortAPICall("POST", VinsExtNetDisconnectAPI, extnetParams) if err != nil { return err } @@ -163,7 +163,7 @@ func resourceVinsUpdate(d *schema.ResourceData, m interface{}) error { if newExtNedId.(int) > 0 { // new external network connection requested - connect ViNS extnetParams.Add("netId", fmt.Sprintf("%d", newExtNedId.(int))) - _, err := controller.decortAPICall("POST", VinsExtNetConnectAPI, extnetParams) + _, err, _ := controller.decortAPICall("POST", VinsExtNetConnectAPI, extnetParams) if err != nil { return err } @@ -196,7 +196,7 @@ func resourceVinsDelete(d *schema.ResourceData, m interface{}) error { params.Add("permanently", "1") // delete ViNS immediately bypassing recycle bin controller := m.(*ControllerCfg) - _, err = controller.decortAPICall("POST", VinsDeleteAPI, params) + _, err, _ = controller.decortAPICall("POST", VinsDeleteAPI, params) if err != nil { return err } diff --git a/decort/rules_subresource.go b/decort/rules_subresource.go new file mode 100644 index 0000000..d41159e --- /dev/null +++ b/decort/rules_subresource.go @@ -0,0 +1,77 @@ +/* +Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +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 rules subresource of PFW resource used +// when creating/managing port forwarding rules for a compute connected +// to the corresponding network + +func rulesSubresourceSchemaMake() 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.", + }, + + // the rest are computed + + "rule_id": { + Type: schema.TypeInt, + Computed: true, + Description: "Rule ID as assigned by the cloud platform.", + }, + + } + return rets +} diff --git a/decort/utility_account.go b/decort/utility_account.go index f1eaf34..c6b1814 100644 --- a/decort/utility_account.go +++ b/decort/utility_account.go @@ -44,7 +44,7 @@ func utilityAccountCheckPresence(d *schema.ResourceData, m interface{}) (string, // get Account right away by its ID log.Debugf("utilityAccountCheckPresence: locating Account by its ID %d", accId.(int)) urlValues.Add("accountId", fmt.Sprintf("%d", accId.(int))) - apiResp, err := controller.decortAPICall("POST", AccountsGetAPI, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", AccountsGetAPI, urlValues) if err != nil { return "", err } @@ -57,7 +57,7 @@ func utilityAccountCheckPresence(d *schema.ResourceData, m interface{}) (string, return "", fmt.Errorf("Cannot check account presence if name is empty and no account ID specified") } - apiResp, err := controller.decortAPICall("POST", AccountsListAPI, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", AccountsListAPI, urlValues) if err != nil { return "", err } @@ -131,7 +131,7 @@ func utilityGetAccountIdBySchema(d *schema.ResourceData, m interface{}) (int, er controller := m.(*ControllerCfg) urlValues := &url.Values{} - apiResp, err := controller.decortAPICall("POST", AccountsListAPI, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", AccountsListAPI, urlValues) if err != nil { return 0, err } diff --git a/decort/utility_compute.go b/decort/utility_compute.go index 8767a25..7ad2626 100644 --- a/decort/utility_compute.go +++ b/decort/utility_compute.go @@ -62,7 +62,7 @@ func (ctrl *ControllerCfg) utilityComputeExtraDisksConfigure(d *schema.ResourceD urlValues := &url.Values{} urlValues.Add("computeId", d.Id()) urlValues.Add("diskId", fmt.Sprintf("%d", disk.(int))) - _, err := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues) + _, err, _ := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues) if err != nil { // failed to attach extra disk - partial resource update apiErrCount++ @@ -85,7 +85,7 @@ func (ctrl *ControllerCfg) utilityComputeExtraDisksConfigure(d *schema.ResourceD urlValues := &url.Values{} urlValues.Add("computeId", d.Id()) urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int))) - _, err := ctrl.decortAPICall("POST", ComputeDiskDetachAPI, urlValues) + _, err, _ := ctrl.decortAPICall("POST", ComputeDiskDetachAPI, urlValues) if err != nil { // failed to detach disk - there will be partial resource update log.Errorf("utilityComputeExtraDisksConfigure: failed to detach disk ID %d from Compute ID %s: %s", diskId.(int), d.Id(), err) @@ -100,7 +100,7 @@ func (ctrl *ControllerCfg) utilityComputeExtraDisksConfigure(d *schema.ResourceD urlValues := &url.Values{} urlValues.Add("computeId", d.Id()) urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int))) - _, err := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues) + _, err, _ := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues) if err != nil { // failed to attach disk - there will be partial resource update log.Errorf("utilityComputeExtraDisksConfigure: failed to attach disk ID %d to Compute ID %s: %s", diskId.(int), d.Id(), err) @@ -145,7 +145,7 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat if ipSet { urlValues.Add("ipAddr", ipaddr.(string)) } - _, err := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues) + _, err, _ := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues) if err != nil { // failed to attach network - partial resource update apiErrCount++ @@ -169,7 +169,7 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat urlValues.Add("computeId", d.Id()) urlValues.Add("ipAddr", net_data["ip_address"].(string)) urlValues.Add("mac", net_data["mac"].(string)) - _, err := ctrl.decortAPICall("POST", ComputeNetDetachAPI, urlValues) + _, err, _ := ctrl.decortAPICall("POST", ComputeNetDetachAPI, urlValues) if err != nil { // failed to detach this network - there will be partial resource update log.Errorf("utilityComputeNetworksConfigure: failed to detach net ID %d of type %s from Compute ID %s: %s", @@ -190,7 +190,7 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat if net_data["ip_address"].(string) != "" { urlValues.Add("ipAddr", net_data["ip_address"].(string)) } - _, err := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues) + _, err, _ := ctrl.decortAPICall("POST", ComputeNetAttachAPI, urlValues) if err != nil { // failed to attach this network - there will be partial resource update log.Errorf("utilityComputeNetworksConfigure: failed to attach net ID %d of type %s to Compute ID %s: %s", @@ -244,7 +244,7 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (string, // compute ID is specified, try to get compute instance straight by this ID log.Debugf("utilityComputeCheckPresence: locating compute by its ID %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 { return "", err } @@ -264,7 +264,7 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (string, } urlValues.Add("rgId", fmt.Sprintf("%d", rgId)) - apiResp, err := controller.decortAPICall("POST", RgListComputesAPI, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", RgListComputesAPI, urlValues) if err != nil { return "", err } @@ -286,7 +286,7 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (string, // we found the Compute we need - now get detailed information via compute/get API cgetValues := &url.Values{} 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 { return "", err } diff --git a/decort/utility_disk.go b/decort/utility_disk.go index 1422c03..db2d953 100644 --- a/decort/utility_disk.go +++ b/decort/utility_disk.go @@ -75,7 +75,7 @@ func utilityDiskCheckPresence(d *schema.ResourceData, m interface{}) (string, er // disk ID is specified, try to get disk instance straight by this ID log.Debugf("utilityDiskCheckPresence: locating disk by its ID %d", theId) urlValues.Add("diskId", fmt.Sprintf("%d", theId)) - diskFacts, err := controller.decortAPICall("POST", DisksGetAPI, urlValues) + diskFacts, err, _ := controller.decortAPICall("POST", DisksGetAPI, urlValues) if err != nil { return "", err } @@ -98,7 +98,7 @@ func utilityDiskCheckPresence(d *schema.ResourceData, m interface{}) (string, er } urlValues.Add("accountId", fmt.Sprintf("%d", validatedAccountId)) - diskFacts, err := controller.decortAPICall("POST", DisksListAPI, urlValues) + diskFacts, err, _ := controller.decortAPICall("POST", DisksListAPI, urlValues) if err != nil { return "", err } diff --git a/decort/utility_location.go b/decort/utility_location.go index c1fd12f..7cc33fd 100644 --- a/decort/utility_location.go +++ b/decort/utility_location.go @@ -41,7 +41,7 @@ func (controller *ControllerCfg) utilityLocationGetDefaultGridID() (int, error) urlValues := &url.Values{} log.Debug("utilityLocationGetDefaultGridID: retrieving locations list") - apiResp, err := controller.decortAPICall("POST", LocationsListAPI, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", LocationsListAPI, urlValues) if err != nil { return 0, err } diff --git a/decort/utility_pfw.go b/decort/utility_pfw.go new file mode 100644 index 0000000..b460c34 --- /dev/null +++ b/decort/utility_pfw.go @@ -0,0 +1,166 @@ +/* +Copyright (c) 2020-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +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. +*/ + +/* +This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +Technology platfom. + +Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates. +*/ + +package decort + +import ( + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" + + log "github.com/sirupsen/logrus" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func utilityPfwCheckPresence(d *schema.ResourceData, m interface{}) (string, 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. + // + + controller := m.(*ControllerCfg) + urlValues := &url.Values{} + + // NOTE on importing PFW into TF state resource: + // + // Port forward rules are NOT represented by any "individual" resource in the platform. + // Consequently, there is no unique ID reported by the platform that could be used to + // identify PFW rule set. + // However, we need some ID to identify PFW resource in TF state, and compute ID is the most + // convenient way, as it is: + // 1) unique; + // 2) compute may have only one PFW rule set. + // + + var compId, vinsId int + + if d.Id() != "" { + log.Debugf("utilityPfwCheckPresence: setting context from d.Id() %s", d.Id()) + idParts := strings.SplitN(d.Id(), ":", 2) + compId, _ = strconv.Atoi(idParts[0]) + vinsId, _ = strconv.Atoi(idParts[1]) + log.Debugf("utilityPfwCheckPresence: extracted Compute ID %d, ViNS %d", compId, vinsId) + if compId <= 0 || vinsId <= 0 { + return "", fmt.Errorf("Ivalid context from d.Id %s", d.Id()) + } + } else { + scId, cSet := d.GetOk("compute_id") + svId, vSet := d.GetOk("vins_id") + if cSet || vSet { + log.Debugf("utilityPfwCheckPresence: setting Compute ID from schema") + compId = scId.(int) + vinsId = svId.(int) + log.Debugf("utilityPfwCheckPresence: extractted Compute ID %d, ViNS %d", compId, vinsId) + } else { + return "", fmt.Errorf("Cannot get context to check PFW rules neither from d.Id() nor from schema") + } + } + + log.Debugf("utilityPfwCheckPresence: preparing to get PFW rules for Compute ID %d on ViNS ID %d", compId, vinsId) + + urlValues.Add("computeId", fmt.Sprintf("%d", compId)) + apiResp, err, respCode := controller.decortAPICall("POST", ComputePfwListAPI, urlValues) + if respCode == 500 { + // this is workaround for API 3.7.0 "feature" - will be removed in one of the future versions + log.Errorf("utilityPfwCheckPresence: Compute ID %d has no PFW and no connection to PFW-ready ViNS", compId) + return "", nil + } + if err != nil { + + return "", err + } + + pfwListResp := ComputePfwListResp{} + + // Note the specifics of compute/pfwList response in API 3.7.x (this may be changed in the future): + // 1) if there are no PFW rules and compute is not connected to any PFW-able ViNS + // the response will be empty string (or HTTP error code 500) + // 2) if there are no PFW rules but compute is connected to a PFW-able ViNS + // the response will contain a list with a single element - prefix (see PfwPrefixRecord) + // 3) if there are port forwarding rules, the response will contain a list which starts + // with prefix (see PfwPrefixRecord) and then followed by one or more rule records + // (see PfwRuleRecord) + // + // EXTRA NOTE: in API 3.7.0 and the likes pfwList returns HTTP response code 500 for a compute + // that is not connected to any PFW-able ViNS - need to implement temporary workaround + + if apiResp == "" { + // No port forward rules defined for this compute + return "", nil + } + + log.Debugf("utilityPfwCheckPresence: ready to split API response string %s", apiResp) + + twoParts := strings.SplitN(apiResp, "},", 2) + if len(twoParts) < 1 || len(twoParts) > 2 { + // Case: invalid format of API response + log.Errorf("utilityPfwCheckPresence: non-empty pfwList response for compute ID %d failed to split properly", compId) + return "", fmt.Errorf("Non-empty pfwList response failed to split properly") + } + + if len(twoParts) == 1 { + // Case: compute is connected to a PWF-ready ViNS but has no PFW rules defined + log.Debugf("utilityPfwCheckPresence: compute ID %d is connected to PFW-ready ViNS but has no PFW rules", compId) + return "", nil + } + + // Case: compute is connected to a PFW ready ViNS and has some PFW rule + prefixResp := strings.TrimSuffix(strings.TrimPrefix(twoParts[0], "["), ",") + "}" + log.Debugf("utilityPfwCheckPresence: ready to unmarshal prefix part %s", prefixResp) + err = json.Unmarshal([]byte(prefixResp), &pfwListResp.Header) + if err != nil { + log.Errorf("utilityPfwCheckPresence: failed to unmarshal prefix part of API response: %s", err) + return "", err + } + + rulesResp := "[" + twoParts[1] + log.Debugf("utilityPfwCheckPresence: ready to unmarshal rules part %s", rulesResp) + err = json.Unmarshal([]byte(rulesResp), &pfwListResp.Rules) + if err != nil { + log.Errorf("utilityPfwCheckPresence: failed to unmarshal rules part of API response: %s", err) + return "", err + } + + log.Debugf("utilityPfwCheckPresence: successfully read %d port forward rules for Compute ID %d, ViNS ID %d", + len(pfwListResp.Rules), compId, pfwListResp.Header.VinsID) + + if pfwListResp.Header.VinsID != vinsId { + log.Errorf("utilityPfwCheckPresence: ViNS ID mismatch for PFW rules on compute ID %d: actual %d, required %d", + compId, pfwListResp.Header.VinsID, vinsId) + return "", fmt.Errorf("ViNS ID mismatch for PFW rules on compute ID %d: actual %d, required %d", + compId, pfwListResp.Header.VinsID, vinsId) + } + + // reconstruct API response string for return + pfwListResp.Header.ComputeID = compId + reencodedItem, err := json.Marshal(pfwListResp) + if err != nil { + return "", err + } + + return string(reencodedItem[:]), nil +} diff --git a/decort/utility_rg.go b/decort/utility_rg.go index 371e678..40abd53 100644 --- a/decort/utility_rg.go +++ b/decort/utility_rg.go @@ -39,7 +39,7 @@ import ( func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupGetResp, error) { urlValues := &url.Values{} urlValues.Add("rgId", fmt.Sprintf("%d", rgid)) - rgFacts, err := ctrl.decortAPICall("POST", ResgroupGetAPI, urlValues) + rgFacts, err, _ := ctrl.decortAPICall("POST", ResgroupGetAPI, urlValues) if err != nil { return nil, err } @@ -109,7 +109,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string // go straight for the RG by its ID log.Debugf("utilityResgroupCheckPresence: locating RG by its ID %d", theId) urlValues.Add("rgId", fmt.Sprintf("%d", theId)) - rgFacts, err := controller.decortAPICall("POST", ResgroupGetAPI, urlValues) + rgFacts, err, _ := controller.decortAPICall("POST", ResgroupGetAPI, urlValues) if err != nil { return "", err } @@ -130,7 +130,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string } urlValues.Add("includedeleted", "false") - apiResp, err := controller.decortAPICall("POST", ResgroupListAPI, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", ResgroupListAPI, urlValues) if err != nil { return "", err } @@ -154,7 +154,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string // Namely, we need resource quota settings reqValues := &url.Values{} reqValues.Add("rgId", fmt.Sprintf("%d", item.ID)) - apiResp, err := controller.decortAPICall("POST", ResgroupGetAPI, reqValues) + apiResp, err, _ := controller.decortAPICall("POST", ResgroupGetAPI, reqValues) if err != nil { return "", err } diff --git a/decort/utility_vins.go b/decort/utility_vins.go index f64cc79..3a2147a 100644 --- a/decort/utility_vins.go +++ b/decort/utility_vins.go @@ -38,7 +38,7 @@ import ( func (ctrl *ControllerCfg) utilityVinsConfigGet(vinsid int) (*VinsRecord, error) { urlValues := &url.Values{} urlValues.Add("vinsId", fmt.Sprintf("%d", vinsid)) - vinsFacts, err := ctrl.decortAPICall("POST", VinsGetAPI, urlValues) + vinsFacts, err, _ := ctrl.decortAPICall("POST", VinsGetAPI, urlValues) if err != nil { return nil, err } @@ -94,7 +94,7 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er // 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 := controller.decortAPICall("POST", VinsGetAPI, urlValues) + vinsFacts, err, _ := controller.decortAPICall("POST", VinsGetAPI, urlValues) if err != nil { return "", err } @@ -125,7 +125,7 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er urlValues.Add("accountId", fmt.Sprintf("%d", accountId.(int))) } - apiResp, err := controller.decortAPICall("POST", VinsSearchAPI, urlValues) + apiResp, err, _ := controller.decortAPICall("POST", VinsSearchAPI, urlValues) if err != nil { return "", err } @@ -154,7 +154,7 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er // 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 := controller.decortAPICall("POST", VinsGetAPI, rqValues) + vinsGetResp, err, _ := controller.decortAPICall("POST", VinsGetAPI, rqValues) if err != nil { return "", err }