Initial code injection, no testing or debugging yet
This commit is contained in:
485
decort/resource_compute.go
Normal file
485
decort/resource_compute.go
Normal file
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
Copyright (c) 2019-2020 Digital Energy Cloud Solutions LLC. All Rights Reserved.
|
||||
Author: Sergey Shubin, <sergey.shubin@digitalenergy.online>, <svs1370@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration
|
||||
Technology platfom.
|
||||
|
||||
Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates.
|
||||
*/
|
||||
|
||||
package decort
|
||||
|
||||
import (
|
||||
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
)
|
||||
|
||||
|
||||
func resourceComputeCreate(d *schema.ResourceData, m interface{}) error {
|
||||
machine := &MachineConfig{
|
||||
ResGroupID: d.Get("rgid").(int),
|
||||
Name: d.Get("name").(string),
|
||||
Cpu: d.Get("cpu").(int),
|
||||
Ram: d.Get("ram").(int),
|
||||
ImageID: d.Get("image_id").(int),
|
||||
Description: d.Get("description").(string),
|
||||
}
|
||||
// BootDisk
|
||||
// DataDisks
|
||||
// Networks
|
||||
// PortForwards
|
||||
// SshKeyData string
|
||||
log.Printf("resourceComputeCreate: called for VM name %q, ResGroupID %d", machine.Name, machine.ResGroupID)
|
||||
|
||||
var subres_list []interface{}
|
||||
var subres_data map[string]interface{}
|
||||
var arg_value interface{}
|
||||
var arg_set bool
|
||||
// boot disk list is a required argument and has only one element,
|
||||
// which is of type diskSubresourceSchema
|
||||
subres_list = d.Get("boot_disk").([]interface{})
|
||||
subres_data = subres_list[0].(map[string]interface{})
|
||||
machine.BootDisk.Label = subres_data["label"].(string)
|
||||
machine.BootDisk.Size = subres_data["size"].(int)
|
||||
machine.BootDisk.Pool = subres_data["pool"].(string)
|
||||
machine.BootDisk.Provider = subres_data["provider"].(string)
|
||||
|
||||
|
||||
arg_value, arg_set = d.GetOk("data_disks")
|
||||
if arg_set {
|
||||
log.Printf("resourceComputeCreate: calling makeDisksConfig")
|
||||
machine.DataDisks, _ = makeDisksConfig(arg_value.([]interface{}))
|
||||
}
|
||||
|
||||
arg_value, arg_set = d.GetOk("networks")
|
||||
if arg_set {
|
||||
log.Printf("resourceComputeCreate: calling makeNetworksConfig")
|
||||
machine.Networks, _ = makeNetworksConfig(arg_value.([]interface{}))
|
||||
}
|
||||
|
||||
arg_value, arg_set = d.GetOk("port_forwards")
|
||||
if arg_set {
|
||||
log.Printf("resourceComputeCreate: calling makePortforwardsConfig")
|
||||
machine.PortForwards, _ = makePortforwardsConfig(arg_value.([]interface{}))
|
||||
}
|
||||
|
||||
arg_value, arg_set = d.GetOk("ssh_keys")
|
||||
if arg_set {
|
||||
log.Printf("resourceComputeCreate: calling makeSshKeysConfig")
|
||||
machine.SshKeys, _ = makeSshKeysConfig(arg_value.([]interface{}))
|
||||
}
|
||||
|
||||
// create basic VM (i.e. without port forwards and ext network connections - those will be done
|
||||
// by separate API calls)
|
||||
d.Partial(true)
|
||||
controller := m.(*ControllerCfg)
|
||||
url_values := &url.Values{}
|
||||
url_values.Add("cloudspaceId", fmt.Sprintf("%d", machine.ResGroupID))
|
||||
url_values.Add("name", machine.Name)
|
||||
url_values.Add("description", machine.Description)
|
||||
url_values.Add("vcpus", fmt.Sprintf("%d", machine.Cpu))
|
||||
url_values.Add("memory", fmt.Sprintf("%d", machine.Ram))
|
||||
url_values.Add("imageId", fmt.Sprintf("%d", machine.ImageID))
|
||||
url_values.Add("disksize", fmt.Sprintf("%d", machine.BootDisk.Size))
|
||||
if len(machine.SshKeys) > 0 {
|
||||
url_values.Add("userdata", makeSshKeysArgString(machine.SshKeys))
|
||||
}
|
||||
api_resp, err := controller.decortAPICall("POST", MachineCreateAPI, url_values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetId(api_resp) // machines/create API plainly returns ID of the new VM on success
|
||||
machine.ID, _ = strconv.Atoi(api_resp)
|
||||
d.SetPartial("name")
|
||||
d.SetPartial("description")
|
||||
d.SetPartial("cpu")
|
||||
d.SetPartial("ram")
|
||||
d.SetPartial("image_id")
|
||||
d.SetPartial("boot_disk")
|
||||
if len(machine.SshKeys) > 0 {
|
||||
d.SetPartial("ssh_keys")
|
||||
}
|
||||
|
||||
log.Printf("resourceComputeCreate: new VM ID %d, name %q created", machine.ID, machine.Name)
|
||||
|
||||
if len(machine.DataDisks) > 0 || len(machine.PortForwards) > 0 {
|
||||
// for data disk or port foreards provisioning we have to know Tenant ID
|
||||
// and Grid ID so we call utilityResgroupConfigGet method to populate these
|
||||
// fields in the machine structure that will be passed to provisionVmDisks or
|
||||
// provisionVmPortforwards
|
||||
log.Printf("resourceComputeCreate: calling utilityResgroupConfigGet")
|
||||
resgroup, err := controller.utilityResgroupConfigGet(machine.ResGroupID)
|
||||
if err == nil {
|
||||
machine.TenantID = resgroup.TenantID
|
||||
machine.GridID = resgroup.GridID
|
||||
machine.ExtIP = resgroup.ExtIP
|
||||
log.Printf("resourceComputeCreate: tenant ID %d, GridID %d, ExtIP %q",
|
||||
machine.TenantID, machine.GridID, machine.ExtIP)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Configure data disks
|
||||
disks_ok := true
|
||||
if len(machine.DataDisks) > 0 {
|
||||
log.Printf("resourceComputeCreate: calling utilityVmDisksProvision for disk count %d", len(machine.DataDisks))
|
||||
if machine.TenantID == 0 {
|
||||
// if TenantID is still 0 it means that we failed to get Resgroup Facts by
|
||||
// a previous call to utilityResgroupGetFacts,
|
||||
// hence we do not have technical ability to provision data disks
|
||||
disks_ok = false
|
||||
} else {
|
||||
// provisionVmDisks accomplishes two steps for each data disk specification
|
||||
// 1) creates the disks
|
||||
// 2) attaches them to the VM
|
||||
err = controller.utilityVmDisksProvision(machine)
|
||||
if err != nil {
|
||||
disks_ok = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if disks_ok {
|
||||
d.SetPartial("data_disks")
|
||||
}
|
||||
|
||||
//
|
||||
// Configure port forward rules
|
||||
pfws_ok := true
|
||||
if len(machine.PortForwards) > 0 {
|
||||
log.Printf("resourceComputeCreate: calling utilityVmPortforwardsProvision for pfw rules count %d", len(machine.PortForwards))
|
||||
if machine.ExtIP == "" {
|
||||
// if ExtIP is still empty it means that we failed to get Resgroup Facts by
|
||||
// a previous call to utilityResgroupGetFacts,
|
||||
// hence we do not have technical ability to provision port forwards
|
||||
pfws_ok = false
|
||||
} else {
|
||||
err := controller.utilityVmPortforwardsProvision(machine)
|
||||
if err != nil {
|
||||
pfws_ok = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if pfws_ok {
|
||||
// there were no errors reported when configuring port forwards
|
||||
d.SetPartial("port_forwards")
|
||||
}
|
||||
|
||||
//
|
||||
// Configure external networks
|
||||
// NOTE: currently only one external network can be attached to each VM, so in the current
|
||||
// implementation we ignore all but the 1st network definition
|
||||
nets_ok := true
|
||||
if len(machine.Networks) > 0 {
|
||||
log.Printf("resourceComputeCreate: calling utilityVmNetworksProvision for networks count %d", len(machine.Networks))
|
||||
err := controller.utilityVmNetworksProvision(machine)
|
||||
if err != nil {
|
||||
nets_ok = false
|
||||
}
|
||||
}
|
||||
if nets_ok {
|
||||
// there were no errors reported when configuring networks
|
||||
d.SetPartial("networks")
|
||||
}
|
||||
|
||||
if ( disks_ok && nets_ok && pfws_ok ) {
|
||||
// if there were no errors in setting any of the subresources, we may leave Partial mode
|
||||
d.Partial(false)
|
||||
}
|
||||
|
||||
// resourceComputeRead will also update resource ID on success, so that Terraform will know
|
||||
// that resource exists
|
||||
return resourceComputeRead(d, m)
|
||||
}
|
||||
|
||||
func resourceComputeRead(d *schema.ResourceData, m interface{}) error {
|
||||
log.Printf("resourceComputeRead: called for VM name %q, ResGroupID %d",
|
||||
d.Get("name").(string), d.Get("rgid").(int))
|
||||
|
||||
comp_facts, err := utilityComputeCheckPresence(d, m)
|
||||
if comp_facts == "" {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// VM was not found
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = flattenCompute(d, comp_facts); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("resourceComputeRead: after flattenCompute: VM ID %s, VM name %q, ResGroupID %d",
|
||||
d.Id(), d.Get("name").(string), d.Get("rgid").(int))
|
||||
|
||||
// Not all parameters, that we may need, are returned by machines/get API
|
||||
// Continue with further reading of VM subresource parameters:
|
||||
controller := m.(*ControllerCfg)
|
||||
url_values := &url.Values{}
|
||||
|
||||
/*
|
||||
// Obtain information on external networks
|
||||
url_values.Add("machineId", d.Id())
|
||||
body_string, err := controller.decortAPICall("POST", VmExtNetworksListAPI, url_values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
net_list := ExtNetworksResp{}
|
||||
err = json.Unmarshal([]byte(body_string), &net_list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(net_list) > 0 {
|
||||
if err = d.Set("networks", flattenNetworks(net_list)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// Ext networks flattening is now done inside flattenCompute because it is currently based
|
||||
// on data read into NICs component by machine/get API call
|
||||
|
||||
if err = d.Set("networks", flattenNetworks()); err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
|
||||
//
|
||||
// Obtain information on port forwards
|
||||
url_values.Add("cloudspaceId", fmt.Sprintf("%d",d.Get("rgid")))
|
||||
url_values.Add("machineId", d.Id())
|
||||
pfw_list := PortforwardsResp{}
|
||||
body_string, err := controller.decortAPICall("POST", PortforwardsListAPI, url_values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal([]byte(body_string), &pfw_list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(pfw_list) > 0 {
|
||||
if err = d.Set("port_forwards", flattenPortforwards(pfw_list)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceComputeUpdate(d *schema.ResourceData, m interface{}) error {
|
||||
log.Printf("resourceComputeUpdate: called for VM name %q, ResGroupID %d",
|
||||
d.Get("name").(string), d.Get("rgid").(int))
|
||||
|
||||
return resourceComputeRead(d, m)
|
||||
}
|
||||
|
||||
func resourceComputeDelete(d *schema.ResourceData, m interface{}) error {
|
||||
// NOTE: this method destroys target VM with flag "permanently", so there is no way to
|
||||
// restore destroyed VM
|
||||
log.Printf("resourceComputeDelete: called for VM name %q, ResGroupID %d",
|
||||
d.Get("name").(string), d.Get("rgid").(int))
|
||||
|
||||
comp_facts, err := utilityComputeCheckPresence(d, m)
|
||||
if comp_facts == "" {
|
||||
// the target VM 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("machineId", d.Id())
|
||||
params.Add("permanently", "true")
|
||||
|
||||
controller := m.(*ControllerCfg)
|
||||
comp_facts, err = controller.decortAPICall("POST", MachineDeleteAPI, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceComputeExists(d *schema.ResourceData, m interface{}) (bool, error) {
|
||||
// Reminder: according to Terraform rules, this function should not modify its ResourceData argument
|
||||
log.Printf("resourceComputeExist: called for VM name %q, ResGroupID %d",
|
||||
d.Get("name").(string), d.Get("rgid").(int))
|
||||
|
||||
comp_facts, err := utilityComputeCheckPresence(d, m)
|
||||
if comp_facts == "" {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func resourceCompute() *schema.Resource {
|
||||
return &schema.Resource {
|
||||
SchemaVersion: 1,
|
||||
|
||||
Create: resourceComputeCreate,
|
||||
Read: resourceComputeRead,
|
||||
Update: resourceComputeUpdate,
|
||||
Delete: resourceComputeDelete,
|
||||
Exists: resourceComputeExists,
|
||||
|
||||
Timeouts: &schema.ResourceTimeout {
|
||||
Create: &Timeout180s,
|
||||
Read: &Timeout30s,
|
||||
Update: &Timeout180s,
|
||||
Delete: &Timeout60s,
|
||||
Default: &Timeout60s,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema {
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of this virtual machine. This parameter is case sensitive.",
|
||||
},
|
||||
|
||||
"rgid": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validation.IntAtLeast(1),
|
||||
Description: "ID of the resource group where this virtual machine should be deployed.",
|
||||
},
|
||||
|
||||
"cpu": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validation.IntBetween(1, 64),
|
||||
Description: "Number of CPUs to allocate to this virtual machine.",
|
||||
},
|
||||
|
||||
"ram": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validation.IntAtLeast(512),
|
||||
Description: "Amount of RAM in MB to allocate to this virtual machine.",
|
||||
},
|
||||
|
||||
"image_id": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
Description: "ID of the OS image to base this virtual machine on.",
|
||||
},
|
||||
|
||||
"boot_disk": {
|
||||
Type: schema.TypeList,
|
||||
Required: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource {
|
||||
Schema: diskSubresourceSchema(),
|
||||
},
|
||||
Description: "Specification for a boot disk on this virtual machine.",
|
||||
},
|
||||
|
||||
"data_disks": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 12,
|
||||
Elem: &schema.Resource {
|
||||
Schema: diskSubresourceSchema(),
|
||||
},
|
||||
Description: "Specification for data disks on this virtual machine.",
|
||||
},
|
||||
|
||||
"guest_logins": {
|
||||
Type: schema.TypeList,
|
||||
Computed: true,
|
||||
Elem: &schema.Resource {
|
||||
Schema: loginsSubresourceSchema(),
|
||||
},
|
||||
Description: "Specification for guest logins on this virtual machine.",
|
||||
},
|
||||
|
||||
"networks": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 8,
|
||||
Elem: &schema.Resource {
|
||||
Schema: networkSubresourceSchema(),
|
||||
},
|
||||
Description: "Specification for the networks to connect this virtual machine to.",
|
||||
},
|
||||
|
||||
"nics": {
|
||||
Type: schema.TypeList,
|
||||
Computed: true,
|
||||
MaxItems: 8,
|
||||
Elem: &schema.Resource {
|
||||
Schema: nicSubresourceSchema(),
|
||||
},
|
||||
Description: "Specification for the virutal NICs allocated to this virtual machine.",
|
||||
},
|
||||
|
||||
"ssh_keys": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 12,
|
||||
Elem: &schema.Resource {
|
||||
Schema: sshSubresourceSchema(),
|
||||
},
|
||||
Description: "SSH keys to authorize on this virtual machine.",
|
||||
},
|
||||
|
||||
"port_forwards": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 12,
|
||||
Elem: &schema.Resource {
|
||||
Schema: portforwardSubresourceSchema(),
|
||||
},
|
||||
Description: "Specification for the port forwards to configure for this virtual machine.",
|
||||
},
|
||||
|
||||
"description": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Description of this virtual machine.",
|
||||
},
|
||||
|
||||
"user": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Default login name for the guest OS on this virtual machine.",
|
||||
},
|
||||
|
||||
"password": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Sensitive: true,
|
||||
Description: "Default password for the guest OS login on this virtual machine.",
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user