End of day backup commit

rc-1.0
Sergey Shubin svs1370 4 years ago
parent 132ed6d65e
commit 871a5e06ee

@ -63,8 +63,8 @@ type ControllerCfg struct {
app_id string // required for oauth2 mode app_id string // required for oauth2 mode
app_secret string // required for oauth2 mode app_secret string // required for oauth2 mode
oauth2_url string // always required oauth2_url string // always required
decort_username string // assigned to either legacy_user (legacy mode) or Oauth2 user (oauth2 mode) upon successful verification decort_username string // assigned to either legacy_user (legacy mode) or Oauth2 user (oauth2 mode) upon successful verification
cc_client *http.Client // assigned when all initial check successfully passed cc_client *http.Client // assigned when all initial checks successfully passed
} }
func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) { func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {

@ -1,5 +1,5 @@
/* /*
Copyright (c) 2019-2020 Digital Energy Cloud Solutions LLC. All Rights Reserved. Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com> Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -39,24 +39,23 @@ import (
func flattenResgroup(d *schema.ResourceData, rg_facts string) error { func flattenResgroup(d *schema.ResourceData, rg_facts string) error {
// NOTE: this function modifies ResourceData argument - as such it should never be called // NOTE: this function modifies ResourceData argument - as such it should never be called
// from resourceRsgroupExists(...) method // from resourceRsgroupExists(...) method
log.Printf("%s", rg_facts) log.Debugf("%s", rg_facts)
log.Printf("flattenResgroup: ready to decode response body from %q", CloudspacesGetAPI) log.Debugf("flattenResgroup: ready to decode response body from %q", CloudspacesGetAPI)
details := CloudspacesGetResp{} details := ResgroupGetResp{}
err := json.Unmarshal([]byte(rg_facts), &details) err := json.Unmarshal([]byte(rg_facts), &details)
if err != nil { if err != nil {
return err return err
} }
log.Printf("flattenResgroup: decoded ResGroup name %q / ID %d, tenant ID %d, public IP %q", log.Debugf("flattenResgroup: decoded ResGroup name %q / ID %d, account ID %d, public IP %q",
details.Name, details.ID, details.TenantID, details.PublicIP) details.Name, details.ID, details.AccountID, details.PublicIP)
d.SetId(fmt.Sprintf("%d", details.ID)) d.SetId(fmt.Sprintf("%d", details.ID))
d.Set("name", details.Name) d.Set("name", details.Name)
d.Set("tenant_id", details.TenantID) d.Set("account_id", details.AccountID)
d.Set("grid_id", details.GridID) d.Set("grid_id", details.GridID)
d.Set("public_ip", details.PublicIP) // legacy field - this may be obsoleted when new network segments are implemented
log.Printf("flattenResgroup: calling flattenQuota()") log.Debugf("flattenResgroup: calling flattenQuota()")
if err = d.Set("quotas", flattenQuota(details.Quotas)); err != nil { if err = d.Set("quotas", flattenQuota(details.Quotas)); err != nil {
return err return err
} }
@ -69,7 +68,7 @@ func dataSourceResgroupRead(d *schema.ResourceData, m interface{}) error {
if rg_facts == "" { if rg_facts == "" {
// if empty string is returned from utilityResgroupCheckPresence then there is no // if empty string is returned from utilityResgroupCheckPresence then there is no
// such resource group and err tells so - just return it to the calling party // such resource group and err tells so - just return it to the calling party
d.SetId("") // ensure ID is empty d.SetId("") // ensure ID is empty in this case
return err return err
} }
@ -95,13 +94,13 @@ func dataSourceResgroup() *schema.Resource {
Description: "Name of this resource group. Names are case sensitive and unique within the context of a tenant.", Description: "Name of this resource group. Names are case sensitive and unique within the context of a tenant.",
}, },
"tenant": &schema.Schema { "account": &schema.Schema {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "Name of the tenant, which this resource group belongs to.", Description: "Name of the tenant, which this resource group belongs to.",
}, },
"tenant_id": &schema.Schema { "account_id": &schema.Schema {
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
Description: "Unique ID of the tenant, which this resource group belongs to.", Description: "Unique ID of the tenant, which this resource group belongs to.",
@ -113,12 +112,6 @@ func dataSourceResgroup() *schema.Resource {
Description: "Unique ID of the grid, where this resource group is deployed.", Description: "Unique ID of the grid, where this resource group is deployed.",
}, },
"location": {
Type: schema.TypeString,
Computed: true,
Description: "Location of this resource group.",
},
"public_ip": { // this may be obsoleted as new network segments and true resource groups are implemented "public_ip": { // this may be obsoleted as new network segments and true resource groups are implemented
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,

@ -60,8 +60,8 @@ type AccountAclRecord struct {
type ResgroupRecord struct { type ResgroupRecord struct {
ACLs []UserAclRecord `json:"ACLs"` ACLs []UserAclRecord `json:"ACLs"`
Owner AccountAclRecord `json:"accountAcl"` Owner AccountAclRecord `json:"accountAcl"`
TenantID int `json:"accountId"` AccountID int `json:"accountId"`
TenantName string `json:"accountName"` AccountName string `json:"accountName"`
CreatedBy string `json:"createdBy"` CreatedBy string `json:"createdBy"`
CreatedTime uint64 `json:"createdTime"` CreatedTime uint64 `json:"createdTime"`
DefaultNetID int `json:"def_net_id"` DefaultNetID int `json:"def_net_id"`
@ -85,23 +85,6 @@ type ResgroupListResp []ResgroupRecord
// structures related to /cloudapi/rg/create API call // structures related to /cloudapi/rg/create API call
// //
const ResgroupCreateAPI= "/restmachine/cloudapi/rg/create" const ResgroupCreateAPI= "/restmachine/cloudapi/rg/create"
type ResgroupCreateParam struct {
TenantID int `json:"accountId"`
GridId int `json:"gid"`
Name string `json:"name"`
Ram int `json:"maxMemoryCapacity"`
Disk int `json:"maxVDiskCapacity"`
Cpu int `json:"maxCPUCapacity"`
NetTraffic int `json:"maxNetworkPeerTransfer"`
ExtIPs int `json:"maxNumPublicIP"`
Owner string `json:"owner"`
DefNet string `json:"def_net"`
IPCidr string `json:"ipcidr"`
Desc string `json:"decs"`
Reason string `json:"reason"`
ExtNetID int `json:"extNetId"`
ExtIP string `json:"extIp"`
}
// //
// structures related to /cloudapi/rg/update API call // structures related to /cloudapi/rg/update API call
@ -139,8 +122,8 @@ const ResgroupGetAPI= "/restmachine/cloudapi/rg/get"
type ResgroupGetResp struct { type ResgroupGetResp struct {
ACLs []UserAclRecord `json:"ACLs"` ACLs []UserAclRecord `json:"ACLs"`
Usage UsageRecord `json:"Resources"` Usage UsageRecord `json:"Resources"`
TenantID int `json:"accountId"` AccountID int `json:"accountId"`
TenantName string `json:"accountName"` AccountName string `json:"accountName"`
CreatedBy string `json:"createdBy"` CreatedBy string `json:"createdBy"`
CreatedTime uint64 `json:"createdTime"` CreatedTime uint64 `json:"createdTime"`
@ -182,11 +165,6 @@ type ResgroupUpdateParam struct {
// structures related to /cloudapi/rg/delete API // structures related to /cloudapi/rg/delete API
// //
const ResgroupDeleteAPI = "/restmachine/cloudapi/rg/delete" const ResgroupDeleteAPI = "/restmachine/cloudapi/rg/delete"
type ResgroupDeleteParam struct {
ID uint `json:"rgId"`
Force bool `json:"force"`
Reason string `json:"reason"`
}
// //
// structures related to /cloudapi/kvmXXX/create APIs // structures related to /cloudapi/kvmXXX/create APIs
@ -245,8 +223,8 @@ type SnapSetRecord struct {
} }
type ComputeRecord struct { type ComputeRecord struct {
TenantID int `json:"accountId"` AccountID int `json:"accountId"`
TenantName string `json:"accountName"` AccountName string `json:"accountName"`
ACLs []UserAclRecord `json:"acl"` ACLs []UserAclRecord `json:"acl"`
Arch string `json:"arch"` Arch string `json:"arch"`
BootDiskSize int `json:"bootdiskSize"` BootDiskSize int `json:"bootdiskSize"`
@ -302,7 +280,7 @@ type SnapshotRecord struct {
type DiskRecord struct { type DiskRecord struct {
// ACLs `json:"ACL"` - it is a dictionary, special parsing required // ACLs `json:"ACL"` - it is a dictionary, special parsing required
// was - Acl map[string]string `json:"acl"` // was - Acl map[string]string `json:"acl"`
TenantID int `json:"accountId"` AccountID int `json:"accountId"`
BootPartition int `json:"bootPartition"` BootPartition int `json:"bootPartition"`
CreatedTime uint64 `json:"creationTime"` CreatedTime uint64 `json:"creationTime"`
DeletedTime uint64 `json:"deletionTime"` DeletedTime uint64 `json:"deletionTime"`
@ -347,8 +325,8 @@ type ComputeGetParam struct {
} }
type ComputeGetResp struct { type ComputeGetResp struct {
// ACLs `json:"ACL"` - it is a dictionary, special parsing required // ACLs `json:"ACL"` - it is a dictionary, special parsing required
TenantID int `json:"accountId"` AccountID int `json:"accountId"`
TenantName string `json:"accountName"` AccountName string `json:"accountName"`
Arch string `json:"arch"` Arch string `json:"arch"`
BootDiskSize int `json:"bootdiskSize"` BootDiskSize int `json:"bootdiskSize"`
CloneReference int `json:"cloneReference"` CloneReference int `json:"cloneReference"`
@ -391,7 +369,7 @@ type ComputeGetResp struct {
// structures related to /restmachine/cloudapi/images/list API // structures related to /restmachine/cloudapi/images/list API
// //
type ImageRecord struct { type ImageRecord struct {
TenantID uint `json:"accountId"` AccountID uint `json:"accountId"`
Arch string `json:"architecture` Arch string `json:"architecture`
BootType string `json:"bootType"` BootType string `json:"bootType"`
IsBootable boo `json:"bootable"` IsBootable boo `json:"bootable"`
@ -411,7 +389,7 @@ type ImageRecord struct {
const ImagesListAPI = "/restmachine/cloudapi/images/list" const ImagesListAPI = "/restmachine/cloudapi/images/list"
type ImagesListParam struct { type ImagesListParam struct {
TenantID int `json:"accountId"` AccountID int `json:"accountId"`
} }
type ImagesListResp []ImageRecord type ImagesListResp []ImageRecord
@ -426,7 +404,7 @@ type ExtNetRecord struct {
const ExtNetListAPI = "/restmachine/cloudapi/extnet/list" const ExtNetListAPI = "/restmachine/cloudapi/extnet/list"
type ExtNetListParam struct { type ExtNetListParam struct {
TenantID int `json:"accountId"` AccountID int `json:"accountId"`
} }
type ExtNetListResp []ExtNetRecord type ExtNetListResp []ExtNetRecord
@ -434,7 +412,7 @@ type ExtNetListResp []ExtNetRecord
// //
// structures related to /cloudapi/accounts/list API // structures related to /cloudapi/accounts/list API
// //
type TenantRecord struct { type AccountRecord struct {
ACLs []UserAclRecord `json:"acl"` ACLs []UserAclRecord `json:"acl"`
CreatedTime uint64 `json:"creationTime"` CreatedTime uint64 `json:"creationTime"`
DeletedTime uint64 `json:"deletionTime"` DeletedTime uint64 `json:"deletionTime"`
@ -444,8 +422,8 @@ type TenantRecord struct {
UpdatedTime uint64 `json:"updateTime"` UpdatedTime uint64 `json:"updateTime"`
} }
const TenantsListAPI = "/restmachine/cloudapi/accounts/list" const AccountsListAPI = "/restmachine/cloudapi/accounts/list"
type TenantsListResp []TenantRecord type AccountsListResp []AccountRecord
// //
// structures related to /cloudapi/portforwarding/list API // structures related to /cloudapi/portforwarding/list API
@ -516,7 +494,7 @@ const ComputeDiskDetachAPI = "/restmachine/cloudapi/compute/diskDetach"
// structures related to /cloudapi/disks/create // structures related to /cloudapi/disks/create
// //
type DiskCreateParam struct { type DiskCreateParam struct {
TenantID int `json:"accountId` AccountID int `json:"accountId`
GridID int `json:"gid"` GridID int `json:"gid"`
Name string `json:"string"` Name string `json:"string"`
Description string `json:"description"` Description string `json:"description"`

@ -1,5 +1,5 @@
/* /*
Copyright (c) 2019-2020 Digital Energy Cloud Solutions LLC. All Rights Reserved. Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com> Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -24,6 +24,8 @@ Visit https://github.com/rudecs/terraform-provider-decort for full source code p
package decort package decort
/*
type DiskConfig struct { type DiskConfig struct {
Label string Label string
Size int Size int
@ -65,7 +67,7 @@ type ComputeConfig struct {
Description string Description string
// The following two parameters are required to create data disks by // The following two parameters are required to create data disks by
// a separate disks/create API call // a separate disks/create API call
TenantID int AccountID int
GridID int GridID int
// The following one paratmeter is required to create port forwards // The following one paratmeter is required to create port forwards
// it will be obsoleted when we implement true Resource Groups // it will be obsoleted when we implement true Resource Groups
@ -81,8 +83,8 @@ type ResgroupQuotaConfig struct {
} }
type ResgroupConfig struct { type ResgroupConfig struct {
TenantID int AccountID int
TenantName string AccountName string
Location string Location string
Name string Name string
ID int ID int
@ -90,4 +92,6 @@ type ResgroupConfig struct {
ExtIP string // legacy field for VDC - this will eventually become obsoleted by true Resource Groups ExtIP string // legacy field for VDC - this will eventually become obsoleted by true Resource Groups
Quota ResgroupQuotaConfig Quota ResgroupQuotaConfig
Network NetworkConfig Network NetworkConfig
} }
*/

@ -1,5 +1,5 @@
/* /*
Copyright (c) 2019 Digital Energy Cloud Solutions LLC. All Rights Reserved. Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com> Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package decs package decort
import ( import (
@ -100,14 +100,18 @@ func Provider() *schema.Provider {
}, },
ResourcesMap: map[string]*schema.Resource { ResourcesMap: map[string]*schema.Resource {
"decs_resgroup": resourceResgroup(), "decort_resgroup": resourceResgroup(),
"decs_vm": resourceVm(), "decort_kvmx86": resourceKvmX86(),
"decort_disk": resourceDisk(),
"decort_vins": resourceVins(),
}, },
DataSourcesMap: map[string]*schema.Resource { DataSourcesMap: map[string]*schema.Resource {
"decs_resgroup": dataSourceResgroup(), "decort_resgroup": dataSourceResgroup(),
"decs_vm": dataSourceVm(), "decs_kvmx86": dataSourceCompute(),
"decs_image": dataSourceImage(), "decort_image": dataSourceImage(),
"decort_disk": dataSourceDisk(),
"decort_vins": dataSourceVins(),
}, },
ConfigureFunc: providerConfigure, ConfigureFunc: providerConfigure,

@ -35,12 +35,12 @@ import (
) )
func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error { func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
log.Printf("resourceResgroupCreate: called for res group name %q, tenant name %q", log.Debugf("resourceResgroupCreate: called for res group name %q, account name %q",
d.Get("name").(string), d.Get("tenant").(string)) d.Get("name").(string), d.Get("account").(string))
rg := &ResgroupConfig{ rg := &ResgroupConfig{
Name: d.Get("name").(string), Name: d.Get("name").(string),
TenantName: d.Get("tenant").(string), AccountName: d.Get("account").(string),
} }
// validate that we have all parameters required to create the new Resource Group // validate that we have all parameters required to create the new Resource Group
@ -49,35 +49,55 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
if arg_set { if arg_set {
rg.Location = arg_value.(string) rg.Location = arg_value.(string)
} else { } else {
return fmt.Errorf("Cannot create new resource group %q for tenant %q: missing location parameter.", return fmt.Errorf("Cannot create new RG %q for account %q: missing location parameter.",
rg.Name, rg.TenantName) rg.Name, rg.AccountName)
} }
// tenant ID is required to create new resource group // account ID is required to create new resource group
// obtain Tenant ID by tenant name - it should not be zero on success // obtain Account ID by account name - it should not be zero on success
tenant_id, err := utilityGetTenantIdByName(rg.TenantName, m) account_id, err := utilityGetAccountIdByName(rg.AccountName, m)
if err != nil { if err != nil {
return err return err
} }
rg.TenantID = tenant_id rg.AccountID = account_id
set_quotas := false set_quotas := false
arg_value, arg_set = d.GetOk("quotas") arg_value, arg_set = d.GetOk("quotas")
if arg_set { if arg_set {
log.Printf("resourceResgroupCreate: calling makeQuotaConfig") log.Debugf("resourceResgroupCreate: calling makeQuotaConfig")
rg.Quota, _ = makeQuotaConfig(arg_value.([]interface{})) rg.Quota, _ = makeQuotaConfig(arg_value.([]interface{}))
set_quotas = true set_quotas = true
} }
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
log.Printf("resourceResgroupCreate: called by user %q for Resource group name %q, for tenant %q / ID %d, location %q", log.Debugf("resourceResgroupCreate: called by user %q for RG name %q, for account %q / ID %d, location %q",
controller.getdecortUsername(), controller.getdecortUsername(),
rg.Name, d.Get("tenant"), rg.TenantID, rg.Location) rg.Name, d.Get("account").(string), rg.AccountID, rg.Location)
/*
type ResgroupCreateParam struct {
AccountID int `json:"accountId"`
GridId int `json:"gid"`
Name string `json:"name"`
Ram int `json:"maxMemoryCapacity"`
Disk int `json:"maxVDiskCapacity"`
Cpu int `json:"maxCPUCapacity"`
NetTraffic int `json:"maxNetworkPeerTransfer"`
ExtIPs int `json:"maxNumPublicIP"`
Owner string `json:"owner"`
DefNet string `json:"def_net"`
IPCidr string `json:"ipcidr"`
Desc string `json:"decs"`
Reason string `json:"reason"`
ExtNetID int `json:"extNetId"`
ExtIP string `json:"extIp"`
}
*/
url_values := &url.Values{} url_values := &url.Values{}
url_values.Add("accountId", fmt.Sprintf("%d", rg.TenantID)) url_values.Add("accountId", fmt.Sprintf("%d", rg.AccountID))
url_values.Add("name", rg.Name) url_values.Add("name", rg.Name)
url_values.Add("location", rg.Location) url_values.Add("gid", rg.Location)
url_values.Add("access", controller.getdecortUsername()) url_values.Add("owner", controller.getdecortUsername())
url_values.Add("def_net", "NONE")
// pass quota values as set // pass quota values as set
if set_quotas { if set_quotas {
url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", rg.Quota.Cpu)) url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", rg.Quota.Cpu))
@ -89,7 +109,7 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
// pass externalnetworkid if set // pass externalnetworkid if set
arg_value, arg_set = d.GetOk("extnet_id") arg_value, arg_set = d.GetOk("extnet_id")
if arg_set { if arg_set {
url_values.Add("externalnetworkid", fmt.Sprintf("%d", arg_value)) url_values.Add("extNetId", fmt.Sprintf("%d", arg_value))
} }
api_resp, err := controller.decortAPICall("POST", ResgroupCreateAPI, url_values) api_resp, err := controller.decortAPICall("POST", ResgroupCreateAPI, url_values)
@ -97,15 +117,15 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
return err return err
} }
d.SetId(api_resp) // cloudspaces/create API plainly returns ID of the newly creted resource group on success d.SetId(api_resp) // rg/create API returns ID of the newly creted resource group on success
rg.ID, _ = strconv.Atoi(api_resp) rg.ID, _ = strconv.Atoi(api_resp)
return resourceResgroupRead(d, m) return resourceResgroupRead(d, m)
} }
func resourceResgroupRead(d *schema.ResourceData, m interface{}) error { func resourceResgroupRead(d *schema.ResourceData, m interface{}) error {
log.Printf("resourceResgroupRead: called for res group name %q, tenant name %q", log.Debugf("resourceResgroupRead: called for RG name %q, account name %q",
d.Get("name").(string), d.Get("tenant").(string)) d.Get("name").(string), d.Get("account").(string))
rg_facts, err := utilityResgroupCheckPresence(d, m) rg_facts, err := utilityResgroupCheckPresence(d, m)
if rg_facts == "" { if rg_facts == "" {
// if empty string is returned from utilityResgroupCheckPresence then there is no // if empty string is returned from utilityResgroupCheckPresence then there is no
@ -119,13 +139,13 @@ func resourceResgroupRead(d *schema.ResourceData, m interface{}) error {
func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error { func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error {
// this method will only update quotas, if any are set // this method will only update quotas, if any are set
log.Printf("resourceResgroupUpdate: called for res group name %q, tenant name %q", log.Debugf("resourceResgroupUpdate: called for RG name %q, account name %q",
d.Get("name").(string), d.Get("tenant").(string)) d.Get("name").(string), d.Get("account").(string))
quota_value, arg_set := d.GetOk("quotas") quota_value, arg_set := d.GetOk("quotas")
if !arg_set { if !arg_set {
// if there are no quotas set explicitly in the resource configuration - no change will be done // if there are no quotas set explicitly in the resource configuration - no change will be done
log.Printf("resourceResgroupUpdate: quotas are not set in the resource config - no update on this resource will be done") log.Debugf("resourceResgroupUpdate: quotas are not set in the resource config - no update on this resource will be done")
return resourceResgroupRead(d, m) return resourceResgroupRead(d, m)
} }
quotaconfig_new, _ := makeQuotaConfig(quota_value.([]interface{})) quotaconfig_new, _ := makeQuotaConfig(quota_value.([]interface{}))
@ -142,66 +162,68 @@ func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error {
if quotaconfig_new.Cpu != quotaconfig_old.Cpu { if quotaconfig_new.Cpu != quotaconfig_old.Cpu {
do_update = true do_update = true
log.Printf("resourceResgroupUpdate: Cpu diff %d <- %d", quotaconfig_new.Cpu, quotaconfig_old.Cpu) log.Debugf("resourceResgroupUpdate: Cpu diff %d <- %d", quotaconfig_new.Cpu, quotaconfig_old.Cpu)
url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", quotaconfig_new.Cpu)) url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", quotaconfig_new.Cpu))
} }
if quotaconfig_new.Disk != quotaconfig_old.Disk { if quotaconfig_new.Disk != quotaconfig_old.Disk {
do_update = true do_update = true
log.Printf("resourceResgroupUpdate: Disk diff %d <- %d", quotaconfig_new.Disk, quotaconfig_old.Disk) log.Debugf("resourceResgroupUpdate: Disk diff %d <- %d", quotaconfig_new.Disk, quotaconfig_old.Disk)
url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", quotaconfig_new.Disk)) url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", quotaconfig_new.Disk))
} }
if quotaconfig_new.Ram != quotaconfig_old.Ram { if quotaconfig_new.Ram != quotaconfig_old.Ram {
do_update = true do_update = true
log.Printf("resourceResgroupUpdate: Ram diff %f <- %f", quotaconfig_new.Ram, quotaconfig_old.Ram) log.Debugf("resourceResgroupUpdate: Ram diff %f <- %f", quotaconfig_new.Ram, quotaconfig_old.Ram)
url_values.Add("maxMemoryCapacity", fmt.Sprintf("%f", quotaconfig_new.Ram)) url_values.Add("maxMemoryCapacity", fmt.Sprintf("%f", quotaconfig_new.Ram))
} }
if quotaconfig_new.NetTraffic != quotaconfig_old.NetTraffic { if quotaconfig_new.NetTraffic != quotaconfig_old.NetTraffic {
do_update = true do_update = true
log.Printf("resourceResgroupUpdate: NetTraffic diff %d <- %d", quotaconfig_new.NetTraffic, quotaconfig_old.NetTraffic) log.Debugf("resourceResgroupUpdate: NetTraffic diff %d <- %d", quotaconfig_new.NetTraffic, quotaconfig_old.NetTraffic)
url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", quotaconfig_new.NetTraffic)) url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", quotaconfig_new.NetTraffic))
} }
if quotaconfig_new.ExtIPs != quotaconfig_old.ExtIPs { if quotaconfig_new.ExtIPs != quotaconfig_old.ExtIPs {
do_update = true do_update = true
log.Printf("resourceResgroupUpdate: ExtIPs diff %d <- %d", quotaconfig_new.ExtIPs, quotaconfig_old.ExtIPs) log.Debugf("resourceResgroupUpdate: ExtIPs diff %d <- %d", quotaconfig_new.ExtIPs, quotaconfig_old.ExtIPs)
url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", quotaconfig_new.ExtIPs)) url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", quotaconfig_new.ExtIPs))
} }
if do_update { if do_update {
log.Printf("resourceResgroupUpdate: some new quotas are set - updating the resource") log.Debugf("resourceResgroupUpdate: some new quotas are set - updating the resource")
_, err := controller.decortAPICall("POST", ResgroupUpdateAPI, url_values) _, err := controller.decortAPICall("POST", ResgroupUpdateAPI, url_values)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
log.Printf("resourceResgroupUpdate: no difference in quotas between old and new state - no update on this resource will be done") log.Debugf("resourceResgroupUpdate: no difference in quotas between old and new state - no update on this resource will be done")
} }
return resourceResgroupRead(d, m) return resourceResgroupRead(d, m)
} }
func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error { func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error {
// NOTE: this method destroys target resource group with flag "permanently", so there is no way to // NOTE: this method forcibly destroys target resource group with flag "permanently", so there is no way to
// restore the destroyed resource group as well all VMs that existed in it // restore the destroyed resource group as well all Computes & VINSes that existed in it
log.Printf("resourceResgroupDelete: called for res group name %q, tenant name %q", log.Debugf("resourceResgroupDelete: called for RG name %q, account name %q",
d.Get("name").(string), d.Get("tenant").(string)) d.Get("name").(string), d.Get("account").(string))
vm_facts, err := utilityResgroupCheckPresence(d, m) rg_facts, err := utilityResgroupCheckPresence(d, m)
if vm_facts == "" { if rg_facts == "" {
// the target VM does not exist - in this case according to Terraform best practice // the target RG does not exist - in this case according to Terraform best practice
// we exit from Destroy method without error // we exit from Destroy method without error
return nil return nil
} }
params := &url.Values{} params := &url.Values{}
params.Add("cloudspaceId", d.Id()) params.Add("rgId", d.Id())
params.Add("force", "true")
params.Add("permanently", "true") params.Add("permanently", "true")
params.Add("reason", "Destroyed by DECORT Terraform provider")
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
vm_facts, err = controller.decortAPICall("POST", CloudspacesDeleteAPI, params) vm_facts, err = controller.decortAPICall("POST", ResgroupDeleteAPI, params)
if err != nil { if err != nil {
return err return err
} }
@ -243,13 +265,13 @@ func resourceResgroup() *schema.Resource {
"name": &schema.Schema { "name": &schema.Schema {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "Name of this resource group. Names are case sensitive and unique within the context of a tenant.", Description: "Name of this resource group. Names are case sensitive and unique within the context of a account.",
}, },
"tenant": &schema.Schema { "account": &schema.Schema {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "Name of the tenant, which this resource group belongs to.", Description: "Name of the account, which this resource group belongs to.",
}, },
"extnet_id": &schema.Schema { "extnet_id": &schema.Schema {
@ -258,31 +280,18 @@ func resourceResgroup() *schema.Resource {
Description: "ID of the external network, which this resource group will be connected to by default.", Description: "ID of the external network, which this resource group will be connected to by default.",
}, },
"tenant_id": &schema.Schema { "account_id": &schema.Schema {
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
Description: "Unique ID of the tenant, which this resource group belongs to.", Description: "Unique ID of the account, which this resource group belongs to.",
}, },
"grid_id": &schema.Schema { "grid_id": &schema.Schema {
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Required: true,
Description: "Unique ID of the grid, where this resource group is deployed.", Description: "Unique ID of the grid, where this resource group is deployed.",
}, },
"location": &schema.Schema {
Type: schema.TypeString,
Optional: true, // note that it is a REQUIRED parameter when creating new resource group
ForceNew: true,
Description: "Name of the location where this resource group should exist.",
},
"public_ip": { // this may be obsoleted as new network segments and true resource groups are implemented
Type: schema.TypeString,
Computed: true,
Description: "Public IP address of this resource group (if any).",
},
"quotas": { "quotas": {
Type: schema.TypeList, Type: schema.TypeList,
Optional: true, Optional: true,

@ -1,5 +1,5 @@
/* /*
Copyright (c) 2019-2020 Digital Energy Cloud Solutions LLC. All Rights Reserved. Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com> Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -36,23 +36,24 @@ import (
// "github.com/hashicorp/terraform/helper/validation" // "github.com/hashicorp/terraform/helper/validation"
) )
func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupConfig, error) { func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupGetResp, error) {
url_values := &url.Values{} url_values := &url.Values{}
url_values.Add("cloudspaceId", fmt.Sprintf("%d", rgid)) url_values.Add("rgId", fmt.Sprintf("%d", rgid))
resgroup_facts, err := ctrl.decortAPICall("POST", CloudspacesGetAPI, url_values) resgroup_facts, err := ctrl.decortAPICall("POST", ResgroupGetAPI, url_values)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Printf("utilityResgroupConfigGet: ready to unmarshal string %q", resgroup_facts) log.Debugf("utilityResgroupConfigGet: ready to unmarshal string %q", resgroup_facts)
model := CloudspacesGetResp{} model := &ResgroupGetResp{}
err = json.Unmarshal([]byte(resgroup_facts), &model) err = json.Unmarshal([]byte(resgroup_facts), model)
if err != nil { if err != nil {
return nil, err return nil, err
} }
/*
ret := &ResgroupConfig{} ret := &ResgroupConfig{}
ret.TenantID = model.TenantID ret.AccountID = model.AccountID
ret.Location = model.Location ret.Location = model.Location
ret.Name = model.Name ret.Name = model.Name
ret.ID = rgid ret.ID = rgid
@ -60,53 +61,58 @@ func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupConfig,
ret.ExtIP = model.ExtIP // legacy field for VDC - this will eventually become obsoleted by true Resource Groups ret.ExtIP = model.ExtIP // legacy field for VDC - this will eventually become obsoleted by true Resource Groups
// Quota ResgroupQuotaConfig // Quota ResgroupQuotaConfig
// Network NetworkConfig // Network NetworkConfig
log.Printf("utilityResgroupConfigGet: tenant ID %d, GridID %d, ExtIP %q", */
model.TenantID, model.GridID, model.ExtIP) log.Debugf("utilityResgroupConfigGet: account ID %d, GridID %d, Name %s",
model.AccountID, model.GridID, model.Name)
return ret, nil return model, nil
} }
func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string, error) { func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string, error) {
// This function tries to locate resource group by its name and tenant name. // This function tries to locate resource group by its name and account name.
// If succeeded, it returns non empty string that contains JSON formatted facts about the // If succeeded, it returns non empty string that contains JSON formatted facts about the
// resource group as returned by cloudspaces/get API call. // resource group as returned by cloudspaces/get API call.
// Otherwise it returns empty string and meaningful error. // Otherwise it returns empty string and meaningful error.
// //
// NOTE: As our provider always deletes RGs permanently, there is no "restore" method and
// consequently we are not interested in matching RGs in DELETED state. Hence, we call
// .../rg/list API with includedeleted=false
//
// This function does not modify its ResourceData argument, so it is safe to use it as core // This function does not modify its ResourceData argument, so it is safe to use it as core
// method for the resource's Exists method. // method for the resource's Exists method.
// //
name := d.Get("name").(string) name := d.Get("name").(string)
tenant_name := d.Get("tenant").(string) account_name := d.Get("account").(string)
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
url_values := &url.Values{} url_values := &url.Values{}
url_values.Add("includedeleted", "false") url_values.Add("includedeleted", "false")
body_string, err := controller.decortAPICall("POST", CloudspacesListAPI, url_values) body_string, err := controller.decortAPICall("POST", ResgroupListAPI, url_values)
if err != nil { if err != nil {
return "", err return "", err
} }
log.Printf("%s", body_string) log.Debugf("%s", body_string)
log.Printf("utilityResgroupCheckPresence: ready to decode response body from %q", CloudspacesListAPI) log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %q", ResgroupListAPI)
model := CloudspacesListResp{} model := CloudspacesListResp{}
err = json.Unmarshal([]byte(body_string), &model) err = json.Unmarshal([]byte(body_string), &model)
if err != nil { if err != nil {
return "", err return "", err
} }
log.Printf("utilityResgroupCheckPresence: traversing decoded Json of length %d", len(model)) log.Debugf("utilityResgroupCheckPresence: traversing decoded Json of length %d", len(model))
for index, item := range model { for index, item := range model {
// need to match VDC by name & tenant name // need to match RG by name & account name
if item.Name == name && item.TenantName == tenant_name { if item.Name == name && item.AccountName == account_name {
log.Printf("utilityResgroupCheckPresence: match ResGroup name %q / ID %d, tenant %q at index %d", log.Debugf("utilityResgroupCheckPresence: match RG name %q / ID %d, account %q at index %d",
item.Name, item.ID, item.TenantName, index) item.Name, item.ID, item.AccountName, index)
// not all required information is returned by cloudspaces/list API, so we need to initiate one more // not all required information is returned by rg/list API, so we need to initiate one more
// call to cloudspaces/get to obtain extra data to complete Resource population. // call to rg/get to obtain extra data to complete Resource population.
// Namely, we need to extract resource quota settings // Namely, we need to extract resource quota settings
req_values := &url.Values{} req_values := &url.Values{}
req_values.Add("cloudspaceId", fmt.Sprintf("%d", item.ID)) req_values.Add("rgId", fmt.Sprintf("%d", item.ID))
body_string, err := controller.decortAPICall("POST", CloudspacesGetAPI, req_values) body_string, err := controller.decortAPICall("POST", ResgroupGetAPI, req_values)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -115,32 +121,32 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
} }
} }
return "", fmt.Errorf("Cannot find resource group name %q owned by tenant %q", name, tenant_name) return "", fmt.Errorf("Cannot find RG name %q owned by account %q", name, account_name)
} }
func utilityGetTenantIdByName(tenant_name string, m interface{}) (int, error) { func utilityGetAccountIdByName(account_name string, m interface{}) (int, error) {
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
url_values := &url.Values{} url_values := &url.Values{}
body_string, err := controller.decortAPICall("POST", TenantsListAPI, url_values) body_string, err := controller.decortAPICall("POST", AccountsListAPI, url_values)
if err != nil { if err != nil {
return 0, err return 0, err
} }
model := TenantsListResp{} model := AccountsListResp{}
err = json.Unmarshal([]byte(body_string), &model) err = json.Unmarshal([]byte(body_string), &model)
if err != nil { if err != nil {
return 0, err return 0, err
} }
log.Printf("utilityGetTenantIdByName: traversing decoded Json of length %d", len(model)) log.Debugf("utilityGetAccountIdByName: traversing decoded Json of length %d", len(model))
for index, item := range model { for index, item := range model {
// need to match Tenant by name // need to match Account by name
if item.Name == tenant_name { if item.Name == account_name {
log.Printf("utilityGetTenantIdByName: match Tenant name %q / ID %d at index %d", log.Debugf("utilityGetAccountIdByName: match Account name %q / ID %d at index %d",
item.Name, item.ID, index) item.Name, item.ID, index)
return item.ID, nil return item.ID, nil
} }
} }
return 0, fmt.Errorf("Cannot find tenant %q for the current user. Check tenant value and your access rights", tenant_name) return 0, fmt.Errorf("Cannot find account %q for the current user. Check account value and your access rights", account_name)
} }
Loading…
Cancel
Save