Implementing ViNS resource and polishing logic in other methods

rc-1.20
Sergey Shubin svs1370 4 years ago
parent 1ea7a27b21
commit e86224f95f

@ -299,6 +299,8 @@ func dataSourceCompute() *schema.Resource {
Description: "Name of this compute instance. NOTE: this parameter is case sensitive.", Description: "Name of this compute instance. NOTE: this parameter is case sensitive.",
}, },
// TODO: consider removing compute_id from the schema, as it not practical to call this data provider if
// corresponding compute ID is already known
"compute_id": { "compute_id": {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,

@ -51,12 +51,14 @@ func flattenVins(d *schema.ResourceData, vins_facts string) error {
vinsRecord.Name, vinsRecord.ID, vinsRecord.AccountID, vinsRecord.RgID) vinsRecord.Name, vinsRecord.ID, vinsRecord.AccountID, vinsRecord.RgID)
d.SetId(fmt.Sprintf("%d", vinsRecord.ID)) d.SetId(fmt.Sprintf("%d", vinsRecord.ID))
d.Set("name", vinsRecord.Name)
d.Set("account_id", vinsRecord.AccountID) d.Set("account_id", vinsRecord.AccountID)
d.Set("account_name", vinsRecord.AccountName) d.Set("account_name", vinsRecord.AccountName)
err = d.Set("rg_id", vinsRecord.RgID) err = d.Set("rg_id", vinsRecord.RgID)
d.Set("description", vinsRecord.Desc) d.Set("description", vinsRecord.Desc)
d.Set("ipcidr", vinsRecord.IPCidr) d.Set("ipcidr", vinsRecord.IPCidr)
noExtNetConnection := true
for _, value := range vinsRecord.VNFs { for _, value := range vinsRecord.VNFs {
if value.Type == "GW" { if value.Type == "GW" {
log.Debugf("flattenVins: discovered GW VNF ID %d in ViNS ID %d", value.ID, vinsRecord.ID) log.Debugf("flattenVins: discovered GW VNF ID %d in ViNS ID %d", value.ID, vinsRecord.ID)
@ -69,10 +71,16 @@ func flattenVins(d *schema.ResourceData, vins_facts string) error {
} else { } else {
return fmt.Errorf("Failed to unmarshal VNF GW Config - structure is invalid.") return fmt.Errorf("Failed to unmarshal VNF GW Config - structure is invalid.")
} }
noExtNetConnection = false
break break
} }
} }
if noExtNetConnection {
d.Set("ext_ip_addr", "")
d.Set("ext_net_id", -1)
}
log.Debugf("flattenVins: EXTRA CHECK - schema rg_id=%d, ext_net_id=%d", d.Get("rg_id").(int), d.Get("ext_net_id").(int)) log.Debugf("flattenVins: EXTRA CHECK - schema rg_id=%d, ext_net_id=%d", d.Get("rg_id").(int), d.Get("ext_net_id").(int))
return nil return nil

@ -562,7 +562,12 @@ type VinsRecord struct { // represents part of the response from API vins/get
const VinsGetAPI = "/restmachine/cloudapi/vins/get" const VinsGetAPI = "/restmachine/cloudapi/vins/get"
const VinsCreateAPI = "/restmachine/cloudapi/vins/create" const VinsCreateInAccountAPI = "/restmachine/cloudapi/vins/createInAccount"
const VinsCreateInRgAPI = "/restmachine/cloudapi/vins/createInRG"
const VinsExtNetConnect = "/restmachine/cloudapi/vins/extNetConnect"
const VinsExtNetDisconnect = "/restmachine/cloudapi/vins/extNetDisconnect"
const VinsDeleteAPI = "/restmachine/cloudapi/vins/delete" const VinsDeleteAPI = "/restmachine/cloudapi/vins/delete"
// //

@ -102,7 +102,7 @@ func Provider() *schema.Provider {
"decort_resgroup": resourceResgroup(), "decort_resgroup": resourceResgroup(),
"decort_kvmvm": resourceCompute(), "decort_kvmvm": resourceCompute(),
"decort_disk": resourceDisk(), "decort_disk": resourceDisk(),
// "decort_vins": resourceVins(), "decort_vins": resourceVins(),
// "decort_pfw": resourcePfw(), // "decort_pfw": resourcePfw(),
}, },

