Add account handler and refine other code. No testing yet!

rc-1.0
Sergey Shubin svs1370 4 years ago
parent 92528adf2b
commit 2f4be0b92a

@ -0,0 +1,136 @@
/*
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"
"log"
// "net/url"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)
func flattenAccount(d *schema.ResourceData, acc_facts string) error {
// NOTE: this function modifies ResourceData argument - as such it should never be called
// from resourceAccountExists(...) method
// log.Debugf("flattenAccount: ready to decode response body from %q", CloudspacesGetAPI)
details := AccountRecord{}
err := json.Unmarshal([]byte(rg_facts), &details)
if err != nil {
return err
}
log.Debugf("flattenAccount: decoded Account name %q / ID %d, status %q",
details.Name, details.ID, details.Status)
d.SetId(fmt.Sprintf("%d", details.ID))
d.Set("name", details.Name)
d.Set("status", details.Status)
return nil
}
func dataSourceAccountRead(d *schema.ResourceData, m interface{}) error {
acc_facts, err := utilityAccountCheckPresence(d, m)
if acc_facts == "" {
// if empty string is returned from utilityAccountCheckPresence then there is no
// such account and err tells so - just return it to the calling party
d.SetId("") // ensure ID is empty in this case
return err
}
return flattenAccount(d, acc_facts)
}
func dataSourceAccount() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
Read: dataSourceAccountRead,
Timeouts: &schema.ResourceTimeout{
Read: &Timeout30s,
Default: &Timeout60s,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Optional: true,
Description: "Name of the account. Names are case sensitive and unique.",
},
"account_id": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Description: "Unique ID of the account. If account ID is specified, then account name is ignored.",
},
"status": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: "Current status of the account."
}
/* We keep the following attributes commented out, as we are not implementing account
management with Terraform plugin, so we do not need this extra info.
"quota": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: quotaRgSubresourceSchema(), // this is a dictionary
},
Description: "Quotas on the resources for this account and all its resource groups.",
},
"resource_groups": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema {
Type: schema.TypeInt,
},
Description: "IDs of resource groups in this account."
},
"vins": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema {
Type: schema.TypeInt,
},
Description: "IDs of VINSes created at the account level."
},
*/
},
},
}
}

@ -0,0 +1,153 @@
/*
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"
"log"
"net/url"
// "strconv"
"github.com/hashicorp/terraform/helper/schema"
// "github.com/hashicorp/terraform/helper/validation"
)
func utilityAccountCheckPresence(d *schema.ResourceData, m interface{}) (string, error) {
controller := m.(*ControllerCfg)
url_values := &url.Values{}
acc_id, arg_set := d.GetOk("account_id")
if arg_set {
// get Account right away by its ID
log.Debugf("utilityAccountCheckPresence: locating Account by its ID %d", acc_id.(int))
url_values.Add("accountId", fmt.Sprintf("%d", acc_id.(int)))
api_resp, err := controller.decortAPICall("POST", AccountsGetAPI, url_values)
if err != nil {
return "", err
}
return api_resp, nil
}
acc_name, arg_set := d.GetOk("name")
if !arg_set {
// neither ID nor name - no account for you!
return "", fmt.Error("Cannot check account presence if name is empty and no account ID specified.")
}
api_resp, err := controller.decortAPICall("POST", AccountsListAPI, url_values)
if err != nil {
return "", err
}
// log.Debugf("%s", api_resp)
// log.Debugf("utilityAccountCheckPresence: ready to decode response body from %q", AccountsListAPI)
acc_list := AccountsListResp{}
err = json.Unmarshal([]byte(api_resp), &acc_list)
if err != nil {
return "", err
}
log.Debugf("utilityAccountCheckPresence: traversing decoded Json of length %d", len(model))
for index, item := range acc_list {
// match by account name
if item.Name == acc_name.(string) {
log.Debugf("utilityAccountCheckPresence: match account name %q / ID %d at index %d",
item.Name, item.ID, index)
// NB: unlike accounts/get API, accounts/list API returns abridged set of account info,
// for instance it does not return quotas
reencoded_item, err := json.Marshal(item)
if err != nil {
return "", err
}
return reencoded_item.(string), nil
}
}
return "", fmt.Errorf("Cannot find account name %q owned by account ID %d", name, validated_account_id)
}
func utilityGetAccountIdBySchema(d *schema.ResourceData, m interface{}) (int, error) {
/*
This function expects schema that contains the following two elements:
"account_name": &schema.Schema{
Type: schema.TypeString,
Required: Optional,
Description: "Name of the account, ....",
},
"account_id": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Description: "Unique ID of the account, ....",
},
Then it will check, which argument is set, and if account name is present, it will
initiate API calls to the DECORT cloud controller and try to match relevant account
by the name.
*/
account_id, arg_set := d.GetOk("account_id")
if arg_set {
if account_id.(int) > 0 {
return account_id.(int), nil
}
return 0, fmt.Error("Account ID must be positive, if set.")
}
account_name, arg_set := d.GetOk("account_name")
if !arg_set {
return 0, fmt.Error("Non-empty account name or positive account ID must be specified.")
}
controller := m.(*ControllerCfg)
url_values := &url.Values{}
body_string, err := controller.decortAPICall("POST", AccountsListAPI, url_values)
if err != nil {
return 0, err
}
model := AccountsListResp{}
err = json.Unmarshal([]byte(body_string), &model)
if err != nil {
return 0, err
}
log.Debugf("utilityGetAccountIdBySchema: traversing decoded Json of length %d", len(model))
for index, item := range model {
// need to match Account by name
if item.Name == account_name.(string) {
log.Debugf("utilityGetAccountIdBySchema: match Account name %q / ID %d at index %d",
item.Name, item.ID, index)
return item.ID, nil
}
}
return 0, fmt.Errorf("Cannot find account %q for the current user. Check account name and your access rights", account_name.(string))
}

