diff --git a/decort/data_source_disk.go b/decort/data_source_disk.go index 16ccef2..f6ff797 100644 --- a/decort/data_source_disk.go +++ b/decort/data_source_disk.go @@ -61,11 +61,11 @@ func flattenDisk(d *schema.ResourceData, disk_facts string) error { 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("compute_id", model.ComputeID) d.Set("description", model.Desc) - d.Set("status", model.Status) - d.Set("tech_status", model.TechStatus) + // d.Set("status", model.Status) + // d.Set("tech_status", model.TechStatus) /* we do not manage snapshots via Terraform yet, so keep this commented out for a while if len(model.Snapshots) > 0 { @@ -96,37 +96,32 @@ func dataSourceDiskSchemaMake() map[string]*schema.Schema { "name": { Type: schema.TypeString, Optional: true, - Description: "Name of this disk. NOTE: disk names are NOT unique within an account.", + 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 name, account and account ID are ignored.", + Description: "ID of the disk to get. If disk ID is specified, then disk name and account ID are ignored.", }, "account_id": { Type: schema.TypeInt, Optional: true, - Description: "ID of the account this disk belongs to.", + Description: "ID of the account this disk belongs to. If disk ID is specified, then account ID is ignored.", }, - "account_name": { - Type: schema.TypeString, - Optional: true, - Description: "Name of the account this disk belongs to. If account ID is specified, account name is ignored.", - }, - - "description": { - Type: schema.TypeString, + // The rest of the data source Disk schema are all computed + "sep_id": { + Type: schema.TypeInt, Computed: true, - Description: "User-defined text description of this disk.", + Description: "Storage end-point provider serving this disk.", }, - "image_id": { - Type: schema.TypeInt, + "pool": { + Type: schema.TypeString, Computed: true, - Description: "ID of the image, which this disk was cloned from.", + Description: "Pool where this disk is located.", }, "size": { @@ -141,21 +136,22 @@ func dataSourceDiskSchemaMake() map[string]*schema.Schema { Description: "Type of this disk.", }, - /* - "snapshots": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource { - Schema: snapshotSubresourceSchemaMake(), - }, - Description: "List of user-created snapshots for this disk." - }, - */ + "description": { + Type: schema.TypeString, + Computed: true, + Description: "User-defined text description of this disk.", + }, - "sep_id": { + "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.", + }, + + "image_id": { Type: schema.TypeInt, Computed: true, - Description: "Storage end-point provider serving this disk.", + Description: "ID of the image, which this disk was cloned from (valid for disk clones only).", }, "sep_type": { @@ -164,11 +160,15 @@ func dataSourceDiskSchemaMake() map[string]*schema.Schema { Description: "Type of the storage end-point provider serving this disk.", }, - "pool": { - Type: schema.TypeString, - Computed: true, - Description: "Pool where this disk is located.", - }, + /* + "snapshots": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource { + Schema: snapshotSubresourceSchemaMake(), + }, + Description: "List of user-created snapshots for this disk." + }, "status": { Type: schema.TypeString, @@ -187,6 +187,7 @@ func dataSourceDiskSchemaMake() map[string]*schema.Schema { Computed: true, Description: "ID of the compute instance where this disk is attached to, or 0 for unattached disk.", }, + */ } return rets diff --git a/decort/models_api.go b/decort/models_api.go index 0b34a05..1c5ced6 100644 --- a/decort/models_api.go +++ b/decort/models_api.go @@ -476,17 +476,6 @@ const ComputeDiskDetachAPI = "/restmachine/cloudapi/compute/diskDetach" // // structures related to /cloudapi/disks/create // -type DiskCreateParam struct { - AccountID int `json:"accountId` - GridID int `json:"gid"` - Name string `json:"string"` - Description string `json:"description"` - Size int `json:"size"` - Type string `json:"type"` - SepID int `json:"sep_id"` - Pool string `json:"pool"` -} - const DiskCreateAPI = "/restmachine/cloudapi/disks/create" // @@ -499,6 +488,20 @@ const DisksGetAPI = "/restmachine/cloudapi/disks/get" // Returns single DiskReco const DisksListAPI = "/restmachine/cloudapi/disks/list" // Returns list of DiskRecord on success type DisksListResp []DiskRecord +// +// Grid ID structures +// +type LocationRecord struct { + GridID int `json:"gid"` + Id int `json:"id"` + LocationCode string `json:"locationCode"` + Name string `json:"name"` + Flag string `json:"flag"` +} + +const LocationsListAPI = "/restmachine/cloudapi/locations/list" // Returns list of GridRecord on success +type LocationsListResp []LocationRecord + // // Auxiliary structures // diff --git a/decort/provider.go b/decort/provider.go index cd829a3..a59f1b3 100644 --- a/decort/provider.go +++ b/decort/provider.go @@ -18,6 +18,7 @@ limitations under the License. package decort import ( + "fmt" "strings" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -110,7 +111,7 @@ func Provider() *schema.Provider { "decort_resgroup": dataSourceResgroup(), "decort_kvmvm": dataSourceCompute(), "decort_image": dataSourceImage(), - // "decort_disk": dataSourceDisk(), + "decort_disk": dataSourceDisk(), // "decort_vins": dataSourceVins(), // "decort_pfw": dataSourcePfw(), }, @@ -128,5 +129,15 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { if err != nil { return nil, err } + + // initialize global default Grid ID - it will be needed to create some resource types, e.g. disks + gridId, err := decsController.utilityLocationGetDefaultGridID() + if err != nil { + return nil, err + } + if gridId == 0 { + return nil, fmt.Errorf("providerConfigure: invalid default Grid ID = 0") + } + return decsController, nil } diff --git a/decort/resource_compute.go b/decort/resource_compute.go index b9c97d1..a590c39 100644 --- a/decort/resource_compute.go +++ b/decort/resource_compute.go @@ -37,7 +37,7 @@ import ( ) func resourceComputeCreate(d *schema.ResourceData, m interface{}) error { - // we assume all mandatiry parameters it takes to create a comptue instance are properly + // 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)) diff --git a/decort/resource_disk.go b/decort/resource_disk.go new file mode 100644 index 0000000..b5eb953 --- /dev/null +++ b/decort/resource_disk.go @@ -0,0 +1,243 @@ +/* +Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +Technology platfom. + +Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates. +*/ + +package decort + +import ( + // "encoding/json" + "fmt" + "net/url" + "strconv" + + log "github.com/sirupsen/logrus" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + // "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceDiskCreate(d *schema.ResourceData, m interface{}) error { + log.Debugf("resourceDiskCreate: called for Disk name %q, Account ID %d", d.Get("name").(string), d.Get("account_id").(int)) + + controller := m.(*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", DefaultGridID)) // we use default Grid ID, which was obtained along with DECORT Controller init + urlValues.Add("name", d.Get("name").(string)) + urlValues.Add("size", d.Get("size").(string)) + urlValues.Add("type", d.Get("type").(string)) + urlValues.Add("sep_id", fmt.Sprintf("%d", d.Get("sep_id").(int))) + urlValues.Add("pool", d.Get("pool").(string)) + + argVal, argSet := d.GetOk("description") + if argSet { + urlValues.Add("decs", argVal.(string)) + } + + apiResp, err := controller.decortAPICall("POST", DiskCreateAPI, urlValues) + if err != nil { + return err + } + + d.SetId(apiResp) // update ID of the resource to tell Terraform that the disk resource exists + diskId, _ := strconv.Atoi(apiResp) + + log.Debugf("resourceDiskCreate: new Disk ID %d, name %q creation sequence complete", diskId, d.Get("name").(string)) + + // 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(d, m) +} + +func resourceDiskRead(d *schema.ResourceData, m interface{}) error { + disk_facts, err := utilityDiskCheckPresence(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 err + } + + return flattenDisk(d, disk_facts) +} + +func resourceDiskUpdate(d *schema.ResourceData, m interface{}) error { + log.Debugf("resourceDiskUpdate: called for disk name %q, Account ID %d", + d.Get("name").(string), d.Get("account_id").(int)) + + log.Warn("resourceDiskUpdate: NOT IMPLEMENTED YET!") + + // we may reuse dataSourceDiskRead here as we maintain similarity + // between Compute resource and Compute data source schemas + return dataSourceDiskRead(d, m) +} + +func resourceDiskDelete(d *schema.ResourceData, m interface{}) error { + log.Warn("resourceDiskDelete: NOT IMPLEMENTED YET!") + return nil +} + +func resourceDiskExists(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 name %q, Account ID %d", + d.Get("name").(string), d.Get("account_id").(int)) + + diskFacts, err := utilityDiskCheckPresence(d, m) + if diskFacts == "" { + if err != nil { + return false, err + } + return false, nil + } + return true, nil +} + +func resourceDiskSchemaMake() 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.", + }, + + "account_id": { + Type: schema.TypeInt, + Optional: true, + Description: "ID of the account this disk belongs to.", + }, + + "sep_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "Storage end-point provider serving this disk. Cannot be changed for existing disk.", + }, + + "pool": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Pool where this disk is located. Cannot be changed for existing disk.", + }, + + "size": { + Type: schema.TypeInt, + Required: true, + Description: "Size of the disk in GB. Note, that existing disks can only be grown in size.", + }, + + "type": { + Type: schema.TypeString, + Optional: true, + Default: "D", + Description: "Optional type of this disk. Defaults to D, i.e. data disk. Cannot be changed for existing disks.", + }, + + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Optional user-defined text description of this disk.", + }, + + // The rest of the attributes are all computed + "account_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the account this disk belongs to.", + }, + + "image_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the image, which this disk was cloned from (if ever cloned).", + }, + + "sep_type": { + Type: schema.TypeString, + Computed: true, + Description: "Type of the storage end-point provider serving this disk.", + }, + + /* + "snapshots": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource { + Schema: snapshotSubresourceSchemaMake(), + }, + 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.", + }, + */ + } + + return rets +} + +func resourceDisk() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + Create: resourceDiskCreate, + Read: resourceDiskRead, + Update: resourceDiskUpdate, + Delete: resourceDiskDelete, + Exists: resourceDiskExists, + + Timeouts: &schema.ResourceTimeout{ + Create: &Timeout180s, + Read: &Timeout30s, + Update: &Timeout180s, + Delete: &Timeout60s, + Default: &Timeout60s, + }, + + Schema: resourceDiskSchemaMake(), + } +} diff --git a/decort/utility_account.go b/decort/utility_account.go index 9b1bbad..94db06f 100644 --- a/decort/utility_account.go +++ b/decort/utility_account.go @@ -122,7 +122,7 @@ func utilityGetAccountIdBySchema(d *schema.ResourceData, m interface{}) (int, er accName, argSet := d.GetOk("account_name") if !argSet { - return 0, fmt.Errorf("Either non-empty account name or positive account ID must be specified") + return 0, fmt.Errorf("Either non-empty account name or valid account ID must be specified") } controller := m.(*ControllerCfg) diff --git a/decort/utility_location.go b/decort/utility_location.go new file mode 100644 index 0000000..c1fd12f --- /dev/null +++ b/decort/utility_location.go @@ -0,0 +1,65 @@ +/* +Copyright (c) 2021 Digital Energy Cloud Solutions LLC. All Rights Reserved. +Author: Sergey Shubin, , + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration +Technology platfom. + +Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates. +*/ + +package decort + +import ( + "encoding/json" + "fmt" + "net/url" + + log "github.com/sirupsen/logrus" + + // "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + // "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +var DefaultGridID int + +func (controller *ControllerCfg) utilityLocationGetDefaultGridID() (int, error) { + urlValues := &url.Values{} + + log.Debug("utilityLocationGetDefaultGridID: retrieving locations list") + apiResp, err := controller.decortAPICall("POST", LocationsListAPI, urlValues) + if err != nil { + return 0, err + } + + locList := LocationsListResp{} + err = json.Unmarshal([]byte(apiResp), &locList) + if err != nil { + return 0, err + } + + if len(locList) == 0 { + DefaultGridID = 0 + return 0, fmt.Errorf("utilityLocationGetDefaultGridID: retrieved 0 length locations list") + } + + DefaultGridID = locList[0].GridID + log.Debugf("utilityLocationGetDefaultGridID: default location GridID %d, name %s", DefaultGridID, locList[0].Name) + + return DefaultGridID, nil +} +