diff --git a/README.md b/README.md index 566daaa..cbfa5b6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ # terraform-provider-decort Terraform provider для платформы Digital Energy Cloud Orchestration Technology (DECORT) -Внимание: провайдер версии rc-1.25 разработан для DECORT API 3.7.x. +Внимание: провайдер версии 3.x разработан для DECORT API 3.8.x. Для более старых версий можно использовать: +- DECORT API 3.7.x - версия провайдера rc-1.25 - DECORT API 3.6.x - версия провайдера rc-1.10 - DECORT API до 3.6.0 - terraform DECS provider (https://github.com/rudecs/terraform-provider-decs) +## Режимы работы +Провайдер позволяет работать в двух режимах: +- Режим пользователя, +- Режим администратора. +Для переключения между режимами используйте флаг DECORT_ADMIN_MODE. +Вики проекта: https://github.com/rudecs/terraform-provider-decort/wiki + ## Возможности провайдера - Работа с Compute instances, - Работа с disks, diff --git a/README_EN.md b/README_EN.md index d942b38..4a23869 100644 --- a/README_EN.md +++ b/README_EN.md @@ -1,10 +1,19 @@ # terraform-provider-decort Terraform provider for Digital Energy Cloud Orchestration Technology (DECORT) platform -NOTE: provider rc-1.25 is designed for DECORT API 3.7.x. For older API versions please use: +NOTE: provider 3.x is designed for DECORT API 3.8.x. For older API versions please use: +- DECORT API 3.7.x versions - provider verion rc-1.25 - DECORT API 3.6.x versions - provider version rc-1.10 - DECORT API versions prior to 3.6.0 - Terraform DECS provider (https://github.com/rudecs/terraform-provider-decs) +## Working modes +The provider support two working modes: +- User mode, +- Administator mode. +Use flag DECORT_ADMIN_MODE for swithcing beetwen modes. +See user guide at https://github.com/rudecs/terraform-provider-decort/wiki + + ## Features - Work with Compute instances, - Work with disks, diff --git a/internal/controller/controller.go b/internal/controller/controller.go index ca9d3e3..a2024cd 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -362,6 +362,9 @@ func (config *ControllerCfg) DecortAPICall(ctx context.Context, method string, a if err != nil { return "", err } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Length", strconv.Itoa(len(params_str))) req.Header.Set("Accept", "application/json") diff --git a/internal/provider/cloudapi/data_sources_map.go b/internal/provider/cloudapi/data_sources_map.go index cd9217b..e1103a1 100644 --- a/internal/provider/cloudapi/data_sources_map.go +++ b/internal/provider/cloudapi/data_sources_map.go @@ -25,6 +25,7 @@ import ( "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/bservice" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/disks" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/extnet" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/image" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/kvmvm" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/locations" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/rg" @@ -41,6 +42,7 @@ func NewDataSourcesMap() map[string]*schema.Resource { "decort_vins": vins.DataSourceVins(), "decort_snapshot_list": snapshot.DataSourceSnapshotList(), "decort_vgpu": vgpu.DataSourceVGPU(), + "decort_disk": disks.DataSourceDisk(), "decort_disk_list": disks.DataSourceDiskList(), "decort_rg_list": rg.DataSourceRgList(), "decort_account_list": account.DataSourceAccountList(), @@ -67,6 +69,8 @@ func NewDataSourcesMap() map[string]*schema.Resource { "decort_vins_list": vins.DataSourceVinsList(), "decort_locations_list": locations.DataSourceLocationsList(), "decort_location_url": locations.DataSourceLocationUrl(), + "decort_image_list": image.DataSourceImageList(), + "decort_image": image.DataSourceImage(), // "decort_pfw": dataSourcePfw(), } diff --git a/internal/provider/cloudapi/resource_map.go b/internal/provider/cloudapi/resource_map.go index fd5c27b..844bd76 100644 --- a/internal/provider/cloudapi/resource_map.go +++ b/internal/provider/cloudapi/resource_map.go @@ -24,6 +24,7 @@ import ( "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/account" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/bservice" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/disks" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/image" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/k8s" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/kvmvm" "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/pfw" @@ -45,5 +46,7 @@ func NewRersourcesMap() map[string]*schema.Resource { "decort_account": account.ResourceAccount(), "decort_bservice": bservice.ResourceBasicService(), "decort_bservice_group": bservice.ResourceBasicServiceGroup(), + "decort_image": image.ResourceImage(), + "decort_image_virtual": image.ResourceImageVirtual(), } } diff --git a/internal/provider/cloudbroker/data_sources_map.go b/internal/provider/cloudbroker/data_sources_map.go index fb633f8..82e74d5 100644 --- a/internal/provider/cloudbroker/data_sources_map.go +++ b/internal/provider/cloudbroker/data_sources_map.go @@ -21,7 +21,8 @@ package cloudbroker import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/rudecs/terraform-provider-decort/internal/service/cloudapi/disks" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/account" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/disks" "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/grid" "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/image" "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/pcidevice" @@ -30,20 +31,30 @@ import ( func NewDataSourcesMap() map[string]*schema.Resource { return map[string]*schema.Resource{ - "decort_image": image.DataSourceImage(), - "decort_disk": disks.DataSourceDisk(), - "decort_grid": grid.DataSourceGrid(), - "decort_grid_list": grid.DataSourceGridList(), - "decort_image_list": image.DataSourceImageList(), - "decort_image_list_stacks": image.DataSourceImageListStacks(), - "decort_pcidevice": pcidevice.DataSourcePcidevice(), - "decort_pcidevice_list": pcidevice.DataSourcePcideviceList(), - "decort_sep_list": sep.DataSourceSepList(), - "decort_sep": sep.DataSourceSep(), - "decort_sep_consumption": sep.DataSourceSepConsumption(), - "decort_sep_disk_list": sep.DataSourceSepDiskList(), - "decort_sep_config": sep.DataSourceSepConfig(), - "decort_sep_pool": sep.DataSourceSepPool(), + "decort_account": account.DataSourceAccount(), + "decort_account_list": account.DataSourceAccountList(), + "decort_account_computes_list": account.DataSourceAccountComputesList(), + "decort_account_deleted_list": account.DataSourceAccountDeletedList(), + "decort_account_disks_list": account.DataSourceAccountDisksList(), + "decort_account_flipgroups_list": account.DataSourceAccountFlipGroupsList(), + "decort_account_rg_list": account.DataSourceAccountRGList(), + "decort_account_vins_list": account.DataSourceAccountVinsList(), + "decort_account_audits_list": account.DataSourceAccountAuditsList(), + "decort_disk": disks.DataSourceDisk(), + "decort_disk_list": disks.DataSourceDiskList(), + "decort_image": image.DataSourceImage(), + "decort_grid": grid.DataSourceGrid(), + "decort_grid_list": grid.DataSourceGridList(), + "decort_image_list": image.DataSourceImageList(), + "decort_image_list_stacks": image.DataSourceImageListStacks(), + "decort_pcidevice": pcidevice.DataSourcePcidevice(), + "decort_pcidevice_list": pcidevice.DataSourcePcideviceList(), + "decort_sep_list": sep.DataSourceSepList(), + "decort_sep": sep.DataSourceSep(), + "decort_sep_consumption": sep.DataSourceSepConsumption(), + "decort_sep_disk_list": sep.DataSourceSepDiskList(), + "decort_sep_config": sep.DataSourceSepConfig(), + "decort_sep_pool": sep.DataSourceSepPool(), // "decort_pfw": dataSourcePfw(), } diff --git a/internal/provider/cloudbroker/resources_map.go b/internal/provider/cloudbroker/resources_map.go index 49ead88..89b1281 100644 --- a/internal/provider/cloudbroker/resources_map.go +++ b/internal/provider/cloudbroker/resources_map.go @@ -21,13 +21,23 @@ package cloudbroker import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/account" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/disks" "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/image" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/k8s" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/kvmvm" "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/pcidevice" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/pfw" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/rg" "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/sep" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/snapshot" + "github.com/rudecs/terraform-provider-decort/internal/service/cloudbroker/vins" ) func NewRersourcesMap() map[string]*schema.Resource { return map[string]*schema.Resource{ + "decort_account": account.ResourceAccount(), + "decort_disk": disks.ResourceDisk(), "decort_image": image.ResourceImage(), "decort_virtual_image": image.ResourceVirtualImage(), "decort_cdrom_image": image.ResourceCDROMImage(), @@ -35,5 +45,12 @@ func NewRersourcesMap() map[string]*schema.Resource { "decort_pcidevice": pcidevice.ResourcePcidevice(), "decort_sep": sep.ResourceSep(), "decort_sep_config": sep.ResourceSepConfig(), + "decort_resgroup": rg.ResourceResgroup(), + "decort_kvmvm": kvmvm.ResourceCompute(), + "decort_vins": vins.ResourceVins(), + "decort_pfw": pfw.ResourcePfw(), + "decort_k8s": k8s.ResourceK8s(), + "decort_k8s_wg": k8s.ResourceK8sWg(), + "decort_snapshot": snapshot.ResourceSnapshot(), } } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index f004704..09c45e0 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -22,6 +22,7 @@ package provider import ( "fmt" + "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" "golang.org/x/net/context" @@ -105,24 +106,22 @@ func Provider() *schema.Provider { DataSourcesMap: selectSchema(true), - ConfigureFunc: providerConfigure, + ConfigureContextFunc: providerConfigure, } } -func providerConfigure(d *schema.ResourceData) (interface{}, error) { +func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { decsController, err := controller.ControllerConfigure(d) if err != nil { - return nil, err + return nil, diag.FromErr(err) } - ctx := context.Background() - gridId, err := location.UtilityLocationGetDefaultGridID(ctx, decsController) if err != nil { - return nil, err + return nil, diag.FromErr(err) } if gridId == 0 { - return nil, fmt.Errorf("providerConfigure: invalid default Grid ID = 0") + return nil, diag.FromErr(fmt.Errorf("providerConfigure: invalid default Grid ID = 0")) } return decsController, nil diff --git a/internal/service/cloudapi/account/data_source_account_list.go b/internal/service/cloudapi/account/data_source_account_list.go index d5370df..f7cbc85 100644 --- a/internal/service/cloudapi/account/data_source_account_list.go +++ b/internal/service/cloudapi/account/data_source_account_list.go @@ -73,51 +73,6 @@ func flattenRgAcl(rgAcls []AccountAclRecord) []map[string]interface{} { return res } -/*uncomment for cloudbroker -func flattenAccountList(al AccountList) []map[string]interface{} { - res := make([]map[string]interface{}, 0) - for _, acc := range al { - temp := map[string]interface{}{ - "dc_location": acc.DCLocation, - "ckey": acc.CKey, - "meta": flattenMeta(acc.Meta), - - "acl": flattenRgAcl(acc.Acl), - - "company": acc.Company, - "companyurl": acc.CompanyUrl, - "created_by": acc.CreatedBy, - - "created_time": acc.CreatedTime, - - "deactivation_time": acc.DeactiovationTime, - "deleted_by": acc.DeletedBy, - - "deleted_time": acc.DeletedTime, - - "displayname": acc.DisplayName, - "guid": acc.GUID, - - "account_id": acc.ID, - "account_name": acc.Name, - - "resource_limits": flattenRgResourceLimits(acc.ResourceLimits), - "send_access_emails": acc.SendAccessEmails, - "service_account": acc.ServiceAccount, - - "status": acc.Status, - "updated_time": acc.UpdatedTime, - - "version": acc.Version, - "vins": acc.Vins, - - } - res = append(res, temp) - } - return res -} -*/ - func dataSourceAccountListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { accountList, err := utilityAccountListCheckPresence(ctx, d, m) if err != nil { @@ -148,22 +103,6 @@ func dataSourceAccountListSchemaMake() map[string]*schema.Schema { Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - /*uncomment for cloudbroker - "dc_location": { - Type: schema.TypeString, - Computed: true, - }, - "ckey": { - Type: schema.TypeString, - Computed: true, - }, - "meta": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - },*/ "acl": { Type: schema.TypeList, Computed: true, @@ -196,48 +135,14 @@ func dataSourceAccountListSchemaMake() map[string]*schema.Schema { }, }, }, - /*uncomment for cloudbroker - "company": { - Type: schema.TypeString, - Computed: true, - }, - "companyurl": { - Type: schema.TypeString, - Computed: true, - }, - "created_by": { - Type: schema.TypeString, - Computed: true, - }, - */ "created_time": { Type: schema.TypeInt, Computed: true, }, - /*uncomment for cloudbroker - "deactivation_time": { - Type: schema.TypeFloat, - Computed: true, - }, - "deleted_by": { - Type: schema.TypeString, - Computed: true, - }, - */ "deleted_time": { Type: schema.TypeInt, Computed: true, }, - /*uncomment for cloudbroker - "displayname": { - Type: schema.TypeString, - Computed: true, - }, - "guid": { - Type: schema.TypeInt, - Computed: true, - }, - */ "account_id": { Type: schema.TypeInt, Computed: true, @@ -246,49 +151,6 @@ func dataSourceAccountListSchemaMake() map[string]*schema.Schema { Type: schema.TypeString, Computed: true, }, - /*uncomment for cloudbroker - "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, - }, - }, - }, - }, - "send_access_emails": { - Type: schema.TypeBool, - Computed: true, - }, - "service_account": { - Type: schema.TypeBool, - Computed: true, - }, - */ "status": { Type: schema.TypeString, Computed: true, @@ -297,19 +159,6 @@ func dataSourceAccountListSchemaMake() map[string]*schema.Schema { Type: schema.TypeInt, Computed: true, }, - /*uncomment for cloudbroker - "version": { - Type: schema.TypeInt, - Computed: true, - }, - "vins": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeInt, - }, - }, - */ }, }, }, diff --git a/internal/service/cloudapi/account/resource_account.go b/internal/service/cloudapi/account/resource_account.go index 3ad2939..4a87285 100644 --- a/internal/service/cloudapi/account/resource_account.go +++ b/internal/service/cloudapi/account/resource_account.go @@ -37,7 +37,6 @@ import ( "strconv" "strings" - "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" @@ -49,22 +48,6 @@ import ( func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { log.Debugf("resourceAccountCreate") - if accountId, ok := d.GetOk("account_id"); ok { - if exists, err := resourceAccountExists(ctx, d, m); exists { - if err != nil { - return diag.FromErr(err) - } - d.SetId(strconv.Itoa(accountId.(int))) - diagnostics := resourceAccountRead(ctx, d, m) - if diagnostics != nil { - return diagnostics - } - - return nil - } - return diag.Errorf("provided account id does not exist") - } - c := m.(*controller.ControllerCfg) urlValues := &url.Values{} @@ -138,7 +121,6 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, m interf return diag.FromErr(err) } - id := uuid.New() d.SetId(accountId) d.Set("account_id", accountId) @@ -147,7 +129,42 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, m interf return diagnostics } - d.SetId(id.String()) + urlValues = &url.Values{} + + if enable, ok := d.GetOk("enable"); ok { + api := accountDisableAPI + enable := enable.(bool) + if enable { + api = accountEnableAPI + } + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + + _, err := c.DecortAPICall(ctx, "POST", api, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + + if users, ok := d.GetOk("users"); ok { + addedUsers := users.([]interface{}) + + if len(addedUsers) > 0 { + for _, user := range addedUsers { + userConv := user.(map[string]interface{}) + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + urlValues.Add("userId", userConv["user_id"].(string)) + urlValues.Add("accesstype", strings.ToUpper(userConv["access_type"].(string))) + _, err := c.DecortAPICall(ctx, "POST", accountAddUserAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + } + } return nil } diff --git a/internal/service/cloudapi/bservice/resource_bservice.go b/internal/service/cloudapi/bservice/resource_bservice.go index 9ce62e4..ab5ff65 100644 --- a/internal/service/cloudapi/bservice/resource_bservice.go +++ b/internal/service/cloudapi/bservice/resource_bservice.go @@ -36,7 +36,6 @@ import ( "net/url" "strconv" - "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" @@ -47,24 +46,6 @@ import ( func resourceBasicServiceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { log.Debugf("resourceBasicServiceCreate") - if serviceId, ok := d.GetOk("service_id"); ok { - if exists, err := resourceBasicServiceExists(ctx, d, m); exists { - if err != nil { - return diag.FromErr(err) - } - id := uuid.New() - d.SetId(strconv.Itoa(serviceId.(int))) - d.Set("service_id", strconv.Itoa(serviceId.(int))) - diagnostics := resourceBasicServiceRead(ctx, d, m) - if diagnostics != nil { - return diagnostics - } - d.SetId(id.String()) - return nil - } - return diag.Errorf("provided service id does not exist") - } - c := m.(*controller.ControllerCfg) urlValues := &url.Values{} @@ -83,7 +64,6 @@ func resourceBasicServiceCreate(ctx context.Context, d *schema.ResourceData, m i return diag.FromErr(err) } - id := uuid.New() d.SetId(serviceId) d.Set("service_id", serviceId) @@ -92,8 +72,6 @@ func resourceBasicServiceCreate(ctx context.Context, d *schema.ResourceData, m i return diagnostics } - d.SetId(id.String()) - return nil } diff --git a/internal/service/cloudapi/bservice/resource_bservice_group.go b/internal/service/cloudapi/bservice/resource_bservice_group.go index 47271c8..145af41 100644 --- a/internal/service/cloudapi/bservice/resource_bservice_group.go +++ b/internal/service/cloudapi/bservice/resource_bservice_group.go @@ -37,7 +37,6 @@ import ( "strconv" "strings" - "github.com/google/uuid" "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" @@ -49,26 +48,6 @@ import ( func resourceBasicServiceGroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { log.Debugf("resourceBasicServiceGroupCreate") - if compgroupId, ok := d.GetOk("compgroup_id"); ok { - if _, ok := d.GetOk("service_id"); ok { - if exists, err := resourceBasicServiceGroupExists(ctx, d, m); exists { - if err != nil { - return diag.FromErr(err) - } - id := uuid.New() - d.SetId(strconv.Itoa(compgroupId.(int))) - d.Set("compgroup_id", strconv.Itoa(compgroupId.(int))) - diagnostics := resourceBasicServiceGroupRead(ctx, d, m) - if diagnostics != nil { - return diagnostics - } - d.SetId(id.String()) - return nil - } - return diag.Errorf("provided compgroup id does not exist") - } - } - c := m.(*controller.ControllerCfg) urlValues := &url.Values{} @@ -124,7 +103,6 @@ func resourceBasicServiceGroupCreate(ctx context.Context, d *schema.ResourceData return diag.FromErr(err) } - id := uuid.New() d.SetId(compgroupId) d.Set("compgroup_id", compgroupId) @@ -133,8 +111,6 @@ func resourceBasicServiceGroupCreate(ctx context.Context, d *schema.ResourceData return diagnostics } - d.SetId(id.String()) - return nil } diff --git a/internal/service/cloudapi/disks/api.go b/internal/service/cloudapi/disks/api.go index 678cd69..9cc2c71 100644 --- a/internal/service/cloudapi/disks/api.go +++ b/internal/service/cloudapi/disks/api.go @@ -31,9 +31,11 @@ Documentation: https://github.com/rudecs/terraform-provider-decort/wiki package disks -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 -const DisksResizeAPI = "/restmachine/cloudapi/disks/resize2" -const DisksRenameAPI = "/restmachine/cloudapi/disks/rename" -const DisksDeleteAPI = "/restmachine/cloudapi/disks/delete" +const disksCreateAPI = "/restmachine/cloudapi/disks/create" +const disksGetAPI = "/restmachine/cloudapi/disks/get" +const disksListAPI = "/restmachine/cloudapi/disks/list" +const disksResizeAPI = "/restmachine/cloudapi/disks/resize2" +const disksRenameAPI = "/restmachine/cloudapi/disks/rename" +const disksDeleteAPI = "/restmachine/cloudapi/disks/delete" +const disksIOLimitAPI = "/restmachine/cloudapi/disks/limitIO" +const disksRestoreAPI = "/restmachine/cloudapi/disks/restore" diff --git a/internal/service/cloudapi/disks/data_source_disk.go b/internal/service/cloudapi/disks/data_source_disk.go index 46b8cdd..f5e1982 100644 --- a/internal/service/cloudapi/disks/data_source_disk.go +++ b/internal/service/cloudapi/disks/data_source_disk.go @@ -34,162 +34,340 @@ package disks import ( "context" "encoding/json" - "fmt" // "net/url" + "github.com/google/uuid" "github.com/rudecs/terraform-provider-decort/internal/constants" - log "github.com/sirupsen/logrus" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func flattenDisk(d *schema.ResourceData, disk_facts string) error { - // This function expects disk_facts string to contain a response from disks/get API - // - // NOTE: this function modifies ResourceData argument - as such it should never be called - // from resourceDiskExists(...) method. Use utilityDiskCheckPresence instead. - - log.Debugf("flattenDisk: ready to unmarshal string %s", disk_facts) - - model := DiskRecord{} - - err := json.Unmarshal([]byte(disk_facts), &model) +func dataSourceDiskRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + disk, err := utilityDiskCheckPresence(ctx, d, m) if err != nil { - return err + return diag.FromErr(err) } - log.Debugf("flattenDisk: disk ID %d, disk AccountID %d", model.ID, model.AccountID) - - d.SetId(fmt.Sprintf("%d", model.ID)) - // d.Set("disk_id", model.ID) - we should NOT update disk_id in the schema. If it was set - it is already set, if it wasn't - we shouldn't - d.Set("name", model.Name) - d.Set("account_id", model.AccountID) - d.Set("account_name", model.AccountName) - d.Set("size", model.SizeMax) - // d.Set("sizeUsed", model.SizeUsed) - d.Set("type", model.Type) - d.Set("image_id", model.ImageID) - d.Set("sep_id", model.SepID) - d.Set("sep_type", model.SepType) - d.Set("pool", model.Pool) - // d.Set("compute_id", model.ComputeID) - - d.Set("description", model.Desc) + id := uuid.New() + d.SetId(id.String()) + + diskAcl, _ := json.Marshal(disk.Acl) + + d.Set("account_id", disk.AccountID) + d.Set("account_name", disk.AccountName) + d.Set("acl", string(diskAcl)) + d.Set("boot_partition", disk.BootPartition) + d.Set("compute_id", disk.ComputeID) + d.Set("compute_name", disk.ComputeName) + d.Set("created_time", disk.CreatedTime) + d.Set("deleted_time", disk.DeletedTime) + d.Set("desc", disk.Desc) + d.Set("destruction_time", disk.DestructionTime) + d.Set("devicename", disk.DeviceName) + d.Set("disk_path", disk.DiskPath) + d.Set("gid", disk.GridID) + d.Set("guid", disk.GUID) + d.Set("disk_id", disk.ID) + d.Set("image_id", disk.ImageID) + d.Set("images", disk.Images) + d.Set("iotune", flattenIOTune(disk.IOTune)) + d.Set("iqn", disk.IQN) + d.Set("login", disk.Login) + d.Set("milestones", disk.Milestones) + d.Set("disk_name", disk.Name) + d.Set("order", disk.Order) + d.Set("params", disk.Params) + d.Set("parent_id", disk.ParentId) + d.Set("passwd", disk.Passwd) + d.Set("pci_slot", disk.PciSlot) + d.Set("pool", disk.Pool) + d.Set("purge_attempts", disk.PurgeAttempts) + d.Set("purge_time", disk.PurgeTime) + d.Set("reality_device_number", disk.RealityDeviceNumber) + d.Set("reference_id", disk.ReferenceId) + d.Set("res_id", disk.ResID) + d.Set("res_name", disk.ResName) + d.Set("role", disk.Role) + d.Set("sep_id", disk.SepID) + d.Set("sep_type", disk.SepType) + d.Set("size_max", disk.SizeMax) + d.Set("size_used", disk.SizeUsed) + d.Set("snapshots", flattendDiskSnapshotList(disk.Snapshots)) + d.Set("status", disk.Status) + d.Set("tech_status", disk.TechStatus) + d.Set("type", disk.Type) + d.Set("vmid", disk.VMID) return nil } -func dataSourceDiskRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - disk_facts, err := utilityDiskCheckPresence(ctx, d, m) - if disk_facts == "" { - // if empty string is returned from utilityDiskCheckPresence then there is no - // such Disk and err tells so - just return it to the calling party - d.SetId("") // ensure ID is empty - return diag.FromErr(err) - } - - return diag.FromErr(flattenDisk(d, disk_facts)) -} - func dataSourceDiskSchemaMake() map[string]*schema.Schema { rets := map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Optional: true, - Description: "Name of this disk. NOTE: disk names are NOT unique within an account. If disk ID is specified, disk name is ignored.", - }, - "disk_id": { - Type: schema.TypeInt, - Optional: true, - Description: "ID of the disk to get. If disk ID is specified, then disk name and account ID are ignored.", + Type: schema.TypeInt, + Required: true, }, - "account_id": { - Type: schema.TypeInt, - Optional: true, - Description: "ID of the account this disk belongs to. If disk ID is specified, then account ID is ignored.", + Type: schema.TypeInt, + Computed: true, }, - - // The rest of the data source Disk schema are all computed - "sep_id": { - Type: schema.TypeInt, - Computed: true, - Description: "Storage end-point provider serving this disk.", + "account_name": { + Type: schema.TypeString, + Computed: true, }, - - "pool": { - Type: schema.TypeString, - Computed: true, - Description: "Pool where this disk is located.", + "acl": { + Type: schema.TypeString, + Computed: true, }, - - "size": { - Type: schema.TypeInt, - Computed: true, - Description: "Size of the disk in GB.", + "boot_partition": { + Type: schema.TypeInt, + Computed: true, }, - - "type": { - Type: schema.TypeString, - Computed: true, - Description: "Type of this disk. E.g. D for data disks, B for boot.", + "compute_id": { + Type: schema.TypeInt, + Computed: true, }, - - "description": { - Type: schema.TypeString, - Computed: true, - Description: "User-defined text description of this disk.", + "compute_name": { + Type: schema.TypeString, + Computed: true, }, - - "account_name": { - Type: schema.TypeString, - Computed: true, - Description: "Name of the account this disk belongs to. If account ID is specified, account name is ignored.", + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "desc": { + Type: schema.TypeString, + Computed: true, + }, + "destruction_time": { + Type: schema.TypeInt, + Computed: true, + }, + "devicename": { + Type: schema.TypeString, + Computed: true, + }, + "disk_path": { + Type: schema.TypeString, + Computed: true, + }, + "gid": { + Type: schema.TypeInt, + Computed: true, + }, + "guid": { + Type: schema.TypeInt, + Computed: true, }, - "image_id": { - Type: schema.TypeInt, - Computed: true, - Description: "ID of the image, which this disk was cloned from (valid for disk clones only).", + Type: schema.TypeInt, + Computed: true, }, - - "sep_type": { - Type: schema.TypeString, - Computed: true, - Description: "Type of the storage end-point provider serving this disk.", + "images": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, }, - - /* - "snapshots": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource { - Schema: snapshotSubresourceSchemaMake(), + "iotune": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "read_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "read_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "read_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "read_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "size_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "total_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "write_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "write_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "write_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "write_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, }, - Description: "List of user-created snapshots for this disk." }, - - "status": { - Type: schema.TypeString, - Computed: true, - Description: "Current model status of this disk.", - }, - - "tech_status": { - Type: schema.TypeString, - Computed: true, - Description: "Current technical status of this disk.", }, - - "compute_id": { - Type: schema.TypeInt, - Computed: true, - Description: "ID of the compute instance where this disk is attached to, or 0 for unattached disk.", + }, + "iqn": { + Type: schema.TypeString, + Computed: true, + }, + "login": { + Type: schema.TypeString, + Computed: true, + }, + "milestones": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_name": { + Type: schema.TypeString, + Computed: true, + }, + "order": { + Type: schema.TypeInt, + Computed: true, + }, + "params": { + Type: schema.TypeString, + Computed: true, + }, + "parent_id": { + Type: schema.TypeInt, + Computed: true, + }, + "passwd": { + Type: schema.TypeString, + Computed: true, + }, + "pci_slot": { + Type: schema.TypeInt, + Computed: true, + }, + "pool": { + Type: schema.TypeString, + Computed: true, + }, + "purge_attempts": { + Type: schema.TypeInt, + Computed: true, + }, + "purge_time": { + Type: schema.TypeInt, + Computed: true, + }, + "reality_device_number": { + Type: schema.TypeInt, + Computed: true, + }, + "reference_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_name": { + Type: schema.TypeString, + Computed: true, + }, + "role": { + Type: schema.TypeString, + Computed: true, + }, + "sep_id": { + Type: schema.TypeInt, + Computed: true, + }, + "sep_type": { + Type: schema.TypeString, + Computed: true, + }, + "size_max": { + Type: schema.TypeInt, + Computed: true, + }, + "size_used": { + Type: schema.TypeInt, + Computed: true, + }, + "snapshots": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "guid": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_guid": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_time": { + Type: schema.TypeInt, + Computed: true, + }, + "timestamp": { + Type: schema.TypeInt, + Computed: true, + }, + }, }, - */ + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tech_status": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "vmid": { + Type: schema.TypeInt, + Computed: true, + }, } return rets diff --git a/internal/service/cloudapi/disks/data_source_disk_list.go b/internal/service/cloudapi/disks/data_source_disk_list.go index dc73729..efe16b6 100644 --- a/internal/service/cloudapi/disks/data_source_disk_list.go +++ b/internal/service/cloudapi/disks/data_source_disk_list.go @@ -41,11 +41,32 @@ import ( "github.com/rudecs/terraform-provider-decort/internal/constants" ) -func flattenDiskList(dl DisksListResp) []map[string]interface{} { +func flattenIOTune(iot IOTune) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + temp := map[string]interface{}{ + "read_bytes_sec": iot.ReadBytesSec, + "read_bytes_sec_max": iot.ReadBytesSecMax, + "read_iops_sec": iot.ReadIopsSec, + "read_iops_sec_max": iot.ReadIopsSecMax, + "size_iops_sec": iot.SizeIopsSec, + "total_bytes_sec": iot.TotalBytesSec, + "total_bytes_sec_max": iot.TotalBytesSecMax, + "total_iops_sec": iot.TotalIopsSec, + "total_iops_sec_max": iot.TotalIopsSecMax, + "write_bytes_sec": iot.WriteBytesSec, + "write_bytes_sec_max": iot.WriteBytesSecMax, + "write_iops_sec": iot.WriteIopsSec, + "write_iops_sec_max": iot.WriteIopsSecMax, + } + + res = append(res, temp) + return res +} + +func flattenDiskList(dl DisksList) []map[string]interface{} { res := make([]map[string]interface{}, 0) for _, disk := range dl { diskAcl, _ := json.Marshal(disk.Acl) - diskIotune, _ := json.Marshal(disk.IOTune) temp := map[string]interface{}{ "account_id": disk.AccountID, "account_name": disk.AccountName, @@ -64,13 +85,13 @@ func flattenDiskList(dl DisksListResp) []map[string]interface{} { "disk_id": disk.ID, "image_id": disk.ImageID, "images": disk.Images, - "iotune": string(diskIotune), + "iotune": flattenIOTune(disk.IOTune), "iqn": disk.IQN, "login": disk.Login, "machine_id": disk.MachineId, "machine_name": disk.MachineName, "milestones": disk.Milestones, - "name": disk.Name, + "disk_name": disk.Name, "order": disk.Order, "params": disk.Params, "parent_id": disk.ParentId, @@ -93,7 +114,6 @@ func flattenDiskList(dl DisksListResp) []map[string]interface{} { "tech_status": disk.TechStatus, "type": disk.Type, "vmid": disk.VMID, - "update_by": disk.UpdateBy, } res = append(res, temp) } @@ -101,7 +121,7 @@ func flattenDiskList(dl DisksListResp) []map[string]interface{} { } -func flattendDiskSnapshotList(sl SnapshotRecordList) []interface{} { +func flattendDiskSnapshotList(sl SnapshotList) []interface{} { res := make([]interface{}, 0) for _, snapshot := range sl { temp := map[string]interface{}{ @@ -231,8 +251,64 @@ func dataSourceDiskListSchemaMake() map[string]*schema.Schema { }, }, "iotune": { - Type: schema.TypeString, + Type: schema.TypeList, Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "read_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "read_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "read_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "read_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "size_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "total_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "write_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "write_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "write_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "write_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, }, "iqn": { Type: schema.TypeString, @@ -254,7 +330,7 @@ func dataSourceDiskListSchemaMake() map[string]*schema.Schema { Type: schema.TypeInt, Computed: true, }, - "name": { + "disk_name": { Type: schema.TypeString, Computed: true, }, @@ -374,10 +450,6 @@ func dataSourceDiskListSchemaMake() map[string]*schema.Schema { Type: schema.TypeInt, Computed: true, }, - "update_by": { - Type: schema.TypeInt, - Computed: true, - }, }, }, }, diff --git a/internal/service/cloudapi/disks/models.go b/internal/service/cloudapi/disks/models.go index 8b997ea..b5298e0 100644 --- a/internal/service/cloudapi/disks/models.go +++ b/internal/service/cloudapi/disks/models.go @@ -31,7 +31,7 @@ Documentation: https://github.com/rudecs/terraform-provider-decort/wiki package disks -type DiskRecord struct { +type Disk struct { Acl map[string]interface{} `json:"acl"` AccountID int `json:"accountId"` AccountName string `json:"accountName"` @@ -49,7 +49,7 @@ type DiskRecord struct { ID uint `json:"id"` ImageID int `json:"imageId"` Images []int `json:"images"` - IOTune map[string]interface{} `json:"iotune"` + IOTune IOTune `json:"iotune"` IQN string `json:"iqn"` Login string `json:"login"` Name string `json:"name"` @@ -73,7 +73,7 @@ type DiskRecord struct { SepID int `json:"sepId"` // NOTE: absent from compute/get output SizeMax int `json:"sizeMax"` SizeUsed int `json:"sizeUsed"` // sum over all snapshots of this disk to report total consumed space - Snapshots []SnapshotRecord `json:"snapshots"` + Snapshots []Snapshot `json:"snapshots"` Status string `json:"status"` TechStatus string `json:"techStatus"` Type string `json:"type"` @@ -81,7 +81,7 @@ type DiskRecord struct { VMID int `json:"vmid"` } -type SnapshotRecord struct { +type Snapshot struct { Guid string `json:"guid"` Label string `json:"label"` ResId string `json:"resId"` @@ -90,6 +90,22 @@ type SnapshotRecord struct { TimeStamp uint64 `json:"timestamp"` } -type SnapshotRecordList []SnapshotRecord +type SnapshotList []Snapshot -type DisksListResp []DiskRecord +type DisksList []Disk + +type IOTune struct { + ReadBytesSec int `json:"read_bytes_sec"` + ReadBytesSecMax int `json:"read_bytes_sec_max"` + ReadIopsSec int `json:"read_iops_sec"` + ReadIopsSecMax int `json:"read_iops_sec_max"` + SizeIopsSec int `json:"size_iops_sec"` + TotalBytesSec int `json:"total_bytes_sec"` + TotalBytesSecMax int `json:"total_bytes_sec_max"` + TotalIopsSec int `json:"total_iops_sec"` + TotalIopsSecMax int `json:"total_iops_sec_max"` + WriteBytesSec int `json:"write_bytes_sec"` + WriteBytesSecMax int `json:"write_bytes_sec_max"` + WriteIopsSec int `json:"write_iops_sec"` + WriteIopsSecMax int `json:"write_iops_sec_max"` +} diff --git a/internal/service/cloudapi/disks/resource_disk.go b/internal/service/cloudapi/disks/resource_disk.go index 1fe2b4a..9cb264b 100644 --- a/internal/service/cloudapi/disks/resource_disk.go +++ b/internal/service/cloudapi/disks/resource_disk.go @@ -32,15 +32,15 @@ Documentation: https://github.com/rudecs/terraform-provider-decort/wiki package disks import ( - // "encoding/json" "context" + "encoding/json" "fmt" "net/url" "strconv" + "strings" "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" @@ -49,16 +49,18 @@ import ( ) func resourceDiskCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - log.Debugf("resourceDiskCreate: called for Disk name %q, Account ID %d", d.Get("name").(string), d.Get("account_id").(int)) - c := m.(*controller.ControllerCfg) urlValues := &url.Values{} - // accountId, gid, name, description, size, type, sep_id, pool + urlValues.Add("accountId", fmt.Sprintf("%d", d.Get("account_id").(int))) - urlValues.Add("gid", fmt.Sprintf("%d", location.DefaultGridID)) // we use default Grid ID, which was obtained along with DECORT Controller init - urlValues.Add("name", d.Get("name").(string)) - urlValues.Add("size", fmt.Sprintf("%d", d.Get("size").(int))) - urlValues.Add("type", "D") // NOTE: only disks of Data type are managed via plugin + urlValues.Add("gid", fmt.Sprintf("%d", d.Get("gid").(int))) + urlValues.Add("name", d.Get("disk_name").(string)) + urlValues.Add("size", fmt.Sprintf("%d", d.Get("size_max").(int))) + if typeRaw, ok := d.GetOk("type"); ok { + urlValues.Add("type", strings.ToUpper(typeRaw.(string))) + } else { + urlValues.Add("type", "D") + } if sepId, ok := d.GetOk("sep_id"); ok { urlValues.Add("sep_id", strconv.Itoa(sepId.(int))) @@ -68,126 +70,210 @@ func resourceDiskCreate(ctx context.Context, d *schema.ResourceData, m interface urlValues.Add("pool", poolName.(string)) } - argVal, argSet := d.GetOk("description") + argVal, argSet := d.GetOk("desc") if argSet { urlValues.Add("description", argVal.(string)) } - apiResp, err := c.DecortAPICall(ctx, "POST", DisksCreateAPI, urlValues) + diskId, err := c.DecortAPICall(ctx, "POST", disksCreateAPI, urlValues) if err != nil { return diag.FromErr(err) } - d.SetId(apiResp) // update ID of the resource to tell Terraform that the disk resource exists - diskId, _ := strconv.Atoi(apiResp) + urlValues = &url.Values{} + + d.SetId(diskId) // update ID of the resource to tell Terraform that the disk resource exists + + if iotuneRaw, ok := d.GetOk("iotune"); ok { + iot := iotuneRaw.([]interface{})[0] + iotune := iot.(map[string]interface{}) + urlValues.Add("diskId", diskId) + urlValues.Add("iops", strconv.Itoa(iotune["total_iops_sec"].(int))) + urlValues.Add("read_bytes_sec", strconv.Itoa(iotune["read_bytes_sec"].(int))) + urlValues.Add("read_bytes_sec_max", strconv.Itoa(iotune["read_bytes_sec_max"].(int))) + urlValues.Add("read_iops_sec", strconv.Itoa(iotune["read_iops_sec"].(int))) + urlValues.Add("read_iops_sec_max", strconv.Itoa(iotune["read_iops_sec_max"].(int))) + urlValues.Add("size_iops_sec", strconv.Itoa(iotune["size_iops_sec"].(int))) + urlValues.Add("total_bytes_sec", strconv.Itoa(iotune["total_bytes_sec"].(int))) + urlValues.Add("total_bytes_sec_max", strconv.Itoa(iotune["total_bytes_sec_max"].(int))) + urlValues.Add("total_iops_sec_max", strconv.Itoa(iotune["total_iops_sec_max"].(int))) + urlValues.Add("write_bytes_sec", strconv.Itoa(iotune["write_bytes_sec"].(int))) + urlValues.Add("write_bytes_sec_max", strconv.Itoa(iotune["write_bytes_sec_max"].(int))) + urlValues.Add("write_iops_sec", strconv.Itoa(iotune["write_iops_sec"].(int))) + urlValues.Add("write_iops_sec_max", strconv.Itoa(iotune["write_iops_sec_max"].(int))) + + _, err := c.DecortAPICall(ctx, "POST", disksIOLimitAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } - log.Debugf("resourceDiskCreate: new Disk ID / name %d / %s creation sequence complete", diskId, d.Get("name").(string)) + dgn := resourceDiskRead(ctx, d, m) + if dgn != nil { + return dgn + } - // We may reuse dataSourceDiskRead here as we maintain similarity - // between Disk resource and Disk data source schemas - // Disk resource read function will also update resource ID on success, so that Terraform - // will know the resource exists (however, we already did it a few lines before) - return dataSourceDiskRead(ctx, d, m) + return nil } func resourceDiskRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - diskFacts, err := utilityDiskCheckPresence(ctx, d, m) - if diskFacts == "" { - // if empty string is returned from utilityDiskCheckPresence then there is no - // such Disk and err tells so - just return it to the calling party - d.SetId("") // ensure ID is empty - return diag.FromErr(err) + disk, err := utilityDiskCheckPresence(ctx, d, m) + if disk == nil { + d.SetId("") + if err != nil { + return diag.FromErr(err) + } + return nil } - return diag.FromErr(flattenDisk(d, diskFacts)) + diskAcl, _ := json.Marshal(disk.Acl) + + d.Set("account_id", disk.AccountID) + d.Set("account_name", disk.AccountName) + d.Set("acl", string(diskAcl)) + d.Set("boot_partition", disk.BootPartition) + d.Set("compute_id", disk.ComputeID) + d.Set("compute_name", disk.ComputeName) + d.Set("created_time", disk.CreatedTime) + d.Set("deleted_time", disk.DeletedTime) + d.Set("desc", disk.Desc) + d.Set("destruction_time", disk.DestructionTime) + d.Set("devicename", disk.DeviceName) + d.Set("disk_path", disk.DiskPath) + d.Set("gid", disk.GridID) + d.Set("guid", disk.GUID) + d.Set("disk_id", disk.ID) + d.Set("image_id", disk.ImageID) + d.Set("images", disk.Images) + d.Set("iotune", flattenIOTune(disk.IOTune)) + d.Set("iqn", disk.IQN) + d.Set("login", disk.Login) + d.Set("milestones", disk.Milestones) + d.Set("disk_name", disk.Name) + d.Set("order", disk.Order) + d.Set("params", disk.Params) + d.Set("parent_id", disk.ParentId) + d.Set("passwd", disk.Passwd) + d.Set("pci_slot", disk.PciSlot) + d.Set("pool", disk.Pool) + d.Set("purge_attempts", disk.PurgeAttempts) + d.Set("purge_time", disk.PurgeTime) + d.Set("reality_device_number", disk.RealityDeviceNumber) + d.Set("reference_id", disk.ReferenceId) + d.Set("res_id", disk.ResID) + d.Set("res_name", disk.ResName) + d.Set("role", disk.Role) + d.Set("sep_id", disk.SepID) + d.Set("sep_type", disk.SepType) + d.Set("size_max", disk.SizeMax) + d.Set("size_used", disk.SizeUsed) + d.Set("snapshots", flattendDiskSnapshotList(disk.Snapshots)) + d.Set("status", disk.Status) + d.Set("tech_status", disk.TechStatus) + d.Set("type", disk.Type) + d.Set("vmid", disk.VMID) + + return nil } func resourceDiskUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - // Update will only change the following attributes of the disk: - // - Size; to keep data safe, shrinking disk is not allowed. - // - Name - // - // Attempt to change disk type will throw an error and mark disk - // resource as partially updated - log.Debugf("resourceDiskUpdate: called for Disk ID / name %s / %s, Account ID %d", - d.Id(), d.Get("name").(string), d.Get("account_id").(int)) c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + if d.HasChange("size_max") { + oldSize, newSize := d.GetChange("size_max") + if oldSize.(int) < newSize.(int) { + log.Debugf("resourceDiskUpdate: resizing disk ID %s - %d GB -> %d GB", + d.Id(), oldSize.(int), newSize.(int)) + urlValues.Add("diskId", d.Id()) + urlValues.Add("size", fmt.Sprintf("%d", newSize.(int))) + _, err := c.DecortAPICall(ctx, "POST", disksResizeAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + d.Set("size_max", newSize) + } else if oldSize.(int) > newSize.(int) { + return diag.FromErr(fmt.Errorf("resourceDiskUpdate: Disk ID %s - reducing disk size is not allowed", d.Id())) + } + urlValues = &url.Values{} + } - oldSize, newSize := d.GetChange("size") - if oldSize.(int) < newSize.(int) { - log.Debugf("resourceDiskUpdate: resizing disk ID %s - %d GB -> %d GB", - d.Id(), oldSize.(int), newSize.(int)) - sizeParams := &url.Values{} - sizeParams.Add("diskId", d.Id()) - sizeParams.Add("size", fmt.Sprintf("%d", newSize.(int))) - _, err := c.DecortAPICall(ctx, "POST", DisksResizeAPI, sizeParams) + if d.HasChange("disk_name") { + urlValues.Add("diskId", d.Id()) + urlValues.Add("name", d.Get("disk_name").(string)) + _, err := c.DecortAPICall(ctx, "POST", disksRenameAPI, urlValues) if err != nil { return diag.FromErr(err) } - d.Set("size", newSize) - } else if oldSize.(int) > newSize.(int) { - return diag.FromErr(fmt.Errorf("resourceDiskUpdate: Disk ID %s - reducing disk size is not allowed", d.Id())) + + urlValues = &url.Values{} } - oldName, newName := d.GetChange("name") - if oldName.(string) != newName.(string) { - log.Debugf("resourceDiskUpdate: renaming disk ID %d - %s -> %s", - d.Get("disk_id").(int), oldName.(string), newName.(string)) - renameParams := &url.Values{} - renameParams.Add("diskId", d.Id()) - renameParams.Add("name", newName.(string)) - _, err := c.DecortAPICall(ctx, "POST", DisksRenameAPI, renameParams) + if d.HasChange("iotune") { + iot := d.Get("iotune").([]interface{})[0] + iotune := iot.(map[string]interface{}) + urlValues.Add("diskId", d.Id()) + urlValues.Add("iops", strconv.Itoa(iotune["total_iops_sec"].(int))) + urlValues.Add("read_bytes_sec", strconv.Itoa(iotune["read_bytes_sec"].(int))) + urlValues.Add("read_bytes_sec_max", strconv.Itoa(iotune["read_bytes_sec_max"].(int))) + urlValues.Add("read_iops_sec", strconv.Itoa(iotune["read_iops_sec"].(int))) + urlValues.Add("read_iops_sec_max", strconv.Itoa(iotune["read_iops_sec_max"].(int))) + urlValues.Add("size_iops_sec", strconv.Itoa(iotune["size_iops_sec"].(int))) + urlValues.Add("total_bytes_sec", strconv.Itoa(iotune["total_bytes_sec"].(int))) + urlValues.Add("total_bytes_sec_max", strconv.Itoa(iotune["total_bytes_sec_max"].(int))) + urlValues.Add("total_iops_sec_max", strconv.Itoa(iotune["total_iops_sec_max"].(int))) + urlValues.Add("write_bytes_sec", strconv.Itoa(iotune["write_bytes_sec"].(int))) + urlValues.Add("write_bytes_sec_max", strconv.Itoa(iotune["write_bytes_sec_max"].(int))) + urlValues.Add("write_iops_sec", strconv.Itoa(iotune["write_iops_sec"].(int))) + urlValues.Add("write_iops_sec_max", strconv.Itoa(iotune["write_iops_sec_max"].(int))) + + _, err := c.DecortAPICall(ctx, "POST", disksIOLimitAPI, urlValues) if err != nil { return diag.FromErr(err) } + + urlValues = &url.Values{} } - /* - NOTE: plugin will manage disks of type "Data" only, and type cannot be changed once disk is created + if d.HasChange("restore") { + if d.Get("restore").(bool) { + urlValues.Add("diskId", d.Id()) + urlValues.Add("reason", d.Get("reason").(string)) - oldType, newType := d.GetChange("type") - if oldType.(string) != newType.(string) { - return fmt.Errorf("resourceDiskUpdate: Disk ID %s - changing type of existing disk not allowed", d.Id()) + _, err := c.DecortAPICall(ctx, "POST", disksRestoreAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} } - */ - // we may reuse dataSourceDiskRead here as we maintain similarity - // between Compute resource and Compute data source schemas - return dataSourceDiskRead(ctx, d, m) + } + + return resourceDiskRead(ctx, d, m) } func resourceDiskDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - // NOTE: this function tries to detach and destroy target Disk "permanently", so - // there is no way to restore it. - // If, however, the disk is attached to a compute, the method will - // fail (by failing the underpinning DECORt API call, which is issued with detach=false) - log.Debugf("resourceDiskDelete: called for Disk ID / name %d / %s, Account ID %d", - d.Get("disk_id").(int), d.Get("name").(string), d.Get("account_id").(int)) - diskFacts, err := utilityDiskCheckPresence(ctx, d, m) - if diskFacts == "" { + disk, err := utilityDiskCheckPresence(ctx, d, m) + if disk == nil { if err != nil { return diag.FromErr(err) } - // the specified Disk does not exist - in this case according to Terraform best practice - // we exit from Destroy method without error return nil } params := &url.Values{} params.Add("diskId", d.Id()) - // NOTE: we are not force-detaching disk from a compute (if attached) thus protecting - // data that may be on that disk from destruction. - // However, this may change in the future, as TF state management logic may want - // to delete disk resource BEFORE it is detached from compute instance, and, while - // perfectly OK from data preservation viewpoint, this is breaking expected TF workflow - // in the eyes of an experienced TF user - params.Add("detach", "0") - params.Add("permanently", "1") + params.Add("detach", strconv.FormatBool(d.Get("detach").(bool))) + params.Add("permanently", strconv.FormatBool(d.Get("permanently").(bool))) + params.Add("reason", d.Get("reason").(string)) c := m.(*controller.ControllerCfg) - _, err = c.DecortAPICall(ctx, "POST", DisksDeleteAPI, params) + _, err = c.DecortAPICall(ctx, "POST", disksDeleteAPI, params) if err != nil { return diag.FromErr(err) } @@ -196,12 +282,9 @@ func resourceDiskDelete(ctx context.Context, d *schema.ResourceData, m interface } func resourceDiskExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { - // Reminder: according to Terraform rules, this function should not modify its ResourceData argument - log.Debugf("resourceDiskExists: called for Disk ID / name %d / %s, Account ID %d", - d.Get("disk_id").(int), d.Get("name").(string), d.Get("account_id").(int)) diskFacts, err := utilityDiskCheckPresence(ctx, d, m) - if diskFacts == "" { + if diskFacts == nil { if err != nil { return false, err } @@ -212,101 +295,318 @@ func resourceDiskExists(ctx context.Context, d *schema.ResourceData, m interface func resourceDiskSchemaMake() map[string]*schema.Schema { rets := map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "Name of this disk. NOTE: disk names are NOT unique within an account. If disk ID is specified, disk name is ignored.", + "account_id": { + Type: schema.TypeInt, + Required: true, }, - - "disk_id": { - Type: schema.TypeInt, - Optional: true, - Description: "ID of the disk to get. If disk ID is specified, then disk name and account ID are ignored.", + "disk_name": { + Type: schema.TypeString, + Required: true, }, - - "account_id": { - Type: schema.TypeInt, - Required: true, - Description: "ID of the account this disk belongs to.", + "size_max": { + Type: schema.TypeInt, + Required: true, + }, + "gid": { + Type: schema.TypeInt, + Required: true, + }, + "pool": { + Type: schema.TypeString, + Optional: true, + Computed: true, }, - "sep_id": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, - Description: "Storage end-point provider serving this disk. Cannot be changed for existing disk.", + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "desc": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{"D", "B", "T"}, false), }, - "pool": { - Type: schema.TypeString, + "detach": { + Type: schema.TypeBool, Optional: true, - Computed: true, - ForceNew: true, - Description: "Pool where this disk is located. Cannot be changed for existing disk.", + Default: false, + Description: "detach disk from machine first", }, - - "size": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntAtLeast(1), - Description: "Size of the disk in GB. Note, that existing disks can only be grown in size.", + "permanently": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "whether to completely delete the disk, works only with non attached disks", }, - - /* We moved "type" attribute to computed attributes section, as plugin manages disks of only - one type - "D", e.g. data disks. - "type": { + "reason": { Type: schema.TypeString, Optional: true, - Default: "D", - StateFunc: stateFuncToUpper, - ValidateFunc: validation.StringInSlice([]string{"B", "D"}, false), - Description: "Optional type of this disk. Defaults to D, i.e. data disk. Cannot be changed for existing disks.", + Default: "", + Description: "reason for an action", }, - */ - - "description": { - Type: schema.TypeString, + "restore": { + Type: schema.TypeBool, Optional: true, - Default: "Disk resource managed by Terraform", - Description: "Optional user-defined text description of this disk.", + Default: false, + Description: "restore deleting disk", }, - // The rest of the attributes are all computed + "disk_id": { + Type: schema.TypeInt, + Computed: true, + }, "account_name": { - Type: schema.TypeString, - Computed: true, - Description: "Name of the account this disk belongs to.", + Type: schema.TypeString, + Computed: true, + }, + "acl": { + Type: schema.TypeString, + Computed: true, + }, + "boot_partition": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_id": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_name": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "destruction_time": { + Type: schema.TypeInt, + Computed: true, + }, + "devicename": { + Type: schema.TypeString, + Computed: true, + }, + "disk_path": { + Type: schema.TypeString, + Computed: true, + }, + "guid": { + Type: schema.TypeInt, + Computed: true, }, - "image_id": { - Type: schema.TypeInt, - Computed: true, - Description: "ID of the image, which this disk was cloned from (if ever cloned).", + Type: schema.TypeInt, + Computed: true, + }, + "images": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "iotune": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "read_bytes_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "read_bytes_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "read_iops_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "read_iops_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "size_iops_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "total_bytes_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "total_bytes_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "total_iops_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "total_iops_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "write_bytes_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "write_bytes_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "write_iops_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "write_iops_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "iqn": { + Type: schema.TypeString, + Computed: true, + }, + "login": { + Type: schema.TypeString, + Computed: true, + }, + "milestones": { + Type: schema.TypeInt, + Computed: true, }, - "type": { - Type: schema.TypeString, - Computed: true, - Description: "Type of this disk.", + "order": { + Type: schema.TypeInt, + Computed: true, + }, + "params": { + Type: schema.TypeString, + Computed: true, + }, + "parent_id": { + Type: schema.TypeInt, + Computed: true, + }, + "passwd": { + Type: schema.TypeString, + Computed: true, + }, + "pci_slot": { + Type: schema.TypeInt, + Computed: true, }, - "sep_type": { - Type: schema.TypeString, - Computed: true, - Description: "Type of the storage end-point provider serving this disk.", + "purge_attempts": { + Type: schema.TypeInt, + Computed: true, + }, + "purge_time": { + Type: schema.TypeInt, + Computed: true, + }, + "reality_device_number": { + Type: schema.TypeInt, + Computed: true, + }, + "reference_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_name": { + Type: schema.TypeString, + Computed: true, + }, + "role": { + Type: schema.TypeString, + Computed: true, }, - /* - "snapshots": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource { - Schema: snapshotSubresourceSchemaMake(), + "sep_type": { + Type: schema.TypeString, + Computed: true, + }, + "size_used": { + Type: schema.TypeInt, + Computed: true, + }, + "snapshots": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "guid": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_guid": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_time": { + Type: schema.TypeInt, + Computed: true, + }, + "timestamp": { + Type: schema.TypeInt, + Computed: true, + }, }, - Description: "List of user-created snapshots for this disk." }, - */ + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tech_status": { + Type: schema.TypeString, + Computed: true, + }, + "vmid": { + Type: schema.TypeInt, + Computed: true, + }, } return rets diff --git a/internal/service/cloudapi/disks/utility_disk.go b/internal/service/cloudapi/disks/utility_disk.go index 73053a8..1fb4544 100644 --- a/internal/service/cloudapi/disks/utility_disk.go +++ b/internal/service/cloudapi/disks/utility_disk.go @@ -34,7 +34,6 @@ package disks import ( "context" "encoding/json" - "fmt" "net/url" "strconv" @@ -44,103 +43,28 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func utilityDiskCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (string, error) { - // This function tries to locate Disk by one of the following algorithms depending on - // the parameters passed: - // - if disk ID is specified -> by disk ID - // - if disk name is specifeid -> by disk name and either account ID or account name - // - // NOTE: disk names are not unique, so the first occurence of this name in the account will - // be returned. There is no such ambiguity when locating disk by its ID. - // - // If succeeded, it returns non empty string that contains JSON formatted facts about the disk - // as returned by disks/get API call. - // Otherwise it returns empty string and meaningful error. - // - // This function does not modify its ResourceData argument, so it is safe to use it as core - // method for resource's Exists method. - // - +func utilityDiskCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*Disk, error) { c := m.(*controller.ControllerCfg) urlValues := &url.Values{} - // make it possible to use "read" & "check presence" functions with disk ID set so - // that Import of preexisting Disk resource is possible - idSet := false - theId, err := strconv.Atoi(d.Id()) - if err != nil || theId <= 0 { - diskId, argSet := d.GetOk("disk_id") - if argSet { - theId = diskId.(int) - idSet = true - } - } else { - idSet = true - } - - if idSet { - // disk ID is specified, try to get disk instance straight by this ID - log.Debugf("utilityDiskCheckPresence: locating disk by its ID %d", theId) - urlValues.Add("diskId", fmt.Sprintf("%d", theId)) - diskFacts, err := c.DecortAPICall(ctx, "POST", DisksGetAPI, urlValues) - if err != nil { - return "", err - } - return diskFacts, nil - } + disk := &Disk{} - // ID or disk_di was not set in the schema upon entering this function - rely on Disk name - // and Account ID to find the disk - diskName, argSet := d.GetOk("name") - if !argSet { - // no disk ID and no disk name - we cannot locate disk in this case - return "", fmt.Errorf("Cannot locate disk if name is empty and no disk ID specified") + if d.Get("disk_id").(int) == 0 { + urlValues.Add("diskId", d.Id()) + } else { + urlValues.Add("diskId", strconv.Itoa(d.Get("disk_id").(int))) } - // Valid account ID is required to locate disks - // obtain Account ID by account name - it should not be zero on success - - urlValues.Add("accountId", fmt.Sprintf("%d", d.Get("account_id").(int))) - diskFacts, err := c.DecortAPICall(ctx, "POST", DisksListAPI, urlValues) + log.Debugf("utilityDiskCheckPresence: load disk") + diskRaw, err := c.DecortAPICall(ctx, "POST", disksGetAPI, urlValues) if err != nil { - return "", err + return nil, err } - log.Debugf("utilityDiskCheckPresence: ready to unmarshal string %s", diskFacts) - - disksList := DisksListResp{} - err = json.Unmarshal([]byte(diskFacts), &disksList) + err = json.Unmarshal([]byte(diskRaw), disk) if err != nil { - return "", err - } - - // log.Printf("%#v", vm_list) - log.Debugf("utilityDiskCheckPresence: traversing decoded JSON of length %d", len(disksList)) - for index, item := range disksList { - // need to match disk by name, return the first match - if item.Name == diskName.(string) && item.Status != "DESTROYED" { - log.Debugf("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 - /* - // TODO: this may not be optimal as it initiates one extra call to the DECORT controller - // in spite of the fact that we already have all required information about the disk in - // item variable - // - get_urlValues := &url.Values{} - get_urlValues.Add("diskId", fmt.Sprintf("%d", item.ID)) - diskFacts, err = controller.decortAPICall("POST", DisksGetAPI, get_urlValues) - if err != nil { - return "", err - } - return diskFacts, nil - */ - reencodedItem, err := json.Marshal(item) - if err != nil { - return "", err - } - return string(reencodedItem[:]), nil - } + return nil, err } - return "", nil // there should be no error if disk does not exist + return disk, nil } diff --git a/internal/service/cloudapi/disks/utility_disk_list.go b/internal/service/cloudapi/disks/utility_disk_list.go index 50d3cf4..1782ac3 100644 --- a/internal/service/cloudapi/disks/utility_disk_list.go +++ b/internal/service/cloudapi/disks/utility_disk_list.go @@ -44,8 +44,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func utilityDiskListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (DisksListResp, error) { - diskList := DisksListResp{} +func utilityDiskListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (DisksList, error) { + diskList := DisksList{} c := m.(*controller.ControllerCfg) urlValues := &url.Values{} @@ -62,8 +62,8 @@ func utilityDiskListCheckPresence(ctx context.Context, d *schema.ResourceData, m urlValues.Add("accountId", strconv.Itoa(accountId.(int))) } - log.Debugf("utilityDiskListCheckPresence: load grid list") - diskListRaw, err := c.DecortAPICall(ctx, "POST", DisksListAPI, urlValues) + log.Debugf("utilityDiskListCheckPresence: load disk list") + diskListRaw, err := c.DecortAPICall(ctx, "POST", disksListAPI, urlValues) if err != nil { return nil, err } diff --git a/internal/service/cloudapi/image/api.go b/internal/service/cloudapi/image/api.go new file mode 100644 index 0000000..49eb1d4 --- /dev/null +++ b/internal/service/cloudapi/image/api.go @@ -0,0 +1,40 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +const imageCreateAPI = "/restmachine/cloudapi/image/createImage" +const imageCreateVirtualAPI = "/restmachine/cloudapi/image/createVirtual" +const imageGetAPI = "/restmachine/cloudapi/image/get" +const imageListGetAPI = "/restmachine/cloudapi/image/list" +const imageDeleteAPI = "/restmachine/cloudapi/image/delete" +const imageEditNameAPI = "/restmachine/cloudapi/image/rename" +const imageLinkAPI = "/restmachine/cloudapi/image/link" diff --git a/internal/service/cloudapi/image/data_source_image.go b/internal/service/cloudapi/image/data_source_image.go new file mode 100644 index 0000000..d5b6ef4 --- /dev/null +++ b/internal/service/cloudapi/image/data_source_image.go @@ -0,0 +1,123 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +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 flattenHistory(history []History) []map[string]interface{} { + temp := make([]map[string]interface{}, 0) + for _, item := range history { + t := map[string]interface{}{ + "id": item.Id, + "guid": item.Guid, + "timestamp": item.Timestamp, + } + + temp = append(temp, t) + } + return temp +} + +func flattenImage(d *schema.ResourceData, img *ImageExtend) { + d.Set("unc_path", img.UNCPath) + d.Set("ckey", img.CKey) + d.Set("account_id", img.AccountId) + d.Set("acl", img.Acl) + d.Set("architecture", img.Architecture) + d.Set("boot_type", img.BootType) + d.Set("bootable", img.Bootable) + d.Set("compute_ci_id", img.ComputeCiId) + d.Set("deleted_time", img.DeletedTime) + d.Set("desc", img.Description) + d.Set("drivers", img.Drivers) + d.Set("enabled", img.Enabled) + d.Set("gid", img.GridId) + d.Set("guid", img.GUID) + d.Set("history", flattenHistory(img.History)) + d.Set("hot_resize", img.HotResize) + d.Set("image_id", img.Id) + d.Set("last_modified", img.LastModified) + d.Set("link_to", img.LinkTo) + d.Set("milestones", img.Milestones) + d.Set("image_name", img.Name) + d.Set("password", img.Password) + d.Set("pool_name", img.Pool) + d.Set("provider_name", img.ProviderName) + d.Set("purge_attempts", img.PurgeAttempts) + d.Set("res_id", img.ResId) + d.Set("rescuecd", img.RescueCD) + d.Set("sep_id", img.SepId) + d.Set("shared_with", img.SharedWith) + d.Set("size", img.Size) + d.Set("status", img.Status) + d.Set("tech_status", img.TechStatus) + d.Set("type", img.Type) + d.Set("username", img.Username) + d.Set("version", img.Version) +} + +func dataSourceImageRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + image, err := utilityImageCheckPresence(ctx, d, m) + if err != nil { + + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + + flattenImage(d, image) + + return nil +} + +func DataSourceImage() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceImageRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceImageExtendSchemaMake(), + } +} diff --git a/internal/service/cloudapi/image/data_source_image_list.go b/internal/service/cloudapi/image/data_source_image_list.go new file mode 100644 index 0000000..08e5f18 --- /dev/null +++ b/internal/service/cloudapi/image/data_source_image_list.go @@ -0,0 +1,126 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +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 flattenImageList(il ImageList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, img := range il { + temp := map[string]interface{}{ + "account_id": img.AccountId, + "architecture": img.Architecture, + "boot_type": img.BootType, + "bootable": img.Bootable, + "cdrom": img.CDROM, + "desc": img.Description, + "drivers": img.Drivers, + "hot_resize": img.HotResize, + "image_id": img.Id, + "link_to": img.LinkTo, + "image_name": img.Name, + "pool_name": img.Pool, + "sep_id": img.SepId, + "size": img.Size, + "status": img.Status, + "type": img.Type, + "username": img.Username, + "virtual": img.Virtual, + } + res = append(res, temp) + } + return res +} + +func dataSourceImageListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + imageList, err := utilityImageListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenImageList(imageList)) + + return nil +} + +func dataSourceImageListSchemaMake() map[string]*schema.Schema { + rets := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Optional: true, + Description: "optional account ID to include account images", + }, + "page": { + Type: schema.TypeInt, + Optional: true, + Description: "page number", + }, + "size": { + Type: schema.TypeInt, + Optional: true, + Description: "page size", + }, + "items": { + Type: schema.TypeList, + Computed: true, + Description: "image list", + Elem: &schema.Resource{ + Schema: dataSourceImageSchemaMake(), + }, + }, + } + + return rets +} + +func DataSourceImageList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceImageListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceImageListSchemaMake(), + } +} diff --git a/internal/service/cloudapi/image/image_ds_subresource.go b/internal/service/cloudapi/image/image_ds_subresource.go new file mode 100644 index 0000000..5b0f97a --- /dev/null +++ b/internal/service/cloudapi/image/image_ds_subresource.go @@ -0,0 +1,208 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceImageExtendSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "image_id": { + Type: schema.TypeInt, + Required: true, + }, + "show_all": { + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + + "unc_path": { + Type: schema.TypeString, + Computed: true, + }, + "ckey": { + Type: schema.TypeString, + Computed: true, + }, + "account_id": { + Type: schema.TypeInt, + Computed: true, + }, + "acl": { + Type: schema.TypeString, + Computed: true, + }, + "architecture": { + Type: schema.TypeString, + Computed: true, + }, + "boot_type": { + Type: schema.TypeString, + Computed: true, + }, + "bootable": { + Type: schema.TypeBool, + Computed: true, + }, + "compute_ci_id": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeString, + Computed: true, + }, + "desc": { + Type: schema.TypeString, + Computed: true, + }, + "drivers": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "gid": { + Type: schema.TypeInt, + Computed: true, + }, + "guid": { + Type: schema.TypeInt, + Computed: true, + }, + "history": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "guid": { + Type: schema.TypeString, + Computed: true, + }, + "id": { + Type: schema.TypeInt, + Computed: true, + }, + "timestamp": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "hot_resize": { + Type: schema.TypeBool, + Computed: true, + }, + + "last_modified": { + Type: schema.TypeInt, + Computed: true, + }, + "link_to": { + Type: schema.TypeInt, + Computed: true, + }, + "milestones": { + Type: schema.TypeInt, + Computed: true, + }, + "image_name": { + Type: schema.TypeString, + Computed: true, + }, + "password": { + Type: schema.TypeString, + Computed: true, + }, + "pool_name": { + Type: schema.TypeString, + Computed: true, + }, + "provider_name": { + Type: schema.TypeString, + Computed: true, + }, + "purge_attempts": { + Type: schema.TypeInt, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "rescuecd": { + Type: schema.TypeBool, + Computed: true, + }, + "sep_id": { + Type: schema.TypeInt, + Computed: true, + }, + "shared_with": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "size": { + Type: schema.TypeInt, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tech_status": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "username": { + Type: schema.TypeString, + Computed: true, + }, + "version": { + Type: schema.TypeString, + Computed: true, + }, + } +} diff --git a/internal/service/cloudapi/image/image_item_subresource.go b/internal/service/cloudapi/image/image_item_subresource.go new file mode 100644 index 0000000..06aa342 --- /dev/null +++ b/internal/service/cloudapi/image/image_item_subresource.go @@ -0,0 +1,132 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceImageSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Computed: true, + Description: "Owner account id", + }, + "architecture": { + Type: schema.TypeString, + Computed: true, + Description: "Image architecture", + }, + "boot_type": { + Type: schema.TypeString, + Computed: true, + Description: "Boot image type", + }, + "bootable": { + Type: schema.TypeBool, + Computed: true, + Description: "Flag, true if image is bootable, otherwise - false", + }, + "cdrom": { + Type: schema.TypeBool, + Computed: true, + Description: "Flag, true if image is cdrom image, otherwise - false", + }, + "desc": { + Type: schema.TypeString, + Computed: true, + Description: "Image description", + }, + "drivers": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Image drivers", + }, + "hot_resize": { + Type: schema.TypeBool, + Computed: true, + Description: "Flag, true if image supports hot resize, else if not", + }, + "image_id": { + Type: schema.TypeInt, + Computed: true, + Description: "Image id", + }, + "link_to": { + Type: schema.TypeInt, + Computed: true, + Description: "For virtual images, id image, which current image linked", + }, + "image_name": { + Type: schema.TypeString, + Computed: true, + Description: "Image name", + }, + "pool_name": { + Type: schema.TypeString, + Computed: true, + Description: "Image pool", + }, + "sep_id": { + Type: schema.TypeInt, + Computed: true, + Description: "Image storage endpoint id", + }, + "size": { + Type: schema.TypeInt, + Computed: true, + Description: "Image size", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Image status", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "Image type", + }, + "username": { + Type: schema.TypeString, + Computed: true, + Description: "username", + }, + "virtual": { + Type: schema.TypeBool, + Computed: true, + Description: "True if image is virtula, otherwise - else", + }, + } +} diff --git a/internal/service/cloudapi/image/image_rs_subresource.go b/internal/service/cloudapi/image/image_rs_subresource.go new file mode 100644 index 0000000..bbd87a7 --- /dev/null +++ b/internal/service/cloudapi/image/image_rs_subresource.go @@ -0,0 +1,158 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceImageSchemaMake(sch map[string]*schema.Schema) map[string]*schema.Schema { + delete(sch, "show_all") + sch["name"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Name of the rescue disk", + } + + sch["url"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "URL where to download media from", + } + + sch["gid"] = &schema.Schema{ + Type: schema.TypeInt, + Required: true, + Description: "grid (platform) ID where this template should be create in", + } + + sch["image_id"] = &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "image id", + } + + sch["boot_type"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"bios", "uefi"}, true), + Description: "Boot type of image bios or uefi", + } + + sch["type"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"linux", "windows", "other"}, true), + Description: "Image type linux, windows or other", + } + + sch["hot_resize"] = &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Does this machine supports hot resize", + } + + sch["username"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Optional username for the image", + } + + sch["password"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Optional password for the image", + } + + sch["account_id"] = &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "AccountId to make the image exclusive", + } + + sch["username_dl"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "username for upload binary media", + } + + sch["password_dl"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "password for upload binary media", + } + + sch["pool_name"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "pool for image create", + } + + sch["sep_id"] = &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "storage endpoint provider ID", + } + + sch["architecture"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{"X86_64", "PPC64_LE"}, true), + Description: "binary architecture of this image, one of X86_64 of PPC64_LE", + } + + sch["drivers"] = &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + } + + sch["permanently"] = &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "whether to completely delete the image", + } + + return sch +} diff --git a/internal/service/cloudapi/image/image_virtual_rs_subresource.go b/internal/service/cloudapi/image/image_virtual_rs_subresource.go new file mode 100644 index 0000000..06c62f4 --- /dev/null +++ b/internal/service/cloudapi/image/image_virtual_rs_subresource.go @@ -0,0 +1,66 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceImageVirtualSchemaMake(sch map[string]*schema.Schema) map[string]*schema.Schema { + delete(sch, "show_all") + sch["name"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Name of the rescue disk", + } + + sch["link_to"] = &schema.Schema{ + Type: schema.TypeInt, + Required: true, + Description: "ID of real image to link this virtual image to upon creation", + } + + sch["permanently"] = &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "whether to completely delete the image", + } + + sch["image_id"] = &schema.Schema{ + Type: schema.TypeInt, + Computed: true, + Description: "Image id", + } + + return sch +} diff --git a/internal/service/cloudapi/image/models.go b/internal/service/cloudapi/image/models.go new file mode 100644 index 0000000..a499849 --- /dev/null +++ b/internal/service/cloudapi/image/models.go @@ -0,0 +1,148 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +/* +type History struct { + Guid string `json:"guid"` + Id int `json:"id"` + Timestamp int64 `json:"timestamp"` +} + +type Image struct { + ImageId int `json:"id"` + Name string `json:"name"` + Url string `json:"url"` + Gid int `json:"gid"` + Guid int `json:"guid"` + Boottype string `json:"bootType"` + Imagetype string `json:"type"` + Drivers []string `json:"drivers"` + Hotresize bool `json:"hotResize"` + Bootable bool `json:"bootable"` + Username string `json:"username"` + Password string `json:"password"` + AccountId int `json:"accountId"` + UsernameDL string `json:"usernameDL"` + PasswordDL string `json:"passwordDL"` + SepId int `json:"sepId"` + PoolName string `json:"pool"` + Architecture string `json:"architecture"` + UNCPath string `json:"UNCPath"` + LinkTo int `json:"linkTo"` + Status string `json:"status"` + TechStatus string `json:"techStatus"` + Size int `json:"size"` + Version string `json:"version"` + Enabled bool `json:"enabled"` + ComputeciId int `json:"computeciId"` + Milestones int `json:"milestones"` + ProviderName string `json:"provider_name"` + PurgeAttempts int `json:"purgeAttempts"` + ReferenceId string `json:"referenceId"` + ResId string `json:"resId"` + ResName string `json:"resName"` + Rescuecd bool `json:"rescuecd"` + Meta []interface{} `json:"_meta"` + History []History `json:"history"` + LastModified int64 `json:"lastModified"` + Desc string `json:"desc"` + SharedWith []int `json:"sharedWith"` +} +*/ + +type Image struct { + AccountId int `json:"accountId"` + Architecture string `json:"architecture"` + BootType string `json:"bootType"` + Bootable bool `json:"bootable"` + CDROM bool `json:"cdrom"` + Description string `json:"desc"` + Drivers []string `json:"drivers"` + HotResize bool `json:"hotResize"` + Id int `json:"id"` + LinkTo int `json:"linkTo"` + 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"` + Virtual bool `json:"virtual"` +} + +type ImageList []Image + +type History struct { + Guid string `json:"guid"` + Id int `json:"id"` + Timestamp int64 `json:"timestamp"` +} + +type ImageExtend struct { + UNCPath string `json:"UNCPath"` + CKey string `json:"_ckey"` + AccountId int `json:"accountId"` + Acl interface{} `json:"acl"` + Architecture string `json:"architecture"` + BootType string `json:"bootType"` + Bootable bool `json:"bootable"` + ComputeCiId int `json:"computeciId"` + DeletedTime int `json:"deletedTime"` + Description string `json:"desc"` + Drivers []string `json:"drivers"` + Enabled bool `json:"enabled"` + GridId int `json:"gid"` + GUID int `json:"guid"` + History []History `json:"history"` + HotResize bool `json:"hotResize"` + Id int `json:"id"` + LastModified int `json:"lastModified"` + LinkTo int `json:"linkTo"` + Milestones int `json:"milestones"` + Name string `json:"name"` + Password string `json:"password"` + Pool string `json:"pool"` + ProviderName string `json:"provider_name"` + PurgeAttempts int `json:"purgeAttempts"` + ResId string `json:"resId"` + RescueCD bool `json:"rescuecd"` + SepId int `json:"sepId"` + SharedWith []int `json:"sharedWith"` + Size int `json:"size"` + Status string `json:"status"` + TechStatus string `json:"techStatus"` + Type string `json:"type"` + Username string `json:"username"` + Version string `json:"version"` +} diff --git a/internal/service/cloudapi/image/resource_image.go b/internal/service/cloudapi/image/resource_image.go new file mode 100644 index 0000000..f963141 --- /dev/null +++ b/internal/service/cloudapi/image/resource_image.go @@ -0,0 +1,259 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +import ( + "context" + "net/url" + "strconv" + + "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" + "github.com/rudecs/terraform-provider-decort/internal/controller" + log "github.com/sirupsen/logrus" +) + +func resourceImageCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceImageCreate: called for image %s", d.Get("name").(string)) + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("name", d.Get("name").(string)) + urlValues.Add("url", d.Get("url").(string)) + urlValues.Add("gid", strconv.Itoa(d.Get("gid").(int))) + urlValues.Add("boottype", d.Get("boot_type").(string)) + urlValues.Add("imagetype", d.Get("image_type").(string)) + + tstr := d.Get("drivers").([]interface{}) + temp := "" + l := len(tstr) + for i, str := range tstr { + s := "\"" + str.(string) + "\"" + if i != (l - 1) { + s += "," + } + temp = temp + s + } + temp = "[" + temp + "]" + urlValues.Add("drivers", temp) + + if hotresize, ok := d.GetOk("hot_resize"); ok { + urlValues.Add("hotresize", strconv.FormatBool(hotresize.(bool))) + } + if username, ok := d.GetOk("username"); ok { + urlValues.Add("username", username.(string)) + } + if password, ok := d.GetOk("password"); ok { + urlValues.Add("password", password.(string)) + } + if accountId, ok := d.GetOk("account_id"); ok { + urlValues.Add("accountId", strconv.Itoa(accountId.(int))) + } + if usernameDL, ok := d.GetOk("username_dl"); ok { + urlValues.Add("usernameDL", usernameDL.(string)) + } + if passwordDL, ok := d.GetOk("password_dl"); ok { + urlValues.Add("passwordDL", passwordDL.(string)) + } + if sepId, ok := d.GetOk("sep_id"); ok { + urlValues.Add("sepId", strconv.Itoa(sepId.(int))) + } + if poolName, ok := d.GetOk("pool_name"); ok { + urlValues.Add("poolName", poolName.(string)) + } + if architecture, ok := d.GetOk("architecture"); ok { + urlValues.Add("architecture", architecture.(string)) + } + + imageId, err := c.DecortAPICall(ctx, "POST", imageCreateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(imageId) + d.Set("image_id", imageId) + + _, err = utilityImageCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + diagnostics := resourceImageRead(ctx, d, m) + if diagnostics != nil { + return diagnostics + } + + return nil +} + +func resourceImageRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceImageRead: called for %s id: %s", d.Get("name").(string), d.Id()) + + img, err := utilityImageCheckPresence(ctx, d, m) + if img == nil { + d.SetId("") + return diag.FromErr(err) + } + + d.Set("unc_path", img.UNCPath) + d.Set("ckey", img.CKey) + d.Set("account_id", img.AccountId) + d.Set("acl", img.Acl) + d.Set("architecture", img.Architecture) + d.Set("boot_type", img.BootType) + d.Set("bootable", img.Bootable) + d.Set("compute_ci_id", img.ComputeCiId) + d.Set("deleted_time", img.DeletedTime) + d.Set("desc", img.Description) + d.Set("drivers", img.Drivers) + d.Set("enabled", img.Enabled) + d.Set("gid", img.GridId) + d.Set("guid", img.GUID) + d.Set("history", flattenHistory(img.History)) + d.Set("hot_resize", img.HotResize) + d.Set("image_id", img.Id) + d.Set("last_modified", img.LastModified) + d.Set("link_to", img.LinkTo) + d.Set("milestones", img.Milestones) + d.Set("image_name", img.Name) + d.Set("password", img.Password) + d.Set("pool_name", img.Pool) + d.Set("provider_name", img.ProviderName) + d.Set("purge_attempts", img.PurgeAttempts) + d.Set("res_id", img.ResId) + d.Set("rescuecd", img.RescueCD) + d.Set("sep_id", img.SepId) + d.Set("shared_with", img.SharedWith) + d.Set("size", img.Size) + d.Set("status", img.Status) + d.Set("tech_status", img.TechStatus) + d.Set("type", img.Type) + d.Set("username", img.Username) + d.Set("version", img.Version) + + return nil +} + +func resourceImageDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceImageDelete: called for %s, id: %s", d.Get("name").(string), d.Id()) + + image, err := utilityImageCheckPresence(ctx, d, m) + if image == nil { + if err != nil { + return diag.FromErr(err) + } + return nil + } + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("imageId", strconv.Itoa(d.Get("image_id").(int))) + + if permanently, ok := d.GetOk("permanently"); ok { + urlValues.Add("permanently", strconv.FormatBool(permanently.(bool))) + } + + _, err = c.DecortAPICall(ctx, "POST", imageDeleteAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + d.SetId("") + + return nil +} + +func resourceImageExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + log.Debugf("resourceImageExists: called for %s, id: %s", d.Get("name").(string), d.Id()) + + image, err := utilityImageCheckPresence(ctx, d, m) + if image == nil { + if err != nil { + return false, err + } + return false, nil + } + + return true, nil +} + +func resourceImageEditName(ctx context.Context, d *schema.ResourceData, m interface{}) error { + log.Debugf("resourceImageEditName: called for %s, id: %s", d.Get("name").(string), d.Id()) + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("imageId", strconv.Itoa(d.Get("image_id").(int))) + urlValues.Add("name", d.Get("name").(string)) + _, err := c.DecortAPICall(ctx, "POST", imageEditNameAPI, urlValues) + if err != nil { + return err + } + + return nil +} + +func resourceImageEdit(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceImageEdit: called for %s, id: %s", d.Get("name").(string), d.Id()) + + if d.HasChange("name") { + err := resourceImageEditName(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + } + + return resourceImageRead(ctx, d, m) +} + +func ResourceImage() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourceImageCreate, + ReadContext: resourceImageRead, + UpdateContext: resourceImageEdit, + DeleteContext: resourceImageDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: &constants.Timeout60s, + Read: &constants.Timeout30s, + Update: &constants.Timeout60s, + Delete: &constants.Timeout60s, + Default: &constants.Timeout60s, + }, + + Schema: resourceImageSchemaMake(dataSourceImageExtendSchemaMake()), + } +} diff --git a/internal/service/cloudapi/image/resource_image_virtual.go b/internal/service/cloudapi/image/resource_image_virtual.go new file mode 100644 index 0000000..4252a7f --- /dev/null +++ b/internal/service/cloudapi/image/resource_image_virtual.go @@ -0,0 +1,132 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +import ( + "context" + "net/url" + "strconv" + + "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" + "github.com/rudecs/terraform-provider-decort/internal/controller" + log "github.com/sirupsen/logrus" +) + +func resourceImageVirtualCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceImageVirtualCreate: called for image %s", d.Get("name").(string)) + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("name", d.Get("name").(string)) + urlValues.Add("targetId", strconv.Itoa(d.Get("target_id").(int))) + + imageId, err := c.DecortAPICall(ctx, "POST", imageCreateVirtualAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(imageId) + d.Set("image_id", imageId) + + _, err = utilityImageCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + diagnostics := resourceImageRead(ctx, d, m) + if diagnostics != nil { + return diagnostics + } + + return nil +} + +func resourceImageVirtualEdit(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceImageEdit: called for %s, id: %s", d.Get("name").(string), d.Id()) + + if d.HasChange("name") { + err := resourceImageEditName(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + } + + if d.HasChange("link_to") { + err := resourceImageVirtualLink(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + } + + return resourceImageRead(ctx, d, m) +} + +func resourceImageVirtualLink(ctx context.Context, d *schema.ResourceData, m interface{}) error { + log.Debugf("resourceVirtualImageLink: called for %s, id: %s", d.Get("name").(string), d.Id()) + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("imageId", strconv.Itoa(d.Get("image_id").(int))) + urlValues.Add("targetId", strconv.Itoa(d.Get("link_to").(int))) + _, err := c.DecortAPICall(ctx, "POST", imageLinkAPI, urlValues) + if err != nil { + return err + } + + return nil +} + +func ResourceImageVirtual() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourceImageVirtualCreate, + ReadContext: resourceImageRead, + UpdateContext: resourceImageVirtualEdit, + DeleteContext: resourceImageDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: &constants.Timeout60s, + Read: &constants.Timeout30s, + Update: &constants.Timeout60s, + Delete: &constants.Timeout60s, + Default: &constants.Timeout60s, + }, + + Schema: resourceImageVirtualSchemaMake(dataSourceImageExtendSchemaMake()), + } +} diff --git a/internal/service/cloudapi/image/utility_image.go b/internal/service/cloudapi/image/utility_image.go new file mode 100644 index 0000000..dd9e58d --- /dev/null +++ b/internal/service/cloudapi/image/utility_image.go @@ -0,0 +1,75 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/rudecs/terraform-provider-decort/internal/controller" +) + +func utilityImageCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*ImageExtend, error) { + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + if (strconv.Itoa(d.Get("image_id").(int))) != "0" { + urlValues.Add("imageId", strconv.Itoa(d.Get("image_id").(int))) + } else { + urlValues.Add("imageId", d.Id()) + } + + if showAll, ok := d.GetOk("show_all"); ok { + urlValues.Add("page", strconv.FormatBool(showAll.(bool))) + } + + resp, err := c.DecortAPICall(ctx, "POST", imageGetAPI, urlValues) + if err != nil { + return nil, err + } + + if resp == "" { + return nil, nil + } + + image := &ImageExtend{} + if err := json.Unmarshal([]byte(resp), image); err != nil { + return nil, errors.New(fmt.Sprint("Can not unmarshall data to image: ", resp, " ", image)) + } + + return image, nil +} diff --git a/internal/service/cloudapi/image/utility_image_list.go b/internal/service/cloudapi/image/utility_image_list.go new file mode 100644 index 0000000..724018e --- /dev/null +++ b/internal/service/cloudapi/image/utility_image_list.go @@ -0,0 +1,74 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 image + +import ( + "context" + "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 utilityImageListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (ImageList, error) { + imageList := ImageList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + if accountId, ok := d.GetOk("account_id"); ok { + urlValues.Add("accountId", strconv.Itoa(accountId.(int))) + } + + if page, ok := d.GetOk("page"); ok { + urlValues.Add("page", strconv.Itoa(page.(int))) + } + if size, ok := d.GetOk("size"); ok { + urlValues.Add("size", strconv.Itoa(size.(int))) + } + + log.Debugf("utilityImageListCheckPresence: load image list") + imageListRaw, err := c.DecortAPICall(ctx, "POST", imageListGetAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(imageListRaw), &imageList) + if err != nil { + return nil, err + } + + return imageList, nil +} diff --git a/internal/service/cloudbroker/account/account_audit_ds_subresource.go b/internal/service/cloudbroker/account/account_audit_ds_subresource.go new file mode 100644 index 0000000..9ceabee --- /dev/null +++ b/internal/service/cloudbroker/account/account_audit_ds_subresource.go @@ -0,0 +1,59 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceAccountAuditSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "call": { + Type: schema.TypeString, + Computed: true, + }, + "responsetime": { + Type: schema.TypeFloat, + Computed: true, + }, + "statuscode": { + Type: schema.TypeInt, + Computed: true, + }, + "timestamp": { + Type: schema.TypeFloat, + Computed: true, + }, + "user": { + Type: schema.TypeString, + Computed: true, + }, + } +} diff --git a/internal/service/cloudbroker/account/account_compute_ds_subresource.go b/internal/service/cloudbroker/account/account_compute_ds_subresource.go new file mode 100644 index 0000000..f6305ae --- /dev/null +++ b/internal/service/cloudbroker/account/account_compute_ds_subresource.go @@ -0,0 +1,119 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceAccountComputeSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Computed: true, + }, + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + "cpus": { + Type: schema.TypeInt, + Computed: true, + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_by": { + Type: schema.TypeString, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_id": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_name": { + Type: schema.TypeString, + Computed: true, + }, + "ram": { + Type: schema.TypeInt, + Computed: true, + }, + "registered": { + Type: schema.TypeBool, + Computed: true, + }, + "rg_id": { + Type: schema.TypeInt, + Computed: true, + }, + "rg_name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tech_status": { + Type: schema.TypeString, + Computed: true, + }, + "total_disks_size": { + Type: schema.TypeInt, + Computed: true, + }, + "updated_by": { + Type: schema.TypeString, + Computed: true, + }, + "updated_time": { + Type: schema.TypeInt, + Computed: true, + }, + "user_managed": { + Type: schema.TypeBool, + Computed: true, + }, + "vins_connected": { + Type: schema.TypeInt, + Computed: true, + }, + } +} diff --git a/internal/service/cloudbroker/account/account_disk_ds_subresource.go b/internal/service/cloudbroker/account/account_disk_ds_subresource.go new file mode 100644 index 0000000..049185c --- /dev/null +++ b/internal/service/cloudbroker/account/account_disk_ds_subresource.go @@ -0,0 +1,63 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceAccountDiskSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "disk_id": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_name": { + Type: schema.TypeString, + Computed: true, + }, + "pool_name": { + Type: schema.TypeString, + Computed: true, + }, + "sep_id": { + Type: schema.TypeInt, + Computed: true, + }, + "size_max": { + Type: schema.TypeInt, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + } +} diff --git a/internal/service/cloudbroker/account/account_ds.go b/internal/service/cloudbroker/account/account_ds.go new file mode 100644 index 0000000..990db78 --- /dev/null +++ b/internal/service/cloudbroker/account/account_ds.go @@ -0,0 +1,262 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceAccountSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Required: true, + }, + "dc_location": { + Type: schema.TypeString, + Computed: true, + }, + "resources": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cpu": { + Type: schema.TypeInt, + Computed: true, + }, + "disksize": { + Type: schema.TypeInt, + Computed: true, + }, + "extips": { + Type: schema.TypeInt, + Computed: true, + }, + "exttraffic": { + Type: schema.TypeInt, + Computed: true, + }, + "gpu": { + Type: schema.TypeInt, + Computed: true, + }, + "ram": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "reserved": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cpu": { + Type: schema.TypeInt, + Computed: true, + }, + "disksize": { + Type: schema.TypeInt, + Computed: true, + }, + "extips": { + Type: schema.TypeInt, + Computed: true, + }, + "exttraffic": { + Type: schema.TypeInt, + Computed: true, + }, + "gpu": { + Type: schema.TypeInt, + Computed: true, + }, + "ram": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "ckey": { + Type: schema.TypeString, + Computed: true, + }, + "meta": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "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, + }, + }, + }, + }, + "company": { + Type: schema.TypeString, + Computed: true, + }, + "companyurl": { + Type: schema.TypeString, + Computed: true, + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deactivation_time": { + Type: schema.TypeFloat, + Computed: true, + }, + "deleted_by": { + Type: schema.TypeString, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "displayname": { + Type: schema.TypeString, + Computed: true, + }, + "guid": { + Type: schema.TypeInt, + Computed: true, + }, + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + "resource_limits": { + Type: schema.TypeList, + Computed: true, + 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, + }, + }, + }, + }, + "send_access_emails": { + Type: schema.TypeBool, + Computed: true, + }, + "service_account": { + Type: schema.TypeBool, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "updated_time": { + Type: schema.TypeInt, + Computed: true, + }, + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "vins": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + } + return res +} diff --git a/internal/service/cloudbroker/account/account_fg_ds_subresource.go b/internal/service/cloudbroker/account/account_fg_ds_subresource.go new file mode 100644 index 0000000..28db8ee --- /dev/null +++ b/internal/service/cloudbroker/account/account_fg_ds_subresource.go @@ -0,0 +1,123 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceAccountFlipGroupSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Computed: true, + }, + "client_type": { + Type: schema.TypeString, + Computed: true, + }, + "conn_type": { + Type: schema.TypeString, + Computed: true, + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "default_gw": { + 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, + }, + "fg_id": { + Type: schema.TypeInt, + Computed: true, + }, + "ip": { + Type: schema.TypeString, + Computed: true, + }, + "milestones": { + Type: schema.TypeInt, + Computed: true, + }, + "fg_name": { + Type: schema.TypeString, + Computed: true, + }, + "net_id": { + Type: schema.TypeInt, + Computed: true, + }, + "net_type": { + Type: schema.TypeString, + Computed: true, + }, + "netmask": { + Type: schema.TypeInt, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "updated_by": { + Type: schema.TypeString, + Computed: true, + }, + "updated_time": { + Type: schema.TypeInt, + Computed: true, + }, + } +} diff --git a/internal/service/cloudbroker/account/account_item_ds_subresource.go b/internal/service/cloudbroker/account/account_item_ds_subresource.go new file mode 100644 index 0000000..9d7e809 --- /dev/null +++ b/internal/service/cloudbroker/account/account_item_ds_subresource.go @@ -0,0 +1,189 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceAccountItemSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "dc_location": { + Type: schema.TypeString, + Computed: true, + }, + "ckey": { + Type: schema.TypeString, + Computed: true, + }, + "meta": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "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, + }, + }, + }, + }, + "company": { + Type: schema.TypeString, + Computed: true, + }, + "companyurl": { + Type: schema.TypeString, + Computed: true, + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deactivation_time": { + Type: schema.TypeFloat, + Computed: true, + }, + "deleted_by": { + Type: schema.TypeString, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "displayname": { + Type: schema.TypeString, + Computed: true, + }, + "guid": { + Type: schema.TypeInt, + Computed: true, + }, + "account_id": { + Type: schema.TypeInt, + Computed: true, + }, + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + "resource_limits": { + Type: schema.TypeList, + Computed: true, + 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, + }, + }, + }, + }, + "send_access_emails": { + Type: schema.TypeBool, + Computed: true, + }, + "service_account": { + Type: schema.TypeBool, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "updated_time": { + Type: schema.TypeInt, + Computed: true, + }, + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "vins": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + } +} diff --git a/internal/service/cloudbroker/account/account_rg_ds_subresource.go b/internal/service/cloudbroker/account/account_rg_ds_subresource.go new file mode 100644 index 0000000..389a663 --- /dev/null +++ b/internal/service/cloudbroker/account/account_rg_ds_subresource.go @@ -0,0 +1,205 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceAccountRGSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "computes": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "started": { + Type: schema.TypeInt, + Computed: true, + }, + "stopped": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "resources": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "consumed": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cpu": { + Type: schema.TypeInt, + Computed: true, + }, + "disksize": { + Type: schema.TypeInt, + Computed: true, + }, + "extips": { + Type: schema.TypeInt, + Computed: true, + }, + "exttraffic": { + Type: schema.TypeInt, + Computed: true, + }, + "gpu": { + Type: schema.TypeInt, + Computed: true, + }, + "ram": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + + "limits": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cpu": { + Type: schema.TypeInt, + Computed: true, + }, + "disksize": { + Type: schema.TypeInt, + Computed: true, + }, + "extips": { + Type: schema.TypeInt, + Computed: true, + }, + "exttraffic": { + Type: schema.TypeInt, + Computed: true, + }, + "gpu": { + Type: schema.TypeInt, + Computed: true, + }, + "ram": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "reserved": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cpu": { + Type: schema.TypeInt, + Computed: true, + }, + "disksize": { + Type: schema.TypeInt, + Computed: true, + }, + "extips": { + Type: schema.TypeInt, + Computed: true, + }, + "exttraffic": { + Type: schema.TypeInt, + Computed: true, + }, + "gpu": { + Type: schema.TypeInt, + Computed: true, + }, + "ram": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_by": { + Type: schema.TypeString, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "rg_id": { + Type: schema.TypeInt, + Computed: true, + }, + "milestones": { + Type: schema.TypeInt, + Computed: true, + }, + "rg_name": { + 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, + }, + "vinses": { + Type: schema.TypeInt, + Computed: true, + }, + } +} diff --git a/internal/service/cloudbroker/account/account_rs.go b/internal/service/cloudbroker/account/account_rs.go new file mode 100644 index 0000000..1ee4115 --- /dev/null +++ b/internal/service/cloudbroker/account/account_rs.go @@ -0,0 +1,320 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func resourceAccountSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "account_name": { + Type: schema.TypeString, + Required: true, + Description: "account name", + }, + "username": { + Type: schema.TypeString, + Required: true, + Description: "username of owner the account", + }, + "emailaddress": { + Type: schema.TypeString, + Optional: true, + Description: "email", + }, + "send_access_emails": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "if true send emails when a user is granted access to resources", + }, + "users": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "user_id": { + Type: schema.TypeString, + Required: true, + }, + "access_type": { + Type: schema.TypeString, + Required: true, + }, + "recursive_delete": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + "restore": { + Type: schema.TypeBool, + Optional: true, + Description: "restore a deleted account", + }, + "permanently": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "whether to completely delete the account", + }, + "enable": { + Type: schema.TypeBool, + Optional: true, + Description: "enable/disable account", + }, + "resource_limits": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cu_c": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "cu_d": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "cu_i": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "cu_m": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "cu_np": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + "gpu_units": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + }, + }, + }, + }, + "account_id": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "dc_location": { + Type: schema.TypeString, + Computed: true, + }, + "resources": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cpu": { + Type: schema.TypeInt, + Computed: true, + }, + "disksize": { + Type: schema.TypeInt, + Computed: true, + }, + "extips": { + Type: schema.TypeInt, + Computed: true, + }, + "exttraffic": { + Type: schema.TypeInt, + Computed: true, + }, + "gpu": { + Type: schema.TypeInt, + Computed: true, + }, + "ram": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "reserved": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cpu": { + Type: schema.TypeInt, + Computed: true, + }, + "disksize": { + Type: schema.TypeInt, + Computed: true, + }, + "extips": { + Type: schema.TypeInt, + Computed: true, + }, + "exttraffic": { + Type: schema.TypeInt, + Computed: true, + }, + "gpu": { + Type: schema.TypeInt, + Computed: true, + }, + "ram": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "ckey": { + Type: schema.TypeString, + Computed: true, + }, + "meta": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "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, + }, + }, + }, + }, + "company": { + Type: schema.TypeString, + Computed: true, + }, + "companyurl": { + Type: schema.TypeString, + Computed: true, + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deactivation_time": { + Type: schema.TypeFloat, + Computed: true, + }, + "deleted_by": { + Type: schema.TypeString, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "displayname": { + Type: schema.TypeString, + Computed: true, + }, + "guid": { + Type: schema.TypeInt, + Computed: true, + }, + "service_account": { + Type: schema.TypeBool, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "updated_time": { + Type: schema.TypeInt, + Computed: true, + }, + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "vins": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + } +} diff --git a/internal/service/cloudbroker/account/account_vins_ds_subresource.go b/internal/service/cloudbroker/account/account_vins_ds_subresource.go new file mode 100644 index 0000000..4032aad --- /dev/null +++ b/internal/service/cloudbroker/account/account_vins_ds_subresource.go @@ -0,0 +1,107 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func dataSourceAccountVinsSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Computed: true, + }, + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + "computes": { + Type: schema.TypeInt, + Computed: true, + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_by": { + Type: schema.TypeString, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "external_ip": { + Type: schema.TypeString, + Computed: true, + }, + "vin_id": { + Type: schema.TypeInt, + Computed: true, + }, + "vin_name": { + Type: schema.TypeString, + Computed: true, + }, + "network": { + Type: schema.TypeString, + Computed: true, + }, + "pri_vnf_dev_id": { + Type: schema.TypeInt, + Computed: true, + }, + "rg_id": { + Type: schema.TypeInt, + Computed: true, + }, + "rg_name": { + 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, + }, + } +} diff --git a/internal/service/cloudbroker/account/api.go b/internal/service/cloudbroker/account/api.go new file mode 100644 index 0000000..3ed17ed --- /dev/null +++ b/internal/service/cloudbroker/account/api.go @@ -0,0 +1,54 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +const accountAddUserAPI = "/restmachine/cloudbroker/account/addUser" +const accountAuditsAPI = "/restmachine/cloudbroker/account/audits" +const accountCreateAPI = "/restmachine/cloudbroker/account/create" +const accountDeleteAPI = "/restmachine/cloudbroker/account/delete" +const accountDeleteUserAPI = "/restmachine/cloudbroker/account/deleteUser" +const accountDisableAPI = "/restmachine/cloudbroker/account/disable" +const accountEnableAPI = "/restmachine/cloudbroker/account/enable" +const accountGetAPI = "/restmachine/cloudbroker/account/get" +const accountListAPI = "/restmachine/cloudbroker/account/list" +const accountListComputesAPI = "/restmachine/cloudbroker/account/listComputes" +const accountListDeletedAPI = "/restmachine/cloudbroker/account/listDeleted" +const accountListDisksAPI = "/restmachine/cloudbroker/account/listDisks" +const accountListFlipGroupsAPI = "/restmachine/cloudbroker/account/listFlipGroups" +const accountListRGAPI = "/restmachine/cloudbroker/account/listRG" +const accountListVinsAPI = "/restmachine/cloudbroker/account/listVins" +const accountRestoreAPI = "/restmachine/cloudbroker/account/restore" +const accountUpdateAPI = "/restmachine/cloudbroker/account/update" +const accountUpdateUserAPI = "/restmachine/cloudbroker/account/updateUser" +const accountsEnableAPI = "/restmachine/cloudbroker/account/enableAccounts" +const accountsDisableAPI = "/restmachine/cloudbroker/account/disableAccounts" +const accountsDeleteAPI = "/restmachine/cloudbroker/account/deleteAccounts" diff --git a/internal/service/cloudbroker/account/data_source_account.go b/internal/service/cloudbroker/account/data_source_account.go new file mode 100644 index 0000000..7bf5ce8 --- /dev/null +++ b/internal/service/cloudbroker/account/data_source_account.go @@ -0,0 +1,108 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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" + "github.com/rudecs/terraform-provider-decort/internal/flattens" +) + +func dataSourceAccountRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + acc, err := utilityAccountCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("dc_location", acc.DCLocation) + d.Set("resources", flattenAccResources(acc.Resources)) + d.Set("ckey", acc.CKey) + d.Set("meta", flattens.FlattenMeta(acc.Meta)) + d.Set("acl", flattenAccAcl(acc.Acl)) + d.Set("company", acc.Company) + d.Set("companyurl", acc.CompanyUrl) + d.Set("created_by", acc.CreatedBy) + d.Set("created_time", acc.CreatedTime) + d.Set("deactivation_time", acc.DeactiovationTime) + d.Set("deleted_by", acc.DeletedBy) + d.Set("deleted_time", acc.DeletedTime) + d.Set("displayname", acc.DisplayName) + d.Set("guid", acc.GUID) + d.Set("account_id", acc.ID) + d.Set("account_name", acc.Name) + d.Set("resource_limits", flattenRgResourceLimits(acc.ResourceLimits)) + d.Set("send_access_emails", acc.SendAccessEmails) + d.Set("service_account", acc.ServiceAccount) + d.Set("status", acc.Status) + d.Set("updated_time", acc.UpdatedTime) + d.Set("version", acc.Version) + d.Set("vins", acc.Vins) + + return nil +} + +func flattenAccAcl(acls []AccountAclRecord) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, acls := range acls { + temp := map[string]interface{}{ + "explicit": acls.IsExplicit, + "guid": acls.Guid, + "right": acls.Rights, + "status": acls.Status, + "type": acls.Type, + "user_group_id": acls.UgroupID, + } + res = append(res, temp) + } + return res +} + +func DataSourceAccount() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceAccountRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceAccountSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/data_source_account_audits_list.go b/internal/service/cloudbroker/account/data_source_account_audits_list.go new file mode 100644 index 0000000..ccaaead --- /dev/null +++ b/internal/service/cloudbroker/account/data_source_account_audits_list.go @@ -0,0 +1,104 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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 flattenAccountAuditsList(aal AccountAuditsList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, aa := range aal { + temp := map[string]interface{}{ + "call": aa.Call, + "responsetime": aa.ResponseTime, + "statuscode": aa.StatusCode, + "timestamp": aa.Timestamp, + "user": aa.User, + } + res = append(res, temp) + } + return res + +} + +func dataSourceAccountAuditsListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + accountAuditsList, err := utilityAccountAuditsListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenAccountAuditsList(accountAuditsList)) + + return nil +} + +func dataSourceAccountAuditsListSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Required: true, + Description: "ID of the account", + }, + "items": { + Type: schema.TypeList, + Computed: true, + Description: "Search Result", + Elem: &schema.Resource{ + Schema: dataSourceAccountAuditSchemaMake(), + }, + }, + } + return res +} + +func DataSourceAccountAuditsList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceAccountAuditsListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceAccountAuditsListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/data_source_account_computes_list.go b/internal/service/cloudbroker/account/data_source_account_computes_list.go new file mode 100644 index 0000000..e181095 --- /dev/null +++ b/internal/service/cloudbroker/account/data_source_account_computes_list.go @@ -0,0 +1,119 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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 flattenAccountComputesList(acl AccountComputesList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, acc := range acl { + temp := map[string]interface{}{ + "account_id": acc.AccountId, + "account_name": acc.AccountName, + "cpus": acc.CPUs, + "created_by": acc.CreatedBy, + "created_time": acc.CreatedTime, + "deleted_by": acc.DeletedBy, + "deleted_time": acc.DeletedTime, + "compute_id": acc.ComputeId, + "compute_name": acc.ComputeName, + "ram": acc.RAM, + "registered": acc.Registered, + "rg_id": acc.RgId, + "rg_name": acc.RgName, + "status": acc.Status, + "tech_status": acc.TechStatus, + "total_disks_size": acc.TotalDisksSize, + "updated_by": acc.UpdatedBy, + "updated_time": acc.UpdatedTime, + "user_managed": acc.UserManaged, + "vins_connected": acc.VinsConnected, + } + res = append(res, temp) + } + return res + +} + +func dataSourceAccountComputesListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + accountComputesList, err := utilityAccountComputesListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenAccountComputesList(accountComputesList)) + + return nil +} + +func dataSourceAccountComputesListSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Required: true, + Description: "ID of the account", + }, + "items": { + Type: schema.TypeList, + Computed: true, + Description: "Search Result", + Elem: &schema.Resource{ + Schema: dataSourceAccountComputeSchemaMake(), + }, + }, + } + return res +} + +func DataSourceAccountComputesList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceAccountComputesListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceAccountComputesListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/data_source_account_deleted_list.go b/internal/service/cloudbroker/account/data_source_account_deleted_list.go new file mode 100644 index 0000000..04120b4 --- /dev/null +++ b/internal/service/cloudbroker/account/data_source_account_deleted_list.go @@ -0,0 +1,69 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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 dataSourceAccountDeletedListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + accountDeletedList, err := utilityAccountDeletedListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenAccountList(accountDeletedList)) + + return nil +} + +func DataSourceAccountDeletedList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceAccountDeletedListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceAccountListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/data_source_account_disks_list.go b/internal/service/cloudbroker/account/data_source_account_disks_list.go new file mode 100644 index 0000000..b823e9a --- /dev/null +++ b/internal/service/cloudbroker/account/data_source_account_disks_list.go @@ -0,0 +1,104 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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 flattenAccountDisksList(adl AccountDisksList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, ad := range adl { + temp := map[string]interface{}{ + "disk_id": ad.ID, + "disk_name": ad.Name, + "pool_name": ad.Pool, + "sep_id": ad.SepId, + "size_max": ad.SizeMax, + "type": ad.Type, + } + res = append(res, temp) + } + return res + +} + +func dataSourceAccountDisksListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + accountDisksList, err := utilityAccountDisksListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenAccountDisksList(accountDisksList)) + + return nil +} + +func dataSourceAccountDisksListSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Required: true, + Description: "ID of the account", + }, + "items": { + Type: schema.TypeList, + Computed: true, + Description: "Search Result", + Elem: &schema.Resource{ + Schema: dataSourceAccountDiskSchemaMake(), + }, + }, + } + return res +} + +func DataSourceAccountDisksList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceAccountDisksListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceAccountDisksListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/data_source_account_flipgroups_list.go b/internal/service/cloudbroker/account/data_source_account_flipgroups_list.go new file mode 100644 index 0000000..4a318b2 --- /dev/null +++ b/internal/service/cloudbroker/account/data_source_account_flipgroups_list.go @@ -0,0 +1,120 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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 flattenAccountFlipGroupsList(afgl AccountFlipGroupsList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, afg := range afgl { + temp := map[string]interface{}{ + "account_id": afg.AccountId, + "client_type": afg.ClientType, + "conn_type": afg.ConnType, + "created_by": afg.CreatedBy, + "created_time": afg.CreatedTime, + "default_gw": afg.DefaultGW, + "deleted_by": afg.DeletedBy, + "deleted_time": afg.DeletedTime, + "desc": afg.Desc, + "gid": afg.GID, + "guid": afg.GUID, + "fg_id": afg.ID, + "ip": afg.IP, + "milestones": afg.Milestones, + "fg_name": afg.Name, + "net_id": afg.NetID, + "net_type": afg.NetType, + "netmask": afg.NetMask, + "status": afg.Status, + "updated_by": afg.UpdatedBy, + "updated_time": afg.UpdatedTime, + } + res = append(res, temp) + } + return res + +} + +func dataSourceAccountFlipGroupsListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + accountFlipGroupsList, err := utilityAccountFlipGroupsListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenAccountFlipGroupsList(accountFlipGroupsList)) + + return nil +} + +func dataSourceAccountFlipGroupsListSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Required: true, + Description: "ID of the account", + }, + "items": { + Type: schema.TypeList, + Computed: true, + Description: "Search Result", + Elem: &schema.Resource{ + Schema: dataSourceAccountFlipGroupSchemaMake(), + }, + }, + } + return res +} + +func DataSourceAccountFlipGroupsList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceAccountFlipGroupsListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceAccountFlipGroupsListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/data_source_account_list.go b/internal/service/cloudbroker/account/data_source_account_list.go new file mode 100644 index 0000000..c733c12 --- /dev/null +++ b/internal/service/cloudbroker/account/data_source_account_list.go @@ -0,0 +1,167 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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" + "github.com/rudecs/terraform-provider-decort/internal/flattens" +) + +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 flattenAccountList(al AccountList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, acc := range al { + temp := map[string]interface{}{ + "dc_location": acc.DCLocation, + "ckey": acc.CKey, + "meta": flattens.FlattenMeta(acc.Meta), + + "acl": flattenRgAcl(acc.Acl), + + "company": acc.Company, + "companyurl": acc.CompanyUrl, + "created_by": acc.CreatedBy, + + "created_time": acc.CreatedTime, + + "deactivation_time": acc.DeactiovationTime, + "deleted_by": acc.DeletedBy, + + "deleted_time": acc.DeletedTime, + + "displayname": acc.DisplayName, + "guid": acc.GUID, + + "account_id": acc.ID, + "account_name": acc.Name, + + "resource_limits": flattenRgResourceLimits(acc.ResourceLimits), + "send_access_emails": acc.SendAccessEmails, + "service_account": acc.ServiceAccount, + + "status": acc.Status, + "updated_time": acc.UpdatedTime, + + "version": acc.Version, + "vins": acc.Vins, + } + 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 dataSourceAccountListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + accountList, err := utilityAccountListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenAccountList(accountList)) + + return nil +} + +func dataSourceAccountListSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "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: dataSourceAccountItemSchemaMake(), + }, + }, + } + return res +} + +func DataSourceAccountList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceAccountListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceAccountListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/data_source_account_rg_list.go b/internal/service/cloudbroker/account/data_source_account_rg_list.go new file mode 100644 index 0000000..f810db4 --- /dev/null +++ b/internal/service/cloudbroker/account/data_source_account_rg_list.go @@ -0,0 +1,157 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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 flattenAccountRGList(argl AccountRGList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, arg := range argl { + temp := map[string]interface{}{ + "computes": flattenAccRGComputes(arg.Computes), + "resources": flattenAccRGResources(arg.Resources), + "created_by": arg.CreatedBy, + "created_time": arg.CreatedTime, + "deleted_by": arg.DeletedBy, + "deleted_time": arg.DeletedTime, + "rg_id": arg.RGID, + "milestones": arg.Milestones, + "rg_name": arg.RGName, + "status": arg.Status, + "updated_by": arg.UpdatedBy, + "updated_time": arg.UpdatedTime, + "vinses": arg.Vinses, + } + res = append(res, temp) + } + return res + +} + +func flattenAccRGComputes(argc AccountRGComputes) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + temp := map[string]interface{}{ + "started": argc.Started, + "stopped": argc.Stopped, + } + res = append(res, temp) + return res +} + +func flattenAccRGResources(argr AccountRGResources) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + temp := map[string]interface{}{ + "consumed": flattenAccResource(argr.Consumed), + "limits": flattenAccResource(argr.Limits), + "reserved": flattenAccResource(argr.Reserved), + } + res = append(res, temp) + return res +} + +func flattenAccResources(r Resources) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + temp := map[string]interface{}{ + "current": flattenAccResource(r.Current), + "reserved": flattenAccResource(r.Reserved), + } + res = append(res, temp) + return res +} + +func flattenAccResource(r Resource) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + temp := map[string]interface{}{ + "cpu": r.CPU, + "disksize": r.Disksize, + "extips": r.Extips, + "exttraffic": r.Exttraffic, + "gpu": r.GPU, + "ram": r.RAM, + } + res = append(res, temp) + return res +} + +func dataSourceAccountRGListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + accountRGList, err := utilityAccountRGListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenAccountRGList(accountRGList)) + + return nil +} + +func dataSourceAccountRGListSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Required: true, + Description: "ID of the account", + }, + "items": { + Type: schema.TypeList, + Computed: true, + Description: "Search Result", + Elem: &schema.Resource{ + Schema: dataSourceAccountRGSchemaMake(), + }, + }, + } + return res +} + +func DataSourceAccountRGList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceAccountRGListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceAccountRGListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/data_source_account_vins_list.go b/internal/service/cloudbroker/account/data_source_account_vins_list.go new file mode 100644 index 0000000..7110629 --- /dev/null +++ b/internal/service/cloudbroker/account/data_source_account_vins_list.go @@ -0,0 +1,116 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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 flattenAccountVinsList(avl AccountVinsList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, av := range avl { + temp := map[string]interface{}{ + "account_id": av.AccountId, + "account_name": av.AccountName, + "computes": av.Computes, + "created_by": av.CreatedBy, + "created_time": av.CreatedTime, + "deleted_by": av.DeletedBy, + "deleted_time": av.DeletedTime, + "external_ip": av.ExternalIP, + "vin_id": av.ID, + "vin_name": av.Name, + "network": av.Network, + "pri_vnf_dev_id": av.PriVnfDevId, + "rg_id": av.RgId, + "rg_name": av.RgName, + "status": av.Status, + "updated_by": av.UpdatedBy, + "updated_time": av.UpdatedTime, + } + res = append(res, temp) + } + return res + +} + +func dataSourceAccountVinsListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + accountVinsList, err := utilityAccountVinsListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenAccountVinsList(accountVinsList)) + + return nil +} + +func dataSourceAccountVinsListSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Required: true, + Description: "ID of the account", + }, + "items": { + Type: schema.TypeList, + Computed: true, + Description: "Search Result", + Elem: &schema.Resource{ + Schema: dataSourceAccountVinsSchema(), + }, + }, + } + return res +} + +func DataSourceAccountVinsList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceAccountVinsListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceAccountVinsListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/models.go b/internal/service/cloudbroker/account/models.go new file mode 100644 index 0000000..d38c953 --- /dev/null +++ b/internal/service/cloudbroker/account/models.go @@ -0,0 +1,219 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +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"` +} + +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 Account struct { + DCLocation string `json:"DCLocation"` + CKey string `jspn:"_ckey"` + Meta []interface{} `json:"_meta"` + Acl []AccountAclRecord `json:"acl"` + Company string `json:"company"` + CompanyUrl string `json:"companyurl"` + CreatedBy string `jspn:"createdBy"` + CreatedTime int `json:"createdTime"` + DeactiovationTime float64 `json:"deactivationTime"` + DeletedBy string `json:"deletedBy"` + DeletedTime int `json:"deletedTime"` + DisplayName string `json:"displayname"` + GUID int `json:"guid"` + ID int `json:"id"` + Name string `json:"name"` + ResourceLimits ResourceLimits `json:"resourceLimits"` + SendAccessEmails bool `json:"sendAccessEmails"` + ServiceAccount bool `json:"serviceAccount"` + Status string `json:"status"` + UpdatedTime int `json:"updatedTime"` + Version int `json:"version"` + Vins []int `json:"vins"` +} + +type AccountList []Account + +type Resource struct { + CPU int `json:"cpu"` + Disksize int `json:"disksize"` + Extips int `json:"extips"` + Exttraffic int `json:"exttraffic"` + GPU int `json:"gpu"` + RAM int `json:"ram"` +} + +type Resources struct { + Current Resource `json:"Current"` + Reserved Resource `json:"Reserved"` +} + +type AccountWithResources struct { + Account + Resources Resources `json:"Resources"` +} + +type AccountCompute struct { + AccountId int `json:"accountId"` + AccountName string `json:"accountName"` + CPUs int `json:"cpus"` + CreatedBy string `json:"createdBy"` + CreatedTime int `json:"createdTime"` + DeletedBy string `json:"deletedBy"` + DeletedTime int `json:"deletedTime"` + ComputeId int `json:"id"` + ComputeName string `json:"name"` + RAM int `json:"ram"` + Registered bool `json:"registered"` + RgId int `json:"rgId"` + RgName string `json:"rgName"` + Status string `json:"status"` + TechStatus string `json:"techStatus"` + TotalDisksSize int `json:"totalDisksSize"` + UpdatedBy string `json:"updatedBy"` + UpdatedTime int `json:"updatedTime"` + UserManaged bool `json:"userManaged"` + VinsConnected int `json:"vinsConnected"` +} + +type AccountComputesList []AccountCompute + +type AccountDisk struct { + ID int `json:"id"` + Name string `json:"name"` + Pool string `json:"pool"` + SepId int `json:"sepId"` + SizeMax int `json:"sizeMax"` + Type string `json:"type"` +} + +type AccountDisksList []AccountDisk + +type AccountVin struct { + AccountId int `json:"accountId"` + AccountName string `json:"accountName"` + Computes int `json:"computes"` + CreatedBy string `json:"createdBy"` + CreatedTime int `json:"createdTime"` + DeletedBy string `json:"deletedBy"` + DeletedTime int `json:"deletedTime"` + ExternalIP string `json:"externalIP"` + ID int `json:"id"` + Name string `json:"name"` + Network string `json:"network"` + PriVnfDevId int `json:"priVnfDevId"` + RgId int `json:"rgId"` + RgName string `json:"rgName"` + Status string `json:"status"` + UpdatedBy string `json:"updatedBy"` + UpdatedTime int `json:"updatedTime"` +} + +type AccountVinsList []AccountVin + +type AccountAudit struct { + Call string `json:"call"` + ResponseTime float64 `json:"responsetime"` + StatusCode int `json:"statuscode"` + Timestamp float64 `json:"timestamp"` + User string `json:"user"` +} + +type AccountAuditsList []AccountAudit + +type AccountRGComputes struct { + Started int `json:"Started"` + Stopped int `json:"Stopped"` +} + +type AccountRGResources struct { + Consumed Resource `json:"Consumed"` + Limits Resource `json:"Limits"` + Reserved Resource `json:"Reserved"` +} + +type AccountRG struct { + Computes AccountRGComputes `json:"Computes"` + Resources AccountRGResources `json:"Resources"` + CreatedBy string `json:"createdBy"` + CreatedTime int `json:"createdTime"` + DeletedBy string `json:"deletedBy"` + DeletedTime int `json:"deletedTime"` + RGID int `json:"id"` + Milestones int `json:"milestones"` + RGName string `json:"name"` + Status string `json:"status"` + UpdatedBy string `json:"updatedBy"` + UpdatedTime int `json:"updatedTime"` + Vinses int `json:"vinses"` +} + +type AccountRGList []AccountRG + +type AccountFlipGroup struct { + AccountId int `json:"accountId"` + ClientType string `json:"clientType"` + ConnType string `json:"connType"` + CreatedBy string `json:"createdBy"` + CreatedTime int `json:"createdTime"` + DefaultGW string `json:"defaultGW"` + DeletedBy string `json:"deletedBy"` + DeletedTime int `json:"deletedTime"` + Desc string `json:"desc"` + GID int `json:"gid"` + GUID int `json:"guid"` + ID int `json:"id"` + IP string `json:"ip"` + Milestones int `json:"milestones"` + Name string `json:"name"` + NetID int `json:"netId"` + NetType string `json:"netType"` + NetMask int `json:"netmask"` + Status string `json:"status"` + UpdatedBy string `json:"updatedBy"` + UpdatedTime int `json:"updatedTime"` +} + +type AccountFlipGroupsList []AccountFlipGroup diff --git a/internal/service/cloudbroker/account/resource_account.go b/internal/service/cloudbroker/account/resource_account.go new file mode 100644 index 0000000..2647b9d --- /dev/null +++ b/internal/service/cloudbroker/account/resource_account.go @@ -0,0 +1,485 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "net/url" + "strconv" + "strings" + + "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" + "github.com/rudecs/terraform-provider-decort/internal/controller" + "github.com/rudecs/terraform-provider-decort/internal/flattens" + log "github.com/sirupsen/logrus" +) + +func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceAccountCreate") + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("name", d.Get("account_name").(string)) + urlValues.Add("username", d.Get("username").(string)) + + if emailaddress, ok := d.GetOk("emailaddress"); ok { + urlValues.Add("emailaddress", emailaddress.(string)) + } + if sendAccessEmails, ok := d.GetOk("send_access_emails"); ok { + urlValues.Add("sendAccessEmails", strconv.FormatBool(sendAccessEmails.(bool))) + } + if resLimits, ok := d.GetOk("resource_limits"); ok { + resLimit := resLimits.([]interface{})[0] + resLimitConv := resLimit.(map[string]interface{}) + if resLimitConv["cu_m"] != nil { + maxMemCap := int(resLimitConv["cu_m"].(float64)) + if maxMemCap == 0 { + urlValues.Add("maxMemoryCapacity", strconv.Itoa(-1)) + } else { + urlValues.Add("maxMemoryCapacity", strconv.Itoa(maxMemCap)) + } + } + if resLimitConv["cu_d"] != nil { + maxDiskCap := int(resLimitConv["cu_d"].(float64)) + if maxDiskCap == 0 { + urlValues.Add("maxVDiskCapacity", strconv.Itoa(-1)) + } else { + urlValues.Add("maxVDiskCapacity", strconv.Itoa(maxDiskCap)) + } + } + if resLimitConv["cu_c"] != nil { + maxCPUCap := int(resLimitConv["cu_c"].(float64)) + if maxCPUCap == 0 { + urlValues.Add("maxCPUCapacity", strconv.Itoa(-1)) + } else { + urlValues.Add("maxCPUCapacity", strconv.Itoa(maxCPUCap)) + } + + } + if resLimitConv["cu_i"] != nil { + maxNumPublicIP := int(resLimitConv["cu_i"].(float64)) + if maxNumPublicIP == 0 { + urlValues.Add("maxNumPublicIP", strconv.Itoa(-1)) + } else { + urlValues.Add("maxNumPublicIP", strconv.Itoa(maxNumPublicIP)) + } + + } + if resLimitConv["cu_np"] != nil { + maxNP := int(resLimitConv["cu_np"].(float64)) + if maxNP == 0 { + urlValues.Add("maxNetworkPeerTransfer", strconv.Itoa(-1)) + } else { + urlValues.Add("maxNetworkPeerTransfer", strconv.Itoa(maxNP)) + } + + } + if resLimitConv["gpu_units"] != nil { + gpuUnits := int(resLimitConv["gpu_units"].(float64)) + if gpuUnits == 0 { + urlValues.Add("gpu_units", strconv.Itoa(-1)) + } else { + urlValues.Add("gpu_units", strconv.Itoa(gpuUnits)) + } + } + } + + accountId, err := c.DecortAPICall(ctx, "POST", accountCreateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(accountId) + d.Set("account_id", accountId) + + diagnostics := resourceAccountRead(ctx, d, m) + if diagnostics != nil { + return diagnostics + } + + if enable, ok := d.GetOk("enable"); ok { + api := accountDisableAPI + enable := enable.(bool) + if enable { + api = accountEnableAPI + } + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + + _, err := c.DecortAPICall(ctx, "POST", api, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + + if users, ok := d.GetOk("users"); ok { + addedUsers := users.([]interface{}) + + if len(addedUsers) > 0 { + for _, user := range addedUsers { + userConv := user.(map[string]interface{}) + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + urlValues.Add("userId", userConv["user_id"].(string)) + urlValues.Add("accesstype", strings.ToUpper(userConv["access_type"].(string))) + _, err := c.DecortAPICall(ctx, "POST", accountAddUserAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + } + } + + return nil +} + +func resourceAccountRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceAccountRead") + + acc, err := utilityAccountCheckPresence(ctx, d, m) + if acc == nil { + d.SetId("") + return diag.FromErr(err) + } + + d.Set("dc_location", acc.DCLocation) + d.Set("resources", flattenAccResources(acc.Resources)) + d.Set("ckey", acc.CKey) + d.Set("meta", flattens.FlattenMeta(acc.Meta)) + d.Set("acl", flattenAccAcl(acc.Acl)) + d.Set("company", acc.Company) + d.Set("companyurl", acc.CompanyUrl) + d.Set("created_by", acc.CreatedBy) + d.Set("created_time", acc.CreatedTime) + d.Set("deactivation_time", acc.DeactiovationTime) + d.Set("deleted_by", acc.DeletedBy) + d.Set("deleted_time", acc.DeletedTime) + d.Set("displayname", acc.DisplayName) + d.Set("guid", acc.GUID) + d.Set("account_id", acc.ID) + d.Set("account_name", acc.Name) + d.Set("resource_limits", flattenRgResourceLimits(acc.ResourceLimits)) + d.Set("send_access_emails", acc.SendAccessEmails) + d.Set("service_account", acc.ServiceAccount) + d.Set("status", acc.Status) + d.Set("updated_time", acc.UpdatedTime) + d.Set("version", acc.Version) + d.Set("vins", acc.Vins) + + return nil +} + +func resourceAccountDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceAccountDelete") + + account, err := utilityAccountCheckPresence(ctx, d, m) + if account == nil { + if err != nil { + return diag.FromErr(err) + } + return nil + } + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + urlValues.Add("permanently", strconv.FormatBool(d.Get("permanently").(bool))) + + _, err = c.DecortAPICall(ctx, "POST", accountDeleteAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + d.SetId("") + + return nil +} + +func resourceAccountExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + log.Debugf("resourceAccountExists") + + account, err := utilityAccountCheckPresence(ctx, d, m) + if account == nil { + if err != nil { + return false, err + } + return false, nil + } + + return true, nil +} + +func resourceAccountEdit(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceAccountEdit") + c := m.(*controller.ControllerCfg) + + urlValues := &url.Values{} + if d.HasChange("enable") { + api := accountDisableAPI + enable := d.Get("enable").(bool) + if enable { + api = accountEnableAPI + } + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + + _, err := c.DecortAPICall(ctx, "POST", api, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + + if d.HasChange("account_name") { + urlValues.Add("name", d.Get("account_name").(string)) + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + _, err := c.DecortAPICall(ctx, "POST", accountUpdateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + if d.HasChange("resource_limits") { + resLimit := d.Get("resource_limits").([]interface{})[0] + resLimitConv := resLimit.(map[string]interface{}) + + if resLimitConv["cu_m"] != nil { + maxMemCap := int(resLimitConv["cu_m"].(float64)) + if maxMemCap == 0 { + urlValues.Add("maxMemoryCapacity", strconv.Itoa(-1)) + } else { + urlValues.Add("maxMemoryCapacity", strconv.Itoa(maxMemCap)) + } + } + if resLimitConv["cu_d"] != nil { + maxDiskCap := int(resLimitConv["cu_d"].(float64)) + if maxDiskCap == 0 { + urlValues.Add("maxVDiskCapacity", strconv.Itoa(-1)) + } else { + urlValues.Add("maxVDiskCapacity", strconv.Itoa(maxDiskCap)) + } + } + if resLimitConv["cu_c"] != nil { + maxCPUCap := int(resLimitConv["cu_c"].(float64)) + if maxCPUCap == 0 { + urlValues.Add("maxCPUCapacity", strconv.Itoa(-1)) + } else { + urlValues.Add("maxCPUCapacity", strconv.Itoa(maxCPUCap)) + } + + } + if resLimitConv["cu_i"] != nil { + maxNumPublicIP := int(resLimitConv["cu_i"].(float64)) + if maxNumPublicIP == 0 { + urlValues.Add("maxNumPublicIP", strconv.Itoa(-1)) + } else { + urlValues.Add("maxNumPublicIP", strconv.Itoa(maxNumPublicIP)) + } + + } + if resLimitConv["cu_np"] != nil { + maxNP := int(resLimitConv["cu_np"].(float64)) + if maxNP == 0 { + urlValues.Add("maxNetworkPeerTransfer", strconv.Itoa(-1)) + } else { + urlValues.Add("maxNetworkPeerTransfer", strconv.Itoa(maxNP)) + } + + } + if resLimitConv["gpu_units"] != nil { + gpuUnits := int(resLimitConv["gpu_units"].(float64)) + if gpuUnits == 0 { + urlValues.Add("gpu_units", strconv.Itoa(-1)) + } else { + urlValues.Add("gpu_units", strconv.Itoa(gpuUnits)) + } + } + + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + _, err := c.DecortAPICall(ctx, "POST", accountUpdateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + + if d.HasChange("send_access_emails") { + urlValues.Add("sendAccessEmails", strconv.FormatBool(d.Get("send_access_emails").(bool))) + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + _, err := c.DecortAPICall(ctx, "POST", accountUpdateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + + if d.HasChange("restore") { + restore := d.Get("restore").(bool) + if restore { + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + _, err := c.DecortAPICall(ctx, "POST", accountRestoreAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + } + + if d.HasChange("users") { + deletedUsers := make([]interface{}, 0) + addedUsers := make([]interface{}, 0) + updatedUsers := make([]interface{}, 0) + + old, new := d.GetChange("users") + oldConv := old.([]interface{}) + newConv := new.([]interface{}) + for _, el := range oldConv { + if !isContainsUser(newConv, el) { + deletedUsers = append(deletedUsers, el) + } + } + for _, el := range newConv { + if !isContainsUser(oldConv, el) { + addedUsers = append(addedUsers, el) + } else { + if isChangedUser(oldConv, el) { + updatedUsers = append(updatedUsers, el) + } + } + } + + if len(deletedUsers) > 0 { + for _, user := range deletedUsers { + userConv := user.(map[string]interface{}) + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + urlValues.Add("userId", userConv["user_id"].(string)) + urlValues.Add("recursivedelete", strconv.FormatBool(userConv["recursive_delete"].(bool))) + _, err := c.DecortAPICall(ctx, "POST", accountDeleteUserAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + } + + if len(addedUsers) > 0 { + for _, user := range addedUsers { + userConv := user.(map[string]interface{}) + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + urlValues.Add("userId", userConv["user_id"].(string)) + urlValues.Add("accesstype", strings.ToUpper(userConv["access_type"].(string))) + _, err := c.DecortAPICall(ctx, "POST", accountAddUserAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + } + + if len(updatedUsers) > 0 { + for _, user := range updatedUsers { + userConv := user.(map[string]interface{}) + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + urlValues.Add("userId", userConv["user_id"].(string)) + urlValues.Add("accesstype", strings.ToUpper(userConv["access_type"].(string))) + _, err := c.DecortAPICall(ctx, "POST", accountUpdateUserAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + } + + } + + return nil +} + +func isContainsUser(els []interface{}, el interface{}) bool { + for _, elOld := range els { + elOldConv := elOld.(map[string]interface{}) + elConv := el.(map[string]interface{}) + if elOldConv["user_id"].(string) == elConv["user_id"].(string) { + return true + } + } + return false +} + +func isChangedUser(els []interface{}, el interface{}) bool { + for _, elOld := range els { + elOldConv := elOld.(map[string]interface{}) + elConv := el.(map[string]interface{}) + if elOldConv["user_id"].(string) == elConv["user_id"].(string) && + (!strings.EqualFold(elOldConv["access_type"].(string), elConv["access_type"].(string)) || + elOldConv["recursive_delete"].(bool) != elConv["recursive_delete"].(bool)) { + return true + } + } + return false +} + +func ResourceAccount() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourceAccountCreate, + ReadContext: resourceAccountRead, + UpdateContext: resourceAccountEdit, + DeleteContext: resourceAccountDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: &constants.Timeout60s, + Read: &constants.Timeout30s, + Update: &constants.Timeout60s, + Delete: &constants.Timeout60s, + Default: &constants.Timeout60s, + }, + + Schema: resourceAccountSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/account/utility_account.go b/internal/service/cloudbroker/account/utility_account.go new file mode 100644 index 0000000..092162b --- /dev/null +++ b/internal/service/cloudbroker/account/utility_account.go @@ -0,0 +1,69 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "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 utilityAccountCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*AccountWithResources, error) { + account := &AccountWithResources{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + if (strconv.Itoa(d.Get("account_id").(int))) != "0" { + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + } else { + urlValues.Add("accountId", d.Id()) + } + + log.Debugf("utilityAccountCheckPresence: load account") + accountRaw, err := c.DecortAPICall(ctx, "POST", accountGetAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(accountRaw), &account) + if err != nil { + return nil, err + } + + return account, nil +} diff --git a/internal/service/cloudbroker/account/utility_account_audits_list.go b/internal/service/cloudbroker/account/utility_account_audits_list.go new file mode 100644 index 0000000..c89cedd --- /dev/null +++ b/internal/service/cloudbroker/account/utility_account_audits_list.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "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 utilityAccountAuditsListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (AccountAuditsList, error) { + accountAuditsList := AccountAuditsList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + + log.Debugf("utilityAccountAuditsListCheckPresence: load account list") + accountAuditsListRaw, err := c.DecortAPICall(ctx, "POST", accountAuditsAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(accountAuditsListRaw), &accountAuditsList) + if err != nil { + return nil, err + } + + return accountAuditsList, nil +} diff --git a/internal/service/cloudbroker/account/utility_account_computes_list.go b/internal/service/cloudbroker/account/utility_account_computes_list.go new file mode 100644 index 0000000..9b746da --- /dev/null +++ b/internal/service/cloudbroker/account/utility_account_computes_list.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "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 utilityAccountComputesListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (AccountComputesList, error) { + accountComputesList := AccountComputesList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + + log.Debugf("utilityAccountComputesListCheckPresence: load account list") + accountComputesListRaw, err := c.DecortAPICall(ctx, "POST", accountListComputesAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(accountComputesListRaw), &accountComputesList) + if err != nil { + return nil, err + } + + return accountComputesList, nil +} diff --git a/internal/service/cloudbroker/account/utility_account_deleted_list.go b/internal/service/cloudbroker/account/utility_account_deleted_list.go new file mode 100644 index 0000000..b4537c1 --- /dev/null +++ b/internal/service/cloudbroker/account/utility_account_deleted_list.go @@ -0,0 +1,70 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "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 utilityAccountDeletedListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (AccountList, error) { + accountDeletedList := AccountList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + if page, ok := d.GetOk("page"); ok { + urlValues.Add("page", strconv.Itoa(page.(int))) + } + if size, ok := d.GetOk("size"); ok { + urlValues.Add("size", strconv.Itoa(size.(int))) + } + + log.Debugf("utilityAccountDeletedListCheckPresence: load") + accountDeletedListRaw, err := c.DecortAPICall(ctx, "POST", accountListDeletedAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(accountDeletedListRaw), &accountDeletedList) + if err != nil { + return nil, err + } + + return accountDeletedList, nil +} diff --git a/internal/service/cloudbroker/account/utility_account_disks_list.go b/internal/service/cloudbroker/account/utility_account_disks_list.go new file mode 100644 index 0000000..7cea8a2 --- /dev/null +++ b/internal/service/cloudbroker/account/utility_account_disks_list.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "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 utilityAccountDisksListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (AccountDisksList, error) { + accountDisksList := AccountDisksList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + + log.Debugf("utilityAccountDisksListCheckPresence: load account list") + accountDisksListRaw, err := c.DecortAPICall(ctx, "POST", accountListDisksAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(accountDisksListRaw), &accountDisksList) + if err != nil { + return nil, err + } + + return accountDisksList, nil +} diff --git a/internal/service/cloudbroker/account/utility_account_flip_groups.go b/internal/service/cloudbroker/account/utility_account_flip_groups.go new file mode 100644 index 0000000..1585f6b --- /dev/null +++ b/internal/service/cloudbroker/account/utility_account_flip_groups.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "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 utilityAccountFlipGroupsListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (AccountFlipGroupsList, error) { + accountFlipGroupsList := AccountFlipGroupsList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + + log.Debugf("utilityAccountFlipGroupsListCheckPresence") + accountFlipGroupsListRaw, err := c.DecortAPICall(ctx, "POST", accountListFlipGroupsAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(accountFlipGroupsListRaw), &accountFlipGroupsList) + if err != nil { + return nil, err + } + + return accountFlipGroupsList, nil +} diff --git a/internal/service/cloudbroker/account/utility_account_list.go b/internal/service/cloudbroker/account/utility_account_list.go new file mode 100644 index 0000000..62b3b83 --- /dev/null +++ b/internal/service/cloudbroker/account/utility_account_list.go @@ -0,0 +1,70 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "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 utilityAccountListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (AccountList, error) { + accountList := AccountList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + if page, ok := d.GetOk("page"); ok { + urlValues.Add("page", strconv.Itoa(page.(int))) + } + if size, ok := d.GetOk("size"); ok { + urlValues.Add("size", strconv.Itoa(size.(int))) + } + + log.Debugf("utilityAccountListCheckPresence: load account list") + accountListRaw, err := c.DecortAPICall(ctx, "POST", accountListAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(accountListRaw), &accountList) + if err != nil { + return nil, err + } + + return accountList, nil +} diff --git a/internal/service/cloudbroker/account/utility_account_rg_list.go b/internal/service/cloudbroker/account/utility_account_rg_list.go new file mode 100644 index 0000000..41c1a76 --- /dev/null +++ b/internal/service/cloudbroker/account/utility_account_rg_list.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "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 utilityAccountRGListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (AccountRGList, error) { + accountRGList := AccountRGList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + + log.Debugf("utilityAccountRGListCheckPresence: load account list") + accountRGListRaw, err := c.DecortAPICall(ctx, "POST", accountListRGAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(accountRGListRaw), &accountRGList) + if err != nil { + return nil, err + } + + return accountRGList, nil +} diff --git a/internal/service/cloudbroker/account/utility_account_vins_list.go b/internal/service/cloudbroker/account/utility_account_vins_list.go new file mode 100644 index 0000000..db2e931 --- /dev/null +++ b/internal/service/cloudbroker/account/utility_account_vins_list.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 account + +import ( + "context" + "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 utilityAccountVinsListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (AccountVinsList, error) { + accountVinsList := AccountVinsList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("accountId", strconv.Itoa(d.Get("account_id").(int))) + + log.Debugf("utilityAccountVinsListCheckPresence: load account list") + accountVinsListRaw, err := c.DecortAPICall(ctx, "POST", accountListVinsAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(accountVinsListRaw), &accountVinsList) + if err != nil { + return nil, err + } + + return accountVinsList, nil +} diff --git a/internal/service/cloudbroker/disks/api.go b/internal/service/cloudbroker/disks/api.go new file mode 100644 index 0000000..173c25b --- /dev/null +++ b/internal/service/cloudbroker/disks/api.go @@ -0,0 +1,41 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 disks + +const disksCreateAPI = "/restmachine/cloudbroker/disks/create" +const disksGetAPI = "/restmachine/cloudbroker/disks/get" +const disksListAPI = "/restmachine/cloudbroker/disks/list" +const disksResizeAPI = "/restmachine/cloudbroker/disks/resize2" +const disksRenameAPI = "/restmachine/cloudbroker/disks/rename" +const disksDeleteAPI = "/restmachine/cloudbroker/disks/delete" +const disksIOLimitAPI = "/restmachine/cloudbroker/disks/limitIO" +const disksRestoreAPI = "/restmachine/cloudbroker/disks/restore" diff --git a/internal/service/cloudbroker/disks/data_source_disk.go b/internal/service/cloudbroker/disks/data_source_disk.go new file mode 100644 index 0000000..f5e1982 --- /dev/null +++ b/internal/service/cloudbroker/disks/data_source_disk.go @@ -0,0 +1,389 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 disks + +import ( + "context" + "encoding/json" + + // "net/url" + + "github.com/google/uuid" + "github.com/rudecs/terraform-provider-decort/internal/constants" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceDiskRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + disk, err := utilityDiskCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + + diskAcl, _ := json.Marshal(disk.Acl) + + d.Set("account_id", disk.AccountID) + d.Set("account_name", disk.AccountName) + d.Set("acl", string(diskAcl)) + d.Set("boot_partition", disk.BootPartition) + d.Set("compute_id", disk.ComputeID) + d.Set("compute_name", disk.ComputeName) + d.Set("created_time", disk.CreatedTime) + d.Set("deleted_time", disk.DeletedTime) + d.Set("desc", disk.Desc) + d.Set("destruction_time", disk.DestructionTime) + d.Set("devicename", disk.DeviceName) + d.Set("disk_path", disk.DiskPath) + d.Set("gid", disk.GridID) + d.Set("guid", disk.GUID) + d.Set("disk_id", disk.ID) + d.Set("image_id", disk.ImageID) + d.Set("images", disk.Images) + d.Set("iotune", flattenIOTune(disk.IOTune)) + d.Set("iqn", disk.IQN) + d.Set("login", disk.Login) + d.Set("milestones", disk.Milestones) + d.Set("disk_name", disk.Name) + d.Set("order", disk.Order) + d.Set("params", disk.Params) + d.Set("parent_id", disk.ParentId) + d.Set("passwd", disk.Passwd) + d.Set("pci_slot", disk.PciSlot) + d.Set("pool", disk.Pool) + d.Set("purge_attempts", disk.PurgeAttempts) + d.Set("purge_time", disk.PurgeTime) + d.Set("reality_device_number", disk.RealityDeviceNumber) + d.Set("reference_id", disk.ReferenceId) + d.Set("res_id", disk.ResID) + d.Set("res_name", disk.ResName) + d.Set("role", disk.Role) + d.Set("sep_id", disk.SepID) + d.Set("sep_type", disk.SepType) + d.Set("size_max", disk.SizeMax) + d.Set("size_used", disk.SizeUsed) + d.Set("snapshots", flattendDiskSnapshotList(disk.Snapshots)) + d.Set("status", disk.Status) + d.Set("tech_status", disk.TechStatus) + d.Set("type", disk.Type) + d.Set("vmid", disk.VMID) + + return nil +} + +func dataSourceDiskSchemaMake() map[string]*schema.Schema { + rets := map[string]*schema.Schema{ + "disk_id": { + Type: schema.TypeInt, + Required: true, + }, + "account_id": { + Type: schema.TypeInt, + Computed: true, + }, + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + "acl": { + Type: schema.TypeString, + Computed: true, + }, + "boot_partition": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_id": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_name": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "desc": { + Type: schema.TypeString, + Computed: true, + }, + "destruction_time": { + Type: schema.TypeInt, + Computed: true, + }, + "devicename": { + Type: schema.TypeString, + Computed: true, + }, + "disk_path": { + Type: schema.TypeString, + Computed: true, + }, + "gid": { + Type: schema.TypeInt, + Computed: true, + }, + "guid": { + Type: schema.TypeInt, + Computed: true, + }, + "image_id": { + Type: schema.TypeInt, + Computed: true, + }, + "images": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "iotune": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "read_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "read_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "read_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "read_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "size_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "total_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "write_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "write_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "write_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "write_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "iqn": { + Type: schema.TypeString, + Computed: true, + }, + "login": { + Type: schema.TypeString, + Computed: true, + }, + "milestones": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_name": { + Type: schema.TypeString, + Computed: true, + }, + "order": { + Type: schema.TypeInt, + Computed: true, + }, + "params": { + Type: schema.TypeString, + Computed: true, + }, + "parent_id": { + Type: schema.TypeInt, + Computed: true, + }, + "passwd": { + Type: schema.TypeString, + Computed: true, + }, + "pci_slot": { + Type: schema.TypeInt, + Computed: true, + }, + "pool": { + Type: schema.TypeString, + Computed: true, + }, + "purge_attempts": { + Type: schema.TypeInt, + Computed: true, + }, + "purge_time": { + Type: schema.TypeInt, + Computed: true, + }, + "reality_device_number": { + Type: schema.TypeInt, + Computed: true, + }, + "reference_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_name": { + Type: schema.TypeString, + Computed: true, + }, + "role": { + Type: schema.TypeString, + Computed: true, + }, + "sep_id": { + Type: schema.TypeInt, + Computed: true, + }, + "sep_type": { + Type: schema.TypeString, + Computed: true, + }, + "size_max": { + Type: schema.TypeInt, + Computed: true, + }, + "size_used": { + Type: schema.TypeInt, + Computed: true, + }, + "snapshots": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "guid": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_guid": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_time": { + Type: schema.TypeInt, + Computed: true, + }, + "timestamp": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tech_status": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "vmid": { + Type: schema.TypeInt, + Computed: true, + }, + } + + return rets +} + +func DataSourceDisk() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceDiskRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceDiskSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/disks/data_source_disk_list.go b/internal/service/cloudbroker/disks/data_source_disk_list.go new file mode 100644 index 0000000..efe16b6 --- /dev/null +++ b/internal/service/cloudbroker/disks/data_source_disk_list.go @@ -0,0 +1,473 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 disks + +import ( + "context" + "encoding/json" + + "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 flattenIOTune(iot IOTune) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + temp := map[string]interface{}{ + "read_bytes_sec": iot.ReadBytesSec, + "read_bytes_sec_max": iot.ReadBytesSecMax, + "read_iops_sec": iot.ReadIopsSec, + "read_iops_sec_max": iot.ReadIopsSecMax, + "size_iops_sec": iot.SizeIopsSec, + "total_bytes_sec": iot.TotalBytesSec, + "total_bytes_sec_max": iot.TotalBytesSecMax, + "total_iops_sec": iot.TotalIopsSec, + "total_iops_sec_max": iot.TotalIopsSecMax, + "write_bytes_sec": iot.WriteBytesSec, + "write_bytes_sec_max": iot.WriteBytesSecMax, + "write_iops_sec": iot.WriteIopsSec, + "write_iops_sec_max": iot.WriteIopsSecMax, + } + + res = append(res, temp) + return res +} + +func flattenDiskList(dl DisksList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, disk := range dl { + diskAcl, _ := json.Marshal(disk.Acl) + temp := map[string]interface{}{ + "account_id": disk.AccountID, + "account_name": disk.AccountName, + "acl": string(diskAcl), + "boot_partition": disk.BootPartition, + "compute_id": disk.ComputeID, + "compute_name": disk.ComputeName, + "created_time": disk.CreatedTime, + "deleted_time": disk.DeletedTime, + "desc": disk.Desc, + "destruction_time": disk.DestructionTime, + "devicename": disk.DeviceName, + "disk_path": disk.DiskPath, + "gid": disk.GridID, + "guid": disk.GUID, + "disk_id": disk.ID, + "image_id": disk.ImageID, + "images": disk.Images, + "iotune": flattenIOTune(disk.IOTune), + "iqn": disk.IQN, + "login": disk.Login, + "machine_id": disk.MachineId, + "machine_name": disk.MachineName, + "milestones": disk.Milestones, + "disk_name": disk.Name, + "order": disk.Order, + "params": disk.Params, + "parent_id": disk.ParentId, + "passwd": disk.Passwd, + "pci_slot": disk.PciSlot, + "pool": disk.Pool, + "purge_attempts": disk.PurgeAttempts, + "purge_time": disk.PurgeTime, + "reality_device_number": disk.RealityDeviceNumber, + "reference_id": disk.ReferenceId, + "res_id": disk.ResID, + "res_name": disk.ResName, + "role": disk.Role, + "sep_id": disk.SepID, + "sep_type": disk.SepType, + "size_max": disk.SizeMax, + "size_used": disk.SizeUsed, + "snapshots": flattendDiskSnapshotList(disk.Snapshots), + "status": disk.Status, + "tech_status": disk.TechStatus, + "type": disk.Type, + "vmid": disk.VMID, + } + res = append(res, temp) + } + return res + +} + +func flattendDiskSnapshotList(sl SnapshotList) []interface{} { + res := make([]interface{}, 0) + for _, snapshot := range sl { + temp := map[string]interface{}{ + "guid": snapshot.Guid, + "label": snapshot.Label, + "res_id": snapshot.ResId, + "snap_set_guid": snapshot.SnapSetGuid, + "snap_set_time": snapshot.SnapSetTime, + "timestamp": snapshot.TimeStamp, + } + res = append(res, temp) + } + + return res + +} + +func dataSourceDiskListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + diskList, err := utilityDiskListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenDiskList(diskList)) + + return nil +} + +func dataSourceDiskListSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Optional: true, + Description: "ID of the account the disks belong to", + }, + "type": { + Type: schema.TypeString, + Optional: true, + Description: "type of the disks", + }, + "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.TypeString, + Computed: true, + }, + "boot_partition": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_id": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_name": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "desc": { + Type: schema.TypeString, + Computed: true, + }, + "destruction_time": { + Type: schema.TypeInt, + Computed: true, + }, + "devicename": { + Type: schema.TypeString, + Computed: true, + }, + "disk_path": { + Type: schema.TypeString, + Computed: true, + }, + "gid": { + Type: schema.TypeInt, + Computed: true, + }, + "guid": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_id": { + Type: schema.TypeInt, + Computed: true, + }, + "image_id": { + Type: schema.TypeInt, + Computed: true, + }, + "images": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "iotune": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "read_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "read_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "read_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "read_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "size_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "total_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "total_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "write_bytes_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "write_bytes_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + "write_iops_sec": { + Type: schema.TypeInt, + Computed: true, + }, + "write_iops_sec_max": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "iqn": { + Type: schema.TypeString, + Computed: true, + }, + "login": { + Type: schema.TypeString, + Computed: true, + }, + "machine_id": { + Type: schema.TypeInt, + Computed: true, + }, + "machine_name": { + Type: schema.TypeString, + Computed: true, + }, + "milestones": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_name": { + Type: schema.TypeString, + Computed: true, + }, + "order": { + Type: schema.TypeInt, + Computed: true, + }, + "params": { + Type: schema.TypeString, + Computed: true, + }, + "parent_id": { + Type: schema.TypeInt, + Computed: true, + }, + "passwd": { + Type: schema.TypeString, + Computed: true, + }, + "pci_slot": { + Type: schema.TypeInt, + Computed: true, + }, + "pool": { + Type: schema.TypeString, + Computed: true, + }, + "purge_attempts": { + Type: schema.TypeInt, + Computed: true, + }, + "purge_time": { + Type: schema.TypeInt, + Computed: true, + }, + "reality_device_number": { + Type: schema.TypeInt, + Computed: true, + }, + "reference_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_name": { + Type: schema.TypeString, + Computed: true, + }, + "role": { + Type: schema.TypeString, + Computed: true, + }, + "sep_id": { + Type: schema.TypeInt, + Computed: true, + }, + "sep_type": { + Type: schema.TypeString, + Computed: true, + }, + "size_max": { + Type: schema.TypeInt, + Computed: true, + }, + "size_used": { + Type: schema.TypeInt, + Computed: true, + }, + "snapshots": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "guid": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_guid": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_time": { + Type: schema.TypeInt, + Computed: true, + }, + "timestamp": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tech_status": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "vmid": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + } + return res +} + +func DataSourceDiskList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceDiskListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceDiskListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/disks/models.go b/internal/service/cloudbroker/disks/models.go new file mode 100644 index 0000000..b5298e0 --- /dev/null +++ b/internal/service/cloudbroker/disks/models.go @@ -0,0 +1,111 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 disks + +type Disk struct { + Acl map[string]interface{} `json:"acl"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + BootPartition int `json:"bootPartition"` + CreatedTime uint64 `json:"creationTime"` + ComputeID int `json:"computeId"` + ComputeName string `json:"computeName"` + DeletedTime uint64 `json:"deletionTime"` + DeviceName string `json:"devicename"` + Desc string `json:"desc"` + DestructionTime uint64 `json:"destructionTime"` + DiskPath string `json:"diskPath"` + GridID int `json:"gid"` + GUID int `json:"guid"` + ID uint `json:"id"` + ImageID int `json:"imageId"` + Images []int `json:"images"` + IOTune IOTune `json:"iotune"` + IQN string `json:"iqn"` + Login string `json:"login"` + Name string `json:"name"` + MachineId int `json:"machineId"` + MachineName string `json:"machineName"` + Milestones uint64 `json:"milestones"` + Order int `json:"order"` + Params string `json:"params"` + Passwd string `json:"passwd"` + ParentId int `json:"parentId"` + PciSlot int `json:"pciSlot"` + Pool string `json:"pool"` + PurgeTime uint64 `json:"purgeTime"` + PurgeAttempts uint64 `json:"purgeAttempts"` + RealityDeviceNumber int `json:"realityDeviceNumber"` + ReferenceId string `json:"referenceId"` + ResID string `json:"resId"` + ResName string `json:"resName"` + Role string `json:"role"` + SepType string `json:"sepType"` + SepID int `json:"sepId"` // NOTE: absent from compute/get output + SizeMax int `json:"sizeMax"` + SizeUsed int `json:"sizeUsed"` // sum over all snapshots of this disk to report total consumed space + Snapshots []Snapshot `json:"snapshots"` + Status string `json:"status"` + TechStatus string `json:"techStatus"` + Type string `json:"type"` + UpdateBy uint64 `json:"updateBy"` + VMID int `json:"vmid"` +} + +type Snapshot struct { + Guid string `json:"guid"` + Label string `json:"label"` + ResId string `json:"resId"` + SnapSetGuid string `json:"snapSetGuid"` + SnapSetTime uint64 `json:"snapSetTime"` + TimeStamp uint64 `json:"timestamp"` +} + +type SnapshotList []Snapshot + +type DisksList []Disk + +type IOTune struct { + ReadBytesSec int `json:"read_bytes_sec"` + ReadBytesSecMax int `json:"read_bytes_sec_max"` + ReadIopsSec int `json:"read_iops_sec"` + ReadIopsSecMax int `json:"read_iops_sec_max"` + SizeIopsSec int `json:"size_iops_sec"` + TotalBytesSec int `json:"total_bytes_sec"` + TotalBytesSecMax int `json:"total_bytes_sec_max"` + TotalIopsSec int `json:"total_iops_sec"` + TotalIopsSecMax int `json:"total_iops_sec_max"` + WriteBytesSec int `json:"write_bytes_sec"` + WriteBytesSecMax int `json:"write_bytes_sec_max"` + WriteIopsSec int `json:"write_iops_sec"` + WriteIopsSecMax int `json:"write_iops_sec_max"` +} diff --git a/internal/service/cloudbroker/disks/resource_disk.go b/internal/service/cloudbroker/disks/resource_disk.go new file mode 100644 index 0000000..9cb264b --- /dev/null +++ b/internal/service/cloudbroker/disks/resource_disk.go @@ -0,0 +1,638 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 disks + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" + + "github.com/rudecs/terraform-provider-decort/internal/constants" + "github.com/rudecs/terraform-provider-decort/internal/controller" + 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 resourceDiskCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("accountId", fmt.Sprintf("%d", d.Get("account_id").(int))) + urlValues.Add("gid", fmt.Sprintf("%d", d.Get("gid").(int))) + urlValues.Add("name", d.Get("disk_name").(string)) + urlValues.Add("size", fmt.Sprintf("%d", d.Get("size_max").(int))) + if typeRaw, ok := d.GetOk("type"); ok { + urlValues.Add("type", strings.ToUpper(typeRaw.(string))) + } else { + urlValues.Add("type", "D") + } + + if sepId, ok := d.GetOk("sep_id"); ok { + urlValues.Add("sep_id", strconv.Itoa(sepId.(int))) + } + + if poolName, ok := d.GetOk("pool"); ok { + urlValues.Add("pool", poolName.(string)) + } + + argVal, argSet := d.GetOk("desc") + if argSet { + urlValues.Add("description", argVal.(string)) + } + + diskId, err := c.DecortAPICall(ctx, "POST", disksCreateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + + d.SetId(diskId) // update ID of the resource to tell Terraform that the disk resource exists + + if iotuneRaw, ok := d.GetOk("iotune"); ok { + iot := iotuneRaw.([]interface{})[0] + iotune := iot.(map[string]interface{}) + urlValues.Add("diskId", diskId) + urlValues.Add("iops", strconv.Itoa(iotune["total_iops_sec"].(int))) + urlValues.Add("read_bytes_sec", strconv.Itoa(iotune["read_bytes_sec"].(int))) + urlValues.Add("read_bytes_sec_max", strconv.Itoa(iotune["read_bytes_sec_max"].(int))) + urlValues.Add("read_iops_sec", strconv.Itoa(iotune["read_iops_sec"].(int))) + urlValues.Add("read_iops_sec_max", strconv.Itoa(iotune["read_iops_sec_max"].(int))) + urlValues.Add("size_iops_sec", strconv.Itoa(iotune["size_iops_sec"].(int))) + urlValues.Add("total_bytes_sec", strconv.Itoa(iotune["total_bytes_sec"].(int))) + urlValues.Add("total_bytes_sec_max", strconv.Itoa(iotune["total_bytes_sec_max"].(int))) + urlValues.Add("total_iops_sec_max", strconv.Itoa(iotune["total_iops_sec_max"].(int))) + urlValues.Add("write_bytes_sec", strconv.Itoa(iotune["write_bytes_sec"].(int))) + urlValues.Add("write_bytes_sec_max", strconv.Itoa(iotune["write_bytes_sec_max"].(int))) + urlValues.Add("write_iops_sec", strconv.Itoa(iotune["write_iops_sec"].(int))) + urlValues.Add("write_iops_sec_max", strconv.Itoa(iotune["write_iops_sec_max"].(int))) + + _, err := c.DecortAPICall(ctx, "POST", disksIOLimitAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + + dgn := resourceDiskRead(ctx, d, m) + if dgn != nil { + return dgn + } + + return nil +} + +func resourceDiskRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + disk, err := utilityDiskCheckPresence(ctx, d, m) + if disk == nil { + d.SetId("") + if err != nil { + return diag.FromErr(err) + } + return nil + } + + diskAcl, _ := json.Marshal(disk.Acl) + + d.Set("account_id", disk.AccountID) + d.Set("account_name", disk.AccountName) + d.Set("acl", string(diskAcl)) + d.Set("boot_partition", disk.BootPartition) + d.Set("compute_id", disk.ComputeID) + d.Set("compute_name", disk.ComputeName) + d.Set("created_time", disk.CreatedTime) + d.Set("deleted_time", disk.DeletedTime) + d.Set("desc", disk.Desc) + d.Set("destruction_time", disk.DestructionTime) + d.Set("devicename", disk.DeviceName) + d.Set("disk_path", disk.DiskPath) + d.Set("gid", disk.GridID) + d.Set("guid", disk.GUID) + d.Set("disk_id", disk.ID) + d.Set("image_id", disk.ImageID) + d.Set("images", disk.Images) + d.Set("iotune", flattenIOTune(disk.IOTune)) + d.Set("iqn", disk.IQN) + d.Set("login", disk.Login) + d.Set("milestones", disk.Milestones) + d.Set("disk_name", disk.Name) + d.Set("order", disk.Order) + d.Set("params", disk.Params) + d.Set("parent_id", disk.ParentId) + d.Set("passwd", disk.Passwd) + d.Set("pci_slot", disk.PciSlot) + d.Set("pool", disk.Pool) + d.Set("purge_attempts", disk.PurgeAttempts) + d.Set("purge_time", disk.PurgeTime) + d.Set("reality_device_number", disk.RealityDeviceNumber) + d.Set("reference_id", disk.ReferenceId) + d.Set("res_id", disk.ResID) + d.Set("res_name", disk.ResName) + d.Set("role", disk.Role) + d.Set("sep_id", disk.SepID) + d.Set("sep_type", disk.SepType) + d.Set("size_max", disk.SizeMax) + d.Set("size_used", disk.SizeUsed) + d.Set("snapshots", flattendDiskSnapshotList(disk.Snapshots)) + d.Set("status", disk.Status) + d.Set("tech_status", disk.TechStatus) + d.Set("type", disk.Type) + d.Set("vmid", disk.VMID) + + return nil +} + +func resourceDiskUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + if d.HasChange("size_max") { + oldSize, newSize := d.GetChange("size_max") + if oldSize.(int) < newSize.(int) { + log.Debugf("resourceDiskUpdate: resizing disk ID %s - %d GB -> %d GB", + d.Id(), oldSize.(int), newSize.(int)) + urlValues.Add("diskId", d.Id()) + urlValues.Add("size", fmt.Sprintf("%d", newSize.(int))) + _, err := c.DecortAPICall(ctx, "POST", disksResizeAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + d.Set("size_max", newSize) + } else if oldSize.(int) > newSize.(int) { + return diag.FromErr(fmt.Errorf("resourceDiskUpdate: Disk ID %s - reducing disk size is not allowed", d.Id())) + } + urlValues = &url.Values{} + } + + if d.HasChange("disk_name") { + urlValues.Add("diskId", d.Id()) + urlValues.Add("name", d.Get("disk_name").(string)) + _, err := c.DecortAPICall(ctx, "POST", disksRenameAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + + if d.HasChange("iotune") { + iot := d.Get("iotune").([]interface{})[0] + iotune := iot.(map[string]interface{}) + urlValues.Add("diskId", d.Id()) + urlValues.Add("iops", strconv.Itoa(iotune["total_iops_sec"].(int))) + urlValues.Add("read_bytes_sec", strconv.Itoa(iotune["read_bytes_sec"].(int))) + urlValues.Add("read_bytes_sec_max", strconv.Itoa(iotune["read_bytes_sec_max"].(int))) + urlValues.Add("read_iops_sec", strconv.Itoa(iotune["read_iops_sec"].(int))) + urlValues.Add("read_iops_sec_max", strconv.Itoa(iotune["read_iops_sec_max"].(int))) + urlValues.Add("size_iops_sec", strconv.Itoa(iotune["size_iops_sec"].(int))) + urlValues.Add("total_bytes_sec", strconv.Itoa(iotune["total_bytes_sec"].(int))) + urlValues.Add("total_bytes_sec_max", strconv.Itoa(iotune["total_bytes_sec_max"].(int))) + urlValues.Add("total_iops_sec_max", strconv.Itoa(iotune["total_iops_sec_max"].(int))) + urlValues.Add("write_bytes_sec", strconv.Itoa(iotune["write_bytes_sec"].(int))) + urlValues.Add("write_bytes_sec_max", strconv.Itoa(iotune["write_bytes_sec_max"].(int))) + urlValues.Add("write_iops_sec", strconv.Itoa(iotune["write_iops_sec"].(int))) + urlValues.Add("write_iops_sec_max", strconv.Itoa(iotune["write_iops_sec_max"].(int))) + + _, err := c.DecortAPICall(ctx, "POST", disksIOLimitAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + + if d.HasChange("restore") { + if d.Get("restore").(bool) { + urlValues.Add("diskId", d.Id()) + urlValues.Add("reason", d.Get("reason").(string)) + + _, err := c.DecortAPICall(ctx, "POST", disksRestoreAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + } + + } + + return resourceDiskRead(ctx, d, m) +} + +func resourceDiskDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + + disk, err := utilityDiskCheckPresence(ctx, d, m) + if disk == nil { + if err != nil { + return diag.FromErr(err) + } + return nil + } + + params := &url.Values{} + params.Add("diskId", d.Id()) + params.Add("detach", strconv.FormatBool(d.Get("detach").(bool))) + params.Add("permanently", strconv.FormatBool(d.Get("permanently").(bool))) + params.Add("reason", d.Get("reason").(string)) + + c := m.(*controller.ControllerCfg) + _, err = c.DecortAPICall(ctx, "POST", disksDeleteAPI, params) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceDiskExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + + diskFacts, err := utilityDiskCheckPresence(ctx, d, m) + if diskFacts == nil { + if err != nil { + return false, err + } + return false, nil + } + return true, nil +} + +func resourceDiskSchemaMake() map[string]*schema.Schema { + rets := map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeInt, + Required: true, + }, + "disk_name": { + Type: schema.TypeString, + Required: true, + }, + "size_max": { + Type: schema.TypeInt, + Required: true, + }, + "gid": { + Type: schema.TypeInt, + Required: true, + }, + "pool": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "sep_id": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "desc": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{"D", "B", "T"}, false), + }, + + "detach": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "detach disk from machine first", + }, + "permanently": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "whether to completely delete the disk, works only with non attached disks", + }, + "reason": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: "reason for an action", + }, + "restore": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "restore deleting disk", + }, + + "disk_id": { + Type: schema.TypeInt, + Computed: true, + }, + "account_name": { + Type: schema.TypeString, + Computed: true, + }, + "acl": { + Type: schema.TypeString, + Computed: true, + }, + "boot_partition": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_id": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_name": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "destruction_time": { + Type: schema.TypeInt, + Computed: true, + }, + "devicename": { + Type: schema.TypeString, + Computed: true, + }, + "disk_path": { + Type: schema.TypeString, + Computed: true, + }, + "guid": { + Type: schema.TypeInt, + Computed: true, + }, + "image_id": { + Type: schema.TypeInt, + Computed: true, + }, + "images": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "iotune": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "read_bytes_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "read_bytes_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "read_iops_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "read_iops_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "size_iops_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "total_bytes_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "total_bytes_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "total_iops_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "total_iops_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "write_bytes_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "write_bytes_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "write_iops_sec": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "write_iops_sec_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "iqn": { + Type: schema.TypeString, + Computed: true, + }, + "login": { + Type: schema.TypeString, + Computed: true, + }, + "milestones": { + Type: schema.TypeInt, + Computed: true, + }, + + "order": { + Type: schema.TypeInt, + Computed: true, + }, + "params": { + Type: schema.TypeString, + Computed: true, + }, + "parent_id": { + Type: schema.TypeInt, + Computed: true, + }, + "passwd": { + Type: schema.TypeString, + Computed: true, + }, + "pci_slot": { + Type: schema.TypeInt, + Computed: true, + }, + + "purge_attempts": { + Type: schema.TypeInt, + Computed: true, + }, + "purge_time": { + Type: schema.TypeInt, + Computed: true, + }, + "reality_device_number": { + Type: schema.TypeInt, + Computed: true, + }, + "reference_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "res_name": { + Type: schema.TypeString, + Computed: true, + }, + "role": { + Type: schema.TypeString, + Computed: true, + }, + + "sep_type": { + Type: schema.TypeString, + Computed: true, + }, + "size_used": { + Type: schema.TypeInt, + Computed: true, + }, + "snapshots": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "guid": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Computed: true, + }, + "res_id": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_guid": { + Type: schema.TypeString, + Computed: true, + }, + "snap_set_time": { + Type: schema.TypeInt, + Computed: true, + }, + "timestamp": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tech_status": { + Type: schema.TypeString, + Computed: true, + }, + "vmid": { + Type: schema.TypeInt, + Computed: true, + }, + } + + return rets +} + +func ResourceDisk() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourceDiskCreate, + ReadContext: resourceDiskRead, + UpdateContext: resourceDiskUpdate, + DeleteContext: resourceDiskDelete, + + 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: resourceDiskSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/disks/utility_disk.go b/internal/service/cloudbroker/disks/utility_disk.go new file mode 100644 index 0000000..1fb4544 --- /dev/null +++ b/internal/service/cloudbroker/disks/utility_disk.go @@ -0,0 +1,70 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 disks + +import ( + "context" + "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 utilityDiskCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*Disk, error) { + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + disk := &Disk{} + + if d.Get("disk_id").(int) == 0 { + urlValues.Add("diskId", d.Id()) + } else { + urlValues.Add("diskId", strconv.Itoa(d.Get("disk_id").(int))) + } + + log.Debugf("utilityDiskCheckPresence: load disk") + diskRaw, err := c.DecortAPICall(ctx, "POST", disksGetAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(diskRaw), disk) + if err != nil { + return nil, err + } + + return disk, nil +} diff --git a/internal/service/cloudbroker/disks/utility_disk_list.go b/internal/service/cloudbroker/disks/utility_disk_list.go new file mode 100644 index 0000000..1782ac3 --- /dev/null +++ b/internal/service/cloudbroker/disks/utility_disk_list.go @@ -0,0 +1,77 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 disks + +import ( + "context" + "encoding/json" + "net/url" + "strconv" + "strings" + + "github.com/rudecs/terraform-provider-decort/internal/controller" + log "github.com/sirupsen/logrus" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func utilityDiskListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (DisksList, error) { + diskList := DisksList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + if page, ok := d.GetOk("page"); ok { + urlValues.Add("page", strconv.Itoa(page.(int))) + } + if size, ok := d.GetOk("size"); ok { + urlValues.Add("size", strconv.Itoa(size.(int))) + } + if diskType, ok := d.GetOk("type"); ok { + urlValues.Add("type", strings.ToUpper(diskType.(string))) + } + if accountId, ok := d.GetOk("accountId"); ok { + urlValues.Add("accountId", strconv.Itoa(accountId.(int))) + } + + log.Debugf("utilityDiskListCheckPresence: load disk list") + diskListRaw, err := c.DecortAPICall(ctx, "POST", disksListAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(diskListRaw), &diskList) + if err != nil { + return nil, err + } + + return diskList, nil +} diff --git a/internal/service/cloudbroker/k8s/api.go b/internal/service/cloudbroker/k8s/api.go new file mode 100644 index 0000000..125f0a2 --- /dev/null +++ b/internal/service/cloudbroker/k8s/api.go @@ -0,0 +1,49 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 k8s + +const K8sCreateAPI = "/restmachine/cloudbroker/k8s/create" +const K8sGetAPI = "/restmachine/cloudbroker/k8s/get" +const K8sUpdateAPI = "/restmachine/cloudbroker/k8s/update" +const K8sDeleteAPI = "/restmachine/cloudbroker/k8s/delete" + +const K8sWgCreateAPI = "/restmachine/cloudbroker/k8s/workersGroupAdd" +const K8sWgDeleteAPI = "/restmachine/cloudbroker/k8s/workersGroupDelete" + +const K8sWorkerAddAPI = "/restmachine/cloudbroker/k8s/workerAdd" +const K8sWorkerDeleteAPI = "/restmachine/cloudbroker/k8s/deleteWorkerFromGroup" + +const K8sGetConfigAPI = "/restmachine/cloudbroker/k8s/getConfig" + +const LbGetAPI = "/restmachine/cloudbroker/lb/get" + +const AsyncTaskGetAPI = "/restmachine/cloudbroker/tasks/get" diff --git a/internal/service/cloudbroker/k8s/models.go b/internal/service/cloudbroker/k8s/models.go new file mode 100644 index 0000000..7634dd0 --- /dev/null +++ b/internal/service/cloudbroker/k8s/models.go @@ -0,0 +1,131 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 k8s + +import ( + "encoding/json" + "fmt" + "strconv" +) + +type K8sNodeRecord struct { + ID int `json:"id"` + Name string `json:"name"` + Disk int `json:"disk"` + Cpu int `json:"cpu"` + Num int `json:"num"` + Ram int `json:"ram"` + DetailedInfo []struct { + ID int `json:"id"` + Name string `json:"name"` + } `json:"detailedInfo"` +} + +//K8sRecord represents k8s instance +type K8sRecord struct { + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + CI int `json:"ciId"` + ID int `json:"id"` + Groups struct { + Masters K8sNodeRecord `json:"masters"` + Workers []K8sNodeRecord `json:"workers"` + } `json:"k8sGroups"` + LbID int `json:"lbId"` + Name string `json:"name"` + RgID int `json:"rgId"` + RgName string `json:"rgName"` +} + +//LbRecord represents load balancer instance +type LbRecord struct { + ID int `json:"id"` + Name string `json:"name"` + RgID int `json:"rgId"` + VinsID int `json:"vinsId"` + ExtNetID int `json:"extnetId"` + PrimaryNode struct { + BackendIP string `json:"backendIp"` + ComputeID int `json:"computeId"` + FrontendIP string `json:"frontendIp"` + NetworkID int `json:"networkId"` + } `json:"primaryNode"` +} + +//Blasphemous workaround for parsing Result value +type TaskResult int + +func (r *TaskResult) UnmarshalJSON(b []byte) error { + if b[0] == '"' { + b := b[1 : len(b)-1] + if len(b) == 0 { + *r = 0 + return nil + } + n, err := strconv.Atoi(string(b)) + if err != nil { + return err + } + *r = TaskResult(n) + } else if b[0] == '[' { + res := []interface{}{} + if err := json.Unmarshal(b, &res); err != nil { + return err + } + if n, ok := res[0].(float64); ok { + *r = TaskResult(n) + } else { + return fmt.Errorf("could not unmarshal %v into int", res[0]) + } + } + + return nil +} + +//AsyncTask represents a long task completion status +type AsyncTask struct { + AuditID string `json:"auditId"` + Completed bool `json:"completed"` + Error string `json:"error"` + Log []string `json:"log"` + Result TaskResult `json:"result"` + Stage string `json:"stage"` + Status string `json:"status"` + UpdateTime uint64 `json:"updateTime"` + UpdatedTime uint64 `json:"updatedTime"` +} + +type SshKeyConfig struct { + User string + SshKey string + UserShell string +} diff --git a/internal/service/cloudbroker/k8s/node_subresource.go b/internal/service/cloudbroker/k8s/node_subresource.go new file mode 100644 index 0000000..78fc9ce --- /dev/null +++ b/internal/service/cloudbroker/k8s/node_subresource.go @@ -0,0 +1,105 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 k8s + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func nodeMasterDefault() K8sNodeRecord { + return K8sNodeRecord{ + Num: 1, + Cpu: 2, + Ram: 2048, + Disk: 0, + } +} + +func nodeWorkerDefault() K8sNodeRecord { + return K8sNodeRecord{ + Num: 1, + Cpu: 1, + Ram: 1024, + Disk: 0, + } +} + +func parseNode(nodeList []interface{}) K8sNodeRecord { + node := nodeList[0].(map[string]interface{}) + + return K8sNodeRecord{ + Num: node["num"].(int), + Cpu: node["cpu"].(int), + Ram: node["ram"].(int), + Disk: node["disk"].(int), + } +} + +func nodeToResource(node K8sNodeRecord) []interface{} { + mp := make(map[string]interface{}) + + mp["num"] = node.Num + mp["cpu"] = node.Cpu + mp["ram"] = node.Ram + mp["disk"] = node.Disk + + return []interface{}{mp} +} + +func nodeK8sSubresourceSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "num": { + Type: schema.TypeInt, + Required: true, + Description: "Number of nodes to create.", + }, + + "cpu": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "Node CPU count.", + }, + + "ram": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "Node RAM in MB.", + }, + + "disk": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "Node boot disk size in GB.", + }, + } +} diff --git a/internal/service/cloudbroker/k8s/resource_k8s.go b/internal/service/cloudbroker/k8s/resource_k8s.go new file mode 100644 index 0000000..a7f6301 --- /dev/null +++ b/internal/service/cloudbroker/k8s/resource_k8s.go @@ -0,0 +1,403 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 k8s + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" + "time" + + "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" + "github.com/rudecs/terraform-provider-decort/internal/controller" + log "github.com/sirupsen/logrus" +) + +func resourceK8sCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceK8sCreate: called with name %s, rg %d", d.Get("name").(string), d.Get("rg_id").(int)) + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("name", d.Get("name").(string)) + urlValues.Add("rgId", strconv.Itoa(d.Get("rg_id").(int))) + urlValues.Add("k8ciId", strconv.Itoa(d.Get("k8sci_id").(int))) + urlValues.Add("workerGroupName", d.Get("wg_name").(string)) + + var masterNode K8sNodeRecord + if masters, ok := d.GetOk("masters"); ok { + masterNode = parseNode(masters.([]interface{})) + } else { + masterNode = nodeMasterDefault() + } + urlValues.Add("masterNum", strconv.Itoa(masterNode.Num)) + urlValues.Add("masterCpu", strconv.Itoa(masterNode.Cpu)) + urlValues.Add("masterRam", strconv.Itoa(masterNode.Ram)) + urlValues.Add("masterDisk", strconv.Itoa(masterNode.Disk)) + + var workerNode K8sNodeRecord + if workers, ok := d.GetOk("workers"); ok { + workerNode = parseNode(workers.([]interface{})) + } else { + workerNode = nodeWorkerDefault() + } + urlValues.Add("workerNum", strconv.Itoa(workerNode.Num)) + urlValues.Add("workerCpu", strconv.Itoa(workerNode.Cpu)) + urlValues.Add("workerRam", strconv.Itoa(workerNode.Ram)) + urlValues.Add("workerDisk", strconv.Itoa(workerNode.Disk)) + + //if withLB, ok := d.GetOk("with_lb"); ok { + //urlValues.Add("withLB", strconv.FormatBool(withLB.(bool))) + //} + urlValues.Add("withLB", strconv.FormatBool(true)) + + if extNet, ok := d.GetOk("extnet_id"); ok { + urlValues.Add("extnetId", strconv.Itoa(extNet.(int))) + } else { + urlValues.Add("extnetId", "0") + } + + //if desc, ok := d.GetOk("desc"); ok { + //urlValues.Add("desc", desc.(string)) + //} + + resp, err := c.DecortAPICall(ctx, "POST", K8sCreateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + urlValues = &url.Values{} + urlValues.Add("auditId", strings.Trim(resp, `"`)) + + for { + resp, err := c.DecortAPICall(ctx, "POST", AsyncTaskGetAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + task := AsyncTask{} + if err := json.Unmarshal([]byte(resp), &task); err != nil { + return diag.FromErr(err) + } + log.Debugf("resourceK8sCreate: instance creating - %s", task.Stage) + + if task.Completed { + if task.Error != "" { + return diag.FromErr(fmt.Errorf("cannot create k8s instance: %v", task.Error)) + } + + d.SetId(strconv.Itoa(int(task.Result))) + break + } + + time.Sleep(time.Second * 10) + } + + k8s, err := utilityK8sCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + d.Set("default_wg_id", k8s.Groups.Workers[0].ID) + + urlValues = &url.Values{} + urlValues.Add("lbId", strconv.Itoa(k8s.LbID)) + + resp, err = c.DecortAPICall(ctx, "POST", LbGetAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + var lb LbRecord + if err := json.Unmarshal([]byte(resp), &lb); err != nil { + return diag.FromErr(err) + } + d.Set("extnet_id", lb.ExtNetID) + d.Set("lb_ip", lb.PrimaryNode.FrontendIP) + + urlValues = &url.Values{} + urlValues.Add("k8sId", d.Id()) + kubeconfig, err := c.DecortAPICall(ctx, "POST", K8sGetConfigAPI, urlValues) + if err != nil { + log.Warnf("could not get kubeconfig: %v", err) + } + d.Set("kubeconfig", kubeconfig) + + return nil +} + +func resourceK8sRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceK8sRead: called with id %s, rg %d", d.Id(), d.Get("rg_id").(int)) + + k8s, err := utilityK8sCheckPresence(ctx, d, m) + if k8s == nil { + d.SetId("") + return diag.FromErr(err) + } + + d.Set("name", k8s.Name) + d.Set("rg_id", k8s.RgID) + d.Set("k8sci_id", k8s.CI) + d.Set("wg_name", k8s.Groups.Workers[0].Name) + d.Set("masters", nodeToResource(k8s.Groups.Masters)) + d.Set("workers", nodeToResource(k8s.Groups.Workers[0])) + d.Set("default_wg_id", k8s.Groups.Workers[0].ID) + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("lbId", strconv.Itoa(k8s.LbID)) + + resp, err := c.DecortAPICall(ctx, "POST", LbGetAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + var lb LbRecord + if err := json.Unmarshal([]byte(resp), &lb); err != nil { + return diag.FromErr(err) + } + d.Set("extnet_id", lb.ExtNetID) + d.Set("lb_ip", lb.PrimaryNode.FrontendIP) + + urlValues = &url.Values{} + urlValues.Add("k8sId", d.Id()) + kubeconfig, err := c.DecortAPICall(ctx, "POST", K8sGetConfigAPI, urlValues) + if err != nil { + log.Warnf("could not get kubeconfig: %v", err) + } + d.Set("kubeconfig", kubeconfig) + + return nil +} + +func resourceK8sUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceK8sUpdate: called with id %s, rg %d", d.Id(), d.Get("rg_id").(int)) + + c := m.(*controller.ControllerCfg) + + if d.HasChange("name") { + urlValues := &url.Values{} + urlValues.Add("k8sId", d.Id()) + urlValues.Add("name", d.Get("name").(string)) + + _, err := c.DecortAPICall(ctx, "POST", K8sUpdateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + } + + if d.HasChange("workers") { + k8s, err := utilityK8sCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + wg := k8s.Groups.Workers[0] + urlValues := &url.Values{} + urlValues.Add("k8sId", d.Id()) + urlValues.Add("workersGroupId", strconv.Itoa(wg.ID)) + + newWorkers := parseNode(d.Get("workers").([]interface{})) + + if newWorkers.Num > wg.Num { + urlValues.Add("num", strconv.Itoa(newWorkers.Num-wg.Num)) + if _, err := c.DecortAPICall(ctx, "POST", K8sWorkerAddAPI, urlValues); err != nil { + return diag.FromErr(err) + } + } else { + for i := wg.Num - 1; i >= newWorkers.Num; i-- { + urlValues.Set("workerId", strconv.Itoa(wg.DetailedInfo[i].ID)) + if _, err := c.DecortAPICall(ctx, "POST", K8sWorkerDeleteAPI, urlValues); err != nil { + return diag.FromErr(err) + } + } + } + } + + return nil +} + +func resourceK8sDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceK8sDelete: called with id %s, rg %d", d.Id(), d.Get("rg_id").(int)) + + k8s, err := utilityK8sCheckPresence(ctx, d, m) + if k8s == nil { + if err != nil { + return diag.FromErr(err) + } + return nil + } + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("k8sId", d.Id()) + urlValues.Add("permanently", "true") + + _, err = c.DecortAPICall(ctx, "POST", K8sDeleteAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceK8sExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + log.Debugf("resourceK8sExists: called with id %s, rg %d", d.Id(), d.Get("rg_id").(int)) + + k8s, err := utilityK8sCheckPresence(ctx, d, m) + if k8s == nil { + return false, err + } + + return true, nil +} + +func resourceK8sSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the cluster.", + }, + + "rg_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "Resource group ID that this instance belongs to.", + }, + + "k8sci_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "ID of the k8s catalog item to base this instance on.", + }, + + "wg_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name for first worker group created with cluster.", + }, + + "masters": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: nodeK8sSubresourceSchemaMake(), + }, + Description: "Master node(s) configuration.", + }, + + "workers": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: nodeK8sSubresourceSchemaMake(), + }, + Description: "Worker node(s) configuration.", + }, + + //"with_lb": { + //Type: schema.TypeBool, + //Optional: true, + //ForceNew: true, + //Default: true, + //Description: "Create k8s with load balancer if true.", + //}, + + "extnet_id": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + Description: "ID of the external network to connect workers to. If omitted network will be chosen by the platfom.", + }, + + //"desc": { + //Type: schema.TypeString, + //Optional: true, + //Description: "Text description of this instance.", + //}, + + "lb_ip": { + Type: schema.TypeString, + Computed: true, + Description: "IP address of default load balancer.", + }, + + "default_wg_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of default workers group for this instace.", + }, + + "kubeconfig": { + Type: schema.TypeString, + Computed: true, + Description: "Kubeconfig for cluster access.", + }, + } +} + +func ResourceK8s() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourceK8sCreate, + ReadContext: resourceK8sRead, + UpdateContext: resourceK8sUpdate, + DeleteContext: resourceK8sDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: &constants.Timeout20m, + Read: &constants.Timeout30s, + Update: &constants.Timeout20m, + Delete: &constants.Timeout60s, + Default: &constants.Timeout60s, + }, + + Schema: resourceK8sSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/k8s/resource_k8s_wg.go b/internal/service/cloudbroker/k8s/resource_k8s_wg.go new file mode 100644 index 0000000..d1eeafb --- /dev/null +++ b/internal/service/cloudbroker/k8s/resource_k8s_wg.go @@ -0,0 +1,258 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 k8s + +import ( + "context" + "net/url" + "strconv" + + "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" + "github.com/rudecs/terraform-provider-decort/internal/controller" + log "github.com/sirupsen/logrus" +) + +func resourceK8sWgCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceK8sWgCreate: called with k8s id %d", d.Get("k8s_id").(int)) + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("k8sId", strconv.Itoa(d.Get("k8s_id").(int))) + urlValues.Add("name", d.Get("name").(string)) + urlValues.Add("workerNum", strconv.Itoa(d.Get("num").(int))) + urlValues.Add("workerCpu", strconv.Itoa(d.Get("cpu").(int))) + urlValues.Add("workerRam", strconv.Itoa(d.Get("ram").(int))) + urlValues.Add("workerDisk", strconv.Itoa(d.Get("disk").(int))) + + resp, err := c.DecortAPICall(ctx, "POST", K8sWgCreateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(resp) + + // This code is the supposed flow, but at the time of writing it's not yet implemented by the platfom + + //urlValues = &url.Values{} + //urlValues.Add("auditId", strings.Trim(resp, `"`)) + + //for { + //resp, err := controller.decortAPICall("POST", AsyncTaskGetAPI, urlValues) + //if err != nil { + //return err + //} + + //task := AsyncTask{} + //if err := json.Unmarshal([]byte(resp), &task); err != nil { + //return err + //} + //log.Debugf("resourceK8sCreate: workers group creating - %s", task.Stage) + + //if task.Completed { + //if task.Error != "" { + //return fmt.Errorf("cannot create workers group: %v", task.Error) + //} + + //d.SetId(strconv.Itoa(int(task.Result))) + //break + //} + + //time.Sleep(time.Second * 5) + //} + + return nil +} + +func resourceK8sWgRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceK8sWgRead: called with k8s id %d", d.Get("k8s_id").(int)) + + wg, err := utilityK8sWgCheckPresence(ctx, d, m) + if wg == nil { + d.SetId("") + return diag.FromErr(err) + } + + d.Set("name", wg.Name) + d.Set("num", wg.Num) + d.Set("cpu", wg.Cpu) + d.Set("ram", wg.Ram) + d.Set("disk", wg.Disk) + + return nil +} + +func resourceK8sWgUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceK8sWgUpdate: called with k8s id %d", d.Get("k8s_id").(int)) + + c := m.(*controller.ControllerCfg) + + wg, err := utilityK8sWgCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + urlValues := &url.Values{} + urlValues.Add("k8sId", strconv.Itoa(d.Get("k8s_id").(int))) + urlValues.Add("workersGroupId", d.Id()) + + if newNum := d.Get("num").(int); newNum > wg.Num { + urlValues.Add("num", strconv.Itoa(newNum-wg.Num)) + _, err := c.DecortAPICall(ctx, "POST", K8sWorkerAddAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + } else { + for i := wg.Num - 1; i >= newNum; i-- { + urlValues.Set("workerId", strconv.Itoa(wg.DetailedInfo[i].ID)) + _, err := c.DecortAPICall(ctx, "POST", K8sWorkerDeleteAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + } + } + + return nil +} + +func resourceK8sWgDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceK8sWgDelete: called with k8s id %d", d.Get("k8s_id").(int)) + + wg, err := utilityK8sWgCheckPresence(ctx, d, m) + if wg == nil { + if err != nil { + return diag.FromErr(err) + } + return nil + } + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("k8sId", strconv.Itoa(d.Get("k8s_id").(int))) + urlValues.Add("workersGroupId", strconv.Itoa(wg.ID)) + + _, err = c.DecortAPICall(ctx, "POST", K8sWgDeleteAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceK8sWgExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + log.Debugf("resourceK8sWgExists: called with k8s id %d", d.Get("k8s_id").(int)) + + wg, err := utilityK8sWgCheckPresence(ctx, d, m) + if wg == nil { + if err != nil { + return false, err + } + return false, nil + } + + return true, nil +} + +func resourceK8sWgSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "k8s_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "ID of k8s instance.", + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the worker group.", + }, + + "num": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + Description: "Number of worker nodes to create.", + }, + + "cpu": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: 1, + Description: "Worker node CPU count.", + }, + + "ram": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: 1024, + Description: "Worker node RAM in MB.", + }, + + "disk": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: 0, + Description: "Worker node boot disk size. If unspecified or 0, size is defined by OS image size.", + }, + } +} + +func ResourceK8sWg() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourceK8sWgCreate, + ReadContext: resourceK8sWgRead, + UpdateContext: resourceK8sWgUpdate, + DeleteContext: resourceK8sWgDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: &constants.Timeout20m, + Read: &constants.Timeout30s, + Update: &constants.Timeout20m, + Delete: &constants.Timeout60s, + Default: &constants.Timeout60s, + }, + + Schema: resourceK8sWgSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/k8s/utility_k8s.go b/internal/service/cloudbroker/k8s/utility_k8s.go new file mode 100644 index 0000000..2c4270c --- /dev/null +++ b/internal/service/cloudbroker/k8s/utility_k8s.go @@ -0,0 +1,63 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 k8s + +import ( + "context" + "encoding/json" + "net/url" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/rudecs/terraform-provider-decort/internal/controller" +) + +func utilityK8sCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*K8sRecord, error) { + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("k8sId", d.Id()) + + resp, err := c.DecortAPICall(ctx, "POST", K8sGetAPI, urlValues) + if err != nil { + return nil, err + } + + if resp == "" { + return nil, nil + } + + var k8s K8sRecord + if err := json.Unmarshal([]byte(resp), &k8s); err != nil { + return nil, err + } + + return &k8s, nil +} diff --git a/internal/service/cloudbroker/k8s/utility_k8s_wg.go b/internal/service/cloudbroker/k8s/utility_k8s_wg.go new file mode 100644 index 0000000..9aef352 --- /dev/null +++ b/internal/service/cloudbroker/k8s/utility_k8s_wg.go @@ -0,0 +1,75 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 k8s + +import ( + "context" + "encoding/json" + "net/url" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/rudecs/terraform-provider-decort/internal/controller" +) + +func utilityK8sWgCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*K8sNodeRecord, error) { + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("k8sId", strconv.Itoa(d.Get("k8s_id").(int))) + + resp, err := c.DecortAPICall(ctx, "POST", K8sGetAPI, urlValues) + if err != nil { + return nil, err + } + + if resp == "" { + return nil, nil + } + + var k8s K8sRecord + if err := json.Unmarshal([]byte(resp), &k8s); err != nil { + return nil, err + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return nil, err + } + + for _, wg := range k8s.Groups.Workers { + if wg.ID == id { + return &wg, nil + } + } + + return nil, nil +} diff --git a/internal/service/cloudbroker/kvmvm/api.go b/internal/service/cloudbroker/kvmvm/api.go new file mode 100644 index 0000000..493d461 --- /dev/null +++ b/internal/service/cloudbroker/kvmvm/api.go @@ -0,0 +1,46 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 kvmvm + +const KvmX86CreateAPI = "/restmachine/cloudbroker/kvmx86/create" +const KvmPPCCreateAPI = "/restmachine/cloudbroker/kvmppc/create" +const ComputeGetAPI = "/restmachine/cloudbroker/compute/get" +const RgListComputesAPI = "/restmachine/cloudbroker/rg/listComputes" +const ComputeNetAttachAPI = "/restmachine/cloudbroker/compute/netAttach" +const ComputeNetDetachAPI = "/restmachine/cloudbroker/compute/netDetach" +const ComputeDiskAttachAPI = "/restmachine/cloudbroker/compute/diskAttach" +const ComputeDiskDetachAPI = "/restmachine/cloudbroker/compute/diskDetach" +const ComputeStartAPI = "/restmachine/cloudbroker/compute/start" +const ComputeStopAPI = "/restmachine/cloudbroker/compute/stop" +const ComputeResizeAPI = "/restmachine/cloudbroker/compute/resize" +const DisksResizeAPI = "/restmachine/cloudbroker/disks/resize2" +const ComputeDeleteAPI = "/restmachine/cloudbroker/compute/delete" diff --git a/internal/service/cloudbroker/kvmvm/data_source_compute.go b/internal/service/cloudbroker/kvmvm/data_source_compute.go new file mode 100644 index 0000000..4622b2b --- /dev/null +++ b/internal/service/cloudbroker/kvmvm/data_source_compute.go @@ -0,0 +1,385 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 kvmvm + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + // "net/url" + + "github.com/rudecs/terraform-provider-decort/internal/constants" + 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/helper/validation" +) + +// Parse list of all disks from API compute/get into a list of "extra disks" attached to this compute +// Extra disks are all compute disks but a boot disk. +func parseComputeDisksToExtraDisks(disks []DiskRecord) []interface{} { + // this return value will be used to d.Set("extra_disks",) item of dataSourceCompute schema, + // which is a simple list of integer disk IDs excluding boot disk ID + length := len(disks) + log.Debugf("parseComputeDisksToExtraDisks: called for %d disks", length) + + if length == 0 || (length == 1 && disks[0].Type == "B") { + // the disk list is empty (which is kind of strange - diskless compute?), or + // there is only one disk in the list and it is a boot disk; + // as we skip boot disks, the result will be of 0 length anyway + return make([]interface{}, 0) + } + + result := make([]interface{}, length-1) + idx := 0 + for _, value := range disks { + if value.Type == "B" { + // skip boot disk when iterating over the list of disks + continue + } + + result[idx] = value.ID + idx++ + } + + return result +} + +func parseBootDiskSize(disks []DiskRecord) int { + // this return value will be used to d.Set("boot_disk_size",) item of dataSourceCompute schema + if len(disks) == 0 { + return 0 + } + + for _, value := range disks { + if value.Type == "B" { + return value.SizeMax + } + } + + return 0 +} + +func parseBootDiskId(disks []DiskRecord) uint { + // this return value will be used to d.Set("boot_disk_id",) item of dataSourceCompute schema + if len(disks) == 0 { + return 0 + } + + for _, value := range disks { + if value.Type == "B" { + return value.ID + } + } + + return 0 +} + +func findBootDisk(disks []DiskRecord) (*DiskRecord, error) { + for _, d := range disks { + if d.Type == "B" { + return &d, nil + } + } + + return nil, errors.New("boot disk not found") +} + +// Parse the list of interfaces from compute/get response into a list of networks +// attached to this compute +func parseComputeInterfacesToNetworks(ifaces []InterfaceRecord) []interface{} { + // return value will be used to d.Set("network") item of dataSourceCompute schema + length := len(ifaces) + log.Debugf("parseComputeInterfacesToNetworks: called for %d ifaces", length) + + result := []interface{}{} + + for _, value := range ifaces { + elem := make(map[string]interface{}) + // Keys in this map should correspond to the Schema definition + // as returned by networkSubresourceSchemaMake() + elem["net_id"] = value.NetID + elem["net_type"] = value.NetType + elem["ip_address"] = value.IPAddress + elem["mac"] = value.MAC + + // log.Debugf(" element %d: net_id=%d, net_type=%s", i, value.NetID, value.NetType) + + result = append(result, elem) + } + + return result +} + +func flattenCompute(d *schema.ResourceData, compFacts string) error { + // This function expects that compFacts string contains response from API compute/get, + // i.e. detailed information about compute instance. + // + // NOTE: this function modifies ResourceData argument - as such it should never be called + // from resourceComputeExists(...) method + model := ComputeGetResp{} + log.Debugf("flattenCompute: ready to unmarshal string %s", compFacts) + err := json.Unmarshal([]byte(compFacts), &model) + if err != nil { + return err + } + + log.Debugf("flattenCompute: ID %d, RG ID %d", model.ID, model.RgID) + + d.SetId(fmt.Sprintf("%d", model.ID)) + // d.Set("compute_id", model.ID) - we should NOT set compute_id in the schema here: if it was set - it is already set, if it wasn't - we shouldn't + d.Set("name", model.Name) + d.Set("rg_id", model.RgID) + d.Set("rg_name", model.RgName) + d.Set("account_id", model.AccountID) + d.Set("account_name", model.AccountName) + d.Set("driver", model.Driver) + d.Set("cpu", model.Cpu) + d.Set("ram", model.Ram) + // d.Set("boot_disk_size", model.BootDiskSize) - bootdiskSize key in API compute/get is always zero, so we set boot_disk_size in another way + d.Set("image_id", model.ImageID) + d.Set("description", model.Desc) + d.Set("cloud_init", "applied") // NOTE: for existing compute we hard-code this value as an indicator for DiffSuppress fucntion + // d.Set("status", model.Status) + // d.Set("tech_status", model.TechStatus) + + if model.TechStatus == "STARTED" { + d.Set("started", true) + } else { + d.Set("started", false) + } + + bootDisk, err := findBootDisk(model.Disks) + if err != nil { + return err + } + + d.Set("boot_disk_size", bootDisk.SizeMax) + d.Set("boot_disk_id", bootDisk.ID) // we may need boot disk ID in resize operations + d.Set("sep_id", bootDisk.SepID) + d.Set("pool", bootDisk.Pool) + + if len(model.Disks) > 0 { + log.Debugf("flattenCompute: calling parseComputeDisksToExtraDisks for %d disks", len(model.Disks)) + if err = d.Set("extra_disks", parseComputeDisksToExtraDisks(model.Disks)); err != nil { + return err + } + } + + if len(model.Interfaces) > 0 { + log.Debugf("flattenCompute: calling parseComputeInterfacesToNetworks for %d interfaces", len(model.Interfaces)) + if err = d.Set("network", parseComputeInterfacesToNetworks(model.Interfaces)); err != nil { + return err + } + } + + if len(model.OsUsers) > 0 { + log.Debugf("flattenCompute: calling parseOsUsers for %d logins", len(model.OsUsers)) + if err = d.Set("os_users", parseOsUsers(model.OsUsers)); err != nil { + return err + } + } + + return nil +} + +func dataSourceComputeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + compFacts, err := utilityComputeCheckPresence(ctx, d, m) + if compFacts == "" { + // if empty string is returned from utilityComputeCheckPresence then there is no + // such Compute and err tells so - just return it to the calling party + d.SetId("") // ensure ID is empty + return diag.FromErr(err) + } + + if err = flattenCompute(d, compFacts); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func DataSourceCompute() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceComputeRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Name of this compute instance. NOTE: this parameter is case sensitive.", + }, + + // TODO: consider removing compute_id from the schema, as it not practical to call this data provider if + // corresponding compute ID is already known + "compute_id": { + Type: schema.TypeInt, + Optional: true, + Description: "ID of the compute instance. If ID is specified, name and resource group ID are ignored.", + }, + + "rg_id": { + Type: schema.TypeInt, + Optional: true, + Description: "ID of the resource group where this compute instance is located.", + }, + + "rg_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the resource group where this compute instance is located.", + }, + + "account_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the account this compute instance belongs to.", + }, + + "account_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the account this compute instance belongs to.", + }, + + "driver": { + Type: schema.TypeString, + Computed: true, + Description: "Hardware architecture of this compute instance.", + }, + + "cpu": { + Type: schema.TypeInt, + Computed: true, + Description: "Number of CPUs allocated for this compute instance.", + }, + + "ram": { + Type: schema.TypeInt, + Computed: true, + Description: "Amount of RAM in MB allocated for this compute instance.", + }, + + "image_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the OS image this compute instance is based on.", + }, + + "image_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the OS image this compute instance is based on.", + }, + + "boot_disk_size": { + Type: schema.TypeInt, + Computed: true, + Description: "This compute instance boot disk size in GB.", + }, + + "boot_disk_id": { + Type: schema.TypeInt, + Computed: true, + Description: "This compute instance boot disk ID.", + }, + + "extra_disks": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + Description: "IDs of the extra disk(s) attached to this compute.", + }, + + /* + "disks": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: dataSourceDiskSchemaMake(), // ID, type, name, size, account ID, SEP ID, SEP type, pool, status, tech status, compute ID, image ID + }, + Description: "Detailed specification for all disks attached to this compute instance (including bood disk).", + }, + */ + + "network": { + Type: schema.TypeSet, + Optional: true, + MaxItems: constants.MaxNetworksPerCompute, + Elem: &schema.Resource{ + Schema: networkSubresourceSchemaMake(), + }, + Description: "Network connection(s) for this compute.", + }, + + "os_users": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: osUsersSubresourceSchemaMake(), + }, + Description: "Guest OS users provisioned on this compute instance.", + }, + + "description": { + Type: schema.TypeString, + Computed: true, + Description: "User-defined text description of this compute instance.", + }, + + "cloud_init": { + Type: schema.TypeString, + Computed: true, + Description: "Placeholder for cloud_init parameters.", + }, + + "started": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Is compute started.", + }, + }, + } +} diff --git a/internal/service/cloudbroker/kvmvm/models.go b/internal/service/cloudbroker/kvmvm/models.go new file mode 100644 index 0000000..220fafd --- /dev/null +++ b/internal/service/cloudbroker/kvmvm/models.go @@ -0,0 +1,190 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 kvmvm + +type DiskRecord struct { + Acl map[string]interface{} `json:"acl"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + BootPartition int `json:"bootPartition"` + CreatedTime uint64 `json:"creationTime"` + ComputeID int `json:"computeId"` + ComputeName string `json:"computeName"` + DeletedTime uint64 `json:"deletionTime"` + DeviceName string `json:"devicename"` + Desc string `json:"desc"` + DestructionTime uint64 `json:"destructionTime"` + DiskPath string `json:"diskPath"` + GridID int `json:"gid"` + GUID int `json:"guid"` + ID uint `json:"id"` + ImageID int `json:"imageId"` + Images []int `json:"images"` + IOTune map[string]interface{} `json:"iotune"` + IQN string `json:"iqn"` + Login string `json:"login"` + Name string `json:"name"` + MachineId int `json:"machineId"` + MachineName string `json:"machineName"` + Milestones uint64 `json:"milestones"` + Order int `json:"order"` + Params string `json:"params"` + Passwd string `json:"passwd"` + ParentId int `json:"parentId"` + PciSlot int `json:"pciSlot"` + Pool string `json:"pool"` + PurgeTime uint64 `json:"purgeTime"` + PurgeAttempts uint64 `json:"purgeAttempts"` + RealityDeviceNumber int `json:"realityDeviceNumber"` + ReferenceId string `json:"referenceId"` + ResID string `json:"resId"` + ResName string `json:"resName"` + Role string `json:"role"` + SepType string `json:"sepType"` + SepID int `json:"sepId"` // NOTE: absent from compute/get output + 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"` + UpdateBy uint64 `json:"updateBy"` + VMID int `json:"vmid"` +} + +type InterfaceRecord struct { + 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 InterfaceQosRecord struct { + ERate int `json:"eRate"` + Guid string `json:"guid"` + InBurst int `json:"inBurst"` + InRate int `json:"inRate"` +} + +type SnapshotRecord struct { + Guid string `json:"guid"` + Label string `json:"label"` + ResId string `json:"resId"` + SnapSetGuid string `json:"snapSetGuid"` + SnapSetTime uint64 `json:"snapSetTime"` + TimeStamp uint64 `json:"timestamp"` +} + +type SnapshotRecordList []SnapshotRecord + +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"` + Cpu int `json:"cpus"` + Desc string `json:"desc"` + Disks []DiskRecord `json:"disks"` + Driver string `json:"driver"` + 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"` // Tags were reworked since DECORT 3.7.1 + 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"` +} + +type OsUserRecord struct { + Guid string `json:"guid"` + Login string `json:"login"` + Password string `json:"password"` + PubKey string `json:"pubkey"` +} + +type SnapSetRecord struct { + Disks []int `json:"disks"` + Guid string `json:"guid"` + Label string `json:"label"` + TimeStamp uint64 `json:"timestamp"` +} + +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"` +} + +type RgListComputesResp []ComputeBriefRecord diff --git a/internal/service/cloudbroker/kvmvm/network_subresource.go b/internal/service/cloudbroker/kvmvm/network_subresource.go new file mode 100644 index 0000000..9bc94b4 --- /dev/null +++ b/internal/service/cloudbroker/kvmvm/network_subresource.go @@ -0,0 +1,154 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 kvmvm + +import ( + "bytes" + "hash/fnv" + + "github.com/rudecs/terraform-provider-decort/internal/statefuncs" + log "github.com/sirupsen/logrus" + + "sort" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +// This is subresource of compute resource used when creating/managing compute network connections + +func networkSubresIPAddreDiffSupperss(key, oldVal, newVal string, d *schema.ResourceData) bool { + if newVal != "" && newVal != oldVal { + log.Debugf("networkSubresIPAddreDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=FALSE", key, oldVal, newVal) + return false + } + log.Debugf("networkSubresIPAddreDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=TRUE", key, oldVal, newVal) + return true // suppress difference +} + +// This function is based on the original Terraform SerializeResourceForHash found +// in helper/schema/serialize.go +// It skips network subresource attributes, which are irrelevant for identification +// of unique network blocks +func networkSubresourceSerialize(output *bytes.Buffer, val interface{}, resource *schema.Resource) { + if val == nil { + return + } + + rs := resource.Schema + m := val.(map[string]interface{}) + + keys := make([]string, 0, len(rs)) + allComputed := true + + for k, val := range rs { + if val.Optional || val.Required { + allComputed = false + } + + keys = append(keys, k) + } + + sort.Strings(keys) + for _, k := range keys { + // explicitly ignore "ip_address" when hashing + if k == "ip_address" { + continue + } + + subSchema := rs[k] + // Skip attributes that are not user-provided. Computed attributes + // do not contribute to the hash since their ultimate value cannot + // be known at plan/diff time. + if !allComputed && !(subSchema.Required || subSchema.Optional) { + continue + } + + output.WriteString(k) + output.WriteRune(':') + value := m[k] + schema.SerializeValueForHash(output, value, subSchema) + } +} + +// HashNetworkSubresource hashes network subresource of compute resource. It uses +// specially designed networkSubresourceSerialize (see above) to make sure hashing +// does not involve attributes that we deem irrelevant to the uniqueness of network +// subresource definitions. +// It is this function that should be specified as SchemaSetFunc when creating Set +// from network subresource (e.g. in flattenCompute) +// +// This function is based on the original Terraform function HashResource from +// helper/schema/set.go +func HashNetworkSubresource(resource *schema.Resource) schema.SchemaSetFunc { + return func(v interface{}) int { + var serialized bytes.Buffer + networkSubresourceSerialize(&serialized, v, resource) + + hs := fnv.New32a() + hs.Write(serialized.Bytes()) + return int(hs.Sum32()) + } +} + +func networkSubresourceSchemaMake() map[string]*schema.Schema { + rets := map[string]*schema.Schema{ + "net_type": { + Type: schema.TypeString, + Required: true, + StateFunc: statefuncs.StateFuncToUpper, + ValidateFunc: validation.StringInSlice([]string{"EXTNET", "VINS"}, false), // observe case while validating + Description: "Type of the network for this connection, either EXTNET or VINS.", + }, + + "net_id": { + Type: schema.TypeInt, + Required: true, + Description: "ID of the network for this connection.", + }, + + "ip_address": { + Type: schema.TypeString, + Optional: true, + Computed: true, + DiffSuppressFunc: networkSubresIPAddreDiffSupperss, + Description: "Optional IP address to assign to this connection. This IP should belong to the selected network and free for use.", + }, + + "mac": { + Type: schema.TypeString, + Computed: true, + Description: "MAC address associated with this connection. MAC address is assigned automatically.", + }, + } + return rets +} diff --git a/internal/service/cloudbroker/kvmvm/osusers_subresource.go b/internal/service/cloudbroker/kvmvm/osusers_subresource.go new file mode 100644 index 0000000..2e6787f --- /dev/null +++ b/internal/service/cloudbroker/kvmvm/osusers_subresource.go @@ -0,0 +1,86 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 kvmvm + +import ( + log "github.com/sirupsen/logrus" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func parseOsUsers(logins []OsUserRecord) []interface{} { + var result = make([]interface{}, len(logins)) + + for index, value := range logins { + elem := make(map[string]interface{}) + + elem["guid"] = value.Guid + elem["login"] = value.Login + elem["password"] = value.Password + elem["public_key"] = value.PubKey + result[index] = elem + log.Debugf("parseOsUsers: parsed element %d - login %q", index, value.Login) + } + + return result +} + +func osUsersSubresourceSchemaMake() map[string]*schema.Schema { + rets := map[string]*schema.Schema{ + "guid": { + Type: schema.TypeString, + Computed: true, + Description: "GUID of this guest OS user.", + }, + + "login": { + Type: schema.TypeString, + Computed: true, + Description: "Login name of this guest OS user.", + }, + + "password": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "Password of this guest OS user.", + }, + + "public_key": { + Type: schema.TypeString, + Computed: true, + Description: "SSH public key of this guest OS user.", + }, + } + + return rets +} diff --git a/internal/service/cloudbroker/kvmvm/resource_compute.go b/internal/service/cloudbroker/kvmvm/resource_compute.go new file mode 100644 index 0000000..2d6c2a3 --- /dev/null +++ b/internal/service/cloudbroker/kvmvm/resource_compute.go @@ -0,0 +1,542 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 kvmvm + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "strconv" + + "github.com/rudecs/terraform-provider-decort/internal/constants" + "github.com/rudecs/terraform-provider-decort/internal/controller" + "github.com/rudecs/terraform-provider-decort/internal/statefuncs" + 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 cloudInitDiffSupperss(key, oldVal, newVal string, d *schema.ResourceData) bool { + if oldVal == "" && newVal != "applied" { + // if old value for "cloud_init" resource is empty string, it means that we are creating new compute + // and there is a chance that the user will want custom cloud init parameters - so we check if + // cloud_init is explicitly set in TF file by making sure that its new value is different from "applied", + // which is a reserved key word. + log.Debugf("cloudInitDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=FALSE", key, oldVal, newVal) + return false // there is a difference between stored and new value + } + log.Debugf("cloudInitDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=TRUE", key, oldVal, newVal) + return true // suppress difference +} + +func resourceComputeCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + // we assume all mandatory parameters it takes to create a comptue instance are properly + // specified - we rely on schema "Required" attributes to let Terraform validate them for us + + log.Debugf("resourceComputeCreate: called for Compute name %q, RG ID %d", d.Get("name").(string), d.Get("rg_id").(int)) + + // create basic Compute (i.e. without extra disks and network connections - those will be attached + // by subsequent individual API calls). + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("rgId", fmt.Sprintf("%d", d.Get("rg_id").(int))) + urlValues.Add("name", d.Get("name").(string)) + urlValues.Add("cpu", fmt.Sprintf("%d", d.Get("cpu").(int))) + urlValues.Add("ram", fmt.Sprintf("%d", d.Get("ram").(int))) + urlValues.Add("imageId", fmt.Sprintf("%d", d.Get("image_id").(int))) + urlValues.Add("bootDisk", fmt.Sprintf("%d", d.Get("boot_disk_size").(int))) + urlValues.Add("netType", "NONE") // at the 1st step create isolated compute + urlValues.Add("start", "0") // at the 1st step create compute in a stopped state + + argVal, argSet := d.GetOk("description") + if argSet { + urlValues.Add("desc", argVal.(string)) + } + + if sepID, ok := d.GetOk("sep_id"); ok { + urlValues.Add("sepId", strconv.Itoa(sepID.(int))) + } + + if pool, ok := d.GetOk("pool"); ok { + urlValues.Add("pool", pool.(string)) + } + + /* + sshKeysVal, sshKeysSet := d.GetOk("ssh_keys") + if sshKeysSet { + // process SSH Key settings and set API values accordingly + log.Debugf("resourceComputeCreate: calling makeSshKeysArgString to setup SSH keys for guest login(s)") + urlValues.Add("userdata", makeSshKeysArgString(sshKeysVal.([]interface{}))) + } + */ + + computeCreateAPI := KvmX86CreateAPI + driver := d.Get("driver").(string) + if driver == "KVM_PPC" { + computeCreateAPI = KvmPPCCreateAPI + log.Debugf("resourceComputeCreate: creating Compute of type KVM VM PowerPC") + } else { // note that we do not validate arch value for explicit "KVM_X86" here + log.Debugf("resourceComputeCreate: creating Compute of type KVM VM x86") + } + + argVal, argSet = d.GetOk("cloud_init") + if argSet { + // userdata must not be empty string and must not be a reserved keyword "applied" + userdata := argVal.(string) + if userdata != "" && userdata != "applied" { + urlValues.Add("userdata", userdata) + } + } + + apiResp, err := c.DecortAPICall(ctx, "POST", computeCreateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + // Compute create API returns ID of the new Compute instance on success + + d.SetId(apiResp) // update ID of the resource to tell Terraform that the resource exists, albeit partially + compId, _ := strconv.Atoi(apiResp) + + log.Debugf("resourceComputeCreate: new simple Compute ID %d, name %s created", compId, d.Get("name").(string)) + + // Configure data disks if any + argVal, argSet = d.GetOk("extra_disks") + if argSet && argVal.(*schema.Set).Len() > 0 { + // urlValues.Add("desc", argVal.(string)) + log.Debugf("resourceComputeCreate: calling utilityComputeExtraDisksConfigure to attach %d extra disk(s)", argVal.(*schema.Set).Len()) + err = utilityComputeExtraDisksConfigure(ctx, d, m, false) // do_delta=false, as we are working on a new compute + if err != nil { + log.Errorf("resourceComputeCreate: error when attaching extra disk(s) to a new Compute ID %d: %v", compId, err) + return diag.FromErr(err) + } + } + // Configure external networks if any + argVal, argSet = d.GetOk("network") + if argSet && argVal.(*schema.Set).Len() > 0 { + log.Debugf("resourceComputeCreate: calling utilityComputeNetworksConfigure to attach %d network(s)", argVal.(*schema.Set).Len()) + err = utilityComputeNetworksConfigure(ctx, d, m, false) // do_delta=false, as we are working on a new compute + if err != nil { + log.Errorf("resourceComputeCreate: error when attaching networks to a new Compute ID %d: %s", compId, err) + return diag.FromErr(err) + } + } + + // Note bene: we created compute in a STOPPED state (this is required to properly attach 1st network interface), + // now we need to start it before we report the sequence complete + if d.Get("started").(bool) { + reqValues := &url.Values{} + reqValues.Add("computeId", fmt.Sprintf("%d", compId)) + log.Debugf("resourceComputeCreate: starting Compute ID %d after completing its resource configuration", compId) + if _, err := c.DecortAPICall(ctx, "POST", ComputeStartAPI, reqValues); err != nil { + return diag.FromErr(err) + } + } + + log.Debugf("resourceComputeCreate: new Compute ID %d, name %s creation sequence complete", compId, d.Get("name").(string)) + + // We may reuse dataSourceComputeRead here as we maintain similarity + // between Compute resource and Compute data source schemas + // Compute read function will also update resource ID on success, so that Terraform + // will know the resource exists + return dataSourceComputeRead(ctx, d, m) +} + +func resourceComputeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceComputeRead: called for Compute name %s, RG ID %d", + d.Get("name").(string), d.Get("rg_id").(int)) + + compFacts, err := utilityComputeCheckPresence(ctx, d, m) + if compFacts == "" { + if err != nil { + return diag.FromErr(err) + } + // Compute with such name and RG ID was not found + return nil + } + + if err = flattenCompute(d, compFacts); err != nil { + return diag.FromErr(err) + } + + log.Debugf("resourceComputeRead: after flattenCompute: Compute ID %s, name %q, RG ID %d", + d.Id(), d.Get("name").(string), d.Get("rg_id").(int)) + + return nil +} + +func resourceComputeUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceComputeUpdate: called for Compute ID %s / name %s, RGID %d", + d.Id(), d.Get("name").(string), d.Get("rg_id").(int)) + + c := m.(*controller.ControllerCfg) + + /* + 1. Resize CPU/RAM + 2. Resize (grow) boot disk + 3. Update extra disks + 4. Update networks + 5. Start/stop + */ + + // 1. Resize CPU/RAM + params := &url.Values{} + doUpdate := false + params.Add("computeId", d.Id()) + + oldCpu, newCpu := d.GetChange("cpu") + if oldCpu.(int) != newCpu.(int) { + params.Add("cpu", fmt.Sprintf("%d", newCpu.(int))) + doUpdate = true + } else { + params.Add("cpu", "0") // no change to CPU allocation + } + + oldRam, newRam := d.GetChange("ram") + if oldRam.(int) != newRam.(int) { + params.Add("ram", fmt.Sprintf("%d", newRam.(int))) + doUpdate = true + } else { + params.Add("ram", "0") + } + + if doUpdate { + log.Debugf("resourceComputeUpdate: changing CPU %d -> %d and/or RAM %d -> %d", + oldCpu.(int), newCpu.(int), + oldRam.(int), newRam.(int)) + params.Add("force", "true") + _, err := c.DecortAPICall(ctx, "POST", ComputeResizeAPI, params) + if err != nil { + return diag.FromErr(err) + } + } + + // 2. Resize (grow) Boot disk + oldSize, newSize := d.GetChange("boot_disk_size") + if oldSize.(int) < newSize.(int) { + bdsParams := &url.Values{} + bdsParams.Add("diskId", fmt.Sprintf("%d", d.Get("boot_disk_id").(int))) + bdsParams.Add("size", fmt.Sprintf("%d", newSize.(int))) + log.Debugf("resourceComputeUpdate: compute ID %s, boot disk ID %d resize %d -> %d", + d.Id(), d.Get("boot_disk_id").(int), oldSize.(int), newSize.(int)) + _, err := c.DecortAPICall(ctx, "POST", DisksResizeAPI, bdsParams) + if err != nil { + return diag.FromErr(err) + } + } else if oldSize.(int) > newSize.(int) { + log.Warnf("resourceComputeUpdate: compute ID %s - shrinking boot disk is not allowed", d.Id()) + } + + // 3. Calculate and apply changes to data disks + err := utilityComputeExtraDisksConfigure(ctx, d, m, true) // pass do_delta = true to apply changes, if any + if err != nil { + return diag.FromErr(err) + } + + // 4. Calculate and apply changes to network connections + err = utilityComputeNetworksConfigure(ctx, d, m, true) // pass do_delta = true to apply changes, if any + if err != nil { + return diag.FromErr(err) + } + + if d.HasChange("started") { + params := &url.Values{} + params.Add("computeId", d.Id()) + if d.Get("started").(bool) { + if _, err := c.DecortAPICall(ctx, "POST", ComputeStartAPI, params); err != nil { + return diag.FromErr(err) + } + } else { + if _, err := c.DecortAPICall(ctx, "POST", ComputeStopAPI, params); err != nil { + return diag.FromErr(err) + } + } + } + + // we may reuse dataSourceComputeRead here as we maintain similarity + // between Compute resource and Compute data source schemas + return dataSourceComputeRead(ctx, d, m) +} + +func resourceComputeDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + // NOTE: this function destroys target Compute instance "permanently", so + // there is no way to restore it. + // If compute being destroyed has some extra disks attached, they are + // detached from the compute + log.Debugf("resourceComputeDelete: called for Compute name %s, RG ID %d", + d.Get("name").(string), d.Get("rg_id").(int)) + + compFacts, err := utilityComputeCheckPresence(ctx, d, m) + if compFacts == "" { + if err != nil { + return diag.FromErr(err) + } + // the target Compute does not exist - in this case according to Terraform best practice + // we exit from Destroy method without error + return nil + } + + c := m.(*controller.ControllerCfg) + + model := ComputeGetResp{} + log.Debugf("resourceComputeDelete: ready to unmarshal string %s", compFacts) + err = json.Unmarshal([]byte(compFacts), &model) + if err == nil && len(model.Disks) > 0 { + // prepare to detach data disks from compute - do it only if compFacts unmarshalled + // properly and the resulting model contains non-empty Disks list + for _, diskFacts := range model.Disks { + if diskFacts.Type == "B" { + // boot disk is never detached on compute delete + continue + } + + log.Debugf("resourceComputeDelete: ready to detach data disk ID %d from compute ID %s", diskFacts.ID, d.Id()) + + detachParams := &url.Values{} + detachParams.Add("computeId", d.Id()) + detachParams.Add("diskId", fmt.Sprintf("%d", diskFacts.ID)) + + _, err = c.DecortAPICall(ctx, "POST", ComputeDiskDetachAPI, detachParams) + if err != nil { + // We do not fail compute deletion on data disk detach errors + log.Errorf("resourceComputeDelete: error when detaching Disk ID %d: %s", diskFacts.ID, err) + } + } + } + + params := &url.Values{} + params.Add("computeId", d.Id()) + params.Add("permanently", "1") + // TODO: this is for the upcoming API update - params.Add("detachdisks", "1") + + _, err = c.DecortAPICall(ctx, "POST", ComputeDeleteAPI, params) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceComputeExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + // Reminder: according to Terraform rules, this function should not modify its ResourceData argument + log.Debugf("resourceComputeExist: called for Compute name %s, RG ID %d", + d.Get("name").(string), d.Get("rg_id").(int)) + + compFacts, err := utilityComputeCheckPresence(ctx, d, m) + if compFacts == "" { + if err != nil { + return false, err + } + return false, nil + } + return true, nil +} + +func ResourceCompute() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourceComputeCreate, + ReadContext: resourceComputeRead, + UpdateContext: resourceComputeUpdate, + DeleteContext: resourceComputeDelete, + + 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 compute. Compute names are case sensitive and must be unique in the resource group.", + }, + + "rg_id": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "ID of the resource group where this compute should be deployed.", + }, + + "driver": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + StateFunc: statefuncs.StateFuncToUpper, + ValidateFunc: validation.StringInSlice([]string{"KVM_X86", "KVM_PPC"}, false), // observe case while validating + Description: "Hardware architecture of this compute instance.", + }, + + "cpu": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, constants.MaxCpusPerCompute), + Description: "Number of CPUs to allocate to this compute instance.", + }, + + "ram": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(constants.MinRamPerCompute), + Description: "Amount of RAM in MB to allocate to this compute instance.", + }, + + "image_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "ID of the OS image to base this compute instance on.", + }, + + "boot_disk_size": { + Type: schema.TypeInt, + Required: true, + Description: "This compute instance boot disk size in GB. Make sure it is large enough to accomodate selected OS image.", + }, + + "sep_id": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + Description: "ID of SEP to create bootDisk on. Uses image's sepId if not set.", + }, + + "pool": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: "Pool to use if sepId is set, can be also empty if needed to be chosen by system.", + }, + + "extra_disks": { + Type: schema.TypeSet, + Optional: true, + MaxItems: constants.MaxExtraDisksPerCompute, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + Description: "Optional list of IDs of extra disks to attach to this compute. You may specify several extra disks.", + }, + + "network": { + Type: schema.TypeSet, + Optional: true, + MaxItems: constants.MaxNetworksPerCompute, + Elem: &schema.Resource{ + Schema: networkSubresourceSchemaMake(), + }, + Description: "Optional network connection(s) for this compute. You may specify several network blocks, one for each connection.", + }, + + /* + "ssh_keys": { + Type: schema.TypeList, + Optional: true, + MaxItems: MaxSshKeysPerCompute, + Elem: &schema.Resource{ + Schema: sshSubresourceSchemaMake(), + }, + Description: "SSH keys to authorize on this compute instance.", + }, + */ + + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Optional text description of this compute instance.", + }, + + "cloud_init": { + Type: schema.TypeString, + Optional: true, + Default: "applied", + DiffSuppressFunc: cloudInitDiffSupperss, + Description: "Optional cloud_init parameters. Applied when creating new compute instance only, ignored in all other cases.", + }, + + // The rest are Compute properties, which are "computed" once it is created + "rg_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the resource group where this compute instance is located.", + }, + + "account_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the account this compute instance belongs to.", + }, + + "account_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the account this compute instance belongs to.", + }, + + "boot_disk_id": { + Type: schema.TypeInt, + Computed: true, + Description: "This compute instance boot disk ID.", + }, + + "os_users": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: osUsersSubresourceSchemaMake(), + }, + Description: "Guest OS users provisioned on this compute instance.", + }, + + "started": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Is compute started.", + }, + }, + } +} diff --git a/internal/service/cloudbroker/kvmvm/utility_compute.go b/internal/service/cloudbroker/kvmvm/utility_compute.go new file mode 100644 index 0000000..0bf64ea --- /dev/null +++ b/internal/service/cloudbroker/kvmvm/utility_compute.go @@ -0,0 +1,310 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 kvmvm + +import ( + "context" + "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" +) + +func utilityComputeExtraDisksConfigure(ctx context.Context, d *schema.ResourceData, m interface{}, do_delta bool) error { + // d is filled with data according to computeResource schema, so extra disks config is retrieved via "extra_disks" key + // If do_delta is true, this function will identify changes between new and existing specs for extra disks and try to + // update compute configuration accordingly + // Otherwise it will apply whatever is found in the new set of "extra_disks" right away. + // Primary use of do_delta=false is when calling this function from compute Create handler. + + // Note that this function will not abort on API errors, but will continue to configure (attach / detach) other individual + // disks via atomic API calls. However, it will not retry failed manipulation on the same disk. + c := m.(*controller.ControllerCfg) + + log.Debugf("utilityComputeExtraDisksConfigure: called for Compute ID %s with do_delta = %t", d.Id(), do_delta) + + // NB: as of rc-1.25 "extra_disks" are TypeSet with the elem of TypeInt + old_set, new_set := d.GetChange("extra_disks") + + apiErrCount := 0 + var lastSavedError error + + if !do_delta { + if new_set.(*schema.Set).Len() < 1 { + return nil + } + + for _, disk := range new_set.(*schema.Set).List() { + urlValues := &url.Values{} + urlValues.Add("computeId", d.Id()) + urlValues.Add("diskId", fmt.Sprintf("%d", disk.(int))) + _, err := c.DecortAPICall(ctx, "POST", ComputeDiskAttachAPI, urlValues) + if err != nil { + // failed to attach extra disk - partial resource update + apiErrCount++ + lastSavedError = err + } + } + + if apiErrCount > 0 { + log.Errorf("utilityComputeExtraDisksConfigure: there were %d error(s) when attaching disks to Compute ID %s. Last error was: %s", + apiErrCount, d.Id(), lastSavedError) + return lastSavedError + } + + return nil + } + + detach_set := old_set.(*schema.Set).Difference(new_set.(*schema.Set)) + log.Debugf("utilityComputeExtraDisksConfigure: detach set has %d items for Compute ID %s", detach_set.Len(), d.Id()) + for _, diskId := range detach_set.List() { + urlValues := &url.Values{} + urlValues.Add("computeId", d.Id()) + urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int))) + _, err := c.DecortAPICall(ctx, "POST", ComputeDiskDetachAPI, urlValues) + if err != nil { + // failed to detach disk - there will be partial resource update + log.Errorf("utilityComputeExtraDisksConfigure: failed to detach disk ID %d from Compute ID %s: %s", diskId.(int), d.Id(), err) + apiErrCount++ + lastSavedError = err + } + } + + attach_set := new_set.(*schema.Set).Difference(old_set.(*schema.Set)) + log.Debugf("utilityComputeExtraDisksConfigure: attach set has %d items for Compute ID %s", attach_set.Len(), d.Id()) + for _, diskId := range attach_set.List() { + urlValues := &url.Values{} + urlValues.Add("computeId", d.Id()) + urlValues.Add("diskId", fmt.Sprintf("%d", diskId.(int))) + _, err := c.DecortAPICall(ctx, "POST", ComputeDiskAttachAPI, urlValues) + if err != nil { + // failed to attach disk - there will be partial resource update + log.Errorf("utilityComputeExtraDisksConfigure: failed to attach disk ID %d to Compute ID %s: %s", diskId.(int), d.Id(), err) + apiErrCount++ + lastSavedError = err + } + } + + if apiErrCount > 0 { + log.Errorf("utilityComputeExtraDisksConfigure: there were %d error(s) when managing disks of Compute ID %s. Last error was: %s", + apiErrCount, d.Id(), lastSavedError) + return lastSavedError + } + + return nil +} + +func utilityComputeNetworksConfigure(ctx context.Context, d *schema.ResourceData, m interface{}, do_delta bool) error { + // "d" is filled with data according to computeResource schema, so extra networks config is retrieved via "network" key + // If do_delta is true, this function will identify changes between new and existing specs for network and try to + // update compute configuration accordingly + // Otherwise it will apply whatever is found in the new set of "network" right away. + // Primary use of do_delta=false is when calling this function from compute Create handler. + + c := m.(*controller.ControllerCfg) + + old_set, new_set := d.GetChange("network") + + apiErrCount := 0 + var lastSavedError error + + if !do_delta { + if new_set.(*schema.Set).Len() < 1 { + return nil + } + + for _, runner := range new_set.(*schema.Set).List() { + urlValues := &url.Values{} + net_data := runner.(map[string]interface{}) + urlValues.Add("computeId", d.Id()) + urlValues.Add("netType", net_data["net_type"].(string)) + urlValues.Add("netId", fmt.Sprintf("%d", net_data["net_id"].(int))) + ipaddr, ipSet := net_data["ip_address"] // "ip_address" key is optional + if ipSet { + urlValues.Add("ipAddr", ipaddr.(string)) + } + _, err := c.DecortAPICall(ctx, "POST", ComputeNetAttachAPI, urlValues) + if err != nil { + // failed to attach network - partial resource update + apiErrCount++ + lastSavedError = err + } + } + + if apiErrCount > 0 { + log.Errorf("utilityComputeNetworksConfigure: there were %d error(s) when managing networks of Compute ID %s. Last error was: %s", + apiErrCount, d.Id(), lastSavedError) + return lastSavedError + } + return nil + } + + detach_set := old_set.(*schema.Set).Difference(new_set.(*schema.Set)) + log.Debugf("utilityComputeNetworksConfigure: detach set has %d items for Compute ID %s", detach_set.Len(), d.Id()) + for _, runner := range detach_set.List() { + urlValues := &url.Values{} + net_data := runner.(map[string]interface{}) + urlValues.Add("computeId", d.Id()) + urlValues.Add("ipAddr", net_data["ip_address"].(string)) + urlValues.Add("mac", net_data["mac"].(string)) + _, err := c.DecortAPICall(ctx, "POST", ComputeNetDetachAPI, urlValues) + if err != nil { + // failed to detach this network - there will be partial resource update + log.Errorf("utilityComputeNetworksConfigure: failed to detach net ID %d of type %s from Compute ID %s: %s", + net_data["net_id"].(int), net_data["net_type"].(string), d.Id(), err) + apiErrCount++ + lastSavedError = err + } + } + + attach_set := new_set.(*schema.Set).Difference(old_set.(*schema.Set)) + log.Debugf("utilityComputeNetworksConfigure: attach set has %d items for Compute ID %s", attach_set.Len(), d.Id()) + for _, runner := range attach_set.List() { + urlValues := &url.Values{} + net_data := runner.(map[string]interface{}) + urlValues.Add("computeId", d.Id()) + urlValues.Add("netId", fmt.Sprintf("%d", net_data["net_id"].(int))) + urlValues.Add("netType", net_data["net_type"].(string)) + if net_data["ip_address"].(string) != "" { + urlValues.Add("ipAddr", net_data["ip_address"].(string)) + } + _, err := c.DecortAPICall(ctx, "POST", ComputeNetAttachAPI, urlValues) + if err != nil { + // failed to attach this network - there will be partial resource update + log.Errorf("utilityComputeNetworksConfigure: failed to attach net ID %d of type %s to Compute ID %s: %s", + net_data["net_id"].(int), net_data["net_type"].(string), d.Id(), err) + apiErrCount++ + lastSavedError = err + } + } + + if apiErrCount > 0 { + log.Errorf("utilityComputeNetworksConfigure: there were %d error(s) when managing networks of Compute ID %s. Last error was: %s", + apiErrCount, d.Id(), lastSavedError) + return lastSavedError + } + + return nil +} + +func utilityComputeCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (string, error) { + // This function tries to locate Compute by one of the following approaches: + // - if compute_id is specified - locate by compute ID + // - if compute_name is specified - locate by a combination of compute name and resource + // group ID + // + // If succeeded, it returns non-empty string that contains JSON formatted facts about the + // Compute as returned by compute/get API call. + // Otherwise it returns empty string and meaningful error. + // + // This function does not modify its ResourceData argument, so it is safe to use it as core + // method for resource's Exists method. + // + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + // make it possible to use "read" & "check presence" functions with compute ID set so + // that Import of Compute resource is possible + idSet := false + theId, err := strconv.Atoi(d.Id()) + if err != nil || theId <= 0 { + computeId, argSet := d.GetOk("compute_id") // NB: compute_id is NOT present in computeResource schema! + if argSet { + theId = computeId.(int) + idSet = true + } + } else { + idSet = true + } + + if idSet { + // compute ID is specified, try to get compute instance straight by this ID + log.Debugf("utilityComputeCheckPresence: locating compute by its ID %d", theId) + urlValues.Add("computeId", fmt.Sprintf("%d", theId)) + computeFacts, err := c.DecortAPICall(ctx, "POST", ComputeGetAPI, urlValues) + if err != nil { + return "", err + } + return computeFacts, nil + } + + // ID was not set in the schema upon entering this function - work through Compute name + // and RG ID + computeName, argSet := d.GetOk("name") + if !argSet { + return "", fmt.Errorf("Cannot locate compute instance if name is empty and no compute ID specified") + } + + rgId, argSet := d.GetOk("rg_id") + if !argSet { + return "", fmt.Errorf("Cannot locate compute by name %s if no resource group ID is set", computeName.(string)) + } + + urlValues.Add("rgId", fmt.Sprintf("%d", rgId)) + apiResp, err := c.DecortAPICall(ctx, "POST", RgListComputesAPI, urlValues) + if err != nil { + return "", err + } + + log.Debugf("utilityComputeCheckPresence: ready to unmarshal string %s", apiResp) + + computeList := RgListComputesResp{} + err = json.Unmarshal([]byte(apiResp), &computeList) + if err != nil { + return "", err + } + + // log.Printf("%#v", computeList) + log.Debugf("utilityComputeCheckPresence: traversing decoded JSON of length %d", len(computeList)) + for index, item := range computeList { + // need to match Compute by name, skip Computes with the same name in DESTROYED satus + if item.Name == computeName.(string) && item.Status != "DESTROYED" { + log.Debugf("utilityComputeCheckPresence: index %d, matched name %s", index, item.Name) + // we found the Compute we need - now get detailed information via compute/get API + cgetValues := &url.Values{} + cgetValues.Add("computeId", fmt.Sprintf("%d", item.ID)) + apiResp, err = c.DecortAPICall(ctx, "POST", ComputeGetAPI, cgetValues) + if err != nil { + return "", err + } + return apiResp, nil + } + } + + return "", nil // there should be no error if Compute does not exist +} diff --git a/internal/service/cloudbroker/pcidevice/resource_pcidevice.go b/internal/service/cloudbroker/pcidevice/resource_pcidevice.go index dbf033c..ebcd5dc 100644 --- a/internal/service/cloudbroker/pcidevice/resource_pcidevice.go +++ b/internal/service/cloudbroker/pcidevice/resource_pcidevice.go @@ -47,22 +47,6 @@ import ( func resourcePcideviceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { log.Debugf("resourcePcideviceCreate: called for pcidevice %s", d.Get("name").(string)) - if deviceId, ok := d.GetOk("device_id"); ok { - if exists, err := resourcePcideviceExists(ctx, d, m); exists { - if err != nil { - return diag.FromErr(err) - } - d.SetId(strconv.Itoa(deviceId.(int))) - diagnostics := resourcePcideviceRead(ctx, d, m) - if diagnostics != nil { - return diagnostics - } - - return nil - } - return diag.Errorf("provided device id does not exist") - } - c := m.(*controller.ControllerCfg) urlValues := &url.Values{} urlValues.Add("name", d.Get("name").(string)) diff --git a/internal/service/cloudbroker/pfw/api.go b/internal/service/cloudbroker/pfw/api.go new file mode 100644 index 0000000..6268336 --- /dev/null +++ b/internal/service/cloudbroker/pfw/api.go @@ -0,0 +1,36 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 pfw + +const ComputePfwListAPI = "/restmachine/cloudbroker/compute/pfwList" +const ComputePfwAddAPI = "/restmachine/cloudbroker/compute/pfwAdd" +const ComputePfwDelAPI = "/restmachine/cloudbroker/compute/pfwDel" diff --git a/internal/service/cloudbroker/pfw/models.go b/internal/service/cloudbroker/pfw/models.go new file mode 100644 index 0000000..e74524b --- /dev/null +++ b/internal/service/cloudbroker/pfw/models.go @@ -0,0 +1,45 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 pfw +type PfwRecord struct { + 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"` +} + +type ComputePfwListResp []PfwRecord + + diff --git a/internal/service/cloudbroker/pfw/resource_pfw.go b/internal/service/cloudbroker/pfw/resource_pfw.go new file mode 100644 index 0000000..199187b --- /dev/null +++ b/internal/service/cloudbroker/pfw/resource_pfw.go @@ -0,0 +1,211 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 pfw + +import ( + "context" + "fmt" + "net/url" + "strconv" + + "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" + "github.com/rudecs/terraform-provider-decort/internal/constants" + "github.com/rudecs/terraform-provider-decort/internal/controller" + log "github.com/sirupsen/logrus" +) + +func resourcePfwCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourcePfwCreate: called for compute %d", d.Get("compute_id").(int)) + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int))) + urlValues.Add("publicPortStart", strconv.Itoa(d.Get("public_port_start").(int))) + urlValues.Add("localBasePort", strconv.Itoa(d.Get("local_base_port").(int))) + urlValues.Add("proto", d.Get("proto").(string)) + + if portEnd, ok := d.GetOk("public_port_end"); ok { + urlValues.Add("publicPortEnd", strconv.Itoa(portEnd.(int))) + } + + pfwId, err := c.DecortAPICall(ctx, "POST", ComputePfwAddAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(fmt.Sprintf("%d-%s", d.Get("compute_id").(int), pfwId)) + + pfw, err := utilityPfwCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + d.Set("local_ip", pfw.LocalIP) + if _, ok := d.GetOk("public_port_end"); !ok { + d.Set("public_port_end", pfw.PublicPortEnd) + } + + return nil +} + +func resourcePfwRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourcePfwRead: called for compute %d, rule %s", d.Get("compute_id").(int), d.Id()) + + pfw, err := utilityPfwCheckPresence(ctx, d, m) + if pfw == nil { + d.SetId("") + return diag.FromErr(err) + } + + d.Set("compute_id", pfw.ComputeID) + d.Set("public_port_start", pfw.PublicPortStart) + d.Set("public_port_end", pfw.PublicPortEnd) + d.Set("local_ip", pfw.LocalIP) + d.Set("local_base_port", pfw.LocalPort) + d.Set("proto", pfw.Protocol) + + return nil +} + +func resourcePfwDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourcePfwDelete: called for compute %d, rule %s", d.Get("compute_id").(int), d.Id()) + + pfw, err := utilityPfwCheckPresence(ctx, d, m) + if pfw == nil { + if err != nil { + return diag.FromErr(err) + } + return nil + } + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int))) + urlValues.Add("ruleId", strconv.Itoa(pfw.ID)) + + _, err = c.DecortAPICall(ctx, "POST", ComputePfwDelAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourcePfwExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + log.Debugf("resourcePfwExists: called for compute %d, rule %s", d.Get("compute_id").(int), d.Id()) + + pfw, err := utilityPfwCheckPresence(ctx, d, m) + if pfw == nil { + if err != nil { + return false, err + } + return false, nil + } + + return true, nil +} + +func resourcePfwSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "compute_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "ID of compute instance.", + }, + + "public_port_start": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 65535), + Description: "External start port number for the rule.", + }, + + "public_port_end": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 65535), + Description: "End port number (inclusive) for the ranged rule.", + }, + + "local_ip": { + Type: schema.TypeString, + Computed: true, + Description: "IP address of compute instance.", + }, + + "local_base_port": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 65535), + Description: "Internal base port number.", + }, + + "proto": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"tcp", "udp"}, false), + Description: "Network protocol, either 'tcp' or 'udp'.", + }, + } +} + +func ResourcePfw() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourcePfwCreate, + ReadContext: resourcePfwRead, + DeleteContext: resourcePfwDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: &constants.Timeout60s, + Read: &constants.Timeout30s, + Update: &constants.Timeout60s, + Delete: &constants.Timeout60s, + Default: &constants.Timeout60s, + }, + + Schema: resourcePfwSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/pfw/utility_pfw.go b/internal/service/cloudbroker/pfw/utility_pfw.go new file mode 100644 index 0000000..0f5729f --- /dev/null +++ b/internal/service/cloudbroker/pfw/utility_pfw.go @@ -0,0 +1,77 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 pfw + +import ( + "context" + "encoding/json" + "net/url" + "strconv" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/rudecs/terraform-provider-decort/internal/controller" +) + +func utilityPfwCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*PfwRecord, error) { + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int))) + resp, err := c.DecortAPICall(ctx, "POST", ComputePfwListAPI, urlValues) + if err != nil { + return nil, err + } + + if resp == "" { + return nil, nil + } + + idS := strings.Split(d.Id(), "-")[1] + id, err := strconv.Atoi(idS) + if err != nil { + return nil, err + } + + var pfws []PfwRecord + if err := json.Unmarshal([]byte(resp), &pfws); err != nil { + return nil, err + } + + for _, pfw := range pfws { + if pfw.ID == id { + return &pfw, nil + } + } + + return nil, nil +} diff --git a/internal/service/cloudbroker/rg/api.go b/internal/service/cloudbroker/rg/api.go new file mode 100644 index 0000000..0d098bc --- /dev/null +++ b/internal/service/cloudbroker/rg/api.go @@ -0,0 +1,39 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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/cloudbroker/rg/create" +const ResgroupUpdateAPI = "/restmachine/cloudbroker/rg/update" +const ResgroupListAPI = "/restmachine/cloudbroker/rg/list" +const ResgroupGetAPI = "/restmachine/cloudbroker/rg/get" +const ResgroupDeleteAPI = "/restmachine/cloudbroker/rg/delete" +const RgListComputesAPI = "/restmachine/cloudbroker/rg/listComputes" diff --git a/internal/service/cloudbroker/rg/data_source_rg.go b/internal/service/cloudbroker/rg/data_source_rg.go new file mode 100644 index 0000000..3afdb69 --- /dev/null +++ b/internal/service/cloudbroker/rg/data_source_rg.go @@ -0,0 +1,196 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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(ctx, 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, + 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.", + }, + */ + }, + } +} diff --git a/internal/service/cloudbroker/rg/data_source_rg_list.go b/internal/service/cloudbroker/rg/data_source_rg_list.go new file mode 100644 index 0000000..dfbb888 --- /dev/null +++ b/internal/service/cloudbroker/rg/data_source_rg_list.go @@ -0,0 +1,324 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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(ctx, 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, + 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(), + } +} diff --git a/internal/service/cloudbroker/rg/models.go b/internal/service/cloudbroker/rg/models.go new file mode 100644 index 0000000..f1f6e05 --- /dev/null +++ b/internal/service/cloudbroker/rg/models.go @@ -0,0 +1,149 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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"` +} diff --git a/internal/service/cloudbroker/rg/quota_subresource.go b/internal/service/cloudbroker/rg/quota_subresource.go new file mode 100644 index 0000000..6da4300 --- /dev/null +++ b/internal/service/cloudbroker/rg/quota_subresource.go @@ -0,0 +1,137 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 +} diff --git a/internal/service/cloudbroker/rg/resource_rg.go b/internal/service/cloudbroker/rg/resource_rg.go new file mode 100644 index 0000000..d893334 --- /dev/null +++ b/internal/service/cloudbroker/rg/resource_rg.go @@ -0,0 +1,442 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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(ctx, "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(ctx, 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(ctx, 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(ctx, "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(ctx, 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(ctx, "POST", ResgroupDeleteAPI, url_values) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceResgroupExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + // Reminder: according to Terraform rules, this function should NOT modify ResourceData argument + rg_facts, err := utilityResgroupCheckPresence(ctx, 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, + + 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.", + }, + */ + }, + } +} diff --git a/internal/service/cloudbroker/rg/utility_rg.go b/internal/service/cloudbroker/rg/utility_rg.go new file mode 100644 index 0000000..c7c3b94 --- /dev/null +++ b/internal/service/cloudbroker/rg/utility_rg.go @@ -0,0 +1,139 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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" + "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(ctx context.Context, 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(ctx, "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(ctx, "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(ctx, "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)) +} diff --git a/internal/service/cloudbroker/rg/utility_rg_list.go b/internal/service/cloudbroker/rg/utility_rg_list.go new file mode 100644 index 0000000..3342105 --- /dev/null +++ b/internal/service/cloudbroker/rg/utility_rg_list.go @@ -0,0 +1,74 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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" + "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(ctx context.Context, 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(ctx, "POST", ResgroupListAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(rgListRaw), &rgList) + if err != nil { + return nil, err + } + + return rgList, nil +} diff --git a/internal/service/cloudbroker/sep/resource_sep.go b/internal/service/cloudbroker/sep/resource_sep.go index 25b54cf..f6561f7 100644 --- a/internal/service/cloudbroker/sep/resource_sep.go +++ b/internal/service/cloudbroker/sep/resource_sep.go @@ -38,7 +38,6 @@ import ( "net/url" "strconv" - "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" @@ -50,22 +49,6 @@ import ( func resourceSepCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { log.Debugf("resourceSepCreate: called for sep %s", d.Get("name").(string)) - if sepId, ok := d.GetOk("sep_id"); ok { - if exists, err := resourceSepExists(ctx, d, m); exists { - if err != nil { - return diag.FromErr(err) - } - d.SetId(strconv.Itoa(sepId.(int))) - diagnostics := resourceSepRead(ctx, d, m) - if diagnostics != nil { - return diagnostics - } - - return nil - } - return diag.Errorf("provided sep id does not exist") - } - c := m.(*controller.ControllerCfg) urlValues := &url.Values{} @@ -114,7 +97,6 @@ func resourceSepCreate(ctx context.Context, d *schema.ResourceData, m interface{ return diag.FromErr(err) } - id := uuid.New() d.SetId(sepId) d.Set("sep_id", sepId) @@ -123,8 +105,6 @@ func resourceSepCreate(ctx context.Context, d *schema.ResourceData, m interface{ return diagnostics } - d.SetId(id.String()) - return nil } diff --git a/internal/service/cloudbroker/snapshot/api.go b/internal/service/cloudbroker/snapshot/api.go new file mode 100644 index 0000000..a974cca --- /dev/null +++ b/internal/service/cloudbroker/snapshot/api.go @@ -0,0 +1,37 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 snapshot + +const snapshotCreateAPI = "/restmachine/cloudbroker/compute/snapshotCreate" +const snapshotDeleteAPI = "/restmachine/cloudbroker/compute/snapshotDelete" +const snapshotRollbackAPI = "/restmachine/cloudbroker/compute/snapshotRollback" +const snapshotListAPI = "/restmachine/cloudbroker/compute/snapshotList" diff --git a/internal/service/cloudbroker/snapshot/data_source_snapshot_list.go b/internal/service/cloudbroker/snapshot/data_source_snapshot_list.go new file mode 100644 index 0000000..0fe01c1 --- /dev/null +++ b/internal/service/cloudbroker/snapshot/data_source_snapshot_list.go @@ -0,0 +1,131 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 snapshot + +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 flattenSnapshotList(gl SnapshotList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, item := range gl { + temp := map[string]interface{}{ + "label": item.Label, + "guid": item.Guid, + "disks": item.Disks, + "timestamp": item.Timestamp, + } + + res = append(res, temp) + } + return res +} + +func dataSourceSnapshotListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + snapshotList, err := utilitySnapshotListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenSnapshotList(snapshotList)) + + return nil +} + +func dataSourceSnapshotListSchemaMake() map[string]*schema.Schema { + rets := map[string]*schema.Schema{ + "compute_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "ID of the compute instance to create snapshot for.", + }, + "items": { + Type: schema.TypeList, + Computed: true, + Description: "snapshot list", + Elem: &schema.Resource{ + Schema: dataSourceSnapshotSchemaMake(), + }, + }, + } + + return rets +} + +func dataSourceSnapshotSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "label": { + Type: schema.TypeString, + Computed: true, + Description: "text label for snapshot. Must be unique among this compute snapshots.", + }, + "disks": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "guid": { + Type: schema.TypeString, + Computed: true, + Description: "guid of the snapshot", + }, + "timestamp": { + Type: schema.TypeInt, + Computed: true, + Description: "timestamp", + }, + } +} + +func DataSourceSnapshotList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceSnapshotListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceSnapshotListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/snapshot/models.go b/internal/service/cloudbroker/snapshot/models.go new file mode 100644 index 0000000..0152191 --- /dev/null +++ b/internal/service/cloudbroker/snapshot/models.go @@ -0,0 +1,41 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 snapshot + +type Snapshot struct { + Disks []int `json:"disks"` + Guid string `json:"guid"` + Label string `json:"label"` + Timestamp uint64 `json:"timestamp"` +} + +type SnapshotList []Snapshot diff --git a/internal/service/cloudbroker/snapshot/resource_snapshot.go b/internal/service/cloudbroker/snapshot/resource_snapshot.go new file mode 100644 index 0000000..9a8fbc7 --- /dev/null +++ b/internal/service/cloudbroker/snapshot/resource_snapshot.go @@ -0,0 +1,206 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 snapshot + +import ( + "context" + "net/url" + "strconv" + "strings" + + "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" + "github.com/rudecs/terraform-provider-decort/internal/controller" + log "github.com/sirupsen/logrus" +) + +func resourceSnapshotCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceSnapshotCreate: called for snapshot %s", d.Get("label").(string)) + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("label", d.Get("label").(string)) + urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int))) + + snapshotId, err := c.DecortAPICall(ctx, "POST", snapshotCreateAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + + snapshotId = strings.ReplaceAll(snapshotId, "\"", "") + + d.SetId(snapshotId) + d.Set("guid", snapshotId) + + diagnostics := resourceSnapshotRead(ctx, d, m) + if diagnostics != nil { + return diagnostics + } + + return nil +} + +func resourceSnapshotRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + snapshot, err := utilitySnapshotCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + d.Set("timestamp", snapshot.Timestamp) + d.Set("guid", snapshot.Guid) + d.Set("disks", snapshot.Disks) + d.Set("label", snapshot.Label) + + return nil +} + +func resourceSnapshotDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceSnapshotDelete: called for %s, id: %s", d.Get("label").(string), d.Id()) + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int))) + urlValues.Add("label", d.Get("label").(string)) + + _, err := c.DecortAPICall(ctx, "POST", snapshotDeleteAPI, urlValues) + if err != nil { + return diag.FromErr(err) + } + d.SetId("") + + return nil +} + +func resourceSnapshotExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + snapshot, err := utilitySnapshotCheckPresence(ctx, d, m) + if err != nil { + return false, err + } + if snapshot == nil { + return false, nil + } + + return true, nil +} + +func resourceSnapshotEdit(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + if d.HasChange("rollback") { + if d.Get("rollback").(bool) { + err := resourceSnapshotRollback(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + } + } + + return nil +} + +func resourceSnapshotRollback(ctx context.Context, d *schema.ResourceData, m interface{}) error { + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int))) + urlValues.Add("label", d.Get("label").(string)) + + _, err := c.DecortAPICall(ctx, "POST", snapshotRollbackAPI, urlValues) + if err != nil { + return err + } + return nil +} + +func resourceSnapshotSchemaMake() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "compute_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "ID of the compute instance to create snapshot for.", + }, + "label": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "text label for snapshot. Must be unique among this compute snapshots.", + }, + "rollback": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "is rollback the snapshot", + }, + "disks": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + }, + "guid": { + Type: schema.TypeString, + Computed: true, + Description: "guid of the snapshot", + }, + "timestamp": { + Type: schema.TypeInt, + Computed: true, + Description: "timestamp", + }, + } +} + +func ResourceSnapshot() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourceSnapshotCreate, + ReadContext: resourceSnapshotRead, + UpdateContext: resourceSnapshotEdit, + DeleteContext: resourceSnapshotDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: &constants.Timeout60s, + Read: &constants.Timeout30s, + Update: &constants.Timeout60s, + Delete: &constants.Timeout60s, + Default: &constants.Timeout60s, + }, + + Schema: resourceSnapshotSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/snapshot/utility_snapshot.go b/internal/service/cloudbroker/snapshot/utility_snapshot.go new file mode 100644 index 0000000..b99509b --- /dev/null +++ b/internal/service/cloudbroker/snapshot/utility_snapshot.go @@ -0,0 +1,63 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 snapshot + +import ( + "context" + "errors" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func utilitySnapshotCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*Snapshot, error) { + snapShotList, err := utilitySnapshotListCheckPresence(ctx, d, m) + if err != nil { + return nil, err + } + + findId := "" + + if (d.Get("guid").(string)) != "" { + findId = d.Get("guid").(string) + } else { + findId = d.Id() + } + + for _, s := range snapShotList { + if s.Guid == findId { + return &s, nil + } + } + + return nil, errors.New("snapshot not found") + +} diff --git a/internal/service/cloudbroker/snapshot/utility_snapshot_list.go b/internal/service/cloudbroker/snapshot/utility_snapshot_list.go new file mode 100644 index 0000000..e06ac5b --- /dev/null +++ b/internal/service/cloudbroker/snapshot/utility_snapshot_list.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 snapshot + +import ( + "context" + "encoding/json" + "net/url" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/rudecs/terraform-provider-decort/internal/controller" +) + +func utilitySnapshotListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (SnapshotList, error) { + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int))) + + resp, err := c.DecortAPICall(ctx, "POST", snapshotListAPI, urlValues) + if err != nil { + return nil, err + } + + if resp == "" { + return nil, nil + } + + snapshotList := SnapshotList{} + if err := json.Unmarshal([]byte(resp), &snapshotList); err != nil { + + return nil, err + } + + return snapshotList, nil +} diff --git a/internal/service/cloudbroker/vins/api.go b/internal/service/cloudbroker/vins/api.go new file mode 100644 index 0000000..bfdefe4 --- /dev/null +++ b/internal/service/cloudbroker/vins/api.go @@ -0,0 +1,44 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 vins + +const VinsListAPI = "/restmachine/cloudbroker/vins/list" +const VinsGetAPI = "/restmachine/cloudbroker/vins/get" +const VinsSearchAPI = "/restmachine/cloudbroker/vins/search" + +const VinsCreateInAccountAPI = "/restmachine/cloudbroker/vins/createInAccount" +const VinsCreateInRgAPI = "/restmachine/cloudbroker/vins/createInRG" + +const VinsExtNetConnectAPI = "/restmachine/cloudbroker/vins/extNetConnect" +const VinsExtNetDisconnectAPI = "/restmachine/cloudbroker/vins/extNetDisconnect" + +const VinsDeleteAPI = "/restmachine/cloudbroker/vins/delete" diff --git a/internal/service/cloudbroker/vins/data_source_vins.go b/internal/service/cloudbroker/vins/data_source_vins.go new file mode 100644 index 0000000..2bcd362 --- /dev/null +++ b/internal/service/cloudbroker/vins/data_source_vins.go @@ -0,0 +1,181 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 vins + +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" + // "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +// vins_facts is a response string from API vins/get +func flattenVins(d *schema.ResourceData, vins_facts string) diag.Diagnostics { + // NOTE: this function modifies ResourceData argument - as such it should never be called + // from resourceVinsExists(...) method + // log.Debugf("flattenVins: ready to decode response body from API %s", vins_facts) + vinsRecord := VinsRecord{} + err := json.Unmarshal([]byte(vins_facts), &vinsRecord) + if err != nil { + return diag.FromErr(err) + } + + log.Debugf("flattenVins: decoded ViNS name:ID %s:%d, account ID %d, RG ID %d", + vinsRecord.Name, vinsRecord.ID, vinsRecord.AccountID, vinsRecord.RgID) + + d.SetId(fmt.Sprintf("%d", vinsRecord.ID)) + d.Set("name", vinsRecord.Name) + d.Set("account_id", vinsRecord.AccountID) + d.Set("account_name", vinsRecord.AccountName) + d.Set("rg_id", vinsRecord.RgID) + d.Set("description", vinsRecord.Desc) + d.Set("ipcidr", vinsRecord.IPCidr) + + noExtNetConnection := true + for _, value := range vinsRecord.VNFs { + if value.Type == "GW" { + log.Debugf("flattenVins: discovered GW VNF ID %d in ViNS ID %d", value.ID, vinsRecord.ID) + extNetID, idOk := value.Config["ext_net_id"] // NOTE: unknown numbers are unmarshalled to float64. This is by design! + extNetIP, ipOk := value.Config["ext_net_ip"] + if idOk && ipOk { + log.Debugf("flattenVins: ViNS ext_net_id=%d, ext_net_ip=%s", int(extNetID.(float64)), extNetIP.(string)) + d.Set("ext_ip_addr", extNetIP.(string)) + d.Set("ext_net_id", int(extNetID.(float64))) + } else { + return diag.Errorf("Failed to unmarshal VNF GW Config - structure is invalid.") + } + noExtNetConnection = false + break + } + } + + if noExtNetConnection { + d.Set("ext_ip_addr", "") + d.Set("ext_net_id", 0) + } + + log.Debugf("flattenVins: EXTRA CHECK - schema rg_id=%d, ext_net_id=%d", d.Get("rg_id").(int), d.Get("ext_net_id").(int)) + + return nil +} + +func dataSourceVinsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + vinsFacts, err := utilityVinsCheckPresence(ctx, d, m) + if vinsFacts == "" { + // if empty string is returned from utilityVinsCheckPresence then there is no + // such ViNS and err tells so - just return it to the calling party + d.SetId("") // ensure ID is empty in this case + return diag.FromErr(err) + } + + return flattenVins(d, vinsFacts) +} + +func DataSourceVins() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceVinsRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the ViNS. Names are case sensitive and unique within the context of an account or resource group.", + }, + + /* + "vins_id": { + Type: schema.TypeInt, + Optional: true, + Description: "Unique ID of the ViNS. If ViNS ID is specified, then ViNS name, rg_id and account_id are ignored.", + }, + */ + + "rg_id": { + Type: schema.TypeInt, + Optional: true, + Description: "Unique ID of the resource group, where this ViNS is belongs to (for ViNS created at resource group level, 0 otherwise).", + }, + + "account_id": { + Type: schema.TypeInt, + Optional: true, + Description: "Unique ID of the account, which this ViNS belongs to.", + }, + + // the rest of attributes are computed + "account_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the account, which this ViNS belongs to.", + }, + + "description": { + Type: schema.TypeString, + Computed: true, + Description: "User-defined text description of this ViNS.", + }, + + "ext_ip_addr": { + Type: schema.TypeString, + Computed: true, + Description: "IP address of the external connection (valid for ViNS connected to external network, empty string otherwise).", + }, + + "ext_net_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the external network this ViNS is connected to (-1 means no external connection).", + }, + + "ipcidr": { + Type: schema.TypeString, + Computed: true, + Description: "Network address used by this ViNS.", + }, + }, + } +} diff --git a/internal/service/cloudbroker/vins/data_source_vins_list.go b/internal/service/cloudbroker/vins/data_source_vins_list.go new file mode 100644 index 0000000..2178eb5 --- /dev/null +++ b/internal/service/cloudbroker/vins/data_source_vins_list.go @@ -0,0 +1,189 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 vins + +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 flattenVinsList(vl VinsList) []map[string]interface{} { + res := make([]map[string]interface{}, 0) + for _, v := range vl { + temp := map[string]interface{}{ + "account_id": v.AccountId, + "account_name": v.AccountName, + "created_by": v.CreatedBy, + "created_time": v.CreatedTime, + "deleted_by": v.DeletedBy, + "deleted_time": v.DeletedTime, + "external_ip": v.ExternalIP, + "vins_id": v.ID, + "vins_name": v.Name, + "network": v.Network, + "rg_id": v.RGID, + "rg_name": v.RGName, + "status": v.Status, + "updated_by": v.UpdatedBy, + "updated_time": v.UpdatedTime, + "vxlan_id": v.VXLanID, + } + res = append(res, temp) + } + return res +} + +func dataSourceVinsListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + vinsList, err := utilityVinsListCheckPresence(ctx, d, m) + if err != nil { + return diag.FromErr(err) + } + + id := uuid.New() + d.SetId(id.String()) + d.Set("items", flattenVinsList(vinsList)) + + return nil +} + +func dataSourceVinsListSchemaMake() map[string]*schema.Schema { + res := map[string]*schema.Schema{ + "include_deleted": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "include deleted computes", + }, + "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, + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeInt, + Computed: true, + }, + "deleted_by": { + Type: schema.TypeString, + Computed: true, + }, + "deleted_time": { + Type: schema.TypeInt, + Computed: true, + }, + "external_ip": { + Type: schema.TypeString, + Computed: true, + }, + "vins_id": { + Type: schema.TypeInt, + Computed: true, + }, + "vins_name": { + Type: schema.TypeString, + Computed: true, + }, + "network": { + Type: schema.TypeString, + Computed: true, + }, + "rg_id": { + Type: schema.TypeInt, + Computed: true, + }, + "rg_name": { + 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, + }, + "vxlan_id": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + } + return res +} + +func DataSourceVinsList() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + ReadContext: dataSourceVinsListRead, + + Timeouts: &schema.ResourceTimeout{ + Read: &constants.Timeout30s, + Default: &constants.Timeout60s, + }, + + Schema: dataSourceVinsListSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/vins/models.go b/internal/service/cloudbroker/vins/models.go new file mode 100644 index 0000000..e8b95d1 --- /dev/null +++ b/internal/service/cloudbroker/vins/models.go @@ -0,0 +1,94 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 vins + +type Vins struct { + AccountId int `json:"accountId"` + AccountName string `json:"accountName"` + CreatedBy string `json:"createdBy"` + CreatedTime int `json:"createdTime"` + DeletedBy string `json:"deletedBy"` + DeletedTime int `json:"deletedTime"` + ExternalIP string `json:"externalIP"` + ID int `json:"id"` + Name string `json:"name"` + Network string `json:"network"` + RGID int `json:"rgId"` + RGName string `json:"rgName"` + Status string `json:"status"` + UpdatedBy string `json:"updatedBy"` + UpdatedTime int `json:"updatedTime"` + VXLanID int `json:"vxlanId"` +} + +type VinsList []Vins + +type VinsSearchResp []VinsSearchRecord + +type VnfRecord struct { + ID int `json:"id"` + AccountID int `json:"accountId"` + Type string `json:"type"` // "DHCP", "NAT", "GW" etc + Config map[string]interface{} `json:"config"` // NOTE: VNF specs vary by VNF type +} + +type VnfGwConfigRecord struct { // describes GW VNF config structure inside ViNS, as returned by API vins/get + ExtNetID int `json:"ext_net_id"` + ExtNetIP string `json:"ext_net_ip"` + ExtNetMask int `json:"ext_net_mask"` + DefaultGW string `json:"default_gw"` +} +type VinsRecord struct { // represents part of the response from API vins/get + ID int `json:"id"` + Name string `json:"name"` + IPCidr string `json:"network"` + VxLanID int `json:"vxlanId"` + ExternalIP string `json:"externalIP"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + RgID int `json:"rgid"` + RgName string `json:"rgName"` + VNFs map[string]VnfRecord `json:"vnfs"` + Desc string `json:"desc"` +} + +type VinsSearchRecord struct { + ID int `json:"id"` + Name string `json:"name"` + IPCidr string `json:"network"` + VxLanID int `json:"vxlanId"` + ExternalIP string `json:"externalIP"` + AccountID int `json:"accountId"` + AccountName string `json:"accountName"` + RgID int `json:"rgId"` + RgName string `json:"rgName"` +} diff --git a/internal/service/cloudbroker/vins/resource_vins.go b/internal/service/cloudbroker/vins/resource_vins.go new file mode 100644 index 0000000..22729f6 --- /dev/null +++ b/internal/service/cloudbroker/vins/resource_vins.go @@ -0,0 +1,323 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 vins + +import ( + "context" + "fmt" + "net/url" + "strconv" + + "github.com/rudecs/terraform-provider-decort/internal/constants" + "github.com/rudecs/terraform-provider-decort/internal/controller" + 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 ipcidrDiffSupperss(key, oldVal, newVal string, d *schema.ResourceData) bool { + if oldVal == "" && newVal != "" { + // if old value for "ipcidr" resource is empty string, it means that we are creating new ViNS + // and there is a chance that the user will want specific IP address range for this ViNS - + // check if "ipcidr" is explicitly set in TF file to a non-empty string. + log.Debugf("ipcidrDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=FALSE", key, oldVal, newVal) + return false // there is a difference between stored and new value + } + log.Debugf("ipcidrDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=TRUE", key, oldVal, newVal) + return true // suppress difference +} + +func resourceVinsCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceVinsCreate: called for ViNS name %s, Account ID %d, RG ID %d", + d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int)) + + apiToCall := VinsCreateInAccountAPI + + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + urlValues.Add("name", d.Get("name").(string)) + + argVal, argSet := d.GetOk("rg_id") + if argSet && argVal.(int) > 0 { + apiToCall = VinsCreateInRgAPI + urlValues.Add("rgId", fmt.Sprintf("%d", argVal.(int))) + } else { + // RG ID either not set at all or set to 0 - user may want ViNS at account level + argVal, argSet = d.GetOk("account_id") + if !argSet || argVal.(int) <= 0 { + // No valid Account ID (and no RG ID either) - cannot create ViNS + return diag.Errorf("resourceVinsCreate: ViNS name %s - no valid account and/or resource group ID specified", d.Id()) + } + urlValues.Add("accountId", fmt.Sprintf("%d", argVal.(int))) + } + + argVal, argSet = d.GetOk("ext_net_id") // NB: even if ext_net_id value is explicitly set to 0, argSet = false anyway + if argSet { + if argVal.(int) > 0 { + // connect to specific external network + urlValues.Add("extNetId", fmt.Sprintf("%d", argVal.(int))) + /* + Commented out, as we've made "ext_net_ip" parameter non-configurable via Terraform! + + // in case of specific ext net connection user may also want a particular IP address + argVal, argSet = d.GetOk("ext_net_ip") + if argSet && argVal.(string) != "" { + urlValues.Add("extIp", argVal.(string)) + } + */ + } else { + // ext_net_id is set to a negative value - connect to default external network + // no particular IP address selection in this case + urlValues.Add("extNetId", "0") + } + } + + argVal, argSet = d.GetOk("ipcidr") + if argSet && argVal.(string) != "" { + log.Debugf("resourceVinsCreate: ipcidr is set to %s", argVal.(string)) + urlValues.Add("ipcidr", argVal.(string)) + } + + argVal, argSet = d.GetOk("description") + if argSet { + urlValues.Add("desc", argVal.(string)) + } + + apiResp, err := c.DecortAPICall(ctx, "POST", apiToCall, urlValues) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(apiResp) // update ID of the resource to tell Terraform that the ViNS resource exists + vinsId, _ := strconv.Atoi(apiResp) + + log.Debugf("resourceVinsCreate: new ViNS ID / name %d / %s creation sequence complete", vinsId, d.Get("name").(string)) + + // We may reuse dataSourceVinsRead here as we maintain similarity + // between ViNS resource and ViNS data source schemas + // ViNS resource read function will also update resource ID on success, so that Terraform + // will know the resource exists (however, we already did it a few lines before) + return dataSourceVinsRead(ctx, d, m) +} + +func resourceVinsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + vinsFacts, err := utilityVinsCheckPresence(ctx, d, m) + if vinsFacts == "" { + // if empty string is returned from utilityVinsCheckPresence then there is no + // such ViNS and err tells so - just return it to the calling party + d.SetId("") // ensure ID is empty + return diag.FromErr(err) + } + + return flattenVins(d, vinsFacts) +} + +func resourceVinsUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + + log.Debugf("resourceVinsUpdate: called for ViNS ID / name %s / %s, Account ID %d, RG ID %d", + d.Id(), d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int)) + + c := m.(*controller.ControllerCfg) + + // 1. Handle external network connection change + oldExtNetId, newExtNedId := d.GetChange("ext_net_id") + if oldExtNetId.(int) != newExtNedId.(int) { + log.Debugf("resourceVinsUpdate: changing ViNS ID %s - ext_net_id %d -> %d", d.Id(), oldExtNetId.(int), newExtNedId.(int)) + + extnetParams := &url.Values{} + extnetParams.Add("vinsId", d.Id()) + + if oldExtNetId.(int) > 0 { + // there was preexisting external net connection - disconnect ViNS + _, err := c.DecortAPICall(ctx, "POST", VinsExtNetDisconnectAPI, extnetParams) + if err != nil { + return diag.FromErr(err) + } + } + + if newExtNedId.(int) > 0 { + // new external network connection requested - connect ViNS + extnetParams.Add("netId", fmt.Sprintf("%d", newExtNedId.(int))) + _, err := c.DecortAPICall(ctx, "POST", VinsExtNetConnectAPI, extnetParams) + if err != nil { + return diag.FromErr(err) + } + } + } + + // we may reuse dataSourceVinsRead here as we maintain similarity + // between Compute resource and Compute data source schemas + return dataSourceVinsRead(ctx, d, m) +} + +func resourceVinsDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + log.Debugf("resourceVinsDelete: called for ViNS ID / name %s / %s, Account ID %d, RG ID %d", + d.Id(), d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int)) + + vinsFacts, err := utilityVinsCheckPresence(ctx, d, m) + if vinsFacts == "" { + if err != nil { + return diag.FromErr(err) + } + // the specified ViNS does not exist - in this case according to Terraform best practice + // we exit from Destroy method without error + return nil + } + + params := &url.Values{} + params.Add("vinsId", d.Id()) + params.Add("force", "1") // disconnect all computes before deleting ViNS + params.Add("permanently", "1") // delete ViNS immediately bypassing recycle bin + + c := m.(*controller.ControllerCfg) + _, err = c.DecortAPICall(ctx, "POST", VinsDeleteAPI, params) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceVinsExists(ctx context.Context, d *schema.ResourceData, m interface{}) (bool, error) { + // Reminder: according to Terraform rules, this function should not modify its ResourceData argument + log.Debugf("resourceVinsExists: called for ViNS name %s, Account ID %d, RG ID %d", + d.Get("name").(string), d.Get("account_id").(int), d.Get("rg_id").(int)) + + vinsFacts, err := utilityVinsCheckPresence(ctx, d, m) + if vinsFacts == "" { + if err != nil { + return false, err + } + return false, nil + } + return true, nil +} + +func resourceVinsSchemaMake() map[string]*schema.Schema { + rets := map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "Name of the ViNS. Names are case sensitive and unique within the context of an account or resource group.", + }, + + /* we do not need ViNS ID as an argument because if we already know this ID, it is not practical to call resource provider. + Resource Import will work anyway, as it obtains the ID of ViNS to be imported through another mechanism. + "vins_id": { + Type: schema.TypeInt, + Optional: true, + Description: "Unique ID of the ViNS. If ViNS ID is specified, then ViNS name, rg_id and account_id are ignored.", + }, + */ + + "rg_id": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: 0, + Description: "ID of the resource group, where this ViNS belongs to. Non-zero for ViNS created at resource group level, 0 otherwise.", + }, + + "account_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "ID of the account, which this ViNS belongs to. For ViNS created at account level, resource group ID is 0.", + }, + + "ext_net_id": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(0), + Description: "ID of the external network this ViNS is connected to. Pass 0 if no external connection required.", + }, + + "ipcidr": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: ipcidrDiffSupperss, + Description: "Network address to use by this ViNS. This parameter is only valid when creating new ViNS.", + }, + + "description": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: "Optional user-defined text description of this ViNS.", + }, + + // the rest of attributes are computed + "account_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the account, which this ViNS belongs to.", + }, + + "ext_ip_addr": { + Type: schema.TypeString, + Computed: true, + Description: "IP address of the external connection (valid for ViNS connected to external network, ignored otherwise).", + }, + } + + return rets +} + +func ResourceVins() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: resourceVinsCreate, + ReadContext: resourceVinsRead, + UpdateContext: resourceVinsUpdate, + DeleteContext: resourceVinsDelete, + + 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: resourceVinsSchemaMake(), + } +} diff --git a/internal/service/cloudbroker/vins/utility_vins.go b/internal/service/cloudbroker/vins/utility_vins.go new file mode 100644 index 0000000..a53ea85 --- /dev/null +++ b/internal/service/cloudbroker/vins/utility_vins.go @@ -0,0 +1,153 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 vins + +import ( + "context" + "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 vins/get, which could be unmarshalled +// into VinsGetResp structure +func utilityVinsCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (string, error) { + // This function tries to locate ViNS by one of the following algorithms depending + // on the parameters passed: + // - if resource group ID is specified -> it looks for a ViNS at the RG level + // - if account ID is specifeid -> it looks for a ViNS at the account level + // + // If succeeded, it returns non empty string that contains JSON formatted facts about the + // ViNS as returned by vins/get API call. + // Otherwise it returns empty string and a meaningful error. + // + // 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 ViNS ID set so + // that Import of ViNS resource is possible + idSet := false + theId, err := strconv.Atoi(d.Id()) + if err != nil || theId <= 0 { + vinsId, argSet := d.GetOk("vins_id") // NB: vins_id is NOT present in vinsResource schema! + if argSet { + theId = vinsId.(int) + idSet = true + } + } else { + idSet = true + } + + if idSet { + // ViNS ID is specified, try to get compute instance straight by this ID + log.Debugf("utilityVinsCheckPresence: locating ViNS by its ID %d", theId) + urlValues.Add("vinsId", fmt.Sprintf("%d", theId)) + vinsFacts, err := c.DecortAPICall(ctx, "POST", VinsGetAPI, urlValues) + if err != nil { + return "", err + } + return vinsFacts, nil + } + + // ID was not set in the schema upon entering this function - work through ViNS name + // and Account / RG ID + + vinsName, argSet := d.GetOk("name") + if !argSet { + // if ViNS name is not set. then we cannot locate ViNS + return "", fmt.Errorf("Cannot check ViNS presence if ViNS name is empty") + } + urlValues.Add("name", vinsName.(string)) + urlValues.Add("show_all", "false") + log.Debugf("utilityVinsCheckPresence: preparing to locate ViNS name %s", vinsName.(string)) + + rgId, rgSet := d.GetOk("rg_id") + if rgSet { + log.Debugf("utilityVinsCheckPresence: limiting ViNS search to RG ID %d", rgId.(int)) + urlValues.Add("rgId", fmt.Sprintf("%d", rgId.(int))) + } + + accountId, accountSet := d.GetOk("account_id") + if accountSet { + log.Debugf("utilityVinsCheckPresence: limiting ViNS search to Account ID %d", accountId.(int)) + urlValues.Add("accountId", fmt.Sprintf("%d", accountId.(int))) + } + + apiResp, err := c.DecortAPICall(ctx, "POST", VinsSearchAPI, urlValues) + if err != nil { + return "", err + } + + // log.Debugf("%s", apiResp) + // log.Debugf("utilityResgroupCheckPresence: ready to decode response body from %s", VinsSearchAPI) + model := VinsSearchResp{} + err = json.Unmarshal([]byte(apiResp), &model) + if err != nil { + return "", err + } + + log.Debugf("utilityVinsCheckPresence: traversing decoded Json of length %d", len(model)) + for index, item := range model { + if item.Name == vinsName.(string) { + if (accountSet && item.AccountID != accountId.(int)) || + (rgSet && item.RgID != rgId.(int)) { + // double check that account ID and Rg ID match, if set in the schema + continue + } + + log.Debugf("utilityVinsCheckPresence: match ViNS name %s / ID %d, account ID %d, RG ID %d at index %d", + item.Name, item.ID, item.AccountID, item.RgID, index) + + // element returned by API vins/search does not contain all information we may need to + // manage ViNS, so we have to get detailed info by calling API vins/get + rqValues := &url.Values{} + rqValues.Add("vinsId", fmt.Sprintf("%d", item.ID)) + vinsGetResp, err := c.DecortAPICall(ctx, "POST", VinsGetAPI, rqValues) + if err != nil { + return "", err + } + return vinsGetResp, nil + } + } + + return "", fmt.Errorf("Cannot find ViNS name %s. Check name and/or RG ID & Account ID and your access rights", vinsName.(string)) +} diff --git a/internal/service/cloudbroker/vins/utility_vins_list.go b/internal/service/cloudbroker/vins/utility_vins_list.go new file mode 100644 index 0000000..4746f23 --- /dev/null +++ b/internal/service/cloudbroker/vins/utility_vins_list.go @@ -0,0 +1,73 @@ +/* +Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Authors: +Petr Krutov, +Stanislav Solovev, + +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 vins + +import ( + "context" + "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 utilityVinsListCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (VinsList, error) { + vinsList := VinsList{} + c := m.(*controller.ControllerCfg) + urlValues := &url.Values{} + + if includeDeleted, ok := d.GetOk("include_deleted"); ok { + urlValues.Add("includeDeleted", strconv.FormatBool(includeDeleted.(bool))) + } + if page, ok := d.GetOk("page"); ok { + urlValues.Add("page", strconv.Itoa(page.(int))) + } + if size, ok := d.GetOk("size"); ok { + urlValues.Add("size", strconv.Itoa(size.(int))) + } + + log.Debugf("utilityVinsListCheckPresence") + vinsListRaw, err := c.DecortAPICall(ctx, "POST", VinsListAPI, urlValues) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(vinsListRaw), &vinsList) + if err != nil { + return nil, err + } + + return vinsList, nil +} diff --git a/samples/README.md b/samples/README.md index d77ed3e..9ec7c53 100644 --- a/samples/README.md +++ b/samples/README.md @@ -2,62 +2,96 @@ Каждый файл снабжен комментариями, которые кратко описывают возможности и параметры ресурса. Для успешной работы необходим установленный terraform. ## Ресурсы в примерах -- data: - - grid - - grid_list - - image - - image_list - - image_list_stacks - - snapshot_list - - pcidevice_list - - pcidevice - - sep - - sep_list - - sep_disk_list - - sep_config - - sep_pool - - sep_consumption - - vgpu - - disk_list - - rg_list - - account_list - - account_computes_list - - account_disks_list - - account_vins_list - - account_audits_list - - account - - account_rg_list - - account_counsumed_units - - account_counsumed_units_by_type - - account_reserved_units - - account_templates_list - - account_deleted_list - - bservice_list - - bservice_snapshot_list - - bservice_deleted_list - - bservice - - bservice_group - - extnet_default - - extnet_list - - extnet - - extnet_computes_list - - vins_list - - locations_list - - location_url -- resources: - - image - - virtual_image - - cdrom_image - - delete_images - - k8s - - k8s_wg - - snapshot - - pcidevice - - sep - - sep_config - - account - - bservice - - bservice_group +- cloudapi: + - data: + - image + - image_list + - image_list_stacks + - snapshot_list + - pcidevice_list + - pcidevice + - vgpu + - disk_list + - rg_list + - account_list + - account_computes_list + - account_disks_list + - account_vins_list + - account_audits_list + - account + - account_rg_list + - account_counsumed_units + - account_counsumed_units_by_type + - account_reserved_units + - account_templates_list + - account_deleted_list + - bservice_list + - bservice_snapshot_list + - bservice_deleted_list + - bservice + - bservice_group + - extnet_default + - extnet_list + - extnet + - extnet_computes_list + - vins_list + - locations_list + - location_url + - resources: + - image + - virtual_image + - cdrom_image + - delete_images + - k8s + - k8s_wg + - snapshot + - pcidevice + - account + - bservice + - bservice_group +- cloudbroker: + - data: + - grid + - grid_list + - image + - image_list + - image_list_stacks + - pcidevice_list + - pcidevice + - sep + - sep_list + - sep_disk_list + - sep_config + - sep_pool + - sep_consumption + - disk_list + - rg_list + - account_list + - account_computes_list + - account_disks_list + - account_vins_list + - account_audits_list + - account + - account_rg_list + - account_counsumed_units + - account_counsumed_units_by_type + - account_reserved_units + - account_templates_list + - account_deleted_list + - vins_list + - resources: + - image + - virtual_image + - cdrom_image + - delete_images + - k8s + - k8s_wg + - snapshot + - pcidevice + - sep + - sep_config + - account + - vins ## Как пользоваться примерами 1. Установить terraform diff --git a/samples/cloudapi/data_disk/main.tf b/samples/cloudapi/data_disk/main.tf new file mode 100644 index 0000000..941dccf --- /dev/null +++ b/samples/cloudapi/data_disk/main.tf @@ -0,0 +1,35 @@ +/* +Пример использования +Получение списка доступных образов +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером + +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} + + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_disk" "acl" { + disk_id = 49304 + +} + +output "test" { + value = data.decort_disk.acl +} diff --git a/samples/cloudapi/data_image/main.tf b/samples/cloudapi/data_image/main.tf new file mode 100644 index 0000000..bf65e29 --- /dev/null +++ b/samples/cloudapi/data_image/main.tf @@ -0,0 +1,43 @@ +/* +Пример использования +Получение информации об образе +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_image" "image" { + #id образа + #обязательный параметр + #тип - число + image_id = 111 + + #позывать ли информацию об удаленном образе + #опциональный параметр + #тип - булево значение + #по умолчанию - false + #show_all = false +} + +output "test" { + value = data.decort_image.image +} diff --git a/samples/cloudapi/data_image_list/main.tf b/samples/cloudapi/data_image_list/main.tf new file mode 100644 index 0000000..06084eb --- /dev/null +++ b/samples/cloudapi/data_image_list/main.tf @@ -0,0 +1,52 @@ +/* +Пример использования +Получение списка доступных образов +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером + +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} + + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_image_list" "il" { + #id аккаунта для включения образов аккаунтов в результат + #пользователь для осуществления успешного запроса должен иметь права доступа к аккаунту + #опциональный параметр + #тип - число + #если не задан - выводятся все общие образа + #account_id = 111 + + #номер страницы для отображения + #опциональный параметр + #тип - число + #если не задан - выводятся все доступные данные + #page = 2 + + #размер страницы + #опциональный параметр + #тип - число + #если не задан - выводятся все доступные данные + #size = 3 + +} + +output "test" { + value = data.decort_image_list.il +} diff --git a/samples/cloudapi/resource_disk/main.tf b/samples/cloudapi/resource_disk/main.tf new file mode 100644 index 0000000..7097213 --- /dev/null +++ b/samples/cloudapi/resource_disk/main.tf @@ -0,0 +1,56 @@ +/* +Пример использования +Получение списка доступных образов +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером + +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} + + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +resource "decort_disk" "acl" { + account_id = 88366 + gid = 212 + disk_name = "super-disk-re" + size_max = 20 + restore = true + permanently = true + reason = "delete" + iotune { + read_bytes_sec = 0 + read_bytes_sec_max = 0 + read_iops_sec = 0 + read_iops_sec_max = 0 + size_iops_sec = 0 + total_bytes_sec = 0 + total_bytes_sec_max = 0 + total_iops_sec = 3000 + total_iops_sec_max = 0 + write_bytes_sec = 0 + write_bytes_sec_max = 0 + write_iops_sec = 0 + write_iops_sec_max = 0 + } + +} + +output "test" { + value = decort_disk.acl +} diff --git a/samples/cloudbroker/data_account_audits_list/main.tf b/samples/cloudbroker/data_account_audits_list/main.tf new file mode 100644 index 0000000..1cd48f5 --- /dev/null +++ b/samples/cloudbroker/data_account_audits_list/main.tf @@ -0,0 +1,40 @@ +/* +Пример использования +Получение информации об использовании аккаунта + +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_account_audits_list" "aal" { + #id аккаунта + #обязательный параметр + #тип - число + account_id = 11111 + +} + +output "test" { + value = data.decort_account_audits_list.aal +} diff --git a/samples/cloudbroker/data_account_computes_list/main.tf b/samples/cloudbroker/data_account_computes_list/main.tf new file mode 100644 index 0000000..26edae2 --- /dev/null +++ b/samples/cloudbroker/data_account_computes_list/main.tf @@ -0,0 +1,39 @@ +/* +Пример использования +Получение списка computes, используемых аккаунтом + +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_account_computes_list" "acl" { + #id аккаунта + #обязательный параметр + #тип - число + account_id = 1111 + +} + +output "test" { + value = data.decort_account_computes_list.acl +} diff --git a/samples/cloudbroker/data_account_deleted_list/main.tf b/samples/cloudbroker/data_account_deleted_list/main.tf new file mode 100644 index 0000000..7309fcd --- /dev/null +++ b/samples/cloudbroker/data_account_deleted_list/main.tf @@ -0,0 +1,45 @@ +/* +Пример использования +Получение информации об удаленных аккаунтах +Информация предоставляется только по аккаунтам, удаленным без флага permanently +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_account_deleted_list" "adl" { + #номер страницы для отображения + #опциональный параметр + #тип - число + #если не задан - выводятся все доступные данные + #page = 2 + + #размер страницы + #опциональный параметр + #тип - число + #если не задан - выводятся все доступные данные + #size = 3 +} + +output "test" { + value = data.decort_account_deleted_list.adl +} diff --git a/samples/cloudbroker/data_account_disks_list/main.tf b/samples/cloudbroker/data_account_disks_list/main.tf new file mode 100644 index 0000000..c0ac605 --- /dev/null +++ b/samples/cloudbroker/data_account_disks_list/main.tf @@ -0,0 +1,39 @@ +/* +Пример использования +Получение информации о дисках, которые использует аккаунт + +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_account_disks_list" "adl" { + #id аккаунта + #обязательный параметр + #тип - число + account_id = 11111 + +} + +output "test" { + value = data.decort_account_disks_list.adl +} diff --git a/samples/cloudbroker/data_account_flipgroups_list/main.tf b/samples/cloudbroker/data_account_flipgroups_list/main.tf new file mode 100644 index 0000000..134d621 --- /dev/null +++ b/samples/cloudbroker/data_account_flipgroups_list/main.tf @@ -0,0 +1,38 @@ +/* +Пример использования +Получение информации о flipgroups, используемых аккаунтом + +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_account_flipgroups_list" "afgl" { + #id аккаунта + #обязательный параметр + #тип - число + account_id = 1111 +} + +output "test" { + value = data.decort_account_flipgroups_list.afgl +} diff --git a/samples/cloudbroker/data_account_list/main.tf b/samples/cloudbroker/data_account_list/main.tf new file mode 100644 index 0000000..381eaed --- /dev/null +++ b/samples/cloudbroker/data_account_list/main.tf @@ -0,0 +1,44 @@ +/* +Пример использования +Получение всех аккаунтов,имеющихся в системе +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_account_list" "al" { + #номер страницы для отображения + #опциональный параметр + #тип - число + #если не задан - выводятся все доступные данные + #page = 2 + + #размер страницы + #опциональный параметр + #тип - число + #если не задан - выводятся все доступные данные + #size = 3 +} + +output "test" { + value = data.decort_account_list.al +} diff --git a/samples/cloudbroker/data_account_rg_list/main.tf b/samples/cloudbroker/data_account_rg_list/main.tf new file mode 100644 index 0000000..13fbb6d --- /dev/null +++ b/samples/cloudbroker/data_account_rg_list/main.tf @@ -0,0 +1,37 @@ +/* +Пример использования +Получение информации о ресурных группах, используемых аккаунтом +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_account_rg_list" "argl" { + #id аккаунта + #обязательный параметр + #тип - число + account_id = 66666 +} + +output "test" { + value = data.decort_account_rg_list.argl +} diff --git a/samples/cloudbroker/data_account_vins_list/main.tf b/samples/cloudbroker/data_account_vins_list/main.tf new file mode 100644 index 0000000..e0e8eda --- /dev/null +++ b/samples/cloudbroker/data_account_vins_list/main.tf @@ -0,0 +1,39 @@ +/* +Пример использования +Получение списка vins, используемых аккаунтом + +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_account_vins_list" "avl" { + #id аккаунта + #обязательный параметр + #тип - число + account_id = 22222 + +} + +output "test" { + value = data.decort_account_vins_list.avl +} diff --git a/samples/cloudbroker/data_disk/main.tf b/samples/cloudbroker/data_disk/main.tf new file mode 100644 index 0000000..941dccf --- /dev/null +++ b/samples/cloudbroker/data_disk/main.tf @@ -0,0 +1,35 @@ +/* +Пример использования +Получение списка доступных образов +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером + +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} + + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_disk" "acl" { + disk_id = 49304 + +} + +output "test" { + value = data.decort_disk.acl +} diff --git a/samples/cloudbroker/data_disk_list/main.tf b/samples/cloudbroker/data_disk_list/main.tf new file mode 100644 index 0000000..1074f83 --- /dev/null +++ b/samples/cloudbroker/data_disk_list/main.tf @@ -0,0 +1,54 @@ +/* +Пример использования +Получение списка доступных дисков +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером +/* +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} +*/ + + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +data "decort_disk_list" "dl" { + #id аккаунта для получения списка дисков + #опциональный параметр + #тип - число + #account_id = 11111 + + #тип диска + #опциональный параметр + #тип - строка + #возможные типы: "b" - boot_disk, "d" - data_disk + #type = "d" + + #кол-во страниц для вывода + #опицональный параметр + #тип - число + #page = 1 + + #размер страницы + #опицональный параметр + #тип - число + #size = 1 +} + +output "test" { + value = data.decort_disk_list.dl +} diff --git a/samples/cloudbroker/resource_disk/main.tf b/samples/cloudbroker/resource_disk/main.tf new file mode 100644 index 0000000..7097213 --- /dev/null +++ b/samples/cloudbroker/resource_disk/main.tf @@ -0,0 +1,56 @@ +/* +Пример использования +Получение списка доступных образов +*/ +#Расскомментируйте этот код, +#и внесите необходимые правки в версию и путь, +#чтобы работать с установленным вручную (не через hashicorp provider registry) провайдером + +terraform { + required_providers { + decort = { + version = "1.1" + source = "digitalenergy.online/decort/decort" + } + } +} + + +provider "decort" { + authenticator = "oauth2" + #controller_url = + controller_url = "https://ds1.digitalenergy.online" + #oauth2_url = + oauth2_url = "https://sso.digitalenergy.online" + allow_unverified_ssl = true +} + +resource "decort_disk" "acl" { + account_id = 88366 + gid = 212 + disk_name = "super-disk-re" + size_max = 20 + restore = true + permanently = true + reason = "delete" + iotune { + read_bytes_sec = 0 + read_bytes_sec_max = 0 + read_iops_sec = 0 + read_iops_sec_max = 0 + size_iops_sec = 0 + total_bytes_sec = 0 + total_bytes_sec_max = 0 + total_iops_sec = 3000 + total_iops_sec_max = 0 + write_bytes_sec = 0 + write_bytes_sec_max = 0 + write_iops_sec = 0 + write_iops_sec_max = 0 + } + +} + +output "test" { + value = decort_disk.acl +}