Migrate to sdkv2 and project structure refactoring

This commit is contained in:
stSolo
2022-06-29 11:34:14 +03:00
parent 3c2eb0407c
commit 3613bbea28
164 changed files with 5947 additions and 3928 deletions

View File

@@ -0,0 +1,39 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
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.
*/
/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.
Source code: https://github.com/rudecs/terraform-provider-decort
Please see README.md to learn where to place source code so that it
builds seamlessly.
Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
*/
package rg
const ResgroupCreateAPI = "/restmachine/cloudapi/rg/create"
const ResgroupUpdateAPI = "/restmachine/cloudapi/rg/update"
const ResgroupListAPI = "/restmachine/cloudapi/rg/list"
const ResgroupGetAPI = "/restmachine/cloudapi/rg/get"
const ResgroupDeleteAPI = "/restmachine/cloudapi/rg/delete"
const RgListComputesAPI = "/restmachine/cloudapi/rg/listComputes"

View File

@@ -0,0 +1,197 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
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.
*/
/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.
Source code: https://github.com/rudecs/terraform-provider-decort
Please see README.md to learn where to place source code so that it
builds seamlessly.
Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
*/
package rg
import (
"context"
"encoding/json"
"fmt"
"github.com/rudecs/terraform-provider-decort/internal/constants"
log "github.com/sirupsen/logrus"
// "net/url"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
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 API")
details := ResgroupGetResp{}
err := json.Unmarshal([]byte(rg_facts), &details)
if err != nil {
return err
}
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("description", details.Desc)
d.Set("status", details.Status)
d.Set("def_net_type", details.DefaultNetType)
d.Set("def_net_id", details.DefaultNetID)
/*
d.Set("vins", details.Vins)
d.Set("computes", details.Computes)
*/
log.Debugf("flattenResgroup: calling flattenQuota()")
if err = d.Set("quota", parseQuota(details.Quota)); err != nil {
return err
}
return nil
}
func dataSourceResgroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
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
d.SetId("") // ensure ID is empty in this case
return diag.FromErr(err)
}
return diag.FromErr(flattenResgroup(d, rg_facts))
}
func DataSourceResgroup() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
ReadContext: dataSourceResgroupRead,
Timeouts: &schema.ResourceTimeout{
Read: &constants.Timeout30s,
Default: &constants.Timeout60s,
},
Schema: map[string]*schema.Schema{
"name": {
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": {
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": {
Type: schema.TypeString,
Computed: true,
Description: "Name of the account, which this resource group belongs to.",
},
"account_id": {
Type: schema.TypeInt,
Required: true,
Description: "Unique ID of the account, which this resource group belongs to.",
},
"description": {
Type: schema.TypeString,
Computed: true,
Description: "User-defined text description of this resource group.",
},
/* commented out, as in this version of provider we use default Grid ID
"grid_id": {
Type: schema.TypeInt,
Computed: true,
Description: "Unique ID of the grid, where this resource group is deployed.",
},
*/
"quota": {
Type: schema.TypeList,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: quotaRgSubresourceSchemaMake(), // this is a dictionary
},
Description: "Quota settings for this resource group.",
},
"def_net_type": {
Type: schema.TypeString,
Computed: true,
Description: "Type of the default network for this resource group.",
},
"def_net_id": {
Type: schema.TypeInt,
Computed: true,
Description: "ID of the default network for this resource group (if any).",
},
/*
"status": {
Type: schema.TypeString,
Computed: true,
Description: "Current status of this resource group.",
},
"vins": {
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,
},
Description: "List of computes deployed in this resource group.",
},
*/
},
}
}

View File