@ -49,8 +49,20 @@ func parseComputeDisks(disks []DiskRecord) []interface{} {
for i, value := range disks { for i, value := range disks {
// keys in this map should correspond to the Schema definition // keys in this map should correspond to the Schema definition
// as returned by dataSourceDiskSchemaMake() // as returned by dataSourceDiskSchemaMake()
elem[" attribute "] = value. attribute elem["name"] = value.Name
... elem["disk_id"] = value.ID
elem["account_id"] = value.AccountID
elem["account_name"] = value.AccountName
elem["description"] = value.Desc
elem["image_id"] = value.ImageID
elem["size"] = value.SizeMax
elem["type"] = value.Type
elem["sep_id"] = value.SepID
elem["sep_type"] = value.SepType
elem["pool"] = value.Pool
elem["status"] = value.Status
elem["tech_status"] = value.TechStatus
elem["compute_id"] = value.ComputeID
result[i] = elem result[i] = elem
} }
@ -71,8 +83,22 @@ func parseComputeInterfaces(ifaces []InterfaceRecord) []interface{} {
for i, value := range ifaces { for i, value := range ifaces {
// Keys in this map should correspond to the Schema definition // Keys in this map should correspond to the Schema definition
// as returned by dataSourceInterfaceSchemaMake() // as returned by dataSourceInterfaceSchemaMake()
elem[" attribute "] = value. attribute elem["net_id"] = value.NetId
... elem["net_type"] = value.NetType
elem["ip_address"] = value.IPAddress
elem["netmask"] = value.NetMask
elem["mac"] = value.MAC
elem["default_gw"] = value.DefaultGW
elem["name"] = value.Name
elem["connection_id"] = value.ConnID
elem["connection_type"] = value.ConnType
qos_schema := interfaceQosSubresourceSchemaMake()
qos_schema.Set("egress_rate", value.QOS.ERate)
qos_schema.Set("ingress_rate", value.QOS.InRate)
qos_schema.Set("ingress_burst", value.QOS.InBurst)
elem["qos"] = qos_schema
result[i] = elem result[i] = elem
} }
@ -93,7 +119,7 @@ func flattenCompute(d *schema.ResourceData, comp_facts string) error {
return err return err
} }
log.Debugf("flattenCompute: model.ID %d, model.ResGroupID %d", model.ID, model.ResGroupID) log.Debugf("flattenCompute: ID %d, ResGroupID %d", model.ID, model.ResGroupID)
d.SetId(fmt.Sprintf("%d", model.ID)) d.SetId(fmt.Sprintf("%d", model.ID))
d.Set("compute_id", model.ID) d.Set("compute_id", model.ID)
@ -119,25 +145,15 @@ func flattenCompute(d *schema.ResourceData, comp_facts string) error {
} }
if len(model.Interfaces) > 0 { if len(model.Interfaces) > 0 {
log.Printf("flattenCompute: calling parseComputeInterfaces for %d interfaces", len(model.Interfaces)) log.Debugf("flattenCompute: calling parseComputeInterfaces for %d interfaces", len(model.Interfaces))
if err = d.Set("interfaces", parseComputeInterfaces(model.Interfaces)); err != nil { if err = d.Set("interfaces", parseComputeInterfaces(model.Interfaces)); err != nil {
return err return err
} }
} }
if len(model.GuestLogins) > 0 { if len(model.GuestLogins) > 0 {
log.Printf("flattenCompute: calling parseGuestLogins") log.Debugf("flattenCompute: calling parseGuestLogins for %d logins", len(model.GuestLogins))
guest_logins := parseGuestLogins(model.GuestLogins) if err = d.Set("guest_logins", parseGuestLogins(model.GuestLogins)); err != nil {
if err = d.Set("guest_logins", guest_logins); err != nil {
return err
}
default_login := guest_logins[0].(map[string]interface{})
// set user & password attributes to the corresponding values of the 1st item in the list
if err = d.Set("user", default_login["login"]); err != nil {
return err
}
if err = d.Set("password", default_login["password"]); err != nil {
return err return err
} }
} }
@ -263,7 +279,7 @@ func dataSourceCompute() *schema.Resource {
Type: schema.TypeList, Type: schema.TypeList,
Computed: true, Computed: true,
Elem: &schema.Resource { Elem: &schema.Resource {
Schema: interfaceSubresourceSchema(), Schema: interfaceSubresourceSchemaMake(),
}, },
Description: "Specification for the virtual NICs configured on this compute instance.", Description: "Specification for the virtual NICs configured on this compute instance.",
}, },