@ -303,7 +303,7 @@ func resourceComputeDelete(d *schema.ResourceData, m interface{}) error {
params := &url.Values{} params := &url.Values{}
params.Add("computeId", d.Id()) params.Add("computeId", d.Id())
params.Add("permanently", "true") params.Add("permanently", "1")
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
_, err = controller.decortAPICall("POST", ComputeDeleteAPI, params) _, err = controller.decortAPICall("POST", ComputeDeleteAPI, params)
@ -435,7 +435,7 @@ func resourceCompute() *schema.Resource {
"description": { "description": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Description: "Description of this compute instance.", Description: "Optional text description of this compute instance.",
}, },

@ -157,14 +157,14 @@ func resourceDiskDelete(d *schema.ResourceData, m interface{}) error {
params := &url.Values{} params := &url.Values{}
params.Add("diskId", d.Id()) params.Add("diskId", d.Id())
// NOTE: we are not force-detaching disk from a compute (if attached) this protecting // NOTE: we are not force-detaching disk from a compute (if attached) thus protecting
// data that may be on that disk from destruction. // data that may be on that disk from destruction.
// However, this may change in the future, as TF state management logic may want // However, this may change in the future, as TF state management logic may want
// to delete disk resource BEFORE it is detached from compute instance, and, while // to delete disk resource BEFORE it is detached from compute instance, and, while
// perfectly OK from data preservation viewpoint, this is breaking expected TF workflow // perfectly OK from data preservation viewpoint, this is breaking expected TF workflow
// in the eyes of an experienced TF user // in the eyes of an experienced TF user
params.Add("detach", "false") params.Add("detach", "0")
params.Add("permanently", "true") params.Add("permanently", "1")
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
_, err = controller.decortAPICall("POST", DisksDeleteAPI, params) _, err = controller.decortAPICall("POST", DisksDeleteAPI, params)

@ -237,8 +237,8 @@ func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error {
url_values := &url.Values{} url_values := &url.Values{}
url_values.Add("rgId", d.Id()) url_values.Add("rgId", d.Id())
url_values.Add("force", "true") url_values.Add("force", "1")
url_values.Add("permanently", "true") url_values.Add("permanently", "1")
url_values.Add("reason", "Destroyed by DECORT Terraform provider") url_values.Add("reason", "Destroyed by DECORT Terraform provider")
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)

@ -0,0 +1,299 @@
/*
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.
*/
/*
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"
log "github.com/sirupsen/logrus"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
// "github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)
func ipcidrDiffSupperss(key, oldVal, newVal string, d *schema.ResourceData) bool {
if oldVal == "" && newVal != "" {
// if old value for "ipcidr" resource is empty string, it means that we are creating new ViNS
// and there is a chance that the user will want specific IP address range for this ViNS -
// check if "ipcidr" is explicitly set in TF file to a non-empty string.
log.Debugf("ipcidrDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=FALSE", key, oldVal, newVal)
return false // there is a difference between stored and new value
}
log.Debugf("ipcidrDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=TRUE", key, oldVal, newVal)
return true // suppress difference
}
func resourceVinsCreate(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceVinsCreate: called for ViNS name %s, Account ID %d, RG ID %d",
d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int))
apiToCall := VinsCreateInAccountAPI
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("name", d.Get("name").(string))
argVal, argSet := d.GetOk("rg_id")
if argSet && argVal.(int) > 0 {
apiToCall = VinsCreateInRgAPI
urlValues.Add("rgId", fmt.Sprintf("%d", argVal.(int)))
} else {
// RG ID either not set at all or set to 0 - user may want ViNS at account level
argVal, argSet = d.GetOk("account_id")
if !argSet || argVal.(int) <= 0 {
// No valid Account ID (and no RG ID either) - cannot create ViNS
return fmt.Errorf("resourceVinsCreate: ViNS name %s - no valid account and/or resource group ID specified", d.Id())
}
urlValues.Add("accountId", fmt.Sprintf("%d", argVal.(int)))
}
argVal, argSet = d.GetOk("ext_net_id") // NB: even if ext_net_id value is explicitly set to 0, argSet = false anyway
if argSet {
if argVal.(int) > 0 {
// connect to specific external network
urlValues.Add("extNetId", fmt.Sprintf("%d", argVal.(int)))
// in case of specific ext net connection user may also want a particular IP address
argVal, argSet = d.GetOk("ext_net_ip")
if argSet && argVal.(string) != "" {
urlValues.Add("extIp", argVal.(string))
}
} else {
// ext_net_id is set to a negative value - connect to default external network
// no particular IP address selection in this case
urlValues.Add("extNetId", "0")
}
}
argVal, argSet = d.GetOk("ipcidr")
if argSet && argVal.(string) != "" {
log.Debugf("resourceVinsCreate: ipcidr is set to %s", argVal.(string))
urlValues.Add("ipcidr", argVal.(string))
}
argVal, argSet = d.GetOk("description")
if argSet {
urlValues.Add("desc", argVal.(string))
}
apiResp, err := controller.decortAPICall("POST", apiToCall, urlValues)
if err != nil {
return err
}
d.SetId(apiResp) // update ID of the resource to tell Terraform that the ViNS resource exists
vinsId, _ := strconv.Atoi(apiResp)
log.Debugf("resourceVinsCreate: new ViNS ID / name %d / %s creation sequence complete", vinsId, d.Get("name").(string))
// We may reuse dataSourceVinsRead here as we maintain similarity
// between ViNS resource and ViNS data source schemas
// ViNS resource read function will also update resource ID on success, so that Terraform
// will know the resource exists (however, we already did it a few lines before)
return dataSourceVinsRead(d, m)
}
func resourceVinsRead(d *schema.ResourceData, m interface{}) error {
vinsFacts, err := utilityVinsCheckPresence(d, m)
if vinsFacts == "" {
// if empty string is returned from utilityVinsCheckPresence then there is no
// such ViNS and err tells so - just return it to the calling party
d.SetId("") // ensure ID is empty
return err
}
return flattenVins(d, vinsFacts)
}
func resourceVinsUpdate(d *schema.ResourceData, m interface{}) error {
return fmt.Errorf("resourceVinsUpdate: method not implemnted yet - ViNS ID %s", d.Id())
/*
log.Debugf("resourceVinsUpdate: called for ViNS ID / name %s / %s, Account ID %d",
d.Id(), d.Get("name").(string), d.Get("account_id").(int))
d.Partial(true)
controller := m.(*ControllerCfg)
oldName, newName := d.GetChange("name")
if oldName.(string) != newName.(string) {
log.Debugf("resourceVinsUpdate: renaming ViNS ID %d - %s -> %s",
d.Get("disk_id").(int), oldName.(string), newName.(string))
renameParams := &url.Values{}
renameParams.Add("vinsId", d.Id())
renameParams.Add("name", newName.(string))
_, err := controller.decortAPICall("POST", VinsRenameAPI, renameParams)
if err != nil {
return err
}
d.SetPartial("name")
}
d.Partial(false)
*/
// we may reuse dataSourceVinsRead here as we maintain similarity
// between Compute resource and Compute data source schemas
// return dataSourceVinsRead(d, m)
}
func resourceVinsDelete(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceVinsDelete: called for ViNS ID / name %s / %s, Account ID %d, RG ID %d",
d.Id(), d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int))
vinsFacts, err := utilityVinsCheckPresence(d, m)
if vinsFacts == "" {
// the specified ViNS does not exist - in this case according to Terraform best practice
// we exit from Destroy method without error
return nil
}
params := &url.Values{}
params.Add("vinsId", d.Id())
params.Add("force", "1") // disconnect all computes before deleting ViNS
params.Add("permanently", "1") // delete ViNS immediately bypassing recycle bin
controller := m.(*ControllerCfg)
_, err = controller.decortAPICall("POST", VinsDeleteAPI, params)
if err != nil {
return err
}
return nil
}
func resourceVinsExists(d *schema.ResourceData, m interface{}) (bool, error) {
// Reminder: according to Terraform rules, this function should not modify its ResourceData argument
log.Debugf("resourceVinsExists: called for ViNS name %s, Account ID %d, RG ID %d",
d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int))
vinsFacts, err := utilityVinsCheckPresence(d, m)
if vinsFacts == "" {
if err != nil {
return false, err
}
return false, nil
}
return true, nil
}
func resourceVinsSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "Name of the ViNS. Names are case sensitive and unique within the context of an account or resource group.",
},
/* we do not need ViNS ID as an argument because if we already know this ID, it is not practical to call resource provider.
Resource Import will work anyway, as it obtains the ID of ViNS to be imported through another mechanism.
"vins_id": {
Type: schema.TypeInt,
Optional: true,
Description: "Unique ID of the ViNS. If ViNS ID is specified, then ViNS name, rg_id and account_id are ignored.",
},
*/
"rg_id": {
Type: schema.TypeInt,
Optional: true,
Description: "ID of the resource group, where this ViNS belongs to. Non-zero for ViNS created at resource group level, 0 otherwise.",
},
"account_id": {
Type: schema.TypeInt,
Required: true,
Description: "ID of the account, which this ViNS belongs to. For ViNS created at account level, resource group ID is 0.",
},
"description": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: "Optional user-defined text description of this ViNS.",
},
"ext_net_id": {
Type: schema.TypeInt,
Optional: true,
Description: "ID of the external network this ViNS is connected to (set to 0 if no external connection required, -1 to connect to default external network).",
},
"ext_ip_addr": {
Type: schema.TypeString,
Optional: true,
Description: "IP address of the external connection (valid for ViNS connected to external network, ignored otherwise).",
},
"ipcidr": {
Type: schema.TypeString,
Optional: true,
DiffSuppressFunc: ipcidrDiffSupperss,
Description: "Network address to use by this ViNS.",
},
// the rest of attributes are computed
"account_name": {
Type: schema.TypeString,
Computed: true,
Description: "Name of the account, which this ViNS belongs to.",
},
}
return rets
}
func resourceVins() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
Create: resourceVinsCreate,
Read: resourceVinsRead,
Update: resourceVinsUpdate,
Delete: resourceVinsDelete,
Exists: resourceVinsExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Timeouts: &schema.ResourceTimeout{
Create: &Timeout180s,
Read: &Timeout30s,
Update: &Timeout180s,
Delete: &Timeout60s,
Default: &Timeout60s,
},
Schema: resourceVinsSchemaMake(),
}
}