@@ -0,0 +1,325 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
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.
*/
/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.
Source code: https://github.com/rudecs/terraform-provider-decort
Please see README.md to learn where to place source code so that it
builds seamlessly.
Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
*/
package rg
import (
"context"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/rudecs/terraform-provider-decort/internal/constants"
)
func flattenRgList(rgl ResgroupListResp) []map[string]interface{} {
res := make([]map[string]interface{}, 0)
for _, rg := range rgl {
temp := map[string]interface{}{
"account_id": rg.AccountID,
"account_name": rg.AccountName,
"acl": flattenRgAcl(rg.ACLs),
"created_by": rg.CreatedBy,
"created_time": rg.CreatedTime,
"def_net_id": rg.DefaultNetID,
"def_net_type": rg.DefaultNetType,
"deleted_by": rg.DeletedBy,
"deleted_time": rg.DeletedTime,
"desc": rg.Decsription,
"gid": rg.GridID,
"guid": rg.GUID,
"rg_id": rg.ID,
"lock_status": rg.LockStatus,
"milestones": rg.Milestones,
"name": rg.Name,
"register_computes": rg.RegisterComputes,
"resource_limits": flattenRgResourceLimits(rg.ResourceLimits),
"secret": rg.Secret,
"status": rg.Status,
"updated_by": rg.UpdatedBy,
"updated_time": rg.UpdatedTime,
"vins": rg.Vins,
"vms": rg.Computes,
}
res = append(res, temp)
}
return res
}
func flattenRgAcl(rgAcls []AccountAclRecord) []map[string]interface{} {
res := make([]map[string]interface{}, 0)
for _, rgAcl := range rgAcls {
temp := map[string]interface{}{
"explicit": rgAcl.IsExplicit,
"guid": rgAcl.Guid,
"right": rgAcl.Rights,
"status": rgAcl.Status,
"type": rgAcl.Type,
"user_group_id": rgAcl.UgroupID,
}
res = append(res, temp)
}
return res
}
func flattenRgResourceLimits(rl ResourceLimits) []map[string]interface{} {
res := make([]map[string]interface{}, 0)
temp := map[string]interface{}{
"cu_c": rl.CUC,
"cu_d": rl.CUD,
"cu_i": rl.CUI,
"cu_m": rl.CUM,
"cu_np": rl.CUNP,
"gpu_units": rl.GpuUnits,
}
res = append(res, temp)
return res
}
func dataSourceRgListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
rgList, err := utilityRgListCheckPresence(d, m)
if err != nil {
return diag.FromErr(err)
}
id := uuid.New()
d.SetId(id.String())
d.Set("items", flattenRgList(rgList))
return nil
}
func dataSourceRgListSchemaMake() map[string]*schema.Schema {
res := map[string]*schema.Schema{
"includedeleted": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "included deleted resource groups",
},
"page": {
Type: schema.TypeInt,
Optional: true,
Description: "Page number",
},
"size": {
Type: schema.TypeInt,
Optional: true,
Description: "Page size",
},
"items": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"account_id": {
Type: schema.TypeInt,
Computed: true,
},
"account_name": {
Type: schema.TypeString,
Computed: true,
},
"acl": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"explicit": {
Type: schema.TypeBool,
Computed: true,
},
"guid": {
Type: schema.TypeString,
Computed: true,
},
"right": {
Type: schema.TypeString,
Computed: true,
},
"status": {
Type: schema.TypeString,
Computed: true,
},
"type": {
Type: schema.TypeString,
Computed: true,
},
"user_group_id": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
"created_by": {
Type: schema.TypeString,
Computed: true,
},
"created_time": {
Type: schema.TypeInt,
Computed: true,
},
"def_net_id": {
Type: schema.TypeInt,
Computed: true,
},
"def_net_type": {
Type: schema.TypeString,
Computed: true,
},
"deleted_by": {
Type: schema.TypeString,
Computed: true,
},
"deleted_time": {
Type: schema.TypeInt,
Computed: true,
},
"desc": {
Type: schema.TypeString,
Computed: true,
},
"gid": {
Type: schema.TypeInt,
Computed: true,
},
"guid": {
Type: schema.TypeInt,
Computed: true,
},
"rg_id": {
Type: schema.TypeInt,
Computed: true,
},
"lock_status": {
Type: schema.TypeString,
Computed: true,
},
"milestones": {
Type: schema.TypeInt,
Computed: true,
},
"name": {
Type: schema.TypeString,
Computed: true,
},
"register_computes": {
Type: schema.TypeBool,
Computed: true,
},
"resource_limits": {
Type: schema.TypeList,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"cu_c": {
Type: schema.TypeFloat,
Computed: true,
},
"cu_d": {
Type: schema.TypeFloat,
Computed: true,
},
"cu_i": {
Type: schema.TypeFloat,
Computed: true,
},
"cu_m": {
Type: schema.TypeFloat,
Computed: true,
},
"cu_np": {
Type: schema.TypeFloat,
Computed: true,
},
"gpu_units": {
Type: schema.TypeFloat,
Computed: true,
},
},
},
},
"secret": {
Type: schema.TypeString,
Computed: true,
},
"status": {
Type: schema.TypeString,
Computed: true,
},
"updated_by": {
Type: schema.TypeString,
Computed: true,
},
"updated_time": {
Type: schema.TypeInt,
Computed: true,
},
"vins": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
"vms": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
},
},
},
}
return res
}
func DataSourceRgList() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
ReadContext: dataSourceRgListRead,
Timeouts: &schema.ResourceTimeout{
Read: &constants.Timeout30s,
Default: &constants.Timeout60s,
},
Schema: dataSourceRgListSchemaMake(),
}
}