@ -74,15 +74,14 @@ func utilityDiskCheckPresence(d *schema.ResourceData, m interface{}) (string, er
return "", fmt.Error("Cannot locate disk if name is empty and no disk ID specified.") return "", fmt.Error("Cannot locate disk if name is empty and no disk ID specified.")
} }
account_id, acc_id_set := d.GetOk("account_id") // Valid account ID is required to locate disks
if !acc_id_set { // obtain Account ID by account name - it should not be zero on success
account_name, arg_set := d.GetOkd("account_name") validated_account_id, err := utilityGetAccountIdBySchema(d, m)
if !arg_set { if err != nil {
return "", fmt.Error("Cannot locate disk by name %s if neither account ID nor account name are set", disk_name.(string)) return err
}
} }
url_values.Add("accountId", fmt.Sprintf("%d", account_id.(int))) url_values.Add("accountId", fmt.Sprintf("%d", validated_account_id))
disk_facts, err := controller.decortAPICall("POST", DisksListAPI, url_values) disk_facts, err := controller.decortAPICall("POST", DisksListAPI, url_values)
if err != nil { if err != nil {
return "", err return "", err
@ -100,7 +99,7 @@ func utilityDiskCheckPresence(d *schema.ResourceData, m interface{}) (string, er
log.Debugf("utilityDiskCheckPresence: traversing decoded JSON of length %d", len(disks_list)) log.Debugf("utilityDiskCheckPresence: traversing decoded JSON of length %d", len(disks_list))
for _, item := range disks_list { for _, item := range disks_list {
// need to match disk by name, return the first match // need to match disk by name, return the first match
if item.Name == disk_name && item.Status != "DESTROYED" { if item.Name == disk_name.(string) && item.Status != "DESTROYED" {
log.Printf("utilityDiskCheckPresence: index %d, matched disk name %q", index, item.Name) log.Printf("utilityDiskCheckPresence: index %d, matched disk name %q", index, item.Name)
// we found the disk we need - not get detailed information via API call to disks/get // we found the disk we need - not get detailed information via API call to disks/get
/* /*

@ -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");
@ -34,39 +34,41 @@ import (
"github.com/hashicorp/terraform/helper/validation" "github.com/hashicorp/terraform/helper/validation"
) )
func dataSourceImageRead(d *schema.ResourceData, m interface{}) error { func dataSourceImageRead(d *schema.ResourceData, m interface{}) error {
name := d.Get("name").(string) name := d.Get("name").(string)
rgid, rgid_set := d.GetOk("rgid") // rg_id, rgid_set := d.GetOk("rg_id")
tenant_id, tenant_set := d.GetOk("tenant_id") account_id, account_set := d.GetOk("account_id")
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
url_values := &url.Values{} url_values := &url.Values{}
if tenant_set { if account_set {
url_values.Add("accountId", fmt.Sprintf("%d",tenant_id.(int))) url_values.Add("accountId", fmt.Sprintf("%d", account_id.(int)))
}
if rgid_set {
url_values.Add("cloudspaceId", fmt.Sprintf("%d",rgid.(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
} }
log.Printf("dataSourceImageRead: ready to decode response body") log.Debugf("dataSourceImageRead: ready to decode response body from %q", ImagesListAPI)
model := ImagesListResp{} model := ImagesListResp{}
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("%#v", model) // log.Printf("%#v", model)
log.Printf("dataSourceImageRead: traversing decoded JSON of length %d", len(model)) log.Debugf("dataSourceImageRead: traversing decoded JSON of length %d", len(model))
for index, item := range model { for index, item := range model {
// need to match VM by name // need to match Image by name
if item.Name == name { if item.Name == name {
log.Printf("dataSourceImageRead: index %d, matched name %q", index, item.Name) log.Printf("dataSourceImageRead: index %d, matched name %q", index, item.Name)
d.SetId(fmt.Sprintf("%d", model[index].ID)) d.SetId(fmt.Sprintf("%d", item.ID))
d.Set("account_id", item.AccountID)
d.Set("arch", item.Arch)
d.Set("sep_id", item.SepID)
d.Set("pool", item.Pool)
d.Set("status", item.Status)
d.Set("size", item.Size)
// d.Set("field_name", value) // d.Set("field_name", value)
return nil return nil
} }
@ -76,35 +78,66 @@ func dataSourceImageRead(d *schema.ResourceData, m interface{}) error {
} }
func dataSourceImage() *schema.Resource { func dataSourceImage() *schema.Resource {
return &schema.Resource { return &schema.Resource{
SchemaVersion: 1, SchemaVersion: 1,
Read: dataSourceImageRead, Read: dataSourceImageRead,
Timeouts: &schema.ResourceTimeout { Timeouts: &schema.ResourceTimeout{
Read: &Timeout30s, Read: &Timeout30s,
Default: &Timeout60s, Default: &Timeout60s,
}, },
Schema: map[string]*schema.Schema { Schema: map[string]*schema.Schema{
"name": { "name": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "Name of the OS image to locate. This parameter is case sensitive.", Description: "Name of the OS image to locate. This parameter is case sensitive.",
}, },
"tenant_id": { "account_id": {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
ValidateFunc: validation.IntAtLeast(1), ValidateFunc: validation.IntAtLeast(1),
Description: "ID of the tenant to limit image search to.", Description: "Optional ID of the account to limit image search to.",
},
"arch": {
Type: schema.TypeString,
Computed: true,
Description: "Binary architecture this image is created for.",
}, },
"rgid": { "sep_id": {
Type: schema.TypeString,
Computed: true,
Description: "Storage end-point provider serving this image.",
},
/*
"sep_type": {
Type: schema.TypeString,
Computed: true,
Description: "Type of the storage end-point provider serving this image.",
},
*/
"pool": {
Type: schema.TypeString,
Computed: true,
Description: "Pool where this image is located.",
},
"size": {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Computed: true,
ValidateFunc: validation.IntAtLeast(1), Description: "Size of the image in GB.",
Description: "ID of the resource group to limit image search to.", },
"status": {
Type: schema.TypeString,
Computed: true,
Description: "Current model status of this disk.",
}, },
}, },
} }

@ -0,0 +1,330 @@
/*
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 contains definitions and code for handling Interface component of Compute schema
*/
package decort
import (
"log"
"strconv"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)
func interfaceSubresourceSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
"net_id": {
Type: schema.TypeInt,
Computed: true,
Description: "ID of the network entity this interface is connected to.",
},
"net_type": {
Type: schema.TypeString,
Computed: true,
Description: "Type of the network entity this interface is connected to.",
},
"ip_address": {
Type: schema.TypeString,
Computed: true,
Description: "IP addresses assigned to this interface.",
},
"netmask": {
Type: schema.TypeInt,
Computed: true,
Description: "Network mask to be used with this interface.",
},
"mac": {
Type: schema.TypeString,
Computed: true,
Description: "MAC address of this interface.",
},
"default_gw": {
Type: schema.TypeString,
Computed: true,
Description: "Default gateway associated with this interface.",
},
"name": {
Type: schema.TypeString,
Computed: true,
Description: "Interface name.",
},
"connection_id": {
Type: schema.TypeInt,
Computed: true,
Description: "VxLAN or VLAN ID this interface is connected to.",
},
"connection_type": {
Type: schema.TypeString,
Computed: true,
Description: "Type of the segment (VLAN or VxLAN) this interface is connected to.",
},
"qos": {
Computed: true,
Elem: &schema.Resource{
Schema: interfaceQosSubresourceSchemaMake(),
},
Description: "Details about the guest OS users provisioned together with this compute instance.",
},
}
return rets
}
func interfaceQosSubresourceSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
"egress_rate": {
Type: schema.TypeInt,
Computed: true,
Description: "Egress rate limit on this interface.",
},
"ingress_burst": {
Type: schema.TypeInt,
Computed: true,
Description: "Ingress burst limit on this interface.",
},
"ingress_rate": {
Type: schema.TypeInt,
Computed: true,
Description: "Ingress rate limit on this interface.",
},
"guid": {
Type: schema.TypeString,
Computed: true,
Description: "GUID of this QoS record.",
},
}
return rets
}
/*
func flattenNetworks(nets []NicRecord) []interface{} {
// this function expects an array of NicRecord as returned by machines/get API call
// NOTE: it does NOT expect a strucutre as returned by externalnetwork/list
var length = 0
var strarray []string
for _, value := range nets {
if value.NicType == "PUBLIC" {
length += 1
}
}
log.Printf("flattenNetworks: found %d NICs with PUBLIC type", length)
result := make([]interface{}, length)
if length == 0 {
return result
}
elem := make(map[string]interface{})
var subindex = 0
for index, value := range nets {
if value.NicType == "PUBLIC" {
// this will be changed as network segments entity
// value.Params for ext net comes in a form "gateway:176.118.165.1 externalnetworkId:6"
// for network_id we need to extract from this string
strarray = strings.Split(value.Params, " ")
substr := strings.Split(strarray[1], ":")
elem["network_id"], _ = strconv.Atoi(substr[1])
elem["ip_range"] = value.IPAddress
// elem["label"] = ... - should be uncommented for the future release
log.Printf("flattenNetworks: parsed element %d - network_id %d, ip_range %q",
index, elem["network_id"].(int), value.IPAddress)
result[subindex] = elem
subindex += 1
}
}
return result
}
func makePortforwardsConfig(arg_list []interface{}) (pfws []PortforwardConfig, count int) {
count = len(arg_list)
if count < 1 {
return nil, 0
}
pfws = make([]PortforwardConfig, count)
var subres_data map[string]interface{}
for index, value := range arg_list {
subres_data = value.(map[string]interface{})
// pfws[index].Label = subres_data["label"].(string) - should be uncommented for future release
pfws[index].ExtPort = subres_data["ext_port"].(int)
pfws[index].IntPort = subres_data["int_port"].(int)
pfws[index].Proto = subres_data["proto"].(string)
}
return pfws, count
}
func flattenPortforwards(pfws []PortforwardRecord) []interface{} {
result := make([]interface{}, len(pfws))
elem := make(map[string]interface{})
var port_num int
for index, value := range pfws {
// elem["label"] = ... - should be uncommented for the future release
// external port field is of TypeInt in the portforwardSubresourceSchema, but string is returned
// by portforwards/list API, so we need conversion here
port_num, _ = strconv.Atoi(value.ExtPort)
elem["ext_port"] = port_num
// internal port field is of TypeInt in the portforwardSubresourceSchema, but string is returned
// by portforwards/list API, so we need conversion here
port_num, _ = strconv.Atoi(value.IntPort)
elem["int_port"] = port_num
elem["proto"] = value.Proto
elem["ext_ip"] = value.ExtIP
elem["int_ip"] = value.IntIP
result[index] = elem
}
return result
}
func portforwardSubresourceSchema() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
/* this should be uncommented for the future release
"label": {
Type: schema.TypeString,
Required: true,
Description: "Unique label of this network connection to identify it amnong other connections for this VM.",
},
*/
"ext_port": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 65535),
Description: "External port number for this port forwarding rule.",
},
"int_port": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 65535),
Description: "Internal port number for this port forwarding rule.",
},
"proto": {
Type: schema.TypeString,
Required: true,
// ValidateFunc: validation.IntBetween(1, ),
Description: "Protocol type for this port forwarding rule. Should be either 'tcp' or 'udp'.",
},
"ext_ip": {
Type: schema.TypeString,
Computed: true,
Description: ".",
},
"int_ip": {
Type: schema.TypeString,
Computed: true,
Description: ".",
},
}
return rets
}
func flattenNICs(nics []NicRecord) []interface{} {
var result = make([]interface{}, len(nics))
elem := make(map[string]interface{})
for index, value := range nics {
elem["status"] = value.Status
elem["type"] = value.NicType
elem["mac"] = value.MacAddress
elem["ip_address"] = value.IPAddress
elem["parameters"] = value.Params
elem["reference_id"] = value.ReferenceID
elem["network_id"] = value.NetworkID
result[index] = elem
}
return result
}
func nicSubresourceSchema() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
"status": {
Type: schema.TypeString,
Computed: true,
Description: "Current status of this NIC.",
},
"type": {
Type: schema.TypeString,
Computed: true,
Description: "Type of this NIC.",
},
"mac": {
Type: schema.TypeString,
Computed: true,
Description: "MAC address assigned to this NIC.",
},
"ip_address": {
Type: schema.TypeString,
Computed: true,
Description: "IP address assigned to this NIC.",
},
"parameters": {
Type: schema.TypeString,
Computed: true,
Description: "Additional NIC parameters.",
},
"reference_id": {
Type: schema.TypeString,
Computed: true,
Description: "Reference ID of this NIC.",
},
"network_id": {
Type: schema.TypeInt,
Computed: true,
Description: "Network ID which this NIC is connected to.",
},
}
return rets
}
*/

