Compare commits

...

3 Commits

@ -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.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 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)

@ -132,7 +132,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
} }
ret_config.auth_mode_code = MODE_LEGACY ret_config.auth_mode_code = MODE_LEGACY
default: 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 { if allow_unverified_ssl {
@ -205,7 +205,7 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
return "", fmt.Errorf("getOAuth2JWT method called for undefined authorization mode.") return "", fmt.Errorf("getOAuth2JWT method called for undefined authorization mode.")
} }
if config.auth_mode_code != MODE_OAUTH2 { 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{} params := url.Values{}
@ -231,7 +231,7 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
// fmt.Println("response Status:", resp.Status) // fmt.Println("response Status:", resp.Status)
// fmt.Println("response Headers:", resp.Header) // fmt.Println("response Headers:", resp.Header)
// fmt.Println("response Headers:", req.URL) // 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) resp.StatusCode, req.URL, config.app_id, params_str)
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -279,7 +279,7 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
return false, err return false, err
} }
if resp.StatusCode != http.StatusOK { 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) resp.StatusCode, req.URL)
} }
defer resp.Body.Close() 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.") return false, fmt.Errorf("validateLegacyUser method called for undefined authorization mode.")
} }
if config.auth_mode_code != MODE_LEGACY { 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{} params := url.Values{}
@ -319,7 +319,7 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
return false, err return false, err
} }
if resp.StatusCode != http.StatusOK { 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) resp.StatusCode, config.legacy_user, config.controller_url)
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -335,13 +335,15 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
return true, nil 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 // 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. // authorization mode for which the provider was initialized and compiles request accordingly.
hrc = 0 // HTTP Response Code
if config.cc_client == nil { if config.cc_client == nil {
// this should never happen if ClientConfig was properly called prior to decortAPICall // 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: // 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 { 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 { 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)) req, err := http.NewRequest(method, config.controller_url + api_name, strings.NewReader(params_str))
if err != nil { if err != nil {
return "", err return "", err, 0
} }
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Content-Length", strconv.Itoa(len(params_str))) 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) resp, err := config.cc_client.Do(req)
if err != nil { if err != nil {
return "", err return "", err, 0
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode == http.StatusOK { if resp.StatusCode == http.StatusOK {
tmp_body, err := ioutil.ReadAll(resp.Body) tmp_body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", err return "", err, resp.StatusCode
} }
json_resp := Jo2JSON(string(tmp_body)) json_resp := Jo2JSON(string(tmp_body))
log.Debugf("decortAPICall: %s %s\n %s", method, api_name, json_resp) log.Debugf("decortAPICall: %s %s\n %s", method, api_name, json_resp)
return json_resp, nil return json_resp, nil, resp.StatusCode
} else { } else {
return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %q with request Body %q", return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %s with request Body %s",
resp.StatusCode, req.URL, params_str) resp.StatusCode, req.URL, params_str), resp.StatusCode
} }
/* /*
if resp.StatusCode == StatusServiceUnavailable { 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
} }

@ -174,32 +174,6 @@ func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []interface{} {
return result 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 // NOTE: this function is retained for historical purposes and actually not used as of rc-1.10
func parseComputeInterfaces(ifaces []InterfaceRecord) []map[string]interface{} { func parseComputeInterfaces(ifaces []InterfaceRecord) []map[string]interface{} {

@ -45,7 +45,7 @@ func dataSourceImageRead(d *schema.ResourceData, m interface{}) error {
if accSet { if accSet {
url_values.Add("accountId", fmt.Sprintf("%d", accId.(int))) 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 { if err != nil {
return err return err
} }

@ -0,0 +1,144 @@
/*
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"
// "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
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)
/*
Here it gets a little bit interesting.
Unlike compute or disk, port forwaring rules are NOT represented by any cloud
platform resource, which might have had a unique ID. They are just a subset of
rules in the list maintained by the corresponding ViNS instance. However,
Terraform needs a unique ID for each resource it manages so that it could be
stored in the state file and retrieved for use.
Therefore we need to make up an ID and supply it to Terraform in a standard
way (i.e. by calling d.SetId(...)).
Fortunately, a combination of Compute ID and ViNS ID with GW VNF, where this
compute is plugged in, makes a unique string, so we use it as an ID for
the PFW ruleset.
The following few lines are legacy from the first attempt to make an ID
as a hash of concatenated Compute ID & ViNS ID, but it did not work as
expected for a number of reasons, which explanation is not a primary
intent of the comment in the source code.
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.",
},
// TODO: consider making "rule" attribute Required with MinItems = 1
"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.",
},
},
}
}

@ -446,7 +446,20 @@ type AccountsListResp []AccountRecord
// //
// structures related to /cloudapi/portforwarding/list API // 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"` ID int `json:"id"`
LocalIP string `json:"localIp"` LocalIP string `json:"localIp"`
LocalPort int `json:"localPort"` LocalPort int `json:"localPort"`
@ -456,9 +469,12 @@ type PfwRecord struct {
ComputeID int `json:"vmId"` 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" const ComputePfwAddAPI = "/restmachine/cloudapi/compute/pfwAdd"

@ -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_pfw": resourcePfw(),
}, },
DataSourcesMap: map[string]*schema.Resource{ DataSourcesMap: map[string]*schema.Resource{
@ -113,7 +113,7 @@ 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_pfw": dataSourcePfw(),
}, },
ConfigureFunc: providerConfigure, ConfigureFunc: providerConfigure,

@ -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 { if err != nil {
return err return err
} }
@ -166,7 +166,7 @@ func resourceComputeCreate(d *schema.ResourceData, m interface{}) error {
reqValues := &url.Values{} reqValues := &url.Values{}
reqValues.Add("computeId", fmt.Sprintf("%d", compId)) reqValues.Add("computeId", fmt.Sprintf("%d", compId))
log.Debugf("resourceComputeCreate: starting Compute ID %d after completing its resource configuration", 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 { if err != nil {
return err return err
} }
@ -186,6 +186,7 @@ func resourceComputeRead(d *schema.ResourceData, m interface{}) error {
compFacts, err := utilityComputeCheckPresence(d, m) compFacts, err := utilityComputeCheckPresence(d, m)
if compFacts == "" { if compFacts == "" {
d.SetId("")
if err != nil { if err != nil {
return err return err
} }
@ -243,7 +244,7 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceComputeUpdate: changing CPU %d -> %d and/or RAM %d -> %d", log.Debugf("resourceComputeUpdate: changing CPU %d -> %d and/or RAM %d -> %d",
oldCpu.(int), newCpu.(int), oldCpu.(int), newCpu.(int),
oldRam.(int), newRam.(int)) oldRam.(int), newRam.(int))
_, err := controller.decortAPICall("POST", ComputeResizeAPI, params) _, err, _ := controller.decortAPICall("POST", ComputeResizeAPI, params)
if err != nil { if err != nil {
return err return err
} }
@ -259,7 +260,7 @@ func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error {
bdsParams.Add("size", fmt.Sprintf("%d", newSize.(int))) bdsParams.Add("size", fmt.Sprintf("%d", newSize.(int)))
log.Debugf("resourceComputeUpdate: compute ID %s, boot disk ID %d resize %d -> %d", 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)) 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 { if err != nil {
return err return err
} }
@ -326,7 +327,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error {
detachParams.Add("computeId", d.Id()) detachParams.Add("computeId", d.Id())
detachParams.Add("diskId", fmt.Sprintf("%d", diskFacts.ID)) detachParams.Add("diskId", fmt.Sprintf("%d", diskFacts.ID))
_, err = controller.decortAPICall("POST", ComputeDiskDetachAPI, detachParams) _, err, _ = controller.decortAPICall("POST", ComputeDiskDetachAPI, detachParams)
if err != nil { if err != nil {
// We do not fail compute deletion on data disk detach errors // We do not fail compute deletion on data disk detach errors
log.Errorf("resourceComputeDelete: error when detaching Disk ID %d: %s", diskFacts.ID, err) log.Errorf("resourceComputeDelete: error when detaching Disk ID %d: %s", diskFacts.ID, err)
@ -339,7 +340,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error {
params.Add("permanently", "1") params.Add("permanently", "1")
// TODO: this is for the upcoming API update - params.Add("detachdisks", "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 { if err != nil {
return err return err
} }

@ -55,7 +55,7 @@ func resourceDiskCreate(d *schema.ResourceData, m interface{}) error {
urlValues.Add("description", argVal.(string)) urlValues.Add("description", argVal.(string))
} }
apiResp, err := controller.decortAPICall("POST", DisksCreateAPI, urlValues) apiResp, err, _ := controller.decortAPICall("POST", DisksCreateAPI, urlValues)
if err != nil { if err != nil {
return err return err
} }
@ -105,7 +105,7 @@ func resourceDiskUpdate(d *schema.ResourceData, m interface{}) error {
sizeParams := &url.Values{} sizeParams := &url.Values{}
sizeParams.Add("diskId", d.Id()) sizeParams.Add("diskId", d.Id())
sizeParams.Add("size", fmt.Sprintf("%d", newSize.(int))) sizeParams.Add("size", fmt.Sprintf("%d", newSize.(int)))
_, err := controller.decortAPICall("POST", DisksResizeAPI, sizeParams) _, err, _ := controller.decortAPICall("POST", DisksResizeAPI, sizeParams)
if err != nil { if err != nil {
return err return err
} }
@ -121,7 +121,7 @@ func resourceDiskUpdate(d *schema.ResourceData, m interface{}) error {
renameParams := &url.Values{} renameParams := &url.Values{}
renameParams.Add("diskId", d.Id()) renameParams.Add("diskId", d.Id())
renameParams.Add("name", newName.(string)) renameParams.Add("name", newName.(string))
_, err := controller.decortAPICall("POST", DisksRenameAPI, renameParams) _, err, _ := controller.decortAPICall("POST", DisksRenameAPI, renameParams)
if err != nil { if err != nil {
return err return err
} }
@ -171,7 +171,7 @@ func resourceDiskDelete(d *schema.ResourceData, m interface{}) error {
params.Add("permanently", "1") params.Add("permanently", "1")
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
_, err = controller.decortAPICall("POST", DisksDeleteAPI, params) _, err, _ = controller.decortAPICall("POST", DisksDeleteAPI, params)
if err != nil { if err != nil {
return err return err
} }

@ -0,0 +1,212 @@
/*
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"
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("rule")
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 resourcePfwRead(d, m)
}
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))
// return resourcePfwRead(d, m)
}
func resourcePfwDelete(d *schema.ResourceData, m interface{}) error {
compId := d.Get("compute_id")
rules_set, ok := d.GetOk("rule")
if !ok || rules_set.(*schema.Set).Len() == 0 {
log.Debugf("resourcePfwDelete: 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["rule_id"].(int)))
log.Debugf("resourcePfwCreate: ready to delete rule ID%s (%d:%d -> %d %s) from Compute ID %d",
rule["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["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.",
},
// TODO: consider making "rule" attribute Required with MinItems = 1 to prevent
// empty PFW list definition
"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.",
},
},
}
}

@ -115,7 +115,7 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
url_values.Add("extIp", ext_ip.(string)) 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 { if err != nil {
return err return err
} }
@ -233,7 +233,7 @@ func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error {
if do_general_update { if do_general_update {
log.Debugf("resourceResgroupUpdate: detected delta between new and old RG specs - updating the RG") 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 { if err != nil {
return err return err
} }
@ -264,7 +264,7 @@ func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error {
url_values.Add("reason", "Destroyed by DECORT Terraform provider") url_values.Add("reason", "Destroyed by DECORT Terraform provider")
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
_, err = controller.decortAPICall("POST", ResgroupDeleteAPI, url_values) _, err, _ = controller.decortAPICall("POST", ResgroupDeleteAPI, url_values)
if err != nil { if err != nil {
return err return err
} }

@ -106,7 +106,7 @@ func resourceVinsCreate(d *schema.ResourceData, m interface{}) error {
urlValues.Add("desc", argVal.(string)) urlValues.Add("desc", argVal.(string))
} }
apiResp, err := controller.decortAPICall("POST", apiToCall, urlValues) apiResp, err, _ := controller.decortAPICall("POST", apiToCall, urlValues)
if err != nil { if err != nil {
return err return err
} }
@ -154,7 +154,7 @@ func resourceVinsUpdate(d *schema.ResourceData, m interface{}) error {
if oldExtNetId.(int) > 0 { if oldExtNetId.(int) > 0 {
// there was preexisting external net connection - disconnect ViNS // there was preexisting external net connection - disconnect ViNS
_, err := controller.decortAPICall("POST", VinsExtNetDisconnectAPI, extnetParams) _, err, _ := controller.decortAPICall("POST", VinsExtNetDisconnectAPI, extnetParams)
if err != nil { if err != nil {
return err return err
} }
@ -163,7 +163,7 @@ func resourceVinsUpdate(d *schema.ResourceData, m interface{}) error {
if newExtNedId.(int) > 0 { if newExtNedId.(int) > 0 {
// new external network connection requested - connect ViNS // new external network connection requested - connect ViNS
extnetParams.Add("netId", fmt.Sprintf("%d", newExtNedId.(int))) extnetParams.Add("netId", fmt.Sprintf("%d", newExtNedId.(int)))
_, err := controller.decortAPICall("POST", VinsExtNetConnectAPI, extnetParams) _, err, _ := controller.decortAPICall("POST", VinsExtNetConnectAPI, extnetParams)
if err != nil { if err != nil {
return err return err
} }
@ -196,7 +196,7 @@ func resourceVinsDelete(d *schema.ResourceData, m interface{}) error {
params.Add("permanently", "1") // delete ViNS immediately bypassing recycle bin params.Add("permanently", "1") // delete ViNS immediately bypassing recycle bin
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
_, err = controller.decortAPICall("POST", VinsDeleteAPI, params) _, err, _ = controller.decortAPICall("POST", VinsDeleteAPI, params)
if err != nil { if err != nil {
return err return err
} }

@ -0,0 +1,77 @@
/*
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 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
}

@ -44,7 +44,7 @@ func utilityAccountCheckPresence(d *schema.ResourceData, m interface{}) (string,
// get Account right away by its ID // get Account right away by its ID
log.Debugf("utilityAccountCheckPresence: locating Account by its ID %d", accId.(int)) log.Debugf("utilityAccountCheckPresence: locating Account by its ID %d", accId.(int))
urlValues.Add("accountId", fmt.Sprintf("%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 { if err != nil {
return "", err 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") 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 { if err != nil {
return "", err return "", err
} }
@ -131,7 +131,7 @@ func utilityGetAccountIdBySchema(d *schema.ResourceData, m interface{}) (int, er
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
urlValues := &url.Values{} urlValues := &url.Values{}
apiResp, err := controller.decortAPICall("POST", AccountsListAPI, urlValues) apiResp, err, _ := controller.decortAPICall("POST", AccountsListAPI, urlValues)
if err != nil { if err != nil {
return 0, err return 0, err
} }

@ -62,7 +62,7 @@ func (ctrl *ControllerCfg) utilityComputeExtraDisksConfigure(d *schema.ResourceD
urlValues := &url.Values{} urlValues := &url.Values{}
urlValues.Add("computeId", d.Id()) urlValues.Add("computeId", d.Id())
urlValues.Add("diskId", fmt.Sprintf("%d", disk.(int))) urlValues.Add("diskId", fmt.Sprintf("%d", disk.(int)))
_, err := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues) _, err, _ := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues)
if err != nil { if err != nil {
// failed to attach extra disk - partial resource update // failed to attach extra disk - partial resource update
apiErrCount++ apiErrCount++
@ -85,7 +85,7 @@ func (ctrl *ControllerCfg) utilityComputeExtraDisksConfigure(d *schema.ResourceD
urlValues := &url.Values{} urlValues := &url.Values{}
urlValues.Add("computeId", d.Id()) urlValues.Add("computeId", d.Id())
urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int))) urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int)))
_, err := ctrl.decortAPICall("POST", ComputeDiskDetachAPI, urlValues) _, err, _ := ctrl.decortAPICall("POST", ComputeDiskDetachAPI, urlValues)
if err != nil { if err != nil {
// failed to detach disk - there will be partial resource update // 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) 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 := &url.Values{}
urlValues.Add("computeId", d.Id()) urlValues.Add("computeId", d.Id())
urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int))) urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int)))
_, err := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues) _, err, _ := ctrl.decortAPICall("POST", ComputeDiskAttachAPI, urlValues)
if err != nil { if err != nil {
// failed to attach disk - there will be partial resource update // 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) 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 { if ipSet {
urlValues.Add("ipAddr", ipaddr.(string)) urlValues.Add("ipAddr", ipaddr.(string))
} }
_, 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++
@ -169,7 +169,7 @@ func (ctrl *ControllerCfg) utilityComputeNetworksConfigure(d *schema.ResourceDat
urlValues.Add("computeId", d.Id()) urlValues.Add("computeId", d.Id())
urlValues.Add("ipAddr", net_data["ip_address"].(string)) urlValues.Add("ipAddr", net_data["ip_address"].(string))
urlValues.Add("mac", net_data["mac"].(string)) urlValues.Add("mac", net_data["mac"].(string))
_, err := ctrl.decortAPICall("POST", ComputeNetDetachAPI, urlValues) _, err, _ := ctrl.decortAPICall("POST", ComputeNetDetachAPI, urlValues)
if err != nil { if err != nil {
// failed to detach this network - there will be partial resource update // 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", 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) != "" { if net_data["ip_address"].(string) != "" {
urlValues.Add("ipAddr", 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 { if err != nil {
// failed to attach this network - there will be partial resource update // 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", 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 // compute ID is specified, try to get compute instance straight by this ID
log.Debugf("utilityComputeCheckPresence: locating compute by its ID %d", theId) log.Debugf("utilityComputeCheckPresence: locating compute by its ID %d", theId)
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 "", err
} }
@ -264,7 +264,7 @@ func utilityComputeCheckPresence(d *schema.ResourceData, m interface{}) (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 "", 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 // we found the Compute we need - now get detailed information via compute/get API
cgetValues := &url.Values{} cgetValues := &url.Values{}
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 "", err
} }

@ -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 // disk ID is specified, try to get disk instance straight by this ID
log.Debugf("utilityDiskCheckPresence: locating disk by its ID %d", theId) log.Debugf("utilityDiskCheckPresence: locating disk by its ID %d", theId)
urlValues.Add("diskId", fmt.Sprintf("%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 { if err != nil {
return "", err return "", err
} }
@ -98,7 +98,7 @@ func utilityDiskCheckPresence(d *schema.ResourceData, m interface{}) (string, er
} }
urlValues.Add("accountId", fmt.Sprintf("%d", validatedAccountId)) urlValues.Add("accountId", fmt.Sprintf("%d", validatedAccountId))
diskFacts, err := controller.decortAPICall("POST", DisksListAPI, urlValues) diskFacts, err, _ := controller.decortAPICall("POST", DisksListAPI, urlValues)
if err != nil { if err != nil {
return "", err return "", err
} }

@ -41,7 +41,7 @@ func (controller *ControllerCfg) utilityLocationGetDefaultGridID() (int, error)
urlValues := &url.Values{} urlValues := &url.Values{}
log.Debug("utilityLocationGetDefaultGridID: retrieving locations list") log.Debug("utilityLocationGetDefaultGridID: retrieving locations list")
apiResp, err := controller.decortAPICall("POST", LocationsListAPI, urlValues) apiResp, err, _ := controller.decortAPICall("POST", LocationsListAPI, urlValues)
if err != nil { if err != nil {
return 0, err return 0, err
} }

@ -0,0 +1,208 @@
/*
Copyright (c) 2020-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.
*/
/*
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.
//
// The string returned by this method mimics response of an imaginary API that will
// report PFW rules (if any) as following:
// {
// "header": {
// "computeId": <int>,
// "vinsId": <int>,
// },
// "rules": [
// {
// "id": <int>,
// "localPort": <int>,
// "protocol": <int>,
// "publicPortStart": <int>,
// "publicPortEnd": <int>,
// "vmId": <int>,
// },
// {
// ...
// },
// ],
// }
// This response unmarshalls into ComputePfwListResp structure.
// NOTE: If there are no rules for this compute, an empty string is returned along with err=nil
//
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
var compId, vinsId int
// PFW resource ID is a combination of compute_id and vins_id separated by ":"
// See a note in "flattenPfw" method explaining the rationale behind this approach.
if d.Id() != "" {
log.Debugf("utilityPfwCheckPresence: setting context from PFW 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: extracted Compute ID %d, ViNS %d", compId, vinsId)
} else {
return "", fmt.Errorf("Cannot get context to check PFW rules neither from PFW 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",
len(pfwListResp.Rules), compId)
if len(pfwListResp.Rules) == 0 {
// this compute technically can have rules, but no rules are currently defined
return "", nil
}
// Now we can double check that the PFWs we've got do belong to the compute & ViNS specified in the
// PFW definition. Do so by reading compute details and comparing NatableVinsID value with the
// specified ViNS ID
//
// We may reuse urlValues here, as it already contains computeId field (see initialization above)
apiResp, err, _ = controller.decortAPICall("POST", ComputeGetAPI, urlValues)
if err != nil || apiResp == "" {
log.Errorf("utilityPfwCheckPresence: failed to get Compute ID %d details", compId)
return "", err
}
compFacts := ComputeGetResp{}
err = json.Unmarshal([]byte(rulesResp), &apiResp)
if err != nil {
log.Errorf("utilityPfwCheckPresence: failed to unmarshal compute details for ID %d: %s", compId, err)
return "", err
}
if compFacts.NatableVinsID <= 0 {
log.Errorf("utilityPfwCheckPresence: compute ID %d is not connected to a NAT-able ViNS", compId)
return "", fmt.Errorf("Compute ID %d is not connected to a NAT-able ViNS", compId)
}
if compFacts.NatableVinsID != vinsId {
log.Errorf("utilityPfwCheckPresence: ViNS ID mismatch for PFW rules on compute ID %d: actual %d, required %d",
compId, compFacts.NatableVinsID, vinsId)
return "", fmt.Errorf("ViNS ID mismatch for PFW rules on compute ID %d: actual %d, required %d",
compId, compFacts.NatableVinsID, vinsId)
}
// reconstruct API response string to return
pfwListResp.Header.ComputeID = compId
pfwListResp.Header.VinsID = compFacts.NatableVinsID
reencodedItem, err := json.Marshal(pfwListResp)
if err != nil {
return "", err
}
return string(reencodedItem[:]), nil
}

@ -39,7 +39,7 @@ import (
func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupGetResp, error) { func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupGetResp, error) {
urlValues := &url.Values{} urlValues := &url.Values{}
urlValues.Add("rgId", fmt.Sprintf("%d", rgid)) urlValues.Add("rgId", fmt.Sprintf("%d", rgid))
rgFacts, err := ctrl.decortAPICall("POST", ResgroupGetAPI, urlValues) rgFacts, err, _ := ctrl.decortAPICall("POST", ResgroupGetAPI, urlValues)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -109,7 +109,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
// go straight for the RG by its ID // go straight for the RG by its ID
log.Debugf("utilityResgroupCheckPresence: locating RG by its ID %d", theId) log.Debugf("utilityResgroupCheckPresence: locating RG by its ID %d", theId)
urlValues.Add("rgId", fmt.Sprintf("%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 { if err != nil {
return "", err return "", err
} }
@ -130,7 +130,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
} }
urlValues.Add("includedeleted", "false") urlValues.Add("includedeleted", "false")
apiResp, err := controller.decortAPICall("POST", ResgroupListAPI, urlValues) apiResp, err, _ := controller.decortAPICall("POST", ResgroupListAPI, urlValues)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -154,7 +154,7 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
// Namely, we need resource quota settings // Namely, we need resource quota settings
reqValues := &url.Values{} reqValues := &url.Values{}
reqValues.Add("rgId", fmt.Sprintf("%d", item.ID)) 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 { if err != nil {
return "", err return "", err
} }

@ -38,7 +38,7 @@ import (
func (ctrl *ControllerCfg) utilityVinsConfigGet(vinsid int) (*VinsRecord, error) { func (ctrl *ControllerCfg) utilityVinsConfigGet(vinsid int) (*VinsRecord, error) {
urlValues := &url.Values{} urlValues := &url.Values{}
urlValues.Add("vinsId", fmt.Sprintf("%d", vinsid)) urlValues.Add("vinsId", fmt.Sprintf("%d", vinsid))
vinsFacts, err := ctrl.decortAPICall("POST", VinsGetAPI, urlValues) vinsFacts, err, _ := ctrl.decortAPICall("POST", VinsGetAPI, urlValues)
if err != nil { if err != nil {
return nil, err 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 // ViNS ID is specified, try to get compute instance straight by this ID
log.Debugf("utilityVinsCheckPresence: locating ViNS by its ID %d", theId) log.Debugf("utilityVinsCheckPresence: locating ViNS by its ID %d", theId)
urlValues.Add("vinsId", fmt.Sprintf("%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 { if err != nil {
return "", err return "", err
} }
@ -125,7 +125,7 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er
urlValues.Add("accountId", fmt.Sprintf("%d", accountId.(int))) 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 { if err != nil {
return "", err 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 // manage ViNS, so we have to get detailed info by calling API vins/get
rqValues := &url.Values{} rqValues := &url.Values{}
rqValues.Add("vinsId", fmt.Sprintf("%d",item.ID)) 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 { if err != nil {
return "", err return "", err
} }

Loading…
Cancel
Save