View File

@@ -0,0 +1,149 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
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.
*/
/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.
Source code: https://github.com/rudecs/terraform-provider-decort
Please see README.md to learn where to place source code so that it
builds seamlessly.
Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
*/
package rg
type ResourceLimits struct {
CUC float64 `json:"CU_C"`
CUD float64 `json:"CU_D"`
CUI float64 `json:"CU_I"`
CUM float64 `json:"CU_M"`
CUNP float64 `json:"CU_NP"`
GpuUnits float64 `json:"gpu_units"`
}
type ResgroupRecord struct {
ACLs []AccountAclRecord `json:"acl"`
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 int `json:"deletedTime"`
Decsription string `json:"desc"`
GridID int `json:"gid"`
GUID int `json:"guid"`
ID uint `json:"id"`
LockStatus string `json:"lockStatus"`
Milestones int `json:"milestones"`
Name string `json:"name"`
RegisterComputes bool `json:"registerComputes"`
ResourceLimits ResourceLimits `json:"resourceLimits"`
Secret string `json:"secret"`
Status string `json:"status"`
UpdatedBy string `json:"updatedBy"`
UpdatedTime uint64 `json:"updatedTime"`
Vins []int `json:"vins"`
Computes []int `json:"vms"`
}
type ResgroupListResp []ResgroupRecord
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"`
}
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"`
CanBeDeleted bool `json:"canBeDeleted"`
}
type ResgroupGetResp struct {
ACLs []UserAclRecord `json:"ACLs"`
Usage UsageRecord `json:"Resources"`
AccountID int `json:"accountId"`
AccountName string `json:"accountName"`
GridID int `json:"gid"`
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"`
Desc 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:"-"`
}
type UserAclRecord struct {
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 QuotaRecord struct { // this is how quota is reported by /api/.../rg/get
Cpu int `json:"CU_C"` // CPU count in pcs
Ram float64 `json:"CU_M"` // RAM volume in MB, it is STILL reported as FLOAT
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"`
}
type UsageRecord struct {
Current ResourceRecord `json:"Current"`
Reserved ResourceRecord `json:"Reserved"`
}

View File