@ -15,17 +15,16 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package decs package decort
import ( import (
"log" "log"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
// "github.com/hashicorp/terraform/helper/validation" // "github.com/hashicorp/terraform/helper/validation"
) )
func flattenGuestLogins(logins []GuestLoginRecord) []interface{} { func parseGuestLogins(logins []OsUserRecord) []interface{} {
var result = make([]interface{}, len(logins)) var result = make([]interface{}, len(logins))
elem := make(map[string]interface{}) elem := make(map[string]interface{})
@ -34,28 +33,28 @@ func flattenGuestLogins(logins []GuestLoginRecord) []interface{} {
elem["guid"] = value.Guid elem["guid"] = value.Guid
elem["login"] = value.Login elem["login"] = value.Login
elem["password"] = value.Password elem["password"] = value.Password
elem["public_key"] = value.PubKey
result[index] = elem result[index] = elem
log.Printf("flattenGuestLogins: parsed element %d - login %q", log.Debugf("parseGuestLogins: parsed element %d - login %q", index, value.Login)
index, value.Login)
} }
return result return result
} }
func loginsSubresourceSchema() map[string]*schema.Schema { func loginsSubresourceSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema { rets := map[string]*schema.Schema{
"guid": { "guid": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Default: "", Default: "",
Description: "GUID of this guest user.", Description: "GUID of this guest OS user.",
}, },
"login": { "login": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Default: "", Default: "",
Description: "Login name of this guest user.", Description: "Login name of this guest OS user.",
}, },
"password": { "password": {
@ -63,7 +62,14 @@ func loginsSubresourceSchema() map[string]*schema.Schema {
Optional: true, Optional: true,
Default: "", Default: "",
Sensitive: true, Sensitive: true,
Description: "Password of this guest user.", Description: "Password of this guest OS user.",
},
"public_key": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: "SSH public key of this guest user.",
}, },
} }