@ -28,6 +28,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"strconv"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -75,6 +76,34 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
urlValues := &url.Values{} urlValues := &url.Values{}
// make it possible to use "read" & "check presence" functions with ViNS ID set so
// that Import of ViNS resource is possible
idSet := false
theId, err := strconv.Atoi(d.Id())
if err != nil || theId <= 0 {
vinsId, argSet := d.GetOk("vins_id") // NB: vins_id is NOT present in vinsResource schema!
if argSet {
theId = vinsId.(int)
idSet = true
}
} else {
idSet = true
}
if idSet {
// ViNS ID is specified, try to get compute instance straight by this ID
log.Debugf("utilityVinsCheckPresence: locating ViNS by its ID %d", theId)
urlValues.Add("vinsId", fmt.Sprintf("%d", theId))
vinsFacts, err := controller.decortAPICall("POST", VinsGetAPI, urlValues)
if err != nil {
return "", err
}
return vinsFacts, nil
}
// ID was not set in the schema upon entering this function - work through ViNS name
// and Account / RG ID
vinsName, argSet := d.GetOk("name") vinsName, argSet := d.GetOk("name")
if !argSet { if !argSet {
// if ViNS name is not set. then we cannot locate ViNS // if ViNS name is not set. then we cannot locate ViNS
@ -82,11 +111,11 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er
} }
urlValues.Add("name", vinsName.(string)) urlValues.Add("name", vinsName.(string))
urlValues.Add("show_all", "false") urlValues.Add("show_all", "false")
log.Debugf("utilityVinsCheckPresence: locating ViNS %s", vinsName.(string)) log.Debugf("utilityVinsCheckPresence: preparing to locate ViNS name %s", vinsName.(string))
rgId, rgSet := d.GetOk("rg_id") rgId, rgSet := d.GetOk("rg_id")
if rgSet { if rgSet {
log.Debugf("utilityVinsCheckPresence: limiting ViNS t search to RG ID %d", rgId.(int)) log.Debugf("utilityVinsCheckPresence: limiting ViNS search to RG ID %d", rgId.(int))
urlValues.Add("rgId", fmt.Sprintf("%d", rgId.(int))) urlValues.Add("rgId", fmt.Sprintf("%d", rgId.(int)))
} }
@ -133,5 +162,5 @@ func utilityVinsCheckPresence(d *schema.ResourceData, m interface{}) (string, er
} }
} }
return "", fmt.Errorf("Cannot find ViNS name %s. Check name and/or RG ID & Account ID", vinsName.(string)) return "", fmt.Errorf("Cannot find ViNS name %s. Check name and/or RG ID & Account ID and your access rights", vinsName.(string))
} }

Loading…
Cancel
Save