@@ -0,0 +1,137 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
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.
*/
/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.
Source code: https://github.com/rudecs/terraform-provider-decort
Please see README.md to learn where to place source code so that it
builds seamlessly.
Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
*/
package rg
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func makeQuotaRecord(arg_list []interface{}) QuotaRecord {
quota := QuotaRecord{
Cpu: -1,
Ram: -1., // this is float64, but may change in the future
Disk: -1,
ExtTraffic: -1,
ExtIPs: -1,
GpuUnits: -1,
}
subres_data := arg_list[0].(map[string]interface{})
if subres_data["cpu"].(int) > 0 {
quota.Cpu = subres_data["cpu"].(int)
}
if subres_data["disk"].(int) > 0 {
quota.Disk = subres_data["disk"].(int) // Disk capacity ib GB
}
if subres_data["ram"].(float64) > 0 {
quota.Ram = subres_data["ram"].(float64) // RAM volume in MB, as float64!
}
if subres_data["ext_traffic"].(int) > 0 {
quota.ExtTraffic = subres_data["ext_traffic"].(int)
}
if subres_data["ext_ips"].(int) > 0 {
quota.ExtIPs = subres_data["ext_ips"].(int)
}
if subres_data["gpu_units"].(int) > 0 {
quota.GpuUnits = subres_data["gpu_units"].(int)
}
return quota
}
func parseQuota(quota QuotaRecord) []interface{} {
quota_map := make(map[string]interface{})
quota_map["cpu"] = quota.Cpu
quota_map["ram"] = quota.Ram // NB: this is float64, unlike the rest of values
quota_map["disk"] = quota.Disk
quota_map["ext_traffic"] = quota.ExtTraffic
quota_map["ext_ips"] = quota.ExtIPs
quota_map["gpu_units"] = quota.GpuUnits
result := make([]interface{}, 1)
result[0] = quota_map
return result // this result will be used to d.Set("quota,") of dataSourceResgroup schema
}
func quotaRgSubresourceSchemaMake() map[string]*schema.Schema {
rets := map[string]*schema.Schema{
"cpu": {
Type: schema.TypeInt,
Optional: true,
Default: -1,
Description: "Limit on the total number of CPUs in this resource group.",
},
"ram": {
Type: schema.TypeFloat, // NB: API expects and returns this as float in units of MB! This may be changed in the future.
Optional: true,
Default: -1.,
Description: "Limit on the total amount of RAM in this resource group, specified in MB.",
},
"disk": {
Type: schema.TypeInt,
Optional: true,
Default: -1,
Description: "Limit on the total volume of storage resources in this resource group, specified in GB.",
},
"ext_traffic": {
Type: schema.TypeInt,
Optional: true,
Default: -1,
Description: "Limit on the total ingress network traffic for this resource group, specified in GB.",
},
"ext_ips": {
Type: schema.TypeInt,
Optional: true,
Default: -1,
Description: "Limit on the total number of external IP addresses this resource group can use.",
},
"gpu_units": {
Type: schema.TypeInt,
Optional: true,
Default: -1,
Description: "Limit on the total number of virtual GPUs this resource group can use.",
},
}
return rets
}

View File