@ -22,11 +22,9 @@ Technology platfom.
Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates. Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates.
*/ */
package decort package decort
import ( import (
"time" "time"
) )
@ -79,17 +77,19 @@ type ResgroupRecord struct {
} }
const ResgroupListAPI = "/restmachine/cloudapi/rg/list" const ResgroupListAPI = "/restmachine/cloudapi/rg/list"
type ResgroupListResp []ResgroupRecord 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"
// //
// structures related to /cloudapi/rg/update API call // structures related to /cloudapi/rg/update API call
// //
const ResgroupUpdateAPI= "/restmachine/cloudapi/rg/update" const ResgroupUpdateAPI = "/restmachine/cloudapi/rg/update"
type ResgroupUpdateParam struct { type ResgroupUpdateParam struct {
RgId int `json:"rgId"` RgId int `json:"rgId"`
Name string `json:"name"` Name string `json:"name"`
@ -127,7 +127,8 @@ type UsageRecord struct {
Reserved ResourceRecord `json:"Reserved"` Reserved ResourceRecord `json:"Reserved"`
} }
const ResgroupGetAPI= "/restmachine/cloudapi/rg/get" 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"`
@ -158,6 +159,7 @@ type ResgroupGetResp struct {
// structures related to /cloudapi/rg/update API // structures related to /cloudapi/rg/update API
// //
const ResgroupUpdateAPI = "/restmachine/cloudapi/rg/update" const ResgroupUpdateAPI = "/restmachine/cloudapi/rg/update"
type ResgroupUpdateParam struct { type ResgroupUpdateParam struct {
ID uint `json:"rgId"` ID uint `json:"rgId"`
Name string `json:"name"` Name string `json:"name"`
@ -190,7 +192,9 @@ type ComputeBriefRecord struct { // this is a brief compute specifiaction as ret
Status string `json:"status"` Status string `json:"status"`
TechStatus string `json:"techStatus"` TechStatus string `json:"techStatus"`
} }
const RgListComputesAPI = "/restmachine/cloudapi/rg/listComputes" const RgListComputesAPI = "/restmachine/cloudapi/rg/listComputes"
type RgListComputesResp []ComputeBriefRecord type RgListComputesResp []ComputeBriefRecord
// //
@ -198,6 +202,7 @@ type RgListComputesResp []ComputeBriefRecord
// //
const KvmX86CreateAPI = "/restmachine/cloudapi/kvmx86/create" const KvmX86CreateAPI = "/restmachine/cloudapi/kvmx86/create"
const KvmPPCCreateAPI = "/restmachine/cloudapi/kvmppc/create" const KvmPPCCreateAPI = "/restmachine/cloudapi/kvmppc/create"
type KvmVmCreateParam struct { // this is unified structure for both x86 and PPC based KVM VMs creation type KvmVmCreateParam struct { // this is unified structure for both x86 and PPC based KVM VMs creation
RgID uint `json:"rgId"` RgID uint `json:"rgId"`
Name string `json:"name"` Name string `json:"name"`
@ -220,21 +225,29 @@ const ComputeDeleteAPI = "/restmachine/cloudapi/compute/delete"
// structures related to /cloudapi/compute/list API // structures related to /cloudapi/compute/list API
// //
type InterfaceQosRecord struct {
ERate int `json:"eRate"`
Guid string `json:"guid"`
InBurst int `json:"inBurst"`
InRate int `json:"inRate"`
}
type InterfaceRecord struct { type InterfaceRecord struct {
ConnID int `json:"connId"` ConnID int `json:"connId"` // This is VLAN ID or VxLAN ID, depending on ConnType
ConnType string `json:"connType"` ConnType string `json:"connType"` // Either "VLAN" or "VXLAN" tag
DefaultGW string `json:"defGw"` DefaultGW string `json:"defGw"`
Guid string `json:"guid"` Guid string `json:"guid"`
IPAddress string `json:"ipAddress"` // without trailing network mask, i.e. "192.168.1.3" IPAddress string `json:"ipAddress"` // without trailing network mask, i.e. "192.168.1.3"
MAC string `json:"mac"` MAC string `json:"mac"`
Name string `json:"name"` Name string `json:"name"`
NetID int `json:"netId"` NetID int `json:"netId"` // This is either ExtNet ID or ViNS ID, depending on NetType
NetMaks int `json:"netMask"` NetMask int `json:"netMask"`
NetType string `json:"netType"` NetType string `json:"netType"` // Either "EXTNET" or "VINS" tag
PciSlot int `json:"pciSlot"` PciSlot int `json:"pciSlot"`
Target string `json:"target"` Target string `json:"target"`
Type string `json:"type"` Type string `json:"type"`
VNFs []int `json:"vnfs"` VNFs []int `json:"vnfs"`
QOS InterfaceQosRecord `json:"qos"`
} }
type SnapSetRecord struct { type SnapSetRecord struct {
@ -283,6 +296,7 @@ type ComputeRecord struct {
} }
const ComputeListAPI = "/restmachine/cloudapi/compute/list" const ComputeListAPI = "/restmachine/cloudapi/compute/list"
type ComputeListResp []ComputeRecord type ComputeListResp []ComputeRecord
// //
@ -341,6 +355,7 @@ type OsUserRecord struct {
} }
const ComputeGetAPI = "/restmachine/cloudapi/compute/get" const ComputeGetAPI = "/restmachine/cloudapi/compute/get"
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
AccountID int `json:"accountId"` AccountID int `json:"accountId"`
@ -390,7 +405,7 @@ type ImageRecord struct {
AccountID 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 bool `json:"bootable"`
IsCdrom bool `json:"cdrom"` IsCdrom bool `json:"cdrom"`
Desc string `json:"description"` Desc string `json:"description"`
IsHotResize bool `json:"hotResize"` IsHotResize bool `json:"hotResize"`
@ -406,9 +421,7 @@ type ImageRecord struct {
} }
const ImagesListAPI = "/restmachine/cloudapi/images/list" const ImagesListAPI = "/restmachine/cloudapi/images/list"
type ImagesListParam struct {
AccountID int `json:"accountId"`
}
type ImagesListResp []ImageRecord type ImagesListResp []ImageRecord
// //
@ -421,26 +434,25 @@ type ExtNetRecord struct {
} }
const ExtNetListAPI = "/restmachine/cloudapi/extnet/list" const ExtNetListAPI = "/restmachine/cloudapi/extnet/list"
type ExtNetListParam struct {
AccountID int `json:"accountId"`
}
type ExtNetListResp []ExtNetRecord
type ExtNetListResp []ExtNetRecord
// //
// structures related to /cloudapi/accounts/list API // structures related to /cloudapi/accounts/list API
// //
type AccountRecord 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"`
ID int `json:"id"` ID int `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Status string `json:"status"` Status string `json:"status"`
UpdatedTime uint64 `json:"updateTime"` // UpdatedTime uint64 `json:"updateTime"`
} }
const AccountsListAPI = "/restmachine/cloudapi/accounts/list" const AccountsGetAPI = "/restmachine/cloudapi/accounts/get" // returns AccountRecord superset
const AccountsListAPI = "/restmachine/cloudapi/accounts/list" // returns list of abdridged info about accounts
type AccountsListResp []AccountRecord type AccountsListResp []AccountRecord
// //
@ -457,53 +469,23 @@ type PfwRecord struct {
} }
const ComputePfwListAPI = "/restmachine/cloudapi/compute/pfwList" const ComputePfwListAPI = "/restmachine/cloudapi/compute/pfwList"
type ComputePfwListResp []PfwRecord type ComputePfwListResp []PfwRecord
type ComputePfwAddParam struct {
ComputeID int `json:"computeId"`
PublicPortStart int `json:"publicPortStart"`
PublicPortEnd int `json:"publicPortEnd"`
LocalBasePort int `json:"localBasePort"`
Protocol string `json:"proto"`
}
const ComputePfwAddAPI = "/restmachine/cloudapi/compute/pfwAdd" const ComputePfwAddAPI = "/restmachine/cloudapi/compute/pfwAdd"
type ComputePfwDelParam struct {
ComputeID int `json:"computeId"`
RuleID int `json:"ruleId"`
PublicPortStart int `json:"publicPortStart"`
PublicPortEnd int `json:"publicPortEnd"`
LocalBasePort int `json:"localBasePort"`
Protocol string `json:"proto"`
}
const ComputePfwDelAPI = "/restmachine/cloudapi/compute/pfwDel" const ComputePfwDelAPI = "/restmachine/cloudapi/compute/pfwDel"
// //
// structures related to /cloudapi/compute/net Attach/Detach API // structures related to /cloudapi/compute/net Attach/Detach API
// //
type ComputeNetAttachParam struct {
ComputeID int `json:"computeId"`
NetType string `json:"netType"`
NetID int `json:"netId"`
IPAddr string `json:"apAddr"`
}
const ComputeNetAttachAPI = "/restmachine/cloudapi/compute/netAttach" const ComputeNetAttachAPI = "/restmachine/cloudapi/compute/netAttach"
type ComputeNetDetachParam struct {
ComputeID int `json:"computeId"`
IPAddr string `json:"apAddr"`
MAC string `json:"mac"`
}
const ComputeNetDetachAPI = "/restmachine/cloudapi/compute/netDetach" const ComputeNetDetachAPI = "/restmachine/cloudapi/compute/netDetach"
// //
// structures related to /cloudapi/compute/disk Attach/Detach API // structures related to /cloudapi/compute/disk Attach/Detach API
// //
type ComputeDiskManipulationParam struct {
ComputeID int `json:"computeId"`
DiskID int `json:"diskId"`
}
const ComputeDiskAttachAPI = "/restmachine/cloudapi/compute/diskAttach" const ComputeDiskAttachAPI = "/restmachine/cloudapi/compute/diskAttach"
const ComputeDiskDetachAPI = "/restmachine/cloudapi/compute/diskDetach" const ComputeDiskDetachAPI = "/restmachine/cloudapi/compute/diskDetach"
@ -521,14 +503,12 @@ type DiskCreateParam struct {
SepID int `json:"sep_id"` SepID int `json:"sep_id"`
Pool string `json:"pool"` Pool string `json:"pool"`
} }
const DiskCreateAPI = "/restmachine/cloudapi/disks/create" const DiskCreateAPI = "/restmachine/cloudapi/disks/create"
// //
// structures related to /cloudapi/disks/get // structures related to /cloudapi/disks/get
// //
type DisksGetParam struct {
DiskID int `json:"diskId`
}
const DisksCreateAPI = "/restmachine/cloudapi/disks/create" const DisksCreateAPI = "/restmachine/cloudapi/disks/create"
const DisksGetAPI = "/restmachine/cloudapi/disks/get" // Returns single DiskRecord on success const DisksGetAPI = "/restmachine/cloudapi/disks/get" // Returns single DiskRecord on success

@ -18,34 +18,32 @@ limitations under the License.
package decort package decort
import ( import (
"strings" "strings"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation" "github.com/hashicorp/terraform/helper/validation"
// "github.com/hashicorp/terraform/terraform" // "github.com/hashicorp/terraform/terraform"
) )
var decsController *ControllerCfg var decsController *ControllerCfg
func Provider() *schema.Provider { func Provider() *schema.Provider {
return &schema.Provider { return &schema.Provider{
Schema: map[string]*schema.Schema { Schema: map[string]*schema.Schema{
"authenticator": { "authenticator": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
StateFunc: stateFuncToLower, StateFunc: stateFuncToLower,
ValidateFunc: validation.StringInSlice([]string{"oauth2", "legacy", "jwt"}, true), // ignore case while validating ValidateFunc: validation.StringInSlice([]string{"oauth2", "legacy", "jwt"}, true), // ignore case while validating
Description: "Authentication mode to use when connecting to DECS cloud API. Should be one of 'oauth2', 'legacy' or 'jwt'.", Description: "Authentication mode to use when connecting to DECORT cloud API. Should be one of 'oauth2', 'legacy' or 'jwt'.",
}, },
"oauth2_url": { "oauth2_url": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
StateFunc: stateFuncToLower, StateFunc: stateFuncToLower,
DefaultFunc: schema.EnvDefaultFunc("DECS_OAUTH2_URL", nil), DefaultFunc: schema.EnvDefaultFunc("DECORT_OAUTH2_URL", nil),
Description: "The Oauth2 application URL in 'oauth2' authentication mode.", Description: "OAuth2 application URL in 'oauth2' authentication mode.",
}, },
"controller_url": { "controller_url": {
@ -53,65 +51,68 @@ func Provider() *schema.Provider {
Required: true, Required: true,
ForceNew: true, ForceNew: true,
StateFunc: stateFuncToLower, StateFunc: stateFuncToLower,
Description: "The URL of DECS Cloud controller to use. API calls will be directed to this URL.", Description: "URL of DECORT Cloud controller to use. API calls will be directed to this URL.",
}, },
"user": { "user": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DECS_USER", nil), DefaultFunc: schema.EnvDefaultFunc("DECORT_USER", nil),
Description: "The user name for DECS cloud API operations in 'legacy' authentication mode.", Description: "User name for DECORT cloud API operations in 'legacy' authentication mode.",
}, },
"password": { "password": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DECS_PASSWORD", nil), DefaultFunc: schema.EnvDefaultFunc("DECORT_PASSWORD", nil),
Description: "The user password for DECS cloud API operations in 'legacy' authentication mode.", Description: "User password for DECORT cloud API operations in 'legacy' authentication mode.",
}, },
"app_id": { "app_id": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DECS_APP_ID", nil), DefaultFunc: schema.EnvDefaultFunc("DECORT_APP_ID", nil),
Description: "Application ID to access DECS cloud API in 'oauth2' authentication mode.", Description: "Application ID to access DECORT cloud API in 'oauth2' authentication mode.",
}, },
"app_secret": { "app_secret": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DECS_APP_SECRET", nil), DefaultFunc: schema.EnvDefaultFunc("DECSORTAPP_SECRET", nil),
Description: "Application secret to access DECS cloud API in 'oauth2' authentication mode.", Description: "Application secret to access DECORT cloud API in 'oauth2' authentication mode.",
}, },
"jwt": { "jwt": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DECS_JWT", nil), DefaultFunc: schema.EnvDefaultFunc("DECS_JWT", nil),
Description: "JWT to access DECS cloud API in 'jwt' authentication mode.", Description: "JWT to access DECORT cloud API in 'jwt' authentication mode.",
}, },
"allow_unverified_ssl": { "allow_unverified_ssl": {
Type: schema.TypeBool, Type: schema.TypeBool,
Optional: true, Optional: true,
Default: false, Default: false,
Description: "If set, DECS API will allow unverifiable SSL certificates.", Description: "If true, DECORT API will not verify SSL certificates. Use this with caution and in trusted environments only!",
}, },
}, },
ResourcesMap: map[string]*schema.Resource { ResourcesMap: map[string]*schema.Resource{
"decort_resgroup": resourceResgroup(), "decort_resgroup": resourceResgroup(),
"decort_kvmx86": resourceKvmX86(), "decort_kvmvm": resourceCompute(),
"decort_disk": resourceDisk(), "decort_disk": resourceDisk(),
"decort_vins": resourceVins(), "decort_vins": resourceVins(),
// "decort_pfw": resourcePfw(),
}, },
DataSourcesMap: map[string]*schema.Resource { DataSourcesMap: map[string]*schema.Resource{
// "decort_account": dataSourceAccount(),
"decort_resgroup": dataSourceResgroup(), "decort_resgroup": dataSourceResgroup(),
"decs_kvmx86": dataSourceCompute(), "decort_kvmvm": dataSourceCompute(),
"decort_image": dataSourceImage(), "decort_image": dataSourceImage(),
"decort_disk": dataSourceDisk(), "decort_disk": dataSourceDisk(),
"decort_vins": dataSourceVins(), "decort_vins": dataSourceVins(),
// "decort_pfw": dataSourcePfw(),
}, },
ConfigureFunc: providerConfigure, ConfigureFunc: providerConfigure,

