From 2f4be0b92aa82acaac2f702dfd0e114dd8515ec3 Mon Sep 17 00:00:00 2001 From: Sergey Shubin svs1370 Date: Thu, 11 Feb 2021 19:31:05 +0300 Subject: [PATCH] Add account handler and refine other code. No testing yet! --- decort/account_data_source.go | 136 +++++++ decort/account_utility.go | 153 ++++++++ decort/compute_data_source.go | 54 ++- decort/disk_utility.go | 15 +- decort/image_data_source.go | 95 +++-- decort/interface_subresource.go | 330 +++++++++++++++++ decort/logins_subresource.go | 26 +- decort/models_api.go | 630 ++++++++++++++++---------------- decort/provider.go | 67 ++-- decort/rg_data_source.go | 94 ++--- decort/rg_resource.go | 190 +++++----- decort/rg_utility.go | 114 +++--- decort/ssh_subresource.go | 28 +- 13 files changed, 1295 insertions(+), 637 deletions(-) create mode 100644 decort/account_data_source.go create mode 100644 decort/account_utility.go create mode 100644 decort/interface_subresource.go diff --git a/decort/account_data_source.go b/decort/account_data_source.go new file mode 100644 index 0000000..a04069b --- /dev/null +++ b/decort/account_data_source.go @@ -0,0 +1,136 @@ +/* +Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +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." + }, + */ + + }, + }, + } +} \ No newline at end of file diff --git a/decort/account_utility.go b/decort/account_utility.go new file mode 100644 index 0000000..e243a34 --- /dev/null +++ b/decort/account_utility.go @@ -0,0 +1,153 @@ +/* +Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +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)) +} diff --git a/decort/compute_data_source.go b/decort/compute_data_source.go index 7877dfb..ac454e1 100644 --- a/decort/compute_data_source.go +++ b/decort/compute_data_source.go @@ -49,8 +49,20 @@ func parseComputeDisks(disks []DiskRecord) []interface{} { for i, value := range disks { // keys in this map should correspond to the Schema definition // 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 } @@ -71,8 +83,22 @@ func parseComputeInterfaces(ifaces []InterfaceRecord) []interface{} { for i, value := range ifaces { // Keys in this map should correspond to the Schema definition // 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 } @@ -93,7 +119,7 @@ func flattenCompute(d *schema.ResourceData, comp_facts string) error { 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.Set("compute_id", model.ID) @@ -119,25 +145,15 @@ func flattenCompute(d *schema.ResourceData, comp_facts string) error { } 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 { return err } } if len(model.GuestLogins) > 0 { - log.Printf("flattenCompute: calling parseGuestLogins") - guest_logins := parseGuestLogins(model.GuestLogins) - 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 { + log.Debugf("flattenCompute: calling parseGuestLogins for %d logins", len(model.GuestLogins)) + if err = d.Set("guest_logins", parseGuestLogins(model.GuestLogins)); err != nil { return err } } @@ -263,7 +279,7 @@ func dataSourceCompute() *schema.Resource { Type: schema.TypeList, Computed: true, Elem: &schema.Resource { - Schema: interfaceSubresourceSchema(), + Schema: interfaceSubresourceSchemaMake(), }, Description: "Specification for the virtual NICs configured on this compute instance.", }, diff --git a/decort/disk_utility.go b/decort/disk_utility.go index ae70db2..45270e5 100644 --- a/decort/disk_utility.go +++ b/decort/disk_utility.go @@ -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.") } - account_id, acc_id_set := d.GetOk("account_id") - if !acc_id_set { - account_name, arg_set := d.GetOkd("account_name") - if !arg_set { - return "", fmt.Error("Cannot locate disk by name %s if neither account ID nor account name are set", disk_name.(string)) - } + // Valid account ID is required to locate disks + // 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("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) if err != nil { 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)) for _, item := range disks_list { // 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) // we found the disk we need - not get detailed information via API call to disks/get /* diff --git a/decort/image_data_source.go b/decort/image_data_source.go index b81f0f7..d44edc3 100644 --- a/decort/image_data_source.go +++ b/decort/image_data_source.go @@ -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, , Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,10 +16,10 @@ limitations under the License. */ /* -This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +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. +Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates. */ package decort @@ -34,39 +34,41 @@ import ( "github.com/hashicorp/terraform/helper/validation" ) - func dataSourceImageRead(d *schema.ResourceData, m interface{}) error { name := d.Get("name").(string) - rgid, rgid_set := d.GetOk("rgid") - tenant_id, tenant_set := d.GetOk("tenant_id") + // rg_id, rgid_set := d.GetOk("rg_id") + account_id, account_set := d.GetOk("account_id") controller := m.(*ControllerCfg) url_values := &url.Values{} - if tenant_set { - url_values.Add("accountId", fmt.Sprintf("%d",tenant_id.(int))) - } - if rgid_set { - url_values.Add("cloudspaceId", fmt.Sprintf("%d",rgid.(int))) + if account_set { + url_values.Add("accountId", fmt.Sprintf("%d", account_id.(int))) } body_string, err := controller.decortAPICall("POST", ImagesListAPI, url_values) if err != nil { return err } - log.Printf("dataSourceImageRead: ready to decode response body") + log.Debugf("dataSourceImageRead: ready to decode response body from %q", ImagesListAPI) model := ImagesListResp{} err = json.Unmarshal([]byte(body_string), &model) if err != nil { return err } - log.Printf("%#v", model) - log.Printf("dataSourceImageRead: traversing decoded JSON of length %d", len(model)) + // log.Printf("%#v", model) + log.Debugf("dataSourceImageRead: traversing decoded JSON of length %d", len(model)) for index, item := range model { - // need to match VM by name + // need to match Image by name if item.Name == 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) return nil } @@ -76,36 +78,67 @@ func dataSourceImageRead(d *schema.ResourceData, m interface{}) error { } func dataSourceImage() *schema.Resource { - return &schema.Resource { + return &schema.Resource{ SchemaVersion: 1, - Read: dataSourceImageRead, + Read: dataSourceImageRead, - Timeouts: &schema.ResourceTimeout { + Timeouts: &schema.ResourceTimeout{ Read: &Timeout30s, Default: &Timeout60s, }, - Schema: map[string]*schema.Schema { + Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, - Description: "Name of the OS image to locate. This parameter is case sensitive.", + Type: schema.TypeString, + Required: true, + Description: "Name of the OS image to locate. This parameter is case sensitive.", }, - "tenant_id": { + "account_id": { Type: schema.TypeInt, Optional: true, 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.", }, - "rgid": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntAtLeast(1), - Description: "ID of the resource group to limit image search to.", + "arch": { + Type: schema.TypeString, + Computed: true, + Description: "Binary architecture this image is created for.", + }, + + "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, + Computed: true, + Description: "Size of the image in GB.", + }, + + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Current model status of this disk.", }, }, } -} \ No newline at end of file +} diff --git a/decort/interface_subresource.go b/decort/interface_subresource.go new file mode 100644 index 0000000..325eb92 --- /dev/null +++ b/decort/interface_subresource.go @@ -0,0 +1,330 @@ +/* +Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + 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 +} + +*/ diff --git a/decort/logins_subresource.go b/decort/logins_subresource.go index 27acec7..ed8efa8 100644 --- a/decort/logins_subresource.go +++ b/decort/logins_subresource.go @@ -15,17 +15,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -package decs +package decort import ( - "log" "github.com/hashicorp/terraform/helper/schema" // "github.com/hashicorp/terraform/helper/validation" ) -func flattenGuestLogins(logins []GuestLoginRecord) []interface{} { +func parseGuestLogins(logins []OsUserRecord) []interface{} { var result = make([]interface{}, len(logins)) elem := make(map[string]interface{}) @@ -34,28 +33,28 @@ func flattenGuestLogins(logins []GuestLoginRecord) []interface{} { elem["guid"] = value.Guid elem["login"] = value.Login elem["password"] = value.Password + elem["public_key"] = value.PubKey result[index] = elem - log.Printf("flattenGuestLogins: parsed element %d - login %q", - index, value.Login) + log.Debugf("parseGuestLogins: parsed element %d - login %q", index, value.Login) } return result } -func loginsSubresourceSchema() map[string]*schema.Schema { - rets := map[string]*schema.Schema { +func loginsSubresourceSchemaMake() map[string]*schema.Schema { + rets := map[string]*schema.Schema{ "guid": { Type: schema.TypeString, Optional: true, Default: "", - Description: "GUID of this guest user.", + Description: "GUID of this guest OS user.", }, "login": { Type: schema.TypeString, Optional: true, Default: "", - Description: "Login name of this guest user.", + Description: "Login name of this guest OS user.", }, "password": { @@ -63,7 +62,14 @@ func loginsSubresourceSchema() map[string]*schema.Schema { Optional: true, Default: "", 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.", }, } diff --git a/decort/models_api.go b/decort/models_api.go index 4e9f490..08d83ba 100644 --- a/decort/models_api.go +++ b/decort/models_api.go @@ -16,17 +16,15 @@ limitations under the License. */ /* -This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +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. +Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates. */ - package decort import ( - "time" ) @@ -40,157 +38,163 @@ var Timeout180s = time.Second * 180 // structures related to /cloudapi/rg/list API // type UserAclRecord struct { - IsExplicit bool `json:"explicit"` - Rights string `json:"right"` - Status string `json:"status"` - Type string `json:"type"` - UgroupID string `json:"userGroupId"` + IsExplicit bool `json:"explicit"` + Rights string `json:"right"` + Status string `json:"status"` + Type string `json:"type"` + UgroupID string `json:"userGroupId"` // CanBeDeleted bool `json:"canBeDeleted"` } type AccountAclRecord struct { - IsExplicit bool `json:"explicit"` - Guid string `json:"guid"` - Rights string `json:"right"` - Status string `json:"status"` - Type string `json:"type"` - UgroupID string `json:"userGroupId"` + IsExplicit bool `json:"explicit"` + Guid string `json:"guid"` + Rights string `json:"right"` + Status string `json:"status"` + Type string `json:"type"` + UgroupID string `json:"userGroupId"` } type ResgroupRecord struct { - ACLs []UserAclRecord `json:"ACLs"` - Owner AccountAclRecord `json:"accountAcl"` - AccountID int `json:"accountId"` - AccountName string `json:"accountName"` - CreatedBy string `json:"createdBy"` - CreatedTime uint64 `json:"createdTime"` - DefaultNetID int `json:"def_net_id"` - DefaultNetType string `json:"def_net_type"` - Decsription string `json:"desc"` - GridID int `json:"gid"` - ID uint `json:"id"` - LockStatus string `json:"lockStatus"` - Name string `json:"name"` - Status string `json:"status"` - UpdatedBy string `json:"updatedBy"` - UpdatedTime uint64 `json:"updatedTime"` - Vins []int `json:"vins"` - Computes []int `json:"vms"` + ACLs []UserAclRecord `json:"ACLs"` + Owner AccountAclRecord `json:"accountAcl"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + CreatedBy string `json:"createdBy"` + CreatedTime uint64 `json:"createdTime"` + DefaultNetID int `json:"def_net_id"` + DefaultNetType string `json:"def_net_type"` + Decsription string `json:"desc"` + GridID int `json:"gid"` + ID uint `json:"id"` + LockStatus string `json:"lockStatus"` + Name string `json:"name"` + Status string `json:"status"` + UpdatedBy string `json:"updatedBy"` + UpdatedTime uint64 `json:"updatedTime"` + Vins []int `json:"vins"` + Computes []int `json:"vms"` } const ResgroupListAPI = "/restmachine/cloudapi/rg/list" + type ResgroupListResp []ResgroupRecord // // 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 // -const ResgroupUpdateAPI= "/restmachine/cloudapi/rg/update" +const ResgroupUpdateAPI = "/restmachine/cloudapi/rg/update" + type ResgroupUpdateParam struct { - RgId int `json:"rgId"` - Name string `json:"name"` - Desc string `json:"decs"` - Ram int `json:"maxMemoryCapacity"` - Disk int `json:"maxVDiskCapacity"` - Cpu int `json:"maxCPUCapacity"` - NetTraffic int `json:"maxNetworkPeerTransfer"` - Reason string `json:"reason"` -} + RgId int `json:"rgId"` + Name string `json:"name"` + Desc string `json:"decs"` + Ram int `json:"maxMemoryCapacity"` + Disk int `json:"maxVDiskCapacity"` + Cpu int `json:"maxCPUCapacity"` + NetTraffic int `json:"maxNetworkPeerTransfer"` + Reason string `json:"reason"` +} // // structures related to /cloudapi/rg/get API call // type QuotaRecord struct { // this is how quota is reported by /api/.../rg/get - Cpu int `json:"CU_C"` // CPU count in pcs - Ram int `json:"CU_M"` // RAM volume in MB - Disk int `json:"CU_D"` // Disk capacity in GB - ExtIPs int `json:"CU_I"` // Ext IPs count - ExtTraffic int `json:"CU_NP"` // Ext network traffic - GpuUnits int `json:"gpu_units"` // GPU count + Cpu int `json:"CU_C"` // CPU count in pcs + Ram int `json:"CU_M"` // RAM volume in MB + Disk int `json:"CU_D"` // Disk capacity in GB + ExtIPs int `json:"CU_I"` // Ext IPs count + ExtTraffic int `json:"CU_NP"` // Ext network traffic + GpuUnits int `json:"gpu_units"` // GPU count } type ResourceRecord struct { // this is how actual usage is reported by /api/.../rg/get - Cpu int `json:"cpu"` - Disk int `json:"disksize"` - ExtIPs int `json:"extips"` - ExtTraffic int `json:"exttraffic"` - Gpu int `json:"gpu"` - Ram int `json:"ram"` + Cpu int `json:"cpu"` + Disk int `json:"disksize"` + ExtIPs int `json:"extips"` + ExtTraffic int `json:"exttraffic"` + Gpu int `json:"gpu"` + Ram int `json:"ram"` } type UsageRecord struct { - Current ResourceRecord `json:"Current"` - Reserved ResourceRecord `json:"Reserved"` + Current ResourceRecord `json:"Current"` + Reserved ResourceRecord `json:"Reserved"` } -const ResgroupGetAPI= "/restmachine/cloudapi/rg/get" +const ResgroupGetAPI = "/restmachine/cloudapi/rg/get" + type ResgroupGetResp struct { - ACLs []UserAclRecord `json:"ACLs"` - Usage UsageRecord `json:"Resources"` - AccountID int `json:"accountId"` - AccountName string `json:"accountName"` - - CreatedBy string `json:"createdBy"` - CreatedTime uint64 `json:"createdTime"` - DefaultNetID int `json:"def_net_id"` - DefaultNetType string `json:"def_net_type"` - DeletedBy string `json:"deletedBy"` - DeletedTime uint64 `json:"deletedTime"` - Decsription string `json:"desc"` - ID uint `json:"id"` - LockStatus string `json:"lockStatus"` - Name string `json:"name"` - Quota QuotaRecord `json:"resourceLimits"` - Status string `json:"status"` - UpdatedBy string `json:"updatedBy"` - UpdatedTime uint64 `json:"updatedTime"` - Vins []int `json:"vins"` - Computes []int `json:"vms"` + ACLs []UserAclRecord `json:"ACLs"` + Usage UsageRecord `json:"Resources"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + + CreatedBy string `json:"createdBy"` + CreatedTime uint64 `json:"createdTime"` + DefaultNetID int `json:"def_net_id"` + DefaultNetType string `json:"def_net_type"` + DeletedBy string `json:"deletedBy"` + DeletedTime uint64 `json:"deletedTime"` + Decsription string `json:"desc"` + ID uint `json:"id"` + LockStatus string `json:"lockStatus"` + Name string `json:"name"` + Quota QuotaRecord `json:"resourceLimits"` + Status string `json:"status"` + UpdatedBy string `json:"updatedBy"` + UpdatedTime uint64 `json:"updatedTime"` + Vins []int `json:"vins"` + Computes []int `json:"vms"` Ignored map[string]interface{} `json:"-"` } -// +// // structures related to /cloudapi/rg/update API // const ResgroupUpdateAPI = "/restmachine/cloudapi/rg/update" + type ResgroupUpdateParam struct { - ID uint `json:"rgId"` - Name string `json:"name"` - Decsription string `json:"desc"` - Cpu int `json:"maxCPUCapacity"` - Ram int `json:"maxMemoryCapacity"` - Disk int `json:"maxVDiskCapacity"` - NetTraffic int `json:"maxNetworkPeerTransfer"` - ExtIPs int `json:"maxNumPublicIP"` - Reason string `json:"reason"` + ID uint `json:"rgId"` + Name string `json:"name"` + Decsription string `json:"desc"` + Cpu int `json:"maxCPUCapacity"` + Ram int `json:"maxMemoryCapacity"` + Disk int `json:"maxVDiskCapacity"` + NetTraffic int `json:"maxNetworkPeerTransfer"` + ExtIPs int `json:"maxNumPublicIP"` + Reason string `json:"reason"` } -// +// // structures related to /cloudapi/rg/delete API // const ResgroupDeleteAPI = "/restmachine/cloudapi/rg/delete" -// +// // structures related to /cloudapi/rg/listComputes API // type ComputeBriefRecord struct { // this is a brief compute specifiaction as returned by API rg/listComputes // we do not even include here all fields as returned by this API, but only the most important that // are really necessary to identify and distinguish computes - AccountID int `json:"accountId"` - AccountName string `json:"accountName"` - Name string `json:"name"` - ID uint `json:"id"` - RgID int `json:"rgId"` - RgName string `json:"rgName"` - Status string `json:"status"` - TechStatus string `json:"techStatus"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + Name string `json:"name"` + ID uint `json:"id"` + RgID int `json:"rgId"` + RgName string `json:"rgName"` + Status string `json:"status"` + TechStatus string `json:"techStatus"` } + const RgListComputesAPI = "/restmachine/cloudapi/rg/listComputes" + type RgListComputesResp []ComputeBriefRecord // @@ -198,339 +202,315 @@ type RgListComputesResp []ComputeBriefRecord // const KvmX86CreateAPI = "/restmachine/cloudapi/kvmx86/create" const KvmPPCCreateAPI = "/restmachine/cloudapi/kvmppc/create" + type KvmVmCreateParam struct { // this is unified structure for both x86 and PPC based KVM VMs creation - RgID uint `json:"rgId"` - Name string `json:"name"` - Cpu int `json:"cpu"` - Ram int `json:"ram"` - ImageID int `json:"imageId"` - BootDisk int `json:"bootDisk"` - NetType string `json:"netType"` - NetId int `json:"netId"` - IPAddr string `json:"ipAddr"` - UserData string `json:"userdata"` - Description string `json:"desc"` - Start bool `json:"start"` + RgID uint `json:"rgId"` + Name string `json:"name"` + Cpu int `json:"cpu"` + Ram int `json:"ram"` + ImageID int `json:"imageId"` + BootDisk int `json:"bootDisk"` + NetType string `json:"netType"` + NetId int `json:"netId"` + IPAddr string `json:"ipAddr"` + UserData string `json:"userdata"` + Description string `json:"desc"` + Start bool `json:"start"` } // structures related to cloudapi/compute/delete API const ComputeDeleteAPI = "/restmachine/cloudapi/compute/delete" -// +// // 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 { - ConnID int `json:"connId"` - ConnType string `json:"connType"` - DefaultGW string `json:"defGw"` - Guid string `json:"guid"` - IPAddress string `json:"ipAddress"` // without trailing network mask, i.e. "192.168.1.3" - MAC string `json:"mac"` - Name string `json:"name"` - NetID int `json:"netId"` - NetMaks int `json:"netMask"` - NetType string `json:"netType"` - PciSlot int `json:"pciSlot"` - Target string `json:"target"` - Type string `json:"type"` - VNFs []int `json:"vnfs"` + ConnID int `json:"connId"` // This is VLAN ID or VxLAN ID, depending on ConnType + ConnType string `json:"connType"` // Either "VLAN" or "VXLAN" tag + DefaultGW string `json:"defGw"` + Guid string `json:"guid"` + IPAddress string `json:"ipAddress"` // without trailing network mask, i.e. "192.168.1.3" + MAC string `json:"mac"` + Name string `json:"name"` + NetID int `json:"netId"` // This is either ExtNet ID or ViNS ID, depending on NetType + NetMask int `json:"netMask"` + NetType string `json:"netType"` // Either "EXTNET" or "VINS" tag + PciSlot int `json:"pciSlot"` + Target string `json:"target"` + Type string `json:"type"` + VNFs []int `json:"vnfs"` + QOS InterfaceQosRecord `json:"qos"` } type SnapSetRecord struct { - Disks []int `json:"disks"` - Guid string `json:"guid"` - Label string `json:"label"` - TimeStamp uint64 `json:"timestamp"` + Disks []int `json:"disks"` + Guid string `json:"guid"` + Label string `json:"label"` + TimeStamp uint64 `json:"timestamp"` } type ComputeRecord struct { - AccountID int `json:"accountId"` - AccountName string `json:"accountName"` - ACLs []UserAclRecord `json:"acl"` - Arch string `json:"arch"` - BootDiskSize int `json:"bootdiskSize"` - CloneReference int `json:"cloneReference"` - Clones []int `json:"clones"` - Cpus int `json:"cpus"` - CreatedBy string `json:"createdBy"` - CreatedTime uint64 `json:"createdTime"` - DeletedBy string `json:"deletedBy"` - DeletedTime uint64 `json:"deletedTime"` - Desc string `json:"desc"` - Disks []int `json:"disks"` - GridID int `json:"gid"` - ID uint `json:"id"` - ImageID int `json:"imageId"` - Interfaces []InterfaceRecord `json:"interfaces` - LockStatus string `json:"lockStatus"` - ManagerID int `json:"managerId"` - Name string `json:"name"` - Ram int `json:"ram"` - RgID int `json:"rgId"` - RgName string `json:"rgName"` - SnapSets []SnapSetRecord `json:"snapSets"` - Status string `json:"status"` - Tags []string `json:"tags"` - TechStatus string `json:"techStatus"` - TotalDiskSize int `json:"totalDiskSize"` - UpdatedBy string `json:"updatedBy"` - UpdateTime uint64 `json:"updateTime"` - UserManaged bool `json:"userManaged"` - Vgpus []int `json:"vgpus"` - VinsConnected int `json:"vinsConnected"` - VirtualImageID int `json:"virtualImageId"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + ACLs []UserAclRecord `json:"acl"` + Arch string `json:"arch"` + BootDiskSize int `json:"bootdiskSize"` + CloneReference int `json:"cloneReference"` + Clones []int `json:"clones"` + Cpus int `json:"cpus"` + CreatedBy string `json:"createdBy"` + CreatedTime uint64 `json:"createdTime"` + DeletedBy string `json:"deletedBy"` + DeletedTime uint64 `json:"deletedTime"` + Desc string `json:"desc"` + Disks []int `json:"disks"` + GridID int `json:"gid"` + ID uint `json:"id"` + ImageID int `json:"imageId"` + Interfaces []InterfaceRecord `json:"interfaces` + LockStatus string `json:"lockStatus"` + ManagerID int `json:"managerId"` + Name string `json:"name"` + Ram int `json:"ram"` + RgID int `json:"rgId"` + RgName string `json:"rgName"` + SnapSets []SnapSetRecord `json:"snapSets"` + Status string `json:"status"` + Tags []string `json:"tags"` + TechStatus string `json:"techStatus"` + TotalDiskSize int `json:"totalDiskSize"` + UpdatedBy string `json:"updatedBy"` + UpdateTime uint64 `json:"updateTime"` + UserManaged bool `json:"userManaged"` + Vgpus []int `json:"vgpus"` + VinsConnected int `json:"vinsConnected"` + VirtualImageID int `json:"virtualImageId"` } const ComputeListAPI = "/restmachine/cloudapi/compute/list" + type ComputeListResp []ComputeRecord // // structures related to /cloudapi/compute/get // type SnapshotRecord struct { - Guid string `json:"guid"` - Label string `json:"label"` - SnapSetGuid string `json:"snapSetGuid"` - SnapSetTime uint64 `json:"snapSetTime"` - TimeStamp uint64 `json:"timestamp"` + Guid string `json:"guid"` + Label string `json:"label"` + SnapSetGuid string `json:"snapSetGuid"` + SnapSetTime uint64 `json:"snapSetTime"` + TimeStamp uint64 `json:"timestamp"` } type DiskRecord struct { // ACLs `json:"ACL"` - it is a dictionary, special parsing required // was - Acl map[string]string `json:"acl"` - AccountID int `json:"accountId"` - AccountName string `json:"accountName"` // NOTE: absent from compute/get output - BootPartition int `json:"bootPartition"` - CreatedTime uint64 `json:"creationTime"` - DeletedTime uint64 `json:"deletionTime"` - Desc string `json:"descr"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` // NOTE: absent from compute/get output + BootPartition int `json:"bootPartition"` + CreatedTime uint64 `json:"creationTime"` + DeletedTime uint64 `json:"deletionTime"` + Desc string `json:"descr"` DestructionTime uint64 `json:"destructionTime"` - DiskPath string `json:"diskPath"` - GridID int `json:"gid"` - ID uint `json:"id"` - ImageID int `json:"imageId"` - Images []int `json:"images"` + DiskPath string `json:"diskPath"` + GridID int `json:"gid"` + ID uint `json:"id"` + ImageID int `json:"imageId"` + Images []int `json:"images"` // IOTune 'json:"iotune" - it is a dictionary - Name string `json:"name"` + Name string `json:"name"` // Order `json:"order"` - ParentId int `json:"parentId"` - PciSlot int `json:"pciSlot"` + ParentId int `json:"parentId"` + PciSlot int `json:"pciSlot"` // ResID string `json:"resId"` // ResName string `json:"resName"` // Params string `json:"params"` - Pool string `json:"pool"` - PurgeTime uint64 `json:"purgeTime"` + Pool string `json:"pool"` + PurgeTime uint64 `json:"purgeTime"` // Role string `json:"role"` - SepType string `json:"sepType"` - SepID int `json:"sepid"` - SizeMax int `json:"sizeMax"` - SizeUsed int `json:"sizeUsed"` // sum over all snapshots of this disk to report total consumed space - Snapshots []SnapshotRecord `json:"snapshots"` - Status string `json:"status"` - TechStatus string `json:"techStatus"` - Type string `json:"type"` - ComputeID int `json:"vmid"` + SepType string `json:"sepType"` + SepID int `json:"sepid"` + SizeMax int `json:"sizeMax"` + SizeUsed int `json:"sizeUsed"` // sum over all snapshots of this disk to report total consumed space + Snapshots []SnapshotRecord `json:"snapshots"` + Status string `json:"status"` + TechStatus string `json:"techStatus"` + Type string `json:"type"` + ComputeID int `json:"vmid"` } type OsUserRecord struct { - Guid string `json:"guid"` - Login string `json:"login"` - Password string `json:"password"` - PubKey string `json:"pubkey"` + Guid string `json:"guid"` + Login string `json:"login"` + Password string `json:"password"` + PubKey string `json:"pubkey"` } const ComputeGetAPI = "/restmachine/cloudapi/compute/get" + type ComputeGetResp struct { // ACLs `json:"ACL"` - it is a dictionary, special parsing required - AccountID int `json:"accountId"` - AccountName string `json:"accountName"` - Arch string `json:"arch"` - BootDiskSize int `json:"bootdiskSize"` - CloneReference int `json:"cloneReference"` - Clones []int `json:"clones"` - Cpus int `json:"cpus"` - Desc string `json:"desc"` - Disks []DiskRecord `json:"disks"` - GridID int `json:"gid"` - ID uint `json:"id"` - ImageID int `json:"imageId"` - ImageName string `json:"imageName"` - Interfaces []InterfaceRecord `json:"interfaces` - LockStatus string `json:"lockStatus"` - ManagerID int `json:"managerId"` - ManagerType string `json:"manageType"` - Name string `json:"name"` - NatableVinsID int `json:"natableVinsId"` - NatableVinsIP string `json:"natableVinsIp"` - NatableVinsName string `json:"natableVinsName"` - NatableVinsNet string `json:"natableVinsNetwork"` - NatableVinsNetName string `json:"natableVinsNetworkName"` - OsUsers []OsUserRecord `json:"osUsers"` - Ram int `json:"ram"` - RgID int `json:"rgId"` - RgName string `json:"rgName"` - SnapSets []SnapSetRecord `json:"snapSets"` - Status string `json:"status"` - Tags []string `json:"tags"` - TechStatus string `json:"techStatus"` - TotalDiskSize int `json:"totalDiskSize"` - UpdatedBy string `json:"updatedBy"` - UpdateTime uint64 `json:"updateTime"` - UserManaged bool `json:"userManaged"` - Vgpus []int `json:"vgpus"` - VinsConnected int `json:"vinsConnected"` - VirtualImageID int `json:"virtualImageId"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + Arch string `json:"arch"` + BootDiskSize int `json:"bootdiskSize"` + CloneReference int `json:"cloneReference"` + Clones []int `json:"clones"` + Cpus int `json:"cpus"` + Desc string `json:"desc"` + Disks []DiskRecord `json:"disks"` + GridID int `json:"gid"` + ID uint `json:"id"` + ImageID int `json:"imageId"` + ImageName string `json:"imageName"` + Interfaces []InterfaceRecord `json:"interfaces` + LockStatus string `json:"lockStatus"` + ManagerID int `json:"managerId"` + ManagerType string `json:"manageType"` + Name string `json:"name"` + NatableVinsID int `json:"natableVinsId"` + NatableVinsIP string `json:"natableVinsIp"` + NatableVinsName string `json:"natableVinsName"` + NatableVinsNet string `json:"natableVinsNetwork"` + NatableVinsNetName string `json:"natableVinsNetworkName"` + OsUsers []OsUserRecord `json:"osUsers"` + Ram int `json:"ram"` + RgID int `json:"rgId"` + RgName string `json:"rgName"` + SnapSets []SnapSetRecord `json:"snapSets"` + Status string `json:"status"` + Tags []string `json:"tags"` + TechStatus string `json:"techStatus"` + TotalDiskSize int `json:"totalDiskSize"` + UpdatedBy string `json:"updatedBy"` + UpdateTime uint64 `json:"updateTime"` + UserManaged bool `json:"userManaged"` + Vgpus []int `json:"vgpus"` + VinsConnected int `json:"vinsConnected"` + VirtualImageID int `json:"virtualImageId"` } // // structures related to /restmachine/cloudapi/images/list API // type ImageRecord struct { - AccountID uint `json:"accountId"` - Arch string `json:"architecture` - BootType string `json:"bootType"` - IsBootable boo `json:"bootable"` - IsCdrom bool `json:"cdrom"` - Desc string `json:"description"` - IsHotResize bool `json:"hotResize"` - ID uint `json:"id"` - Name string `json:"name"` - Pool string `json:"pool"` - SepID int `json:"sepid"` - Size int `json:"size"` - Status string `json:"status"` - Type string `json:"type"` - Username string `json:"username"` - IsVirtual bool `json:"virtual"` + AccountID uint `json:"accountId"` + Arch string `json:"architecture` + BootType string `json:"bootType"` + IsBootable bool `json:"bootable"` + IsCdrom bool `json:"cdrom"` + Desc string `json:"description"` + IsHotResize bool `json:"hotResize"` + ID uint `json:"id"` + Name string `json:"name"` + Pool string `json:"pool"` + SepID int `json:"sepid"` + Size int `json:"size"` + Status string `json:"status"` + Type string `json:"type"` + Username string `json:"username"` + IsVirtual bool `json:"virtual"` } const ImagesListAPI = "/restmachine/cloudapi/images/list" -type ImagesListParam struct { - AccountID int `json:"accountId"` -} + type ImagesListResp []ImageRecord // // structures related to /cloudapi/extnet/list API // type ExtNetRecord struct { - Name string `json:"name"` - ID uint `json:"id"` - IPCIDR string `json:"ipcidr"` -} + Name string `json:"name"` + ID uint `json:"id"` + IPCIDR string `json:"ipcidr"` +} 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 // type AccountRecord struct { - ACLs []UserAclRecord `json:"acl"` - CreatedTime uint64 `json:"creationTime"` - DeletedTime uint64 `json:"deletionTime"` - ID int `json:"id"` - Name string `json:"name"` - Status string `json:"status"` - UpdatedTime uint64 `json:"updateTime"` + // ACLs []UserAclRecord `json:"acl"` + // CreatedTime uint64 `json:"creationTime"` + // DeletedTime uint64 `json:"deletionTime"` + ID int `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + // 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 // // structures related to /cloudapi/portforwarding/list API // type PfwRecord struct { - ID int `json:"id"` - LocalIP string `json:"localIp` - LocalPort int `json:"localPort"` - Protocol string `json:"protocol"` - PublicPortEnd int `json:"publicPortEnd"` + ID int `json:"id"` + LocalIP string `json:"localIp` + LocalPort int `json:"localPort"` + Protocol string `json:"protocol"` + PublicPortEnd int `json:"publicPortEnd"` PublicPortStart int `json:"publicPortStart"` - ComputeID int `json:"vmId"` -} + ComputeID int `json:"vmId"` +} const ComputePfwListAPI = "/restmachine/cloudapi/compute/pfwList" + 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" -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" // // 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" -type ComputeNetDetachParam struct { - ComputeID int `json:"computeId"` - IPAddr string `json:"apAddr"` - MAC string `json:"mac"` -} const ComputeNetDetachAPI = "/restmachine/cloudapi/compute/netDetach" - // // 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 ComputeDiskDetachAPI = "/restmachine/cloudapi/compute/diskDetach" // // structures related to /cloudapi/disks/create -// +// type DiskCreateParam struct { - AccountID int `json:"accountId` - GridID int `json:"gid"` - Name string `json:"string"` - Description string `json:"description"` - Size int `json:"size"` - Type string `json:"type"` - SepID int `json:"sep_id"` - Pool string `json:"pool"` + AccountID int `json:"accountId` + GridID int `json:"gid"` + Name string `json:"string"` + Description string `json:"description"` + Size int `json:"size"` + Type string `json:"type"` + SepID int `json:"sep_id"` + Pool string `json:"pool"` } + const DiskCreateAPI = "/restmachine/cloudapi/disks/create" // // structures related to /cloudapi/disks/get -// -type DisksGetParam struct { - DiskID int `json:"diskId` -} +// const DisksCreateAPI = "/restmachine/cloudapi/disks/create" const DisksGetAPI = "/restmachine/cloudapi/disks/get" // Returns single DiskRecord on success -const DisksListAPI = "/restmachine/cloudapi/disks/list" // Returns list of DiskRecord on success \ No newline at end of file +const DisksListAPI = "/restmachine/cloudapi/disks/list" // Returns list of DiskRecord on success diff --git a/decort/provider.go b/decort/provider.go index 15d9185..4275efd 100644 --- a/decort/provider.go +++ b/decort/provider.go @@ -18,34 +18,32 @@ limitations under the License. package decort import ( - "strings" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" // "github.com/hashicorp/terraform/terraform" - ) var decsController *ControllerCfg func Provider() *schema.Provider { - return &schema.Provider { - Schema: map[string]*schema.Schema { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ "authenticator": { - Type: schema.TypeString, - Required: true, - StateFunc: stateFuncToLower, + Type: schema.TypeString, + Required: true, + StateFunc: stateFuncToLower, 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": { Type: schema.TypeString, Optional: true, StateFunc: stateFuncToLower, - DefaultFunc: schema.EnvDefaultFunc("DECS_OAUTH2_URL", nil), - Description: "The Oauth2 application URL in 'oauth2' authentication mode.", + DefaultFunc: schema.EnvDefaultFunc("DECORT_OAUTH2_URL", nil), + Description: "OAuth2 application URL in 'oauth2' authentication mode.", }, "controller_url": { @@ -53,67 +51,70 @@ func Provider() *schema.Provider { Required: true, ForceNew: true, 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": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("DECS_USER", nil), - Description: "The user name for DECS cloud API operations in 'legacy' authentication mode.", + DefaultFunc: schema.EnvDefaultFunc("DECORT_USER", nil), + Description: "User name for DECORT cloud API operations in 'legacy' authentication mode.", }, "password": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("DECS_PASSWORD", nil), - Description: "The user password for DECS cloud API operations in 'legacy' authentication mode.", + DefaultFunc: schema.EnvDefaultFunc("DECORT_PASSWORD", nil), + Description: "User password for DECORT cloud API operations in 'legacy' authentication mode.", }, "app_id": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("DECS_APP_ID", nil), - Description: "Application ID to access DECS cloud API in 'oauth2' authentication mode.", + DefaultFunc: schema.EnvDefaultFunc("DECORT_APP_ID", nil), + Description: "Application ID to access DECORT cloud API in 'oauth2' authentication mode.", }, "app_secret": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("DECS_APP_SECRET", nil), - Description: "Application secret to access DECS cloud API in 'oauth2' authentication mode.", + DefaultFunc: schema.EnvDefaultFunc("DECSORTAPP_SECRET", nil), + Description: "Application secret to access DECORT cloud API in 'oauth2' authentication mode.", }, "jwt": { Type: schema.TypeString, Optional: true, 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": { Type: schema.TypeBool, Optional: true, 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_kvmx86": resourceKvmX86(), - "decort_disk": resourceDisk(), - "decort_vins": resourceVins(), + "decort_kvmvm": resourceCompute(), + "decort_disk": resourceDisk(), + "decort_vins": resourceVins(), + // "decort_pfw": resourcePfw(), }, - DataSourcesMap: map[string]*schema.Resource { + DataSourcesMap: map[string]*schema.Resource{ + // "decort_account": dataSourceAccount(), "decort_resgroup": dataSourceResgroup(), - "decs_kvmx86": dataSourceCompute(), - "decort_image": dataSourceImage(), - "decort_disk": dataSourceDisk(), - "decort_vins": dataSourceVins(), + "decort_kvmvm": dataSourceCompute(), + "decort_image": dataSourceImage(), + "decort_disk": dataSourceDisk(), + "decort_vins": dataSourceVins(), + // "decort_pfw": dataSourcePfw(), }, - + ConfigureFunc: providerConfigure, } } @@ -128,4 +129,4 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return nil, err } return decsController, nil -} \ No newline at end of file +} diff --git a/decort/rg_data_source.go b/decort/rg_data_source.go index f9734c9..41c8192 100644 --- a/decort/rg_data_source.go +++ b/decort/rg_data_source.go @@ -16,42 +16,43 @@ limitations under the License. */ /* -This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +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. +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 flattenResgroup(d *schema.ResourceData, rg_facts string) error { // NOTE: this function modifies ResourceData argument - as such it should never be called // from resourceRsgroupExists(...) method - log.Debugf("%s", rg_facts) - log.Debugf("flattenResgroup: ready to decode response body from %q", CloudspacesGetAPI) + // log.Debugf("%s", rg_facts) + log.Debugf("flattenResgroup: ready to decode response body from API") details := ResgroupGetResp{} err := json.Unmarshal([]byte(rg_facts), &details) if err != nil { return err } - log.Debugf("flattenResgroup: decoded ResGroup name %q / ID %d, account ID %d, public IP %q", - details.Name, details.ID, details.AccountID, details.PublicIP) + log.Debugf("flattenResgroup: decoded RG name %q / ID %d, account ID %d", + details.Name, details.ID, details.AccountID) d.SetId(fmt.Sprintf("%d", details.ID)) + d.Set("rg_id", details.ID) d.Set("name", details.Name) + d.Set("account_name", details.AccountName) d.Set("account_id", details.AccountID) d.Set("grid_id", details.GridID) d.Set("desc", details.Description) @@ -73,7 +74,7 @@ func dataSourceResgroupRead(d *schema.ResourceData, m interface{}) error { rg_facts, err := utilityResgroupCheckPresence(d, m) if rg_facts == "" { // 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 in this case return err } @@ -81,92 +82,97 @@ func dataSourceResgroupRead(d *schema.ResourceData, m interface{}) error { return flattenResgroup(d, rg_facts) } - func dataSourceResgroup() *schema.Resource { - return &schema.Resource { + return &schema.Resource{ SchemaVersion: 1, - Read: dataSourceResgroupRead, + Read: dataSourceResgroupRead, - Timeouts: &schema.ResourceTimeout { + Timeouts: &schema.ResourceTimeout{ Read: &Timeout30s, Default: &Timeout60s, }, - Schema: map[string]*schema.Schema { + Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, - Description: "Name of this resource group. Names are case sensitive and unique within the context of an account.", + Type: schema.TypeString, + Optional: true, + Description: "Name of the resource group. Names are case sensitive and unique within the context of an account.", + }, + + "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": &schema.Schema { + "account_name": &schema.Schema{ Type: schema.TypeString, - Required: true, + Required: Optional, Description: "Name of the account, which this resource group belongs to.", }, - "account_id": &schema.Schema { + "account_id": &schema.Schema{ Type: schema.TypeInt, - Computed: true, - Description: "Unique ID of the account, which this resource group belongs to.", + Optional: Optional, + 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, Computed: true, Description: "User-defined text description of this resource group.", }, - "grid_id": &schema.Schema { + "grid_id": &schema.Schema{ Type: schema.TypeInt, Computed: true, Description: "Unique ID of the grid, where this resource group is deployed.", }, "quotas": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource { - Schema: quotaRgSubresourceSchema(), // this is a dictionary + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: quotaRgSubresourceSchema(), // this is a dictionary }, Description: "Quotas on the resources for this resource group.", }, - "status": { - Type: schema.TypeString, - Computed: true, - Description: "Current status of this resource group.", + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Current status of this resource group.", }, - "def_net": &schema.Schema { + "def_net": &schema.Schema{ Type: schema.TypeString, Computed: true, Description: "Type of the default network for this resource group.", }, - "def_net_id": &schema.Schema { + "def_net_id": &schema.Schema{ Type: schema.TypeInt, Computed: true, Description: "ID of the default network for this resource group (if any).", }, "vins": { - Type: schema.TypeList, // this is a list of ints - Computed: true, - MaxItems: LimitMaxVinsPerResgroup, - Elem: &schema.Schema { - Type: schema.TypeInt, + Type: schema.TypeList, // this is a list of ints + Computed: true, + MaxItems: LimitMaxVinsPerResgroup, + Elem: &schema.Schema{ + Type: schema.TypeInt, }, Description: "List of VINs deployed in this resource group.", }, "computes": { - Type: schema.TypeList, //t his is a list of ints - Computed: true, - Elem: &schema.Schema { - Type: schema.TypeInt, + Type: schema.TypeList, //t his is a list of ints + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, }, Description: "List of computes deployed in this resource group.", }, diff --git a/decort/rg_resource.go b/decort/rg_resource.go index 077d89b..c5924e5 100644 --- a/decort/rg_resource.go +++ b/decort/rg_resource.go @@ -15,57 +15,51 @@ limitations under the License. */ /* -This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +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. +Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates. */ package decort import ( - "fmt" "log" "net/url" "strconv" - "strings" - - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/schema" ) func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error { // First validate that we have all parameters required to create the new Resource Group - arg_set := false - account_name, arg_set := d.GetOk("account") - if !arg_set { - return fmt.Errorf("Cannot create new RG: missing account.") + + // 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 := utilityGetAccountIdBySchema(d, m) + if err != nil { + return err } + rg_name, arg_set := d.GetOk("name") 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") if !arg_set { - return fmt.Errorf("Cannot create new RG %q for account %q: missing Grid ID.", - rg_name.(string), account_name.(string)) + return fmt.Errorf("Cannot create new RG %q in account ID %d: missing Grid ID.", + rg_name.(string), validated_account_id) } // 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", - account_name.(string), rg_name.(string)) - - // 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 - } + log.Debugf("resourceResgroupCreate: called for RG name %q, account ID %d", + rg_name.(string), validated_account_id) // quota settings are optional set_quota := false - var quota_record QuotaRecord + var quota_record QuotaRecord arg_value, arg_set = d.GetOk("quota") if arg_set { log.Debugf("resourceResgroupCreate: setting Quota on RG requested") @@ -75,34 +69,34 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error { controller := m.(*ControllerCfg) log.Debugf("resourceResgroupCreate: called by user %q for RG name %q, account %q / ID %d, Grid ID %d", - controller.getdecortUsername(), - rg_name.(string), account_name.(string), validated_account_id, gird_id.(int)) + controller.getdecortUsername(), + rg_name.(string), account_name.(string), validated_account_id, gird_id.(int)) /* - 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"` -} + 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.Add("accountId", fmt.Sprintf("%d", validated_account_id)) url_values.Add("name", rg_name.(string)) url_values.Add("gid", fmt.Sprintf("%d", grid_id.(int))) url_values.Add("owner", controller.getdecortUsername()) - + // pass quota values as set if set_quota { url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", quota_record.Cpu)) @@ -133,7 +127,7 @@ func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error { if arg_set { ulr_values.Add("extIp", ext_ip.(string)) } - + api_resp, err := controller.decortAPICall("POST", ResgroupCreateAPI, url_values) if err != nil { return err @@ -147,12 +141,12 @@ func resourceResgroupCreate(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", - d.Get("name").(string), d.Get("account").(string)) + log.Debugf("resourceResgroupRead: called for RG name %q, account name %q", + d.Get("name").(string), d.Get("account_name").(string)) rg_facts, err := utilityResgroupCheckPresence(d, m) if rg_facts == "" { // 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 return err } @@ -161,8 +155,8 @@ func resourceResgroupRead(d *schema.ResourceData, m interface{}) error { } func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error { - log.Debugf("resourceResgroupUpdate: called for RG name %q, account name %q", - d.Get("name").(string), d.Get("account").(string)) + log.Debugf("resourceResgroupUpdate: called for RG name %q, account name %q", + d.Get("name").(string), d.Get("account").(string)) do_update := false @@ -179,7 +173,7 @@ func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error { url_values.Add("name", name_new.(string)) } } - + quota_value, quota_set := d.GetOk("quota") if quota_set { log.Debugf("resourceResgroupUpdate: quota specified - looking for deltas from the old quota.") @@ -192,25 +186,25 @@ func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error { log.Debugf("resourceResgroupUpdate: Cpu diff %d <- %d", quotarecord_new.Cpu, quotarecord_old.Cpu) url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", quotarecord_new.Cpu)) } - + if quotarecord_new.Disk != quotarecord_old.Disk { do_update = true log.Debugf("resourceResgroupUpdate: Disk diff %d <- %d", quotarecord_new.Disk, quotarecord_old.Disk) url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", quotarecord_new.Disk)) } - + if quotarecord_new.Ram != quotarecord_old.Ram { do_update = true log.Debugf("resourceResgroupUpdate: Ram diff %f <- %f", quotarecord_new.Ram, quotarecord_old.Ram) url_values.Add("maxMemoryCapacity", fmt.Sprintf("%f", quotarecord_new.Ram)) } - + if quotarecord_new.ExtTraffic != quotarecord_old.ExtTraffic { do_update = true log.Debugf("resourceResgroupUpdate: NetTraffic diff %d <- %d", quotarecord_new.ExtTraffic, quotarecord_old.ExtTraffic) url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", quotarecord_new.NetTraffic)) } - + if quotarecord_new.ExtIPs != quotarecord_old.ExtIPs { do_update = true log.Debugf("resourceResgroupUpdate: ExtIPs diff %d <- %d", quotarecord_new.ExtIPs, quotarecord_old.ExtIPs) @@ -237,19 +231,19 @@ func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error { } else { log.Debugf("resourceResgroupUpdate: no difference between old and new state - no update on the RG will be done") } - + return resourceResgroupRead(d, m) } 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 // 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", - d.Get("name").(string), d.Get("account").(string)) + log.Debugf("resourceResgroupDelete: called for RG name %q, account name %q", + d.Get("name").(string), d.Get("account_name").(string)) rg_facts, err := utilityResgroupCheckPresence(d, m) if rg_facts == "" { - // the target RG 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 return nil } @@ -282,7 +276,7 @@ func resourceResgroupExists(d *schema.ResourceData, m interface{}) (bool, error) } func resourceResgroup() *schema.Resource { - return &schema.Resource { + return &schema.Resource{ SchemaVersion: 1, Create: resourceResgroupCreate, @@ -291,7 +285,7 @@ func resourceResgroup() *schema.Resource { Delete: resourceResgroupDelete, Exists: resourceResgroupExists, - Timeouts: &schema.ResourceTimeout { + Timeouts: &schema.ResourceTimeout{ Create: &Timeout180s, Read: &Timeout30s, Update: &Timeout180s, @@ -299,103 +293,109 @@ func resourceResgroup() *schema.Resource { Default: &Timeout60s, }, - Schema: map[string]*schema.Schema { - "name": &schema.Schema { + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ Type: schema.TypeString, Required: true, 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, - Required: true, + Optional: true, Description: "Name of the account, which this resource group belongs to.", }, - "def_net": &schema.Schema { + "def_net": &schema.Schema{ Type: schema.TypeString, 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.", }, - "ipcidr": &schema.Schema { + "ipcidr": &schema.Schema{ Type: schema.TypeString, Optional: true, 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, Optional: true, Default: 0, 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, Optional: true, Description: "IP address on the external netowrk to request, if def_net=PUBLIC", }, - "account_id": &schema.Schema { + "account_id": &schema.Schema{ Type: schema.TypeInt, Computed: true, Description: "Unique ID of the account, which this resource group belongs to.", }, - "grid_id": &schema.Schema { + "grid_id": &schema.Schema{ Type: schema.TypeInt, Required: true, Description: "Unique ID of the grid, where this resource group is deployed.", }, "quota": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource { - Schema: quotasSubresourceSchema(), + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: quotasSubresourceSchema(), }, Description: "Quota settings for this resource group.", }, "desc": { - Type: schema.TypeString, - Optional: true, - Description: "User-defined text description of this resource group.", + Type: schema.TypeString, + Optional: true, + Description: "User-defined text description of this resource group.", }, - "status": { - Type: schema.TypeString, - Computed: true, - Description: "Current status of this resource group.", + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Current status of this resource group.", }, - "def_net_id": &schema.Schema { + "def_net_id": &schema.Schema{ Type: schema.TypeInt, Computed: true, Description: "ID of the default network for this resource group (if any).", }, "vins": { - Type: schema.TypeList, // this is a list of ints - Computed: true, - MaxItems: LimitMaxVinsPerResgroup, - Elem: &schema.Schema { - Type: schema.TypeInt, + Type: schema.TypeList, // this is a list of ints + Computed: true, + MaxItems: LimitMaxVinsPerResgroup, + Elem: &schema.Schema{ + Type: schema.TypeInt, }, Description: "List of VINs deployed in this resource group.", }, "computes": { - Type: schema.TypeList, // this is a list of ints - Computed: true, - Elem: &schema.Schema { - Type: schema.TypeInt, + Type: schema.TypeList, // this is a list of ints + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, }, Description: "List of computes deployed in this resource group.", }, }, } -} \ No newline at end of file +} diff --git a/decort/rg_utility.go b/decort/rg_utility.go index 480a3eb..331bf82 100644 --- a/decort/rg_utility.go +++ b/decort/rg_utility.go @@ -16,20 +16,20 @@ limitations under the License. */ /* -This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +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. +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" @@ -52,18 +52,18 @@ func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupGetResp, } /* - ret := &ResgroupConfig{} - ret.AccountID = model.AccountID - ret.Location = model.Location - ret.Name = model.Name - ret.ID = rgid - ret.GridID = model.GridID - ret.ExtIP = model.ExtIP // legacy field for VDC - this will eventually become obsoleted by true Resource Groups - // Quota ResgroupQuotaConfig - // Network NetworkConfig + ret := &ResgroupConfig{} + ret.AccountID = model.AccountID + ret.Location = model.Location + ret.Name = model.Name + ret.ID = rgid + ret.GridID = model.GridID + ret.ExtIP = model.ExtIP // legacy field for VDC - this will eventually become obsoleted by true Resource Groups + // Quota ResgroupQuotaConfig + // Network NetworkConfig */ - log.Debugf("utilityResgroupConfigGet: account ID %d, GridID %d, Name %s", - model.AccountID, model.GridID, model.Name) + log.Debugf("utilityResgroupConfigGet: account ID %d, GridID %d, Name %s", + model.AccountID, model.GridID, model.Name) return model, nil } @@ -71,31 +71,58 @@ func (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupGetResp, // On success this function returns a string, as returned by API rg/get, which could be unmarshalled // into ResgroupGetResp structure func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string, error) { - // 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 + // 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 // resource group as returned by cloudspaces/get API call. // 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 + // 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 // method for the Terraform resource Exists method. // - name := d.Get("name").(string) - account_name := d.Get("account").(string) controller := m.(*ControllerCfg) 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") body_string, err := controller.decortAPICall("POST", ResgroupListAPI, url_values) if err != nil { return "", err } - - log.Debugf("%s", body_string) - log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %q", ResgroupListAPI) + // log.Debugf("%s", body_string) + // log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %q", ResgroupListAPI) model := ResgroupListResp{} err = json.Unmarshal([]byte(body_string), &model) if err != nil { @@ -104,15 +131,15 @@ func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string log.Debugf("utilityResgroupCheckPresence: traversing decoded Json of length %d", len(model)) for index, item := range model { - // need to match RG by name & account name - if item.Name == name && item.AccountName == account_name { - log.Debugf("utilityResgroupCheckPresence: match RG name %q / ID %d, account %q at index %d", - item.Name, item.ID, item.AccountName, index) + // match by RG name & account ID + if item.Name == rg_name.(string) && item.AccountID == validated_account_id { + log.Debugf("utilityResgroupCheckPresence: match RG name %q / ID %d, account ID %d at index %d", + item.Name, item.ID, item.AccountID, index) // 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. - // Namely, we need to extract resource quota settings - req_values := &url.Values{} + // Namely, we need resource quota settings + req_values := &url.Values{} req_values.Add("rgId", fmt.Sprintf("%d", item.ID)) body_string, err := controller.decortAPICall("POST", ResgroupGetAPI, req_values) if err != nil { @@ -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) -} \ No newline at end of file diff --git a/decort/ssh_subresource.go b/decort/ssh_subresource.go index 1b56b4b..fe26664 100644 --- a/decort/ssh_subresource.go +++ b/decort/ssh_subresource.go @@ -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, , 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. */ -package decs +package decort import ( - "fmt" "github.com/hashicorp/terraform/helper/schema" @@ -26,7 +25,7 @@ import ( ) func makeSshKeysConfig(arg_list []interface{}) (sshkeys []SshKeyConfig, count int) { - count = len(arg_list) + count = len(arg_list) if count < 1 { return nil, 0 } @@ -48,13 +47,13 @@ func makeSshKeysArgString(sshkeys []SshKeyConfig) string { // It is designed to be passed as "userdata" argument of virtual machine create API call. // The following format is expected: // '{"users": [{"ssh-authorized-keys": ["SSH_PUBCIC_KEY_VALUE"], "shell": "SHELL_VALUE", "name": "USERNAME_VALUE"}, {...}, ]}' - + /* - `%s\n - - name: %s\n - ssh-authorized-keys: - - %s\n - shell: /bin/bash` + `%s\n + - name: %s\n + ssh-authorized-keys: + - %s\n + shell: /bin/bash` */ if len(sshkeys) < 1 { return "" @@ -70,18 +69,18 @@ func makeSshKeysArgString(sshkeys []SshKeyConfig) string { return out } -func sshSubresourceSchema() map[string]*schema.Schema { - rets := map[string]*schema.Schema { +func sshSubresourceSchemaMake() map[string]*schema.Schema { + rets := map[string]*schema.Schema{ "user": { Type: schema.TypeString, 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": { Type: schema.TypeString, 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": { @@ -94,4 +93,3 @@ func sshSubresourceSchema() map[string]*schema.Schema { return rets } -