@@ -0,0 +1,443 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
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.
*/
/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.
Source code: https://github.com/rudecs/terraform-provider-decort
Please see README.md to learn where to place source code so that it
builds seamlessly.
Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
*/
package rg
import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/rudecs/terraform-provider-decort/internal/constants"
"github.com/rudecs/terraform-provider-decort/internal/controller"
"github.com/rudecs/terraform-provider-decort/internal/location"
log "github.com/sirupsen/logrus"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
func resourceResgroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
// First validate that we have all parameters required to create the new Resource Group
// Valid account ID is required to create new resource group
// obtain Account ID by account name - it should not be zero on success
rg_name, arg_set := d.GetOk("name")
if !arg_set {
return diag.FromErr(fmt.Errorf("Cannot create new RG: missing name."))
}
/* Current version of provider works with default grid id (same is true for disk resources)
grid_id, arg_set := d.GetOk("grid_id")
if !arg_set {
return fmt.Errorf("Cannot create new RG %q in account ID %d: missing Grid ID.",
rg_name.(string), validated_account_id)
}
if grid_id.(int) < 1 {
grid_id = DefaultGridID
}
*/
// all required parameters are set in the schema - we can continue with RG creation
log.Debugf("resourceResgroupCreate: called for RG name %s, account ID %d",
rg_name.(string), d.Get("account_id").(int))
// quota settings are optional
set_quota := false
var quota_record QuotaRecord
arg_value, arg_set := d.GetOk("quota")
if arg_set {
log.Debugf("resourceResgroupCreate: setting Quota on RG requested")
quota_record = makeQuotaRecord(arg_value.([]interface{}))
set_quota = true
}
c := m.(*controller.ControllerCfg)
log.Debugf("resourceResgroupCreate: called by user %q for RG name %s, account ID %d",
c.GetDecortUsername(),
rg_name.(string), d.Get("account_id").(int))
url_values := &url.Values{}
url_values.Add("accountId", fmt.Sprintf("%d", d.Get("account_id").(int)))
url_values.Add("name", rg_name.(string))
url_values.Add("gid", fmt.Sprintf("%d", location.DefaultGridID)) // use default Grid ID, similar to disk resource mgmt convention
url_values.Add("owner", c.GetDecortUsername())
// pass quota values as set
if set_quota {
url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", quota_record.Cpu))
url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", quota_record.Disk))
url_values.Add("maxMemoryCapacity", fmt.Sprintf("%f", quota_record.Ram)) // RAM quota is float; this may change in the future
url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", quota_record.ExtTraffic))
url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", quota_record.ExtIPs))
// url_values.Add("???", fmt.Sprintf("%d", quota_record.GpuUnits))
}
// parse and handle network settings
def_net_type, arg_set := d.GetOk("def_net_type")
if arg_set {
url_values.Add("def_net", def_net_type.(string)) // NOTE: in API default network type is set by "def_net" parameter
}
ipcidr, arg_set := d.GetOk("ipcidr")
if arg_set {
url_values.Add("ipcidr", ipcidr.(string))
}
ext_net_id, arg_set := d.GetOk("ext_net_id")
if arg_set {
url_values.Add("extNetId", fmt.Sprintf("%d", ext_net_id.(int)))
}
ext_ip, arg_set := d.GetOk("ext_ip")
if arg_set {
url_values.Add("extIp", ext_ip.(string))
}
api_resp, err := c.DecortAPICall("POST", ResgroupCreateAPI, url_values)
if err != nil {
return diag.FromErr(err)
}
d.SetId(api_resp) // rg/create API returns ID of the newly creted resource group on success
// rg.ID, _ = strconv.Atoi(api_resp)
if !set_quota {
resp, err := utilityResgroupCheckPresence(d, m)
if err != nil {
return diag.FromErr(err)
}
rg := ResgroupGetResp{}
if err := json.Unmarshal([]byte(resp), &rg); err != nil {
return diag.FromErr(err)
}
d.Set("quota", parseQuota(rg.Quota))
}
// re-read newly created RG to make sure schema contains complete and up to date set of specifications
return resourceResgroupRead(ctx, d, m)
}
func resourceResgroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
log.Debugf("resourceResgroupRead: called for RG name %s, account ID %d",
d.Get("name").(string), d.Get("account_id").(int))
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
d.SetId("") // ensure ID is empty
return diag.FromErr(err)
}
return diag.FromErr(flattenResgroup(d, rg_facts))
}
func resourceResgroupUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
log.Debugf("resourceResgroupUpdate: called for RG name %s, account ID %d",
d.Get("name").(string), d.Get("account_id").(int))
/* NOTE: we do not allow changing the following attributes of an existing RG via terraform:
- def_net_type
- ipcidr
- ext_net_id
- ext_ip
The following code fragment checks if any of these have been changed and generates error.
*/
for _, attr := range []string{"def_net_type", "ipcidr", "ext_ip"} {
attr_new, attr_old := d.GetChange("def_net_type")
if attr_new.(string) != attr_old.(string) {
return diag.FromErr(fmt.Errorf("resourceResgroupUpdate: RG ID %s: changing %s for existing RG is not allowed", d.Id(), attr))
}
}
attr_new, attr_old := d.GetChange("ext_net_id")
if attr_new.(int) != attr_old.(int) {
return diag.FromErr(fmt.Errorf("resourceResgroupUpdate: RG ID %s: changing ext_net_id for existing RG is not allowed", d.Id()))
}
do_general_update := false // will be true if general RG update is necessary (API rg/update)
c := m.(*controller.ControllerCfg)
url_values := &url.Values{}
url_values.Add("rgId", d.Id())
name_new, name_set := d.GetOk("name")
if name_set {
log.Debugf("resourceResgroupUpdate: name specified - looking for deltas from the old settings.")
name_old, _ := d.GetChange("name")
if name_old.(string) != name_new.(string) {
do_general_update = true
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.")
quotarecord_new := makeQuotaRecord(quota_value.([]interface{}))
quota_value_old, _ := d.GetChange("quota") // returns old as 1st, new as 2nd return value
quotarecord_old := makeQuotaRecord(quota_value_old.([]interface{}))
if quotarecord_new.Cpu != quotarecord_old.Cpu {
do_general_update = true
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_general_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 { // NB: quota on RAM is stored as float32, in units of MB
do_general_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_general_update = true
log.Debugf("resourceResgroupUpdate: ExtTraffic diff %d <- %d", quotarecord_new.ExtTraffic, quotarecord_old.ExtTraffic)
url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", quotarecord_new.ExtTraffic))
}
if quotarecord_new.ExtIPs != quotarecord_old.ExtIPs {
do_general_update = true
log.Debugf("resourceResgroupUpdate: ExtIPs diff %d <- %d", quotarecord_new.ExtIPs, quotarecord_old.ExtIPs)
url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", quotarecord_new.ExtIPs))
}
}
desc_new, desc_set := d.GetOk("description")
if desc_set {
log.Debugf("resourceResgroupUpdate: description specified - looking for deltas from the old settings.")
desc_old, _ := d.GetChange("description")
if desc_old.(string) != desc_new.(string) {
do_general_update = true
url_values.Add("desc", desc_new.(string))
}
}
if do_general_update {
log.Debugf("resourceResgroupUpdate: detected delta between new and old RG specs - updating the RG")
_, err := c.DecortAPICall("POST", ResgroupUpdateAPI, url_values)
if err != nil {
return diag.FromErr(err)
}
} else {
log.Debugf("resourceResgroupUpdate: no difference between old and new state - no update on the RG will be done")
}
return resourceResgroupRead(ctx, d, m)
}
func resourceResgroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
// 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 %s, account ID %d",
d.Get("name").(string), d.Get("account_id").(int))
rg_facts, err := utilityResgroupCheckPresence(d, m)
if rg_facts == "" {
if err != nil {
return diag.FromErr(err)
}
// the target RG does not exist - in this case according to Terraform best practice
// we exit from Destroy method without error
return nil
}
url_values := &url.Values{}
url_values.Add("rgId", d.Id())
url_values.Add("force", "1")
url_values.Add("permanently", "1")
url_values.Add("reason", "Destroyed by DECORT Terraform provider")
c := m.(*controller.ControllerCfg)
_, err = c.DecortAPICall("POST", ResgroupDeleteAPI, url_values)
if err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceResgroupExists(d *schema.ResourceData, m interface{}) (bool, error) {
// Reminder: according to Terraform rules, this function should NOT modify ResourceData argument
rg_facts, err := utilityResgroupCheckPresence(d, m)
if rg_facts == "" {
if err != nil {
return false, err
}
return false, nil
}
return true, nil
}
func ResourceResgroup() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
CreateContext: resourceResgroupCreate,
ReadContext: resourceResgroupRead,
UpdateContext: resourceResgroupUpdate,
DeleteContext: resourceResgroupDelete,
Exists: resourceResgroupExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Timeouts: &schema.ResourceTimeout{
Create: &constants.Timeout180s,
Read: &constants.Timeout30s,
Update: &constants.Timeout180s,
Delete: &constants.Timeout60s,
Default: &constants.Timeout60s,
},
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 a account.",
},
"account_id": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1),
Description: "Unique ID of the account, which this resource group belongs to.",
},
"def_net_type": {
Type: schema.TypeString,
Optional: true,
Default: "PRIVATE",
ValidateFunc: validation.StringInSlice([]string{"PRIVATE", "PUBLIC", "NONE"}, false),
Description: "Type of the network, which this resource group will use as default for its computes - PRIVATE or PUBLIC or NONE.",
},
"def_net_id": {
Type: schema.TypeInt,
Computed: true,
Description: "ID of the default network for this resource group (if any).",
},
"ipcidr": {
Type: schema.TypeString,
Optional: true,
Description: "Address of the netowrk inside the private network segment (aka ViNS) if def_net_type=PRIVATE",
},
"ext_net_id": {
Type: schema.TypeInt,
Optional: true,
Default: 0,
Description: "ID of the external network for default ViNS. Pass 0 if def_net_type=PUBLIC or no external connection required for the defult ViNS when def_net_type=PRIVATE",
},
"ext_ip": {
Type: schema.TypeString,
Optional: true,
Description: "IP address on the external netowrk to request when def_net_type=PRIVATE and ext_net_id is not 0",
},
/* commented out, as in this version of provider we use default Grid ID
"grid_id": {
Type: schema.TypeInt,
Optional: true,
Default: 0, // if 0 is passed, default Grid ID will be used
// DefaultFunc: utilityResgroupGetDefaultGridID,
ForceNew: true, // change of Grid ID will require new RG
Description: "Unique ID of the grid, where this resource group is deployed.",
},
*/
"quota": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: quotaRgSubresourceSchemaMake(),
},
Description: "Quota settings for this resource group.",
},
"description": {
Type: schema.TypeString,
Optional: true,
Description: "User-defined text description of this resource group.",
},
"account_name": {
Type: schema.TypeString,
Computed: true,
Description: "Name of the account, which this resource group belongs to.",
},
/*
"status": {
Type: schema.TypeString,
Computed: true,
Description: "Current status of this resource group.",
},
"vins": {
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,
},
Description: "List of computes deployed in this resource group.",
},
*/
},
}
}