@ -25,33 +25,34 @@ Visit https://github.com/rudecs/terraform-provider-decort for full source code p
package decort package decort
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
// "net/url" // "net/url"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
// "github.com/hashicorp/terraform/helper/validation" // "github.com/hashicorp/terraform/helper/validation"
) )
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.Debugf("%s", rg_facts) // log.Debugf("%s", rg_facts)
log.Debugf("flattenResgroup: ready to decode response body from %q", CloudspacesGetAPI) log.Debugf("flattenResgroup: ready to decode response body from API")
details := ResgroupGetResp{} 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.Debugf("flattenResgroup: decoded ResGroup name %q / ID %d, account ID %d, public IP %q", log.Debugf("flattenResgroup: decoded RG name %q / ID %d, account ID %d",
details.Name, details.ID, details.AccountID, details.PublicIP) details.Name, details.ID, details.AccountID)
d.SetId(fmt.Sprintf("%d", details.ID)) d.SetId(fmt.Sprintf("%d", details.ID))
d.Set("rg_id", details.ID)
d.Set("name", details.Name) d.Set("name", details.Name)
d.Set("account_name", details.AccountName)
d.Set("account_id", details.AccountID) d.Set("account_id", details.AccountID)
d.Set("grid_id", details.GridID) d.Set("grid_id", details.GridID)
d.Set("desc", details.Description) d.Set("desc", details.Description)
@ -81,44 +82,49 @@ func dataSourceResgroupRead(d *schema.ResourceData, m interface{}) error {
return flattenResgroup(d, rg_facts) return flattenResgroup(d, rg_facts)
} }
func dataSourceResgroup() *schema.Resource { func dataSourceResgroup() *schema.Resource {
return &schema.Resource { return &schema.Resource{
SchemaVersion: 1, SchemaVersion: 1,
Read: dataSourceResgroupRead, Read: dataSourceResgroupRead,
Timeouts: &schema.ResourceTimeout { Timeouts: &schema.ResourceTimeout{
Read: &Timeout30s, Read: &Timeout30s,
Default: &Timeout60s, Default: &Timeout60s,
}, },
Schema: map[string]*schema.Schema { Schema: map[string]*schema.Schema{
"name": { "name": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Optional: true,
Description: "Name of this resource group. Names are case sensitive and unique within the context of an account.", Description: "Name of the resource group. Names are case sensitive and unique within the context of an account.",
}, },
"account": &schema.Schema { "rg_id": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Description: "Unique ID of the resource group. If this ID is specified, then resource group name is ignored.",
},
"account_name": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: Optional,
Description: "Name of the account, which this resource group belongs to.", Description: "Name of the account, which this resource group belongs to.",
}, },
"account_id": &schema.Schema { "account_id": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Optional: Optional,
Description: "Unique ID of the account, which this resource group belongs to.", Description: "Unique ID of the account, which this resource group belongs to. If account ID is specified, then account name is ignored.",
}, },
"desc": &schema.Schema { "desc": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Description: "User-defined text description of this resource group.", Description: "User-defined text description of this resource group.",
}, },
"grid_id": &schema.Schema { "grid_id": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: 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.",
@ -128,7 +134,7 @@ func dataSourceResgroup() *schema.Resource {
Type: schema.TypeList, Type: schema.TypeList,
Optional: true, Optional: true,
MaxItems: 1, MaxItems: 1,
Elem: &schema.Resource { Elem: &schema.Resource{
Schema: quotaRgSubresourceSchema(), // this is a dictionary Schema: quotaRgSubresourceSchema(), // this is a dictionary
}, },
Description: "Quotas on the resources for this resource group.", Description: "Quotas on the resources for this resource group.",
@ -140,13 +146,13 @@ func dataSourceResgroup() *schema.Resource {
Description: "Current status of this resource group.", Description: "Current status of this resource group.",
}, },
"def_net": &schema.Schema { "def_net": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Description: "Type of the default network for this resource group.", Description: "Type of the default network for this resource group.",
}, },
"def_net_id": &schema.Schema { "def_net_id": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
Description: "ID of the default network for this resource group (if any).", Description: "ID of the default network for this resource group (if any).",
@ -156,7 +162,7 @@ func dataSourceResgroup() *schema.Resource {
Type: schema.TypeList, // this is a list of ints Type: schema.TypeList, // this is a list of ints
Computed: true, Computed: true,
MaxItems: LimitMaxVinsPerResgroup, MaxItems: LimitMaxVinsPerResgroup,
Elem: &schema.Schema { Elem: &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
}, },
Description: "List of VINs deployed in this resource group.", Description: "List of VINs deployed in this resource group.",
@ -165,7 +171,7 @@ func dataSourceResgroup() *schema.Resource {
"computes": { "computes": {
Type: schema.TypeList, //t his is a list of ints Type: schema.TypeList, //t his is a list of ints
Computed: true, Computed: true,
Elem: &schema.Schema { Elem: &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
}, },
Description: "List of computes deployed in this resource group.", Description: "List of computes deployed in this resource group.",

@ -24,44 +24,38 @@ Visit https://github.com/rudecs/terraform-provider-decort for full source code p
package decort package decort
import ( import (
"fmt" "fmt"
"log" "log"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
) )
func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error { func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
// First validate that we have all parameters required to create the new Resource Group // First validate that we have all parameters required to create the new Resource Group
arg_set := false
account_name, arg_set := d.GetOk("account") // Valid account ID is required to create new resource group
if !arg_set { // obtain Account ID by account name - it should not be zero on success
return fmt.Errorf("Cannot create new RG: missing account.") validated_account_id, err := utilityGetAccountIdBySchema(d, m)
if err != nil {
return err
} }
rg_name, arg_set := d.GetOk("name") rg_name, arg_set := d.GetOk("name")
if !arg_set { if !arg_set {
return fmt.Errorf("Cannot create new RG: missing name.") return fmt.Errorf("Cannot create new RG: missing name.")
} }
grid_id, arg_set := d.GetOk("grid_id") grid_id, arg_set := d.GetOk("grid_id")
if !arg_set { if !arg_set {
return fmt.Errorf("Cannot create new RG %q for account %q: missing Grid ID.", return fmt.Errorf("Cannot create new RG %q in account ID %d: missing Grid ID.",
rg_name.(string), account_name.(string)) rg_name.(string), validated_account_id)
} }
// all required parameters are set in the schema - we can continue with RG creation // all required parameters are set in the schema - we can continue with RG creation
log.Debugf("resourceResgroupCreate: called for RG name %q, account name %q", log.Debugf("resourceResgroupCreate: called for RG name %q, account ID %d",
account_name.(string), rg_name.(string)) rg_name.(string), validated_account_id)
// Valid account ID is required to create new resource group
// obtain Account ID by account name - it should not be zero on success
validated_account_id, err := utilityGetAccountIdByName(account_name.(string), m)
if err != nil {
return err
}
// quota settings are optional // quota settings are optional
set_quota := false set_quota := false
@ -94,7 +88,7 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
Reason string `json:"reason"` Reason string `json:"reason"`
ExtNetID int `json:"extNetId"` ExtNetID int `json:"extNetId"`
ExtIP string `json:"extIp"` ExtIP string `json:"extIp"`
} }
*/ */
url_values := &url.Values{} url_values := &url.Values{}
@ -148,7 +142,7 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
func resourceResgroupRead(d *schema.ResourceData, m interface{}) error { func resourceResgroupRead(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceResgroupRead: called for RG name %q, account name %q", log.Debugf("resourceResgroupRead: called for RG name %q, account name %q",
d.Get("name").(string), d.Get("account").(string)) d.Get("name").(string), d.Get("account_name").(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
@ -245,7 +239,7 @@ func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error {
// NOTE: this method forcibly 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 Computes & VINSes that existed in it // restore the destroyed resource group as well all Computes & VINSes that existed in it
log.Debugf("resourceResgroupDelete: called for RG name %q, account name %q", log.Debugf("resourceResgroupDelete: called for RG name %q, account name %q",
d.Get("name").(string), d.Get("account").(string)) d.Get("name").(string), d.Get("account_name").(string))
rg_facts, err := utilityResgroupCheckPresence(d, m) rg_facts, err := utilityResgroupCheckPresence(d, m)
if rg_facts == "" { if rg_facts == "" {
@ -282,7 +276,7 @@ func resourceResgroupExists(d *schema.ResourceData, m interface{}) (bool, error)
} }
func resourceResgroup() *schema.Resource { func resourceResgroup() *schema.Resource {
return &schema.Resource { return &schema.Resource{
SchemaVersion: 1, SchemaVersion: 1,
Create: resourceResgroupCreate, Create: resourceResgroupCreate,
@ -291,7 +285,7 @@ func resourceResgroup() *schema.Resource {
Delete: resourceResgroupDelete, Delete: resourceResgroupDelete,
Exists: resourceResgroupExists, Exists: resourceResgroupExists,
Timeouts: &schema.ResourceTimeout { Timeouts: &schema.ResourceTimeout{
Create: &Timeout180s, Create: &Timeout180s,
Read: &Timeout30s, Read: &Timeout30s,
Update: &Timeout180s, Update: &Timeout180s,
@ -299,52 +293,58 @@ func resourceResgroup() *schema.Resource {
Default: &Timeout60s, Default: &Timeout60s,
}, },
Schema: map[string]*schema.Schema { Schema: map[string]*schema.Schema{
"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 account.", Description: "Name of this resource group. Names are case sensitive and unique within the context of a account.",
}, },
"account": &schema.Schema { "account_id": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Description: "Unique ID of the account, which this resource group belongs to. If account ID is specified, then account name is ignored.",
},
"account_name": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Optional: true,
Description: "Name of the account, which this resource group belongs to.", Description: "Name of the account, which this resource group belongs to.",
}, },
"def_net": &schema.Schema { "def_net": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Default: "PRIVATE" Default: "PRIVATE",
Description: "Type of the network, which this resource group will use as default for its computes - PRIVATE or PUBLIC or NONE.", Description: "Type of the network, which this resource group will use as default for its computes - PRIVATE or PUBLIC or NONE.",
}, },
"ipcidr": &schema.Schema { "ipcidr": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Description: "Address of the netowrk inside the private network segment (aka ViNS) if def_net=PRIVATE", Description: "Address of the netowrk inside the private network segment (aka ViNS) if def_net=PRIVATE",
}, },
"ext_net_id": &schema.Schema { "ext_net_id": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
Default: 0, Default: 0,
Description: "ID of the external network, which this resource group will use as default for its computes if def_net=PUBLIC", Description: "ID of the external network, which this resource group will use as default for its computes if def_net=PUBLIC",
}, },
"ext_ip": &schema.Schema { "ext_ip": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Description: "IP address on the external netowrk to request, if def_net=PUBLIC", Description: "IP address on the external netowrk to request, if def_net=PUBLIC",
}, },
"account_id": &schema.Schema { "account_id": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
Description: "Unique ID of the account, 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,
Required: 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.",
@ -354,7 +354,7 @@ func resourceResgroup() *schema.Resource {
Type: schema.TypeList, Type: schema.TypeList,
Optional: true, Optional: true,
MaxItems: 1, MaxItems: 1,
Elem: &schema.Resource { Elem: &schema.Resource{
Schema: quotasSubresourceSchema(), Schema: quotasSubresourceSchema(),
}, },
Description: "Quota settings for this resource group.", Description: "Quota settings for this resource group.",
@ -372,7 +372,7 @@ func resourceResgroup() *schema.Resource {
Description: "Current status of this resource group.", Description: "Current status of this resource group.",
}, },
"def_net_id": &schema.Schema { "def_net_id": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
Description: "ID of the default network for this resource group (if any).", Description: "ID of the default network for this resource group (if any).",
@ -382,7 +382,7 @@ func resourceResgroup() *schema.Resource {
Type: schema.TypeList, // this is a list of ints Type: schema.TypeList, // this is a list of ints
Computed: true, Computed: true,
MaxItems: LimitMaxVinsPerResgroup, MaxItems: LimitMaxVinsPerResgroup,
Elem: &schema.Schema { Elem: &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
}, },
Description: "List of VINs deployed in this resource group.", Description: "List of VINs deployed in this resource group.",
@ -391,7 +391,7 @@ func resourceResgroup() *schema.Resource {
"computes": { "computes": {
Type: schema.TypeList, // this is a list of ints Type: schema.TypeList, // this is a list of ints
Computed: true, Computed: true,
Elem: &schema.Schema { Elem: &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
}, },
Description: "List of computes deployed in this resource group.", Description: "List of computes deployed in this resource group.",

@ -25,11 +25,11 @@ Visit https://github.com/rudecs/terraform-provider-decort for full source code p
package decort package decort
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"net/url" "net/url"
// "strconv" // "strconv"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
@ -71,7 +71,11 @@ func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupGetResp,
// On success this function returns a string, as returned by API rg/get, which could be unmarshalled // On success this function returns a string, as returned by API rg/get, which could be unmarshalled
// into ResgroupGetResp structure // into ResgroupGetResp structure
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 account name. // This function tries to locate resource group by one of the following algorithms depending
// on the parameters passed:
// - if resource group ID is specified -> by RG ID
// - if resource group name is specifeid -> by RG name and either account ID or 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.
@ -83,19 +87,42 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
// 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 Terraform resource Exists method. // method for the Terraform resource Exists method.
// //
name := d.Get("name").(string)
account_name := d.Get("account").(string)
controller := m.(*ControllerCfg) controller := m.(*ControllerCfg)
url_values := &url.Values{} url_values := &url.Values{}
rg_id, arg_set := d.GetOk("rg_id")
if arg_set {
// go straight for the RG by its ID
log.Debugf("utilityResgroupCheckPresence: locating RG by its ID %d", rg_id.(int))
url_values.Add("rgId", fmt.Sprintf("%d", rg_id.(int)))
rg_facts, err := controller.decortAPICall("POST", ResgroupGetAPI, url_values)
if err != nil {
return "", err
}
return rg_facts, nil
}
rg_name, arg_set := d.GetOk("name")
if !arg_set {
// no RG ID and no RG name - we cannot locate resource group in this case
return "", fmt.Error("Cannot check resource group presence if name is empty and no resource group ID specified.")
}
// Valid account ID is required to locate a resource group
// obtain Account ID by account name - it should not be zero on success
validated_account_id, err := utilityGetAccountIdBySchema(d, m)
if err != nil {
return err
}
url_values.Add("includedeleted", "false") url_values.Add("includedeleted", "false")
body_string, err := controller.decortAPICall("POST", ResgroupListAPI, url_values) body_string, err := controller.decortAPICall("POST", ResgroupListAPI, url_values)
if err != nil { if err != nil {
return "", err return "", err
} }
// log.Debugf("%s", body_string)
log.Debugf("%s", body_string) // log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %q", ResgroupListAPI)
log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %q", ResgroupListAPI)
model := ResgroupListResp{} model := ResgroupListResp{}
err = json.Unmarshal([]byte(body_string), &model) err = json.Unmarshal([]byte(body_string), &model)
if err != nil { if err != nil {
@ -104,14 +131,14 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
log.Debugf("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 RG by name & account name // match by RG name & account ID
if item.Name == name && item.AccountName == account_name { if item.Name == rg_name.(string) && item.AccountID == validated_account_id {
log.Debugf("utilityResgroupCheckPresence: match RG name %q / ID %d, account %q at index %d", log.Debugf("utilityResgroupCheckPresence: match RG name %q / ID %d, account ID %d at index %d",
item.Name, item.ID, item.AccountName, index) item.Name, item.ID, item.AccountID, index)
// not all required information is returned by rg/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 rg/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 resource quota settings
req_values := &url.Values{} req_values := &url.Values{}
req_values.Add("rgId", fmt.Sprintf("%d", item.ID)) req_values.Add("rgId", fmt.Sprintf("%d", item.ID))
body_string, err := controller.decortAPICall("POST", ResgroupGetAPI, req_values) body_string, err := controller.decortAPICall("POST", ResgroupGetAPI, req_values)
@ -123,32 +150,5 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string
} }
} }
return "", fmt.Errorf("Cannot find RG name %q owned by account %q", name, account_name) return "", fmt.Errorf("Cannot find RG name %q owned by account ID %d", name, validated_account_id)
}
func utilityGetAccountIdByName(account_name string, m interface{}) (int, error) {
controller := m.(*ControllerCfg)
url_values := &url.Values{}
body_string, err := controller.decortAPICall("POST", AccountsListAPI, url_values)
if err != nil {
return 0, err
}
model := AccountsListResp{}
err = json.Unmarshal([]byte(body_string), &model)
if err != nil {
return 0, err
}
log.Debugf("utilityGetAccountIdByName: traversing decoded Json of length %d", len(model))
for index, item := range model {
// need to match Account by name
if item.Name == account_name {
log.Debugf("utilityGetAccountIdByName: match Account name %q / ID %d at index %d",
item.Name, item.ID, index)
return item.ID, nil
}
}
return 0, fmt.Errorf("Cannot find account %q for the current user. Check account name and your access rights", account_name)
} }

@ -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,10 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package decs package decort
import ( import (
"fmt" "fmt"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
@ -70,18 +69,18 @@ func makeSshKeysArgString(sshkeys []SshKeyConfig) string {
return out return out
} }
func sshSubresourceSchema() map[string]*schema.Schema { func sshSubresourceSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema { rets := map[string]*schema.Schema{
"user": { "user": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "Name of the user on the guest OS of the new VM, for which the following SSH key will be authorized.", Description: "Name of the guest OS user of a new compute, for which the following SSH key will be authorized.",
}, },
"public_key": { "public_key": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
Description: "Public part of SSH key to authorize to the specified user on the VM being created.", Description: "Public SSH key to authorize to the specified guest OS user on the compute being created.",
}, },
"shell": { "shell": {
@ -94,4 +93,3 @@ func sshSubresourceSchema() map[string]*schema.Schema {
return rets return rets
} }

Loading…
Cancel
Save