View File

@@ -0,0 +1,138 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
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.
*/
/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.
Source code: https://github.com/rudecs/terraform-provider-decort
Please see README.md to learn where to place source code so that it
builds seamlessly.
Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
*/
package rg
import (
"encoding/json"
"fmt"
"net/url"
"strconv"
"github.com/rudecs/terraform-provider-decort/internal/controller"
log "github.com/sirupsen/logrus"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// 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 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 rg/get API call.
// Otherwise it returns empty string and a meaningful error.
//
// NOTE: As our provider always deletes RGs permanently, there is no "restore" method and
// consequently we are not interested in matching RGs in DELETED state. Hence, we call
// .../rg/list API with includedeleted=false
//
// This function does not modify its ResourceData argument, so it is safe to use it as core
// method for the Terraform resource Exists method.
//
c := m.(*controller.ControllerCfg)
urlValues := &url.Values{}
// make it possible to use "read" & "check presence" functions with RG ID set so
// that Import of RG resource is possible
idSet := false
theId, err := strconv.Atoi(d.Id())
if err != nil || theId <= 0 {
rgId, argSet := d.GetOk("rg_id")
if argSet {
theId = rgId.(int)
idSet = true
}
} else {
idSet = true
}
if idSet {
// go straight for the RG by its ID
log.Debugf("utilityResgroupCheckPresence: locating RG by its ID %d", theId)
urlValues.Add("rgId", fmt.Sprintf("%d", theId))
rgFacts, err := c.DecortAPICall("POST", ResgroupGetAPI, urlValues)
if err != nil {
return "", err
}
return rgFacts, nil
}
rgName, argSet := d.GetOk("name")
if !argSet {
// no RG ID and no RG name - we cannot locate resource group in this case
return "", fmt.Errorf("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
urlValues.Add("includedeleted", "false")
apiResp, err := c.DecortAPICall("POST", ResgroupListAPI, urlValues)
if err != nil {
return "", err
}
// log.Debugf("%s", apiResp)
log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %s", ResgroupListAPI)
model := ResgroupListResp{}
err = json.Unmarshal([]byte(apiResp), &model)
if err != nil {
return "", err
}
log.Debugf("utilityResgroupCheckPresence: traversing decoded Json of length %d", len(model))
for index, item := range model {
// match by RG name & account ID
if item.Name == rgName.(string) && item.AccountID == d.Get("account_id").(int) {
log.Debugf("utilityResgroupCheckPresence: match RG name %s / 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 resource quota settings
reqValues := &url.Values{}
reqValues.Add("rgId", fmt.Sprintf("%d", item.ID))
apiResp, err := c.DecortAPICall("POST", ResgroupGetAPI, reqValues)
if err != nil {
return "", err
}
return apiResp, nil
}
}
return "", fmt.Errorf("Cannot find RG name %s owned by account ID %d", rgName, d.Get("account_id").(int))
}

View File

@@ -0,0 +1,73 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
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.
*/
/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.
Source code: https://github.com/rudecs/terraform-provider-decort
Please see README.md to learn where to place source code so that it
builds seamlessly.
Documentation: https://github.com/rudecs/terraform-provider-decort/wiki
*/
package rg
import (
"encoding/json"
"net/url"
"strconv"
"github.com/rudecs/terraform-provider-decort/internal/controller"
log "github.com/sirupsen/logrus"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func utilityRgListCheckPresence(d *schema.ResourceData, m interface{}) (ResgroupListResp, error) {
c := m.(*controller.ControllerCfg)
urlValues := &url.Values{}
rgList := ResgroupListResp{}
if size, ok := d.GetOk("size"); ok {
urlValues.Add("size", strconv.Itoa(size.(int)))
}
if page, ok := d.GetOk("page"); ok {
urlValues.Add("page", strconv.Itoa(page.(int)))
}
if includedeleted, ok := d.GetOk("includedeleted"); ok {
urlValues.Add("includedeleted", strconv.FormatBool(includedeleted.(bool)))
}
log.Debugf("utilityRgListCheckPresence: load rg list")
rgListRaw, err := c.DecortAPICall("POST", ResgroupListAPI, urlValues)
if err != nil {
return nil, err
}
err = json.Unmarshal([]byte(rgListRaw), &rgList)
if err != nil {
return nil, err
}
return rgList, nil
}