Initial code injection, no testing or debugging yet
This commit is contained in:
407
decort/controller.go
Normal file
407
decort/controller.go
Normal file
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
Copyright (c) 2019-2021 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 (
|
||||
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
// "time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
// "github.com/hashicorp/terraform/terraform"
|
||||
|
||||
)
|
||||
|
||||
// enumerated constants that define authentication modes
|
||||
const (
|
||||
MODE_UNDEF = iota // this is the invalid mode - it should never be seen
|
||||
MODE_LEGACY = iota
|
||||
MODE_OAUTH2 = iota
|
||||
MODE_JWT = iota
|
||||
)
|
||||
|
||||
type ControllerCfg struct {
|
||||
controller_url string // always required
|
||||
auth_mode_code int // always required
|
||||
auth_mode_txt string // always required, it is a text representation of auth mode
|
||||
legacy_user string // required for legacy mode
|
||||
legacy_password string // required for legacy mode
|
||||
legacy_sid string // obtained from DECORT controller on successful login in legacy mode
|
||||
jwt string // obtained from Outh2 provider on successful login in oauth2 mode, required in jwt mode
|
||||
app_id string // required for oauth2 mode
|
||||
app_secret string // required for oauth2 mode
|
||||
oauth2_url string // always required
|
||||
decort_username string // assigned to either legacy_user (legacy mode) or Oauth2 user (oauth2 mode) upon successful verification
|
||||
cc_client *http.Client // assigned when all initial check successfully passed
|
||||
}
|
||||
|
||||
func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
||||
// This function first will check that all required provider parameters for the
|
||||
// selected authenticator mode are set correctly and initialize ControllerCfg structure
|
||||
// based on the provided parameters.
|
||||
//
|
||||
// Next, it will check for validity of supplied credentials by initiating connection to the specified
|
||||
// DECORT controller URL and, if succeeded, completes ControllerCfg structure with the rest of computed
|
||||
// parameters (e.g. JWT, session ID and Oauth2 user name).
|
||||
//
|
||||
// The structure created by this function should be used with subsequent calls to decortAPICall() method,
|
||||
// which is a DECORT authentication mode aware wrapper around standard HTTP requests.
|
||||
|
||||
ret_config := &ControllerCfg{
|
||||
controller_url: d.Get("controller_url").(string),
|
||||
auth_mode_code: MODE_UNDEF,
|
||||
legacy_user: d.Get("user").(string),
|
||||
legacy_password: d.Get("password").(string),
|
||||
legacy_sid: "",
|
||||
jwt: d.Get("jwt").(string),
|
||||
app_id: d.Get("app_id").(string),
|
||||
app_secret: d.Get("app_secret").(string),
|
||||
oauth2_url: d.Get("oauth2_url").(string),
|
||||
decort_username: "",
|
||||
}
|
||||
|
||||
var allow_unverified_ssl bool
|
||||
allow_unverified_ssl = d.Get("allow_unverified_ssl").(bool)
|
||||
|
||||
if ret_config.controller_url == "" {
|
||||
return nil, fmt.Errorf("Empty DECORT cloud controller URL provided.")
|
||||
}
|
||||
|
||||
// this should have already been done by StateFunc defined in Schema, but we want to be sure
|
||||
ret_config.auth_mode_txt = strings.ToLower(d.Get("authenticator").(string))
|
||||
|
||||
switch ret_config.auth_mode_txt {
|
||||
case "jwt":
|
||||
if ret_config.jwt == "" {
|
||||
return nil, fmt.Errorf("Authenticator mode 'jwt' specified but no JWT provided.")
|
||||
}
|
||||
ret_config.auth_mode_code = MODE_JWT
|
||||
case "oauth2":
|
||||
if ret_config.oauth2_url == "" {
|
||||
return nil, fmt.Errorf("Authenticator mode 'oauth2' specified but no OAuth2 URL provided.")
|
||||
}
|
||||
if ret_config.app_id == "" {
|
||||
return nil, fmt.Errorf("Authenticator mode 'oauth2' specified but no Application ID provided.")
|
||||
}
|
||||
if ret_config.app_secret == "" {
|
||||
return nil, fmt.Errorf("Authenticator mode 'oauth2' specified but no Secret ID provided.")
|
||||
}
|
||||
ret_config.auth_mode_code = MODE_OAUTH2
|
||||
case "legacy":
|
||||
//
|
||||
ret_config.legacy_user = d.Get("user").(string)
|
||||
if ret_config.legacy_user == "" {
|
||||
return nil, fmt.Errorf("Authenticator mode 'legacy' specified but no user provided.")
|
||||
}
|
||||
ret_config.legacy_password = d.Get("password").(string)
|
||||
if ret_config.legacy_password == "" {
|
||||
return nil, fmt.Errorf("Authenticator mode 'legacy' specified but no password provided.")
|
||||
}
|
||||
ret_config.auth_mode_code = MODE_LEGACY
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown authenticator mode %q provided.", ret_config.auth_mode_txt)
|
||||
}
|
||||
|
||||
if allow_unverified_ssl {
|
||||
log.Printf("ControllerConfigure: allow_unverified_ssl is set - will not check certificates!")
|
||||
transCfg := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},}
|
||||
ret_config.cc_client = &http.Client{
|
||||
Transport: transCfg,
|
||||
Timeout: Timeout180s,
|
||||
}
|
||||
} else {
|
||||
ret_config.cc_client = &http.Client{
|
||||
Timeout: Timeout180s, // time.Second * 30,
|
||||
}
|
||||
}
|
||||
|
||||
switch ret_config.auth_mode_code {
|
||||
case MODE_LEGACY:
|
||||
ok, err := ret_config.validateLegacyUser()
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
ret_config.decort_username = ret_config.legacy_user
|
||||
case MODE_JWT:
|
||||
//
|
||||
ok, err := ret_config.validateJWT("")
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
case MODE_OAUTH2:
|
||||
// on success getOAuth2JWT will set config.jwt to the obtained JWT, so there is no
|
||||
// need to set it once again here
|
||||
_, err := ret_config.getOAuth2JWT()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// we are not verifying the JWT when parsing because actual verification is done on the
|
||||
// OVC controller side. Here we do parsing solely to extract Oauth2 user name (claim "user")
|
||||
// and JWT issuer name (claim "iss")
|
||||
parser := jwt.Parser{}
|
||||
token, _, err := parser.ParseUnverified(ret_config.jwt, jwt.MapClaims{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||
var tbuf bytes.Buffer
|
||||
tbuf.WriteString(claims["username"].(string))
|
||||
tbuf.WriteString("@")
|
||||
tbuf.WriteString(claims["iss"].(string))
|
||||
ret_config.decort_username = tbuf.String()
|
||||
} else {
|
||||
return nil, fmt.Errorf("Failed to extract user and iss fields from JWT token in oauth2 mode.")
|
||||
}
|
||||
default:
|
||||
// FYI, this should never happen due to all above checks, but we want to be fool proof
|
||||
return nil, fmt.Errorf("Unknown authenticator mode code %d provided.", ret_config.auth_mode_code)
|
||||
}
|
||||
|
||||
// All checks passed successfully, credentials corresponding to the selected authenticator mode
|
||||
// obtained and validated.
|
||||
return ret_config, nil
|
||||
}
|
||||
|
||||
func (config *ControllerCfg) getDecsUsername() (string) {
|
||||
return config.decort_username
|
||||
}
|
||||
|
||||
func (config *ControllerCfg) getOAuth2JWT() (string, error) {
|
||||
// Obtain JWT from the Oauth2 provider using application ID and application secret provided in config.
|
||||
if config.auth_mode_code == MODE_UNDEF {
|
||||
return "", fmt.Errorf("getOAuth2JWT method called for undefined authorization mode.")
|
||||
}
|
||||
if config.auth_mode_code != MODE_OAUTH2 {
|
||||
return "", fmt.Errorf("getOAuth2JWT method called for incompatible authorization mode %q.", config.auth_mode_txt)
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "client_credentials")
|
||||
params.Add("client_id", config.app_id)
|
||||
params.Add("client_secret", config.app_secret)
|
||||
params.Add("response_type", "id_token")
|
||||
params.Add("validity", "3600")
|
||||
params_str := params.Encode()
|
||||
|
||||
req, err := http.NewRequest("POST", config.oauth2_url + "/v1/oauth/access_token", strings.NewReader(params_str))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(params_str)))
|
||||
|
||||
resp, err := config.cc_client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// fmt.Println("response Status:", resp.Status)
|
||||
// fmt.Println("response Headers:", resp.Header)
|
||||
// fmt.Println("response Headers:", req.URL)
|
||||
return "", fmt.Errorf("getOauth2JWT: unexpected status code %d when obtaining JWT from %q for APP_ID %q, request Body %q",
|
||||
resp.StatusCode, req.URL, config.app_id, params_str)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
responseData, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// validation successful - store JWT in the corresponding field of the ControllerCfg structure
|
||||
config.jwt = strings.TrimSpace(string(responseData))
|
||||
|
||||
return config.jwt, nil
|
||||
}
|
||||
|
||||
func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
|
||||
/*
|
||||
Validate JWT against DECORT controller. JWT can be supplied as argument to this method. If empty string supplied as
|
||||
argument, JWT will be taken from config attribute.
|
||||
DECORT controller URL will always be taken from the config attribute assigned at instantiation.
|
||||
Validation is accomplished by attempting API call that lists accounts for the invoking user.
|
||||
*/
|
||||
if jwt == "" {
|
||||
if config.jwt == "" {
|
||||
return false, fmt.Errorf("validateJWT method called, but no meaningful JWT provided.")
|
||||
}
|
||||
jwt = config.jwt
|
||||
}
|
||||
|
||||
if config.oauth2_url == "" {
|
||||
return false, fmt.Errorf("validateJWT method called, but no OAuth2 URL provided.")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", config.controller_url + "/restmachine/cloudapi/accounts/list", nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", jwt))
|
||||
// req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
// req.Header.Set("Content-Length", strconv.Itoa(0))
|
||||
|
||||
resp, err := config.cc_client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, fmt.Errorf("validateJWT: unexpected status code %d when validating JWT against %q.",
|
||||
resp.StatusCode, req.URL)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (config *ControllerCfg) validateLegacyUser() (bool, error) {
|
||||
/*
|
||||
Validate legacy user by obtaining a session key, which will be used for authenticating subsequent API calls
|
||||
to DECORT controller.
|
||||
If successful, the session key is stored in config.legacy_sid and true is returned. If unsuccessful for any
|
||||
reason, the method will return false and error.
|
||||
*/
|
||||
if config.auth_mode_code == MODE_UNDEF {
|
||||
return false, fmt.Errorf("validateLegacyUser method called for undefined authorization mode.")
|
||||
}
|
||||
if config.auth_mode_code != MODE_LEGACY {
|
||||
return false, fmt.Errorf("validateLegacyUser method called for incompatible authorization mode %q.", config.auth_mode_txt)
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("username", config.legacy_user)
|
||||
params.Add("password", config.legacy_password)
|
||||
params_str := params.Encode()
|
||||
|
||||
req, err := http.NewRequest("POST", config.controller_url + "/restmachine/cloudapi/users/authenticate", strings.NewReader(params_str))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(params_str)))
|
||||
|
||||
resp, err := config.cc_client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, fmt.Errorf("validateLegacyUser: unexpected status code %d when validating legacy user %q against %q.",
|
||||
resp.StatusCode, config.legacy_user, config.controller_url)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
responseData, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// validation successful - keep session ID for future use
|
||||
config.legacy_sid = string(responseData)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (config *ControllerCfg) decortAPICall(method string, api_name string, url_values *url.Values) (json_resp string, err error) {
|
||||
// This is a convenience wrapper around standard HTTP request methods that is aware of the
|
||||
// authorization mode for which the provider was initialized and compiles request accordingly.
|
||||
|
||||
if config.cc_client == nil {
|
||||
// this should never happen if ClientConfig was properly called prior to decortAPICall
|
||||
return "", fmt.Errorf("decortAPICall method called with unconfigured DECORT cloud controller HTTP client.")
|
||||
}
|
||||
|
||||
// Example: to create api_params, one would generally do the following:
|
||||
//
|
||||
// data := []byte(`{"machineId": "2638"}`)
|
||||
// api_params := bytes.NewBuffer(data))
|
||||
//
|
||||
// Or:
|
||||
//
|
||||
// params := url.Values{}
|
||||
// params.Add("machineId", "2638")
|
||||
// params.Add("username", "u")
|
||||
// params.Add("password", "b")
|
||||
// req, _ := http.NewRequest(method, url, strings.NewReader(params.Encode()))
|
||||
//
|
||||
|
||||
if config.auth_mode_code == MODE_UNDEF {
|
||||
return "", fmt.Errorf("decortAPICall method called for unknown authorization mode.")
|
||||
}
|
||||
|
||||
if config.auth_mode_code == MODE_LEGACY {
|
||||
url_values.Add("authkey", config.legacy_sid)
|
||||
}
|
||||
params_str := url_values.Encode()
|
||||
|
||||
req, err := http.NewRequest(method, config.controller_url + api_name, strings.NewReader(params_str))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(params_str)))
|
||||
|
||||
if config.auth_mode_code == MODE_OAUTH2 || config.auth_mode_code == MODE_JWT {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", config.jwt))
|
||||
}
|
||||
|
||||
resp, err := config.cc_client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
tmp_body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
json_resp := Jo2JSON(string(tmp_body))
|
||||
log.Printf("decortAPICall:\n %s", json_resp)
|
||||
return json_resp, nil
|
||||
} else {
|
||||
return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %q with request Body %q",
|
||||
resp.StatusCode, req.URL, params_str)
|
||||
}
|
||||
|
||||
/*
|
||||
if resp.StatusCode == StatusServiceUnavailable {
|
||||
return nil, fmt.Errorf("decortAPICall method called for incompatible authorization mode %q.", config.auth_mode_txt)
|
||||
}
|
||||
*/
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
245
decort/data_source_compute.go
Normal file
245
decort/data_source_compute.go
Normal file
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
Copyright (c) 2019-2021 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"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
)
|
||||
|
||||
func flattenCompute(d *schema.ResourceData, comp_facts string) error {
|
||||
// NOTE: this function modifies ResourceData argument - as such it should never be called
|
||||
// from resourceComputeExists(...) method
|
||||
model := MachinesGetResp{}
|
||||
log.Printf("flattenCompute: ready to unmarshal string %q", comp_facts)
|
||||
err := json.Unmarshal([]byte(comp_facts), &model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("flattenCompute: model.ID %d, model.ResGroupID %d", model.ID, model.ResGroupID)
|
||||
|
||||
d.SetId(fmt.Sprintf("%d", model.ID))
|
||||
d.Set("name", model.Name)
|
||||
d.Set("rgid", model.ResGroupID)
|
||||
d.Set("cpu", model.Cpu)
|
||||
d.Set("ram", model.Ram)
|
||||
// d.Set("boot_disk", model.BootDisk)
|
||||
d.Set("image_id", model.ImageID)
|
||||
d.Set("description", model.Description)
|
||||
|
||||
bootdisk_map := make(map[string]interface{})
|
||||
bootdisk_map["size"] = model.BootDisk
|
||||
bootdisk_map["label"] = "boot"
|
||||
bootdisk_map["pool"] = "default"
|
||||
bootdisk_map["provider"] = "default"
|
||||
|
||||
if err = d.Set("boot_disk", []interface{}{bootdisk_map}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(model.DataDisks) > 0 {
|
||||
log.Printf("flattenCompute: calling flattenDataDisks")
|
||||
if err = d.Set("data_disks", flattenDataDisks(model.DataDisks)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(model.NICs) > 0 {
|
||||
log.Printf("flattenCompute: calling flattenNICs")
|
||||
if err = d.Set("nics", flattenNICs(model.NICs)); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("flattenCompute: calling flattenNetworks")
|
||||
if err = d.Set("networks", flattenNetworks(model.NICs)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(model.GuestLogins) > 0 {
|
||||
log.Printf("flattenCompute: calling flattenGuestLogins")
|
||||
guest_logins := flattenGuestLogins(model.GuestLogins)
|
||||
if err = d.Set("guest_logins", guest_logins); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default_login := guest_logins[0].(map[string]interface{})
|
||||
// set user & password attributes to the corresponding values of the 1st item in the list
|
||||
if err = d.Set("user", default_login["login"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = d.Set("password", default_login["password"]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dataSourceComputeRead(d *schema.ResourceData, m interface{}) error {
|
||||
comp_facts, err := utilityComputeCheckPresence(d, m)
|
||||
if comp_facts == "" {
|
||||
// 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 err
|
||||
}
|
||||
|
||||
return flattenCompute(d, comp_facts)
|
||||
}
|
||||
|
||||
func dataSourceCompute() *schema.Resource {
|
||||
return &schema.Resource {
|
||||
SchemaVersion: 1,
|
||||
|
||||
Read: dataSourceComputeRead,
|
||||
|
||||
Timeouts: &schema.ResourceTimeout {
|
||||
Read: &Timeout30s,
|
||||
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 is located.",
|
||||
},
|
||||
|
||||
/*
|
||||
"internal_ip": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Internal IP address of this Compute.",
|
||||
},
|
||||
*/
|
||||
|
||||
"cpu": {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "Number of CPUs allocated for this virtual machine.",
|
||||
},
|
||||
|
||||
"ram": {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "Amount of RAM in MB allocated for this virtual machine.",
|
||||
},
|
||||
|
||||
"image_id": {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "ID of the OS image this virtual machine is based on.",
|
||||
},
|
||||
|
||||
/*
|
||||
"image_name": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Name of the OS image this virtual machine is based on.",
|
||||
},
|
||||
*/
|
||||
|
||||
"boot_disk": {
|
||||
Type: schema.TypeList,
|
||||
Computed: true,
|
||||
MinItems: 1,
|
||||
Elem: &schema.Resource {
|
||||
Schema: diskSubresourceSchema(),
|
||||
},
|
||||
Description: "Specification for a boot disk on this virtual machine.",
|
||||
},
|
||||
|
||||
"data_disks": {
|
||||
Type: schema.TypeList,
|
||||
Computed: true,
|
||||
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,
|
||||
Computed: true,
|
||||
Elem: &schema.Resource {
|
||||
Schema: networkSubresourceSchema(),
|
||||
},
|
||||
Description: "Specification for the networks to connect this virtual machine to.",
|
||||
},
|
||||
|
||||
"nics": {
|
||||
Type: schema.TypeList,
|
||||
Computed: true,
|
||||
Elem: &schema.Resource {
|
||||
Schema: nicSubresourceSchema(),
|
||||
},
|
||||
Description: "Specification for the virutal NICs allocated to this virtual machine.",
|
||||
},
|
||||
|
||||
"description": {
|
||||
Type: schema.TypeString,
|
||||
Computed: 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.",
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
111
decort/data_source_image.go
Normal file
111
decort/data_source_image.go
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
)
|
||||
|
||||
|
||||
func dataSourceImageRead(d *schema.ResourceData, m interface{}) error {
|
||||
name := d.Get("name").(string)
|
||||
rgid, rgid_set := d.GetOk("rgid")
|
||||
tenant_id, tenant_set := d.GetOk("tenant_id")
|
||||
|
||||
controller := m.(*ControllerCfg)
|
||||
url_values := &url.Values{}
|
||||
if tenant_set {
|
||||
url_values.Add("accountId", fmt.Sprintf("%d",tenant_id.(int)))
|
||||
}
|
||||
if rgid_set {
|
||||
url_values.Add("cloudspaceId", fmt.Sprintf("%d",rgid.(int)))
|
||||
}
|
||||
body_string, err := controller.decortAPICall("POST", ImagesListAPI, url_values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("dataSourceImageRead: ready to decode response body")
|
||||
model := ImagesListResp{}
|
||||
err = json.Unmarshal([]byte(body_string), &model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("%#v", model)
|
||||
log.Printf("dataSourceImageRead: traversing decoded JSON of length %d", len(model))
|
||||
for index, item := range model {
|
||||
// need to match VM by name
|
||||
if item.Name == name {
|
||||
log.Printf("dataSourceImageRead: index %d, matched name %q", index, item.Name)
|
||||
d.SetId(fmt.Sprintf("%d", model[index].ID))
|
||||
// d.Set("field_name", value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Cannot find OS Image name %q", name)
|
||||
}
|
||||
|
||||
func dataSourceImage() *schema.Resource {
|
||||
return &schema.Resource {
|
||||
SchemaVersion: 1,
|
||||
|
||||
Read: dataSourceImageRead,
|
||||
|
||||
Timeouts: &schema.ResourceTimeout {
|
||||
Read: &Timeout30s,
|
||||
Default: &Timeout60s,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema {
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of the OS image to locate. This parameter is case sensitive.",
|
||||
},
|
||||
|
||||
"tenant_id": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ValidateFunc: validation.IntAtLeast(1),
|
||||
Description: "ID of the tenant to limit image search to.",
|
||||
},
|
||||
|
||||
"rgid": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ValidateFunc: validation.IntAtLeast(1),
|
||||
Description: "ID of the resource group to limit image search to.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
139
decort/data_source_rg.go
Normal file
139
decort/data_source_rg.go
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
// "github.com/hashicorp/terraform/helper/validation"
|
||||
|
||||
)
|
||||
|
||||
func flattenResgroup(d *schema.ResourceData, rg_facts string) error {
|
||||
// NOTE: this function modifies ResourceData argument - as such it should never be called
|
||||
// from resourceRsgroupExists(...) method
|
||||
log.Printf("%s", rg_facts)
|
||||
log.Printf("flattenResgroup: ready to decode response body from %q", CloudspacesGetAPI)
|
||||
details := CloudspacesGetResp{}
|
||||
err := json.Unmarshal([]byte(rg_facts), &details)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("flattenResgroup: decoded ResGroup name %q / ID %d, tenant ID %d, public IP %q",
|
||||
details.Name, details.ID, details.TenantID, details.PublicIP)
|
||||
|
||||
d.SetId(fmt.Sprintf("%d", details.ID))
|
||||
d.Set("name", details.Name)
|
||||
d.Set("tenant_id", details.TenantID)
|
||||
d.Set("grid_id", details.GridID)
|
||||
d.Set("public_ip", details.PublicIP) // legacy field - this may be obsoleted when new network segments are implemented
|
||||
|
||||
log.Printf("flattenResgroup: calling flattenQuota()")
|
||||
if err = d.Set("quotas", flattenQuota(details.Quotas)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dataSourceResgroupRead(d *schema.ResourceData, m interface{}) error {
|
||||
rg_facts, err := utilityResgroupCheckPresence(d, m)
|
||||
if rg_facts == "" {
|
||||
// if empty string is returned from utilityResgroupCheckPresence then there is no
|
||||
// such resource group and err tells so - just return it to the calling party
|
||||
d.SetId("") // ensure ID is empty
|
||||
return err
|
||||
}
|
||||
|
||||
return flattenResgroup(d, rg_facts)
|
||||
}
|
||||
|
||||
|
||||
func dataSourceResgroup() *schema.Resource {
|
||||
return &schema.Resource {
|
||||
SchemaVersion: 1,
|
||||
|
||||
Read: dataSourceResgroupRead,
|
||||
|
||||
Timeouts: &schema.ResourceTimeout {
|
||||
Read: &Timeout30s,
|
||||
Default: &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 tenant.",
|
||||
},
|
||||
|
||||
"tenant": &schema.Schema {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of the tenant, which this resource group belongs to.",
|
||||
},
|
||||
|
||||
"tenant_id": &schema.Schema {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "Unique ID of the tenant, which this resource group belongs to.",
|
||||
},
|
||||
|
||||
"grid_id": &schema.Schema {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "Unique ID of the grid, where this resource group is deployed.",
|
||||
},
|
||||
|
||||
"location": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Location of this resource group.",
|
||||
},
|
||||
|
||||
"public_ip": { // this may be obsoleted as new network segments and true resource groups are implemented
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Public IP address of this resource group (if any).",
|
||||
},
|
||||
|
||||
"quotas": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource {
|
||||
Schema: quotasSubresourceSchema(),
|
||||
},
|
||||
Description: "Quotas on the resources for this resource group.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
130
decort/disk_subresource.go
Normal file
130
decort/disk_subresource.go
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Copyright (c) 2019 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.
|
||||
*/
|
||||
|
||||
package decs
|
||||
|
||||
import (
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
)
|
||||
|
||||
func makeDisksConfig(arg_list []interface{}) (disks []DiskConfig, count int) {
|
||||
count = len(arg_list)
|
||||
if count < 1 {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// allocate DataDisks list and fill it
|
||||
disks = make([]DiskConfig, count)
|
||||
var subres_data map[string]interface{}
|
||||
for index, value := range arg_list {
|
||||
subres_data = value.(map[string]interface{})
|
||||
disks[index].Label = subres_data["label"].(string)
|
||||
disks[index].Size = subres_data["size"].(int)
|
||||
disks[index].Pool = subres_data["pool"].(string)
|
||||
disks[index].Provider = subres_data["provider"].(string)
|
||||
}
|
||||
|
||||
return disks, count
|
||||
}
|
||||
|
||||
func flattenDataDisks(disks []DataDiskRecord) []interface{} {
|
||||
var length = 0
|
||||
for _, value := range disks {
|
||||
if value.DiskType == "D" {
|
||||
length += 1
|
||||
}
|
||||
}
|
||||
log.Printf("flattenDataDisks: found %d disks with D type", length)
|
||||
|
||||
result := make([]interface{}, length)
|
||||
if length == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
elem := make(map[string]interface{})
|
||||
|
||||
var subindex = 0
|
||||
for _, value := range disks {
|
||||
if value.DiskType == "D" {
|
||||
elem["label"] = value.Label
|
||||
elem["size"] = value.SizeMax
|
||||
elem["disk_id"] = value.ID
|
||||
elem["pool"] = "default"
|
||||
elem["provider"] = "default"
|
||||
result[subindex] = elem
|
||||
subindex += 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
func makeDataDisksArgString(disks []DiskConfig) string {
|
||||
// Prepare a string with the sizes of data disks for the virtual machine.
|
||||
// It is designed to be passed as "datadisks" argument of virtual machine create API call.
|
||||
if len(disks) < 1 {
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
*/
|
||||
|
||||
func diskSubresourceSchema() map[string]*schema.Schema {
|
||||
rets := map[string]*schema.Schema {
|
||||
"label": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Unique label to identify this disk among other disks connected to this VM.",
|
||||
},
|
||||
|
||||
"size": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validation.IntAtLeast(1),
|
||||
Description: "Size of the disk in GB.",
|
||||
},
|
||||
|
||||
"pool": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "default",
|
||||
Description: "Pool from which this disk should be provisioned.",
|
||||
},
|
||||
|
||||
"provider": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "default",
|
||||
Description: "Storage provider (storage technology type) by which this disk should be served.",
|
||||
},
|
||||
|
||||
"disk_id": {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "ID of this disk resource.",
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
return rets
|
||||
}
|
||||
71
decort/logins_subresource.go
Normal file
71
decort/logins_subresource.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright (c) 2019-2021 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.
|
||||
*/
|
||||
|
||||
package decs
|
||||
|
||||
import (
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
// "github.com/hashicorp/terraform/helper/validation"
|
||||
)
|
||||
|
||||
func flattenGuestLogins(logins []GuestLoginRecord) []interface{} {
|
||||
var result = make([]interface{}, len(logins))
|
||||
|
||||
elem := make(map[string]interface{})
|
||||
|
||||
for index, value := range logins {
|
||||
elem["guid"] = value.Guid
|
||||
elem["login"] = value.Login
|
||||
elem["password"] = value.Password
|
||||
result[index] = elem
|
||||
log.Printf("flattenGuestLogins: parsed element %d - login %q",
|
||||
index, value.Login)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func loginsSubresourceSchema() map[string]*schema.Schema {
|
||||
rets := map[string]*schema.Schema {
|
||||
"guid": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: "GUID of this guest user.",
|
||||
},
|
||||
|
||||
"login": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: "Login name of this guest user.",
|
||||
},
|
||||
|
||||
"password": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Sensitive: true,
|
||||
Description: "Password of this guest user.",
|
||||
},
|
||||
}
|
||||
|
||||
return rets
|
||||
}
|
||||
538
decort/models_api.go
Normal file
538
decort/models_api.go
Normal file
@@ -0,0 +1,538 @@
|
||||
/*
|
||||
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 (
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
//
|
||||
// timeouts for API calls from CRUD functions of Terraform plugin
|
||||
var Timeout30s = time.Second * 30
|
||||
var Timeout60s = time.Second * 60
|
||||
var Timeout180s = time.Second * 180
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/rg/list API
|
||||
//
|
||||
type UserAclRecord struct {
|
||||
IsExplicit bool `json:"explicit"`
|
||||
Rights string `json:"right"`
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
UgroupID string `json:"userGroupId"`
|
||||
// CanBeDeleted bool `json:"canBeDeleted"`
|
||||
}
|
||||
|
||||
type AccountAclRecord struct {
|
||||
IsExplicit bool `json:"explicit"`
|
||||
Guid string `json:"guid"`
|
||||
Rights string `json:"right"`
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
UgroupID string `json:"userGroupId"`
|
||||
}
|
||||
|
||||
type ResgroupRecord struct {
|
||||
ACLs []UserAclRecord `json:"ACLs"`
|
||||
Owner AccountAclRecord `json:"accountAcl"`
|
||||
TenantID int `json:"accountId"`
|
||||
TenantName string `json:"accountName"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
CreatedTime uint64 `json:"createdTime"`
|
||||
DefaultNetID int `json:"def_net_id"`
|
||||
DefaultNetType string `json:"def_net_type"`
|
||||
Decsription string `json:"desc"`
|
||||
GridID int `json:"gid"`
|
||||
ID uint `json:"id"`
|
||||
LockStatus string `json:"lockStatus"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
UpdatedTime uint64 `json:"updatedTime"`
|
||||
Vins []int `json:"vins"`
|
||||
Computes []int `json:"vms"`
|
||||
}
|
||||
|
||||
const ResgroupListAPI = "/restmachine/cloudapi/rg/list"
|
||||
type ResgroupListResp []ResgroupRecord
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/rg/create API call
|
||||
//
|
||||
const ResgroupCreateAPI= "/restmachine/cloudapi/rg/create"
|
||||
type ResgroupCreateParam struct {
|
||||
TenantID int `json:"accountId"`
|
||||
GridId int `json:"gid"`
|
||||
Name string `json:"name"`
|
||||
Ram int `json:"maxMemoryCapacity"`
|
||||
Disk int `json:"maxVDiskCapacity"`
|
||||
Cpu int `json:"maxCPUCapacity"`
|
||||
NetTraffic int `json:"maxNetworkPeerTransfer"`
|
||||
ExtIPs int `json:"maxNumPublicIP"`
|
||||
Owner string `json:"owner"`
|
||||
DefNet string `json:"def_net"`
|
||||
IPCidr string `json:"ipcidr"`
|
||||
Desc string `json:"decs"`
|
||||
Reason string `json:"reason"`
|
||||
ExtNetID int `json:"extNetId"`
|
||||
ExtIP string `json:"extIp"`
|
||||
}
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/rg/update API call
|
||||
//
|
||||
const ResgroupUpdateAPI= "/restmachine/cloudapi/rg/update"
|
||||
type ResgroupUpdateParam struct {
|
||||
RgId int `json:"rgId"`
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"decs"`
|
||||
Ram int `json:"maxMemoryCapacity"`
|
||||
Disk int `json:"maxVDiskCapacity"`
|
||||
Cpu int `json:"maxCPUCapacity"`
|
||||
NetTraffic int `json:"maxNetworkPeerTransfer"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/rg/get API call
|
||||
//
|
||||
type ResourceRecord struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
const ResgroupGetAPI= "/restmachine/cloudapi/rg/get"
|
||||
type ResgroupGetResp struct {
|
||||
ACLs []UserAclRecord `json:"ACLs"`
|
||||
Usage UsageRecord `json:"Resources"`
|
||||
TenantID int `json:"accountId"`
|
||||
TenantName string `json:"accountName"`
|
||||
|
||||
CreatedBy string `json:"createdBy"`
|
||||
CreatedTime uint64 `json:"createdTime"`
|
||||
DefaultNetID int `json:"def_net_id"`
|
||||
DefaultNetType string `json:"def_net_type"`
|
||||
DeletedBy string `json:"deletedBy"`
|
||||
DeletedTime uint64 `json:"deletedTime"`
|
||||
Decsription string `json:"desc"`
|
||||
ID uint `json:"id"`
|
||||
LockStatus string `json:"lockStatus"`
|
||||
Name string `json:"name"`
|
||||
Quotas QuotaRecord `json:"resourceLimits"`
|
||||
Status string `json:"status"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
UpdatedTime uint64 `json:"updatedTime"`
|
||||
Vins []int `json:"vins"`
|
||||
Computes []int `json:"vms"`
|
||||
|
||||
Ignored map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/rg/update API
|
||||
//
|
||||
const ResgroupUpdateAPI = "/restmachine/cloudapi/rg/update"
|
||||
type ResgroupUpdateParam struct {
|
||||
ID uint `json:"rgId"`
|
||||
Name string `json:"name"`
|
||||
Decsription string `json:"desc"`
|
||||
Cpu int `json:"maxCPUCapacity"`
|
||||
Ram int `json:"maxMemoryCapacity"`
|
||||
Disk int `json:"maxVDiskCapacity"`
|
||||
NetTraffic int `json:"maxNetworkPeerTransfer"`
|
||||
ExtIPs int `json:"maxNumPublicIP"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/rg/delete API
|
||||
//
|
||||
const ResgroupDeleteAPI = "/restmachine/cloudapi/rg/delete"
|
||||
type ResgroupDeleteParam struct {
|
||||
ID uint `json:"rgId"`
|
||||
Force bool `json:"force"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/kvmXXX/create APIs
|
||||
//
|
||||
const KvmX86CreateAPI = "/restmachine/cloudapi/kvmx86/create"
|
||||
const KvmPPCCreateAPI = "/restmachine/cloudapi/kvmppc/create"
|
||||
type KvmXXXCreateParam struct { // this is unified structure for both x86 and PPC based VMs creation
|
||||
RgID uint `json:"rgId"`
|
||||
Name string `json:"name"`
|
||||
Cpu int `json:"cpu"`
|
||||
Ram int `json:"ram"`
|
||||
ImageID int `json:"imageId"`
|
||||
BootDisk int `json:"bootDisk"`
|
||||
NetType string `json:"netType"`
|
||||
NetId int `json:"netId"`
|
||||
IPAddr string `json:"ipAddr"`
|
||||
UserData string `json:"userdata"`
|
||||
Description string `json:"desc"`
|
||||
Start bool `json:"start"`
|
||||
}
|
||||
|
||||
// structures related to cloudapi/compute/delete API
|
||||
const ComputeDeleteAPI = "/restmachine/cloudapi/compute/delete"
|
||||
|
||||
type ComputeDeleteParam struct {
|
||||
ComputeID int `json:"computeId"`
|
||||
Permanently bool `json:"permanently"`
|
||||
}
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/compute/list API
|
||||
//
|
||||
|
||||
type InterfaceRecord struct {
|
||||
ConnID int `json:"connId"`
|
||||
ConnType string `json:"connType"`
|
||||
DefaultGW string `json:"defGw"`
|
||||
Guid string `json:"guid"`
|
||||
IPAddress string `json:"ipAddress"` // without trailing network mask, i.e. "192.168.1.3"
|
||||
MAC string `json:"mac"`
|
||||
Name string `json:"name"`
|
||||
NetID int `json:"netId"`
|
||||
NetMaks int `json:"netMask"`
|
||||
NetType string `json:"netType"`
|
||||
PciSlot int `json:"pciSlot"`
|
||||
Target string `json:"target"`
|
||||
Type string `json:"type"`
|
||||
VNFs []int `json:"vnfs"`
|
||||
}
|
||||
|
||||
type SnapSetRecord struct {
|
||||
Disks []int `json:"disks"`
|
||||
Guid string `json:"guid"`
|
||||
Label string `json:"label"`
|
||||
TimeStamp uint64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type ComputeRecord struct {
|
||||
TenantID int `json:"accountId"`
|
||||
TenantName string `json:"accountName"`
|
||||
ACLs []UserAclRecord `json:"acl"`
|
||||
Arch string `json:"arch"`
|
||||
BootDiskSize int `json:"bootdiskSize"`
|
||||
CloneReference int `json:"cloneReference"`
|
||||
Clones []int `json:"clones"`
|
||||
Cpus int `json:"cpus"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
CreatedTime uint64 `json:"createdTime"`
|
||||
DeletedBy string `json:"deletedBy"`
|
||||
DeletedTime uint64 `json:"deletedTime"`
|
||||
Desc string `json:"desc"`
|
||||
Disks []int `json:"disks"`
|
||||
GridID int `json:"gid"`
|
||||
ID uint `json:"id"`
|
||||
ImageID int `json:"imageId"`
|
||||
Interfaces []InterfaceRecord `json:"interfaces`
|
||||
LockStatus string `json:"lockStatus"`
|
||||
ManagerID int `json:"managerId"`
|
||||
Name string `json:"name"`
|
||||
Ram int `json:"ram"`
|
||||
RgID int `json:"rgId"`
|
||||
RgName string `json:"rgName"`
|
||||
SnapSets []SnapSetRecord `json:"snapSets"`
|
||||
Status string `json:"status"`
|
||||
Tags []string `json:"tags"`
|
||||
TechStatus string `json:"techStatus"`
|
||||
TotalDiskSize int `json:"totalDiskSize"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
UpdateTime uint64 `json:"updateTime"`
|
||||
UserManaged bool `json:"userManaged"`
|
||||
Vgpus []int `json:"vgpus"`
|
||||
VinsConnected int `json:"vinsConnected"`
|
||||
VirtualImageID int `json:"virtualImageId"`
|
||||
}
|
||||
|
||||
const ComputeListAPI = "/restmachine/cloudapi/compute/list"
|
||||
type ComputeListParam struct {
|
||||
IncludeDeleted bool `json:"includedeleted"`
|
||||
}
|
||||
type ComputeListResp []ComputeRecord
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/compute/get
|
||||
//
|
||||
type SnapshotRecord struct {
|
||||
Guid string `json:"guid"`
|
||||
Label string `json:"label"`
|
||||
SnapSetGuid string `json:"snapSetGuid"`
|
||||
SnapSetTime uint64 `json:"snapSetTime"`
|
||||
TimeStamp uint64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type DiskRecord struct {
|
||||
// ACLs `json:"ACL"` - it is a dictionary, special parsing required
|
||||
// was - Acl map[string]string `json:"acl"`
|
||||
TenantID int `json:"accountId"`
|
||||
BootPartition int `json:"bootPartition"`
|
||||
CreatedTime uint64 `json:"creationTime"`
|
||||
DeletedTime uint64 `json:"deletionTime"`
|
||||
Description string `json:"descr"`
|
||||
DestructionTime uint64 `json:"destructionTime"`
|
||||
DiskPath string `json:"diskPath"`
|
||||
GridID int `json:"gid"`
|
||||
ID uint `json:"id"`
|
||||
ImageID int `json:"imageId"`
|
||||
Images []int `json:"images"`
|
||||
// IOTune 'json:"iotune" - it is a dictionary
|
||||
Name string `json:"name"`
|
||||
ParentId int `json:"parentId"`
|
||||
PciSlot int `json:"pciSlot"`
|
||||
// ResID string `json:"resId"`
|
||||
// ResName string `json:"resName"`
|
||||
// Params string `json:"params"`
|
||||
Pool string `json:"pool"`
|
||||
PurgeTime uint64 `json:"purgeTime"`
|
||||
// Role string `json:"role"`
|
||||
SepType string `json:"sepType"`
|
||||
SepID int `json:"sepid"`
|
||||
SizeMax int `json:"sizeMax"`
|
||||
SizeUsed int `json:"sizeUsed"`
|
||||
Snapshots []SnapshotRecord `json:"snapshots"`
|
||||
Status string `json:"status"`
|
||||
TechStatus string `json:"techStatus"`
|
||||
Type string `json:"type"`
|
||||
ComputeID int `json:"vmId"`
|
||||
}
|
||||
|
||||
type OsUserRecord struct {
|
||||
Guid string `json:"guid"`
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
PubKey string `json:"pubkey"`
|
||||
}
|
||||
|
||||
const ComputeGetAPI = "/restmachine/cloudapi/compute/get"
|
||||
type ComputeGetParam struct {
|
||||
ComputeID int `json:"computeId"`
|
||||
}
|
||||
type ComputeGetResp struct {
|
||||
// ACLs `json:"ACL"` - it is a dictionary, special parsing required
|
||||
TenantID int `json:"accountId"`
|
||||
TenantName string `json:"accountName"`
|
||||
Arch string `json:"arch"`
|
||||
BootDiskSize int `json:"bootdiskSize"`
|
||||
CloneReference int `json:"cloneReference"`
|
||||
Clones []int `json:"clones"`
|
||||
Cpus int `json:"cpus"`
|
||||
Desc string `json:"desc"`
|
||||
Disks []DiskRecord `json:"disks"`
|
||||
GridID int `json:"gid"`
|
||||
ID uint `json:"id"`
|
||||
ImageID int `json:"imageId"`
|
||||
ImageName string `json:"imageName"`
|
||||
Interfaces []InterfaceRecord `json:"interfaces`
|
||||
LockStatus string `json:"lockStatus"`
|
||||
ManagerID int `json:"managerId"`
|
||||
ManagerType string `json:"manageType"`
|
||||
Name string `json:"name"`
|
||||
NatableVinsID int `json:"natableVinsId"`
|
||||
NatableVinsIP string `json:"natableVinsIp"`
|
||||
NatableVinsName string `json:"natableVinsName"`
|
||||
NatableVinsNet string `json:"natableVinsNetwork"`
|
||||
NatableVinsNetName string `json:"natableVinsNetworkName"`
|
||||
OsUsers []OsUserRecord `json:"osUsers"`
|
||||
Ram int `json:"ram"`
|
||||
RgID int `json:"rgId"`
|
||||
RgName string `json:"rgName"`
|
||||
SnapSets []SnapSetRecord `json:"snapSets"`
|
||||
Status string `json:"status"`
|
||||
Tags []string `json:"tags"`
|
||||
TechStatus string `json:"techStatus"`
|
||||
TotalDiskSize int `json:"totalDiskSize"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
UpdateTime uint64 `json:"updateTime"`
|
||||
UserManaged bool `json:"userManaged"`
|
||||
Vgpus []int `json:"vgpus"`
|
||||
VinsConnected int `json:"vinsConnected"`
|
||||
VirtualImageID int `json:"virtualImageId"`
|
||||
}
|
||||
|
||||
//
|
||||
// structures related to /restmachine/cloudapi/images/list API
|
||||
//
|
||||
type ImageRecord struct {
|
||||
TenantID uint `json:"accountId"`
|
||||
Arch string `json:"architecture`
|
||||
BootType string `json:"bootType"`
|
||||
IsBootable boo `json:"bootable"`
|
||||
IsCdrom bool `json:"cdrom"`
|
||||
Desc string `json:"description"`
|
||||
IsHotResize bool `json:"hotResize"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Pool string `json:"pool"`
|
||||
SepID int `json:"sepid"`
|
||||
Size int `json:"size"`
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
Username string `json:"username"`
|
||||
IsVirtual bool `json:"virtual"`
|
||||
}
|
||||
|
||||
const ImagesListAPI = "/restmachine/cloudapi/images/list"
|
||||
type ImagesListParam struct {
|
||||
TenantID int `json:"accountId"`
|
||||
}
|
||||
type ImagesListResp []ImageRecord
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/extnet/list API
|
||||
//
|
||||
type ExtNetRecord struct {
|
||||
Name string `json:"name"`
|
||||
ID uint `json:"id"`
|
||||
IPCIDR string `json:"ipcidr"`
|
||||
}
|
||||
|
||||
const ExtNetListAPI = "/restmachine/cloudapi/extnet/list"
|
||||
type ExtNetListParam struct {
|
||||
TenantID int `json:"accountId"`
|
||||
}
|
||||
type ExtNetListResp []ExtNetRecord
|
||||
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/accounts/list API
|
||||
//
|
||||
type TenantRecord struct {
|
||||
ACLs []UserAclRecord `json:"acl"`
|
||||
CreatedTime uint64 `json:"creationTime"`
|
||||
DeletedTime uint64 `json:"deletionTime"`
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
UpdatedTime uint64 `json:"updateTime"`
|
||||
}
|
||||
|
||||
const TenantsListAPI = "/restmachine/cloudapi/accounts/list"
|
||||
type TenantsListResp []TenantRecord
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/portforwarding/list API
|
||||
//
|
||||
type PfwRecord struct {
|
||||
ID int `json:"id"`
|
||||
LocalIP string `json:"localIp`
|
||||
LocalPort int `json:"localPort"`
|
||||
Protocol string `json:"protocol"`
|
||||
PublicPortEnd int `json:"publicPortEnd"`
|
||||
PublicPortStart int `json:"publicPortStart"`
|
||||
ComputeID int `json:"vmId"`
|
||||
}
|
||||
|
||||
const ComputePfwListAPI = "/restmachine/cloudapi/compute/pfwList"
|
||||
type ComputePfwListResp []PfwRecord
|
||||
|
||||
type ComputePfwAddParam struct {
|
||||
ComputeID int `json:"computeId"`
|
||||
PublicPortStart int `json:"publicPortStart"`
|
||||
PublicPortEnd int `json:"publicPortEnd"`
|
||||
LocalBasePort int `json:"localBasePort"`
|
||||
Protocol string `json:"proto"`
|
||||
}
|
||||
const ComputePfwAddAPI = "/restmachine/cloudapi/compute/pfwAdd"
|
||||
|
||||
type ComputePfwDelParam struct {
|
||||
ComputeID int `json:"computeId"`
|
||||
RuleID int `json:"ruleId"`
|
||||
PublicPortStart int `json:"publicPortStart"`
|
||||
PublicPortEnd int `json:"publicPortEnd"`
|
||||
LocalBasePort int `json:"localBasePort"`
|
||||
Protocol string `json:"proto"`
|
||||
}
|
||||
const ComputePfwDelAPI = "/restmachine/cloudapi/compute/pfwDel"
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/compute/net Attach/Detach API
|
||||
//
|
||||
type ComputeNetAttachParam struct {
|
||||
ComputeID int `json:"computeId"`
|
||||
NetType string `json:"netType"`
|
||||
NetID int `json:"netId"`
|
||||
IPAddr string `json:"apAddr"`
|
||||
}
|
||||
const ComputeNetAttachAPI = "/restmachine/cloudapi/compute/netAttach"
|
||||
|
||||
type ComputeNetDetachParam struct {
|
||||
ComputeID int `json:"computeId"`
|
||||
IPAddr string `json:"apAddr"`
|
||||
MAC string `json:"mac"`
|
||||
}
|
||||
const ComputeNetDetachAPI = "/restmachine/cloudapi/compute/netDetach"
|
||||
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/compute/disk Attach/Detach API
|
||||
//
|
||||
type ComputeDiskManipulationParam struct {
|
||||
ComputeID int `json:"computeId"`
|
||||
DiskID int `json:"diskId"`
|
||||
}
|
||||
const ComputeDiskAttachAPI = "/restmachine/cloudapi/compute/diskAttach"
|
||||
|
||||
const ComputeDiskDetachAPI = "/restmachine/cloudapi/compute/diskDetach"
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/disks/create
|
||||
//
|
||||
type DiskCreateParam struct {
|
||||
TenantID int `json:"accountId`
|
||||
GridID int `json:"gid"`
|
||||
Name string `json:"string"`
|
||||
Description string `json:"description"`
|
||||
Size int `json:"size"`
|
||||
Type string `json:"type"`
|
||||
SepID int `json:"sep_id"`
|
||||
Pool string `json:"pool"`
|
||||
}
|
||||
const DiskCreateAPI = "/restmachine/cloudapi/disks/create"
|
||||
|
||||
//
|
||||
// structures related to /cloudapi/disks/get
|
||||
//
|
||||
type DisksGetParam struct {
|
||||
DiskID int `json:"diskId`
|
||||
}
|
||||
const DisksCreateAPI = "/restmachine/cloudapi/disks/create"
|
||||
|
||||
const DisksGetAPI = "/restmachine/cloudapi/disks/get" // Returns single DiskRecord on success
|
||||
93
decort/models_objects.go
Normal file
93
decort/models_objects.go
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
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
|
||||
|
||||
type DiskConfig struct {
|
||||
Label string
|
||||
Size int
|
||||
Pool string
|
||||
Provider string
|
||||
ID int
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Label string
|
||||
NetworkID int
|
||||
}
|
||||
|
||||
type PortforwardConfig struct {
|
||||
Label string
|
||||
ExtPort int
|
||||
IntPort int
|
||||
Proto string
|
||||
}
|
||||
|
||||
type SshKeyConfig struct {
|
||||
User string
|
||||
SshKey string
|
||||
UserShell string
|
||||
}
|
||||
|
||||
type ComputeConfig struct {
|
||||
ResGroupID int
|
||||
Name string
|
||||
ID int
|
||||
Cpu int
|
||||
Ram int
|
||||
ImageID int
|
||||
BootDisk DiskConfig
|
||||
DataDisks []DiskConfig
|
||||
Networks []NetworkConfig
|
||||
PortForwards []PortforwardConfig
|
||||
SshKeys []SshKeyConfig
|
||||
Description string
|
||||
// The following two parameters are required to create data disks by
|
||||
// a separate disks/create API call
|
||||
TenantID int
|
||||
GridID int
|
||||
// The following one paratmeter is required to create port forwards
|
||||
// it will be obsoleted when we implement true Resource Groups
|
||||
ExtIP string
|
||||
}
|
||||
|
||||
type ResgroupQuotaConfig struct {
|
||||
Cpu int
|
||||
Ram float32 // NOTE: it is float32! However, int would be enough here
|
||||
Disk int
|
||||
NetTraffic int
|
||||
ExtIPs int
|
||||
}
|
||||
|
||||
type ResgroupConfig struct {
|
||||
TenantID int
|
||||
TenantName string
|
||||
Location string
|
||||
Name string
|
||||
ID int
|
||||
GridID int
|
||||
ExtIP string // legacy field for VDC - this will eventually become obsoleted by true Resource Groups
|
||||
Quota ResgroupQuotaConfig
|
||||
Network NetworkConfig
|
||||
}
|
||||
278
decort/network_subresource.go
Normal file
278
decort/network_subresource.go
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
Copyright (c) 2019 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.
|
||||
*/
|
||||
|
||||
package decs
|
||||
|
||||
import (
|
||||
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
)
|
||||
|
||||
func makeNetworksConfig(arg_list []interface{}) (nets []NetworkConfig, count int) {
|
||||
count = len(arg_list)
|
||||
if count < 1 {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// allocate Networks list and fill it
|
||||
nets = make([]NetworkConfig, count)
|
||||
var subres_data map[string]interface{}
|
||||
for index, value := range arg_list {
|
||||
subres_data = value.(map[string]interface{})
|
||||
// nets[index].Label = subres_data["label"].(string)
|
||||
nets[index].NetworkID = subres_data["network_id"].(int)
|
||||
}
|
||||
|
||||
return nets, count
|
||||
}
|
||||
|
||||
func flattenNetworks(nets []NicRecord) []interface{} {
|
||||
// this function expects an array of NicRecord as returned by machines/get API call
|
||||
// NOTE: it does NOT expect a strucutre as returned by externalnetwork/list
|
||||
var length = 0
|
||||
var strarray []string
|
||||
|
||||
for _, value := range nets {
|
||||
if value.NicType == "PUBLIC" {
|
||||
length += 1
|
||||
}
|
||||
}
|
||||
log.Printf("flattenNetworks: found %d NICs with PUBLIC type", length)
|
||||
|
||||
result := make([]interface{}, length)
|
||||
if length == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
elem := make(map[string]interface{})
|
||||
|
||||
var subindex = 0
|
||||
for index, value := range nets {
|
||||
if value.NicType == "PUBLIC" {
|
||||
// this will be changed as network segments entity
|
||||
// value.Params for ext net comes in a form "gateway:176.118.165.1 externalnetworkId:6"
|
||||
// for network_id we need to extract from this string
|
||||
strarray = strings.Split(value.Params, " ")
|
||||
substr := strings.Split(strarray[1], ":")
|
||||
elem["network_id"], _ = strconv.Atoi(substr[1])
|
||||
elem["ip_range"] = value.IPAddress
|
||||
// elem["label"] = ... - should be uncommented for the future release
|
||||
log.Printf("flattenNetworks: parsed element %d - network_id %d, ip_range %q",
|
||||
index, elem["network_id"].(int), value.IPAddress)
|
||||
result[subindex] = elem
|
||||
subindex += 1
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func networkSubresourceSchema() map[string]*schema.Schema {
|
||||
rets := map[string]*schema.Schema {
|
||||
"network_id": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validation.IntAtLeast(1),
|
||||
Description: "ID of the network to attach to this VM.",
|
||||
},
|
||||
|
||||
/* should be uncommented for the future release
|
||||
"label": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Unique label of this network connection to identify it among other connections for this VM.",
|
||||
},
|
||||
*/
|
||||
|
||||
"ip_range": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Range of IP addresses defined for this network.",
|
||||
},
|
||||
|
||||
"mac": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "MAC address of the interface connected to this network.",
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
return rets
|
||||
}
|
||||
|
||||
func makePortforwardsConfig(arg_list []interface{}) (pfws []PortforwardConfig, count int) {
|
||||
count = len(arg_list)
|
||||
if count < 1 {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
pfws = make([]PortforwardConfig, count)
|
||||
var subres_data map[string]interface{}
|
||||
for index, value := range arg_list {
|
||||
subres_data = value.(map[string]interface{})
|
||||
// pfws[index].Label = subres_data["label"].(string) - should be uncommented for future release
|
||||
pfws[index].ExtPort = subres_data["ext_port"].(int)
|
||||
pfws[index].IntPort = subres_data["int_port"].(int)
|
||||
pfws[index].Proto = subres_data["proto"].(string)
|
||||
}
|
||||
|
||||
return pfws, count
|
||||
}
|
||||
|
||||
func flattenPortforwards(pfws []PortforwardRecord) []interface{} {
|
||||
result := make([]interface{}, len(pfws))
|
||||
elem := make(map[string]interface{})
|
||||
var port_num int
|
||||
|
||||
for index, value := range pfws {
|
||||
// elem["label"] = ... - should be uncommented for the future release
|
||||
|
||||
// external port field is of TypeInt in the portforwardSubresourceSchema, but string is returned
|
||||
// by portforwards/list API, so we need conversion here
|
||||
port_num, _ = strconv.Atoi(value.ExtPort)
|
||||
elem["ext_port"] = port_num
|
||||
// internal port field is of TypeInt in the portforwardSubresourceSchema, but string is returned
|
||||
// by portforwards/list API, so we need conversion here
|
||||
port_num, _ = strconv.Atoi(value.IntPort)
|
||||
elem["int_port"] = port_num
|
||||
elem["proto"] = value.Proto
|
||||
elem["ext_ip"] = value.ExtIP
|
||||
elem["int_ip"] = value.IntIP
|
||||
result[index] = elem
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func portforwardSubresourceSchema() map[string]*schema.Schema {
|
||||
rets := map[string]*schema.Schema {
|
||||
/* this should be uncommented for the future release
|
||||
"label": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Unique label of this network connection to identify it amnong other connections for this VM.",
|
||||
},
|
||||
*/
|
||||
|
||||
"ext_port": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validation.IntBetween(1, 65535),
|
||||
Description: "External port number for this port forwarding rule.",
|
||||
},
|
||||
|
||||
"int_port": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validation.IntBetween(1, 65535),
|
||||
Description: "Internal port number for this port forwarding rule.",
|
||||
},
|
||||
|
||||
"proto": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
// ValidateFunc: validation.IntBetween(1, ),
|
||||
Description: "Protocol type for this port forwarding rule. Should be either 'tcp' or 'udp'.",
|
||||
},
|
||||
|
||||
"ext_ip": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: ".",
|
||||
},
|
||||
|
||||
"int_ip": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: ".",
|
||||
},
|
||||
}
|
||||
|
||||
return rets
|
||||
}
|
||||
|
||||
func flattenNICs(nics []NicRecord) []interface{} {
|
||||
var result = make([]interface{}, len(nics))
|
||||
elem := make(map[string]interface{})
|
||||
|
||||
for index, value := range nics {
|
||||
elem["status"] = value.Status
|
||||
elem["type"] = value.NicType
|
||||
elem["mac"] = value.MacAddress
|
||||
elem["ip_address"] = value.IPAddress
|
||||
elem["parameters"] = value.Params
|
||||
elem["reference_id"] = value.ReferenceID
|
||||
elem["network_id"] = value.NetworkID
|
||||
result[index] = elem
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func nicSubresourceSchema() map[string]*schema.Schema {
|
||||
rets := map[string]*schema.Schema {
|
||||
"status": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Current status of this NIC.",
|
||||
},
|
||||
|
||||
"type": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Type of this NIC.",
|
||||
},
|
||||
|
||||
"mac": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "MAC address assigned to this NIC.",
|
||||
},
|
||||
|
||||
"ip_address": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "IP address assigned to this NIC.",
|
||||
},
|
||||
|
||||
"parameters": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Additional NIC parameters.",
|
||||
},
|
||||
|
||||
"reference_id": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Reference ID of this NIC.",
|
||||
},
|
||||
|
||||
"network_id": {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "Network ID which this NIC is connected to.",
|
||||
},
|
||||
}
|
||||
|
||||
return rets
|
||||
}
|
||||
127
decort/provider.go
Normal file
127
decort/provider.go
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright (c) 2019 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.
|
||||
*/
|
||||
|
||||
package decs
|
||||
|
||||
import (
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
// "github.com/hashicorp/terraform/terraform"
|
||||
|
||||
)
|
||||
|
||||
var decsController *ControllerCfg
|
||||
|
||||
func Provider() *schema.Provider {
|
||||
return &schema.Provider {
|
||||
Schema: map[string]*schema.Schema {
|
||||
"authenticator": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
StateFunc: stateFuncToLower,
|
||||
ValidateFunc: validation.StringInSlice([]string{"oauth2", "legacy", "jwt"}, true), // ignore case while validating
|
||||
Description: "Authentication mode to use when connecting to DECS cloud API. Should be one of 'oauth2', 'legacy' or 'jwt'.",
|
||||
},
|
||||
|
||||
"oauth2_url": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
StateFunc: stateFuncToLower,
|
||||
DefaultFunc: schema.EnvDefaultFunc("DECS_OAUTH2_URL", nil),
|
||||
Description: "The Oauth2 application URL in 'oauth2' authentication mode.",
|
||||
},
|
||||
|
||||
"controller_url": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
StateFunc: stateFuncToLower,
|
||||
Description: "The URL of DECS Cloud controller to use. API calls will be directed to this URL.",
|
||||
},
|
||||
|
||||
"user": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("DECS_USER", nil),
|
||||
Description: "The user name for DECS cloud API operations in 'legacy' authentication mode.",
|
||||
},
|
||||
|
||||
"password": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("DECS_PASSWORD", nil),
|
||||
Description: "The user password for DECS cloud API operations in 'legacy' authentication mode.",
|
||||
},
|
||||
|
||||
"app_id": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("DECS_APP_ID", nil),
|
||||
Description: "Application ID to access DECS cloud API in 'oauth2' authentication mode.",
|
||||
},
|
||||
|
||||
"app_secret": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("DECS_APP_SECRET", nil),
|
||||
Description: "Application secret to access DECS cloud API in 'oauth2' authentication mode.",
|
||||
},
|
||||
|
||||
"jwt": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("DECS_JWT", nil),
|
||||
Description: "JWT to access DECS cloud API in 'jwt' authentication mode.",
|
||||
},
|
||||
|
||||
"allow_unverified_ssl": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
Description: "If set, DECS API will allow unverifiable SSL certificates.",
|
||||
},
|
||||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource {
|
||||
"decs_resgroup": resourceResgroup(),
|
||||
"decs_vm": resourceVm(),
|
||||
},
|
||||
|
||||
DataSourcesMap: map[string]*schema.Resource {
|
||||
"decs_resgroup": dataSourceResgroup(),
|
||||
"decs_vm": dataSourceVm(),
|
||||
"decs_image": dataSourceImage(),
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
}
|
||||
}
|
||||
|
||||
func stateFuncToLower(argval interface{}) string {
|
||||
return strings.ToLower(argval.(string))
|
||||
}
|
||||
|
||||
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||
decsController, err := ControllerConfigure(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decsController, nil
|
||||
}
|
||||
119
decort/quota_subresource.go
Normal file
119
decort/quota_subresource.go
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright (c) 2019 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.
|
||||
*/
|
||||
|
||||
package decs
|
||||
|
||||
import (
|
||||
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "log"
|
||||
// "net/url"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
// "github.com/hashicorp/terraform/helper/validation"
|
||||
|
||||
)
|
||||
|
||||
func makeQuotaConfig(arg_list []interface{}) (ResgroupQuotaConfig, int) {
|
||||
quota := ResgroupQuotaConfig{
|
||||
Cpu: -1,
|
||||
Ram: -1,
|
||||
Disk: -1,
|
||||
NetTraffic: -1,
|
||||
ExtIPs: -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)
|
||||
}
|
||||
|
||||
if subres_data["ram"].(int) > 0 {
|
||||
ram_limit := subres_data["ram"].(int)
|
||||
quota.Ram = float32(ram_limit) // /1024 // legacy fix - this can be obsoleted once redmine FR #1465 is implemented
|
||||
}
|
||||
|
||||
if subres_data["net_traffic"].(int) > 0 {
|
||||
quota.NetTraffic = subres_data["net_traffic"].(int)
|
||||
}
|
||||
|
||||
if subres_data["ext_ips"].(int) > 0 {
|
||||
quota.ExtIPs = subres_data["ext_ips"].(int)
|
||||
}
|
||||
|
||||
return quota, 1
|
||||
}
|
||||
|
||||
func flattenQuota(quotas QuotaRecord) []interface{} {
|
||||
quotas_map := make(map[string]interface{})
|
||||
|
||||
quotas_map["cpu"] = quotas.Cpu
|
||||
quotas_map["ram"] = int(quotas.Ram)
|
||||
quotas_map["disk"] = quotas.Disk
|
||||
quotas_map["net_traffic"] = quotas.NetTraffic
|
||||
quotas_map["ext_ips"] = quotas.ExtIPs
|
||||
|
||||
result := make([]interface{}, 1)
|
||||
result[0] = quotas_map
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func quotasSubresourceSchema() map[string]*schema.Schema {
|
||||
rets := map[string]*schema.Schema {
|
||||
"cpu": &schema.Schema {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: -1,
|
||||
Description: "The quota on the total number of CPUs in this resource group.",
|
||||
},
|
||||
|
||||
"ram": &schema.Schema {
|
||||
Type: schema.TypeInt, // NB: API expects and returns this as float! This may be changed in the future.
|
||||
Optional: true,
|
||||
Default: -1,
|
||||
Description: "The quota on the total amount of RAM in this resource group, specified in GB (Gigabytes!).",
|
||||
},
|
||||
|
||||
"disk": &schema.Schema {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: -1,
|
||||
Description: "The quota on the total volume of storage resources in this resource group, specified in GB.",
|
||||
},
|
||||
|
||||
"net_traffic": &schema.Schema {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: -1,
|
||||
Description: "The quota on the total ingress network traffic for this resource group, specified in GB.",
|
||||
},
|
||||
|
||||
"ext_ips": &schema.Schema {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: -1,
|
||||
Description: "The quota on the total number of external IP addresses this resource group can use.",
|
||||
},
|
||||
}
|
||||
return rets
|
||||
}
|
||||
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.",
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
297
decort/resource_resgroup.go
Normal file
297
decort/resource_resgroup.go
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
Copyright (c) 2019-2020 Digital Energy Cloud Solutions. All Rights Reserved.
|
||||
|
||||
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 (
|
||||
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
)
|
||||
|
||||
func resourceResgroupCreate(d *schema.ResourceData, m interface{}) error {
|
||||
log.Printf("resourceResgroupCreate: called for res group name %q, tenant name %q",
|
||||
d.Get("name").(string), d.Get("tenant").(string))
|
||||
|
||||
rg := &ResgroupConfig{
|
||||
Name: d.Get("name").(string),
|
||||
TenantName: d.Get("tenant").(string),
|
||||
}
|
||||
|
||||
// validate that we have all parameters required to create the new Resource Group
|
||||
// location code is required to create new resource group
|
||||
arg_value, arg_set := d.GetOk("location")
|
||||
if arg_set {
|
||||
rg.Location = arg_value.(string)
|
||||
} else {
|
||||
return fmt.Errorf("Cannot create new resource group %q for tenant %q: missing location parameter.",
|
||||
rg.Name, rg.TenantName)
|
||||
}
|
||||
// tenant ID is required to create new resource group
|
||||
// obtain Tenant ID by tenant name - it should not be zero on success
|
||||
tenant_id, err := utilityGetTenantIdByName(rg.TenantName, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rg.TenantID = tenant_id
|
||||
|
||||
set_quotas := false
|
||||
arg_value, arg_set = d.GetOk("quotas")
|
||||
if arg_set {
|
||||
log.Printf("resourceResgroupCreate: calling makeQuotaConfig")
|
||||
rg.Quota, _ = makeQuotaConfig(arg_value.([]interface{}))
|
||||
set_quotas = true
|
||||
}
|
||||
|
||||
controller := m.(*ControllerCfg)
|
||||
log.Printf("resourceResgroupCreate: called by user %q for Resource group name %q, for tenant %q / ID %d, location %q",
|
||||
controller.getdecortUsername(),
|
||||
rg.Name, d.Get("tenant"), rg.TenantID, rg.Location)
|
||||
|
||||
url_values := &url.Values{}
|
||||
url_values.Add("accountId", fmt.Sprintf("%d", rg.TenantID))
|
||||
url_values.Add("name", rg.Name)
|
||||
url_values.Add("location", rg.Location)
|
||||
url_values.Add("access", controller.getdecortUsername())
|
||||
// pass quota values as set
|
||||
if set_quotas {
|
||||
url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", rg.Quota.Cpu))
|
||||
url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", rg.Quota.Disk))
|
||||
url_values.Add("maxMemoryCapacity", fmt.Sprintf("%f", rg.Quota.Ram))
|
||||
url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", rg.Quota.NetTraffic))
|
||||
url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", rg.Quota.ExtIPs))
|
||||
}
|
||||
// pass externalnetworkid if set
|
||||
arg_value, arg_set = d.GetOk("extnet_id")
|
||||
if arg_set {
|
||||
url_values.Add("externalnetworkid", fmt.Sprintf("%d", arg_value))
|
||||
}
|
||||
|
||||
api_resp, err := controller.decortAPICall("POST", ResgroupCreateAPI, url_values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(api_resp) // cloudspaces/create API plainly returns ID of the newly creted resource group on success
|
||||
rg.ID, _ = strconv.Atoi(api_resp)
|
||||
|
||||
return resourceResgroupRead(d, m)
|
||||
}
|
||||
|
||||
func resourceResgroupRead(d *schema.ResourceData, m interface{}) error {
|
||||
log.Printf("resourceResgroupRead: called for res group name %q, tenant name %q",
|
||||
d.Get("name").(string), d.Get("tenant").(string))
|
||||
rg_facts, err := utilityResgroupCheckPresence(d, m)
|
||||
if rg_facts == "" {
|
||||
// if empty string is returned from utilityResgroupCheckPresence then there is no
|
||||
// such resource group and err tells so - just return it to the calling party
|
||||
d.SetId("") // ensure ID is empty
|
||||
return err
|
||||
}
|
||||
|
||||
return flattenResgroup(d, rg_facts)
|
||||
}
|
||||
|
||||
func resourceResgroupUpdate(d *schema.ResourceData, m interface{}) error {
|
||||
// this method will only update quotas, if any are set
|
||||
log.Printf("resourceResgroupUpdate: called for res group name %q, tenant name %q",
|
||||
d.Get("name").(string), d.Get("tenant").(string))
|
||||
|
||||
quota_value, arg_set := d.GetOk("quotas")
|
||||
if !arg_set {
|
||||
// if there are no quotas set explicitly in the resource configuration - no change will be done
|
||||
log.Printf("resourceResgroupUpdate: quotas are not set in the resource config - no update on this resource will be done")
|
||||
return resourceResgroupRead(d, m)
|
||||
}
|
||||
quotaconfig_new, _ := makeQuotaConfig(quota_value.([]interface{}))
|
||||
|
||||
quota_value, _ = d.GetChange("quotas") // returns old as 1st, new as 2nd argument
|
||||
quotaconfig_old, _ := makeQuotaConfig(quota_value.([]interface{}))
|
||||
|
||||
controller := m.(*ControllerCfg)
|
||||
url_values := &url.Values{}
|
||||
url_values.Add("cloudspaceId", d.Id())
|
||||
url_values.Add("name", d.Get("name").(string))
|
||||
|
||||
do_update := false
|
||||
|
||||
if quotaconfig_new.Cpu != quotaconfig_old.Cpu {
|
||||
do_update = true
|
||||
log.Printf("resourceResgroupUpdate: Cpu diff %d <- %d", quotaconfig_new.Cpu, quotaconfig_old.Cpu)
|
||||
url_values.Add("maxCPUCapacity", fmt.Sprintf("%d", quotaconfig_new.Cpu))
|
||||
}
|
||||
|
||||
if quotaconfig_new.Disk != quotaconfig_old.Disk {
|
||||
do_update = true
|
||||
log.Printf("resourceResgroupUpdate: Disk diff %d <- %d", quotaconfig_new.Disk, quotaconfig_old.Disk)
|
||||
url_values.Add("maxVDiskCapacity", fmt.Sprintf("%d", quotaconfig_new.Disk))
|
||||
}
|
||||
|
||||
if quotaconfig_new.Ram != quotaconfig_old.Ram {
|
||||
do_update = true
|
||||
log.Printf("resourceResgroupUpdate: Ram diff %f <- %f", quotaconfig_new.Ram, quotaconfig_old.Ram)
|
||||
url_values.Add("maxMemoryCapacity", fmt.Sprintf("%f", quotaconfig_new.Ram))
|
||||
}
|
||||
|
||||
if quotaconfig_new.NetTraffic != quotaconfig_old.NetTraffic {
|
||||
do_update = true
|
||||
log.Printf("resourceResgroupUpdate: NetTraffic diff %d <- %d", quotaconfig_new.NetTraffic, quotaconfig_old.NetTraffic)
|
||||
url_values.Add("maxNetworkPeerTransfer", fmt.Sprintf("%d", quotaconfig_new.NetTraffic))
|
||||
}
|
||||
|
||||
if quotaconfig_new.ExtIPs != quotaconfig_old.ExtIPs {
|
||||
do_update = true
|
||||
log.Printf("resourceResgroupUpdate: ExtIPs diff %d <- %d", quotaconfig_new.ExtIPs, quotaconfig_old.ExtIPs)
|
||||
url_values.Add("maxNumPublicIP", fmt.Sprintf("%d", quotaconfig_new.ExtIPs))
|
||||
}
|
||||
|
||||
if do_update {
|
||||
log.Printf("resourceResgroupUpdate: some new quotas are set - updating the resource")
|
||||
_, err := controller.decortAPICall("POST", ResgroupUpdateAPI, url_values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Printf("resourceResgroupUpdate: no difference in quotas between old and new state - no update on this resource will be done")
|
||||
}
|
||||
|
||||
return resourceResgroupRead(d, m)
|
||||
}
|
||||
|
||||
func resourceResgroupDelete(d *schema.ResourceData, m interface{}) error {
|
||||
// NOTE: this method destroys target resource group with flag "permanently", so there is no way to
|
||||
// restore the destroyed resource group as well all VMs that existed in it
|
||||
log.Printf("resourceResgroupDelete: called for res group name %q, tenant name %q",
|
||||
d.Get("name").(string), d.Get("tenant").(string))
|
||||
|
||||
vm_facts, err := utilityResgroupCheckPresence(d, m)
|
||||
if vm_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("cloudspaceId", d.Id())
|
||||
params.Add("permanently", "true")
|
||||
|
||||
controller := m.(*ControllerCfg)
|
||||
vm_facts, err = controller.decortAPICall("POST", CloudspacesDeleteAPI, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceResgroupExists(d *schema.ResourceData, m interface{}) (bool, error) {
|
||||
// Reminder: according to Terraform rules, this function should not modify ResourceData argument
|
||||
rg_facts, err := utilityResgroupCheckPresence(d, m)
|
||||
if rg_facts == "" {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func resourceResgroup() *schema.Resource {
|
||||
return &schema.Resource {
|
||||
SchemaVersion: 1,
|
||||
|
||||
Create: resourceResgroupCreate,
|
||||
Read: resourceResgroupRead,
|
||||
Update: resourceResgroupUpdate,
|
||||
Delete: resourceResgroupDelete,
|
||||
Exists: resourceResgroupExists,
|
||||
|
||||
Timeouts: &schema.ResourceTimeout {
|
||||
Create: &Timeout180s,
|
||||
Read: &Timeout30s,
|
||||
Update: &Timeout180s,
|
||||
Delete: &Timeout60s,
|
||||
Default: &Timeout60s,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema {
|
||||
"name": &schema.Schema {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of this resource group. Names are case sensitive and unique within the context of a tenant.",
|
||||
},
|
||||
|
||||
"tenant": &schema.Schema {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of the tenant, which this resource group belongs to.",
|
||||
},
|
||||
|
||||
"extnet_id": &schema.Schema {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Description: "ID of the external network, which this resource group will be connected to by default.",
|
||||
},
|
||||
|
||||
"tenant_id": &schema.Schema {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "Unique ID of the tenant, which this resource group belongs to.",
|
||||
},
|
||||
|
||||
"grid_id": &schema.Schema {
|
||||
Type: schema.TypeInt,
|
||||
Computed: true,
|
||||
Description: "Unique ID of the grid, where this resource group is deployed.",
|
||||
},
|
||||
|
||||
"location": &schema.Schema {
|
||||
Type: schema.TypeString,
|
||||
Optional: true, // note that it is a REQUIRED parameter when creating new resource group
|
||||
ForceNew: true,
|
||||
Description: "Name of the location where this resource group should exist.",
|
||||
},
|
||||
|
||||
"public_ip": { // this may be obsoleted as new network segments and true resource groups are implemented
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Description: "Public IP address of this resource group (if any).",
|
||||
},
|
||||
|
||||
"quotas": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource {
|
||||
Schema: quotasSubresourceSchema(),
|
||||
},
|
||||
Description: "Quotas on the resources for this resource group.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
97
decort/ssh_subresource.go
Normal file
97
decort/ssh_subresource.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright (c) 2019 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.
|
||||
*/
|
||||
|
||||
package decs
|
||||
|
||||
import (
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
// "github.com/hashicorp/terraform/helper/validation"
|
||||
)
|
||||
|
||||
func makeSshKeysConfig(arg_list []interface{}) (sshkeys []SshKeyConfig, count int) {
|
||||
count = len(arg_list)
|
||||
if count < 1 {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
sshkeys = make([]SshKeyConfig, count)
|
||||
var subres_data map[string]interface{}
|
||||
for index, value := range arg_list {
|
||||
subres_data = value.(map[string]interface{})
|
||||
sshkeys[index].User = subres_data["user"].(string)
|
||||
sshkeys[index].SshKey = subres_data["public_key"].(string)
|
||||
sshkeys[index].UserShell = subres_data["shell"].(string)
|
||||
}
|
||||
|
||||
return sshkeys, count
|
||||
}
|
||||
|
||||
func makeSshKeysArgString(sshkeys []SshKeyConfig) string {
|
||||
// Prepare a string with username and public ssh key value in a format recognized by cloud-init utility.
|
||||
// It is designed to be passed as "userdata" argument of virtual machine create API call.
|
||||
// The following format is expected:
|
||||
// '{"users": [{"ssh-authorized-keys": ["SSH_PUBCIC_KEY_VALUE"], "shell": "SHELL_VALUE", "name": "USERNAME_VALUE"}, {...}, ]}'
|
||||
|
||||
/*
|
||||
`%s\n
|
||||
- name: %s\n
|
||||
ssh-authorized-keys:
|
||||
- %s\n
|
||||
shell: /bin/bash`
|
||||
*/
|
||||
if len(sshkeys) < 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
out := `{"users": [`
|
||||
const UserdataTemplate = `%s{"ssh-authorized-keys": ["%s"], "shell": "%s", "name": "%s"}, `
|
||||
const out_suffix = `]}`
|
||||
for _, elem := range sshkeys {
|
||||
out = fmt.Sprintf(UserdataTemplate, out, elem.SshKey, elem.UserShell, elem.User)
|
||||
}
|
||||
out = fmt.Sprintf("%s %s", out, out_suffix)
|
||||
return out
|
||||
}
|
||||
|
||||
func sshSubresourceSchema() map[string]*schema.Schema {
|
||||
rets := map[string]*schema.Schema {
|
||||
"user": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of the user on the guest OS of the new VM, for which the following SSH key will be authorized.",
|
||||
},
|
||||
|
||||
"public_key": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Public part of SSH key to authorize to the specified user on the VM being created.",
|
||||
},
|
||||
|
||||
"shell": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "/bin/bash",
|
||||
Description: "Guest user shell. This parameter is optional, default is /bin/bash.",
|
||||
},
|
||||
}
|
||||
|
||||
return rets
|
||||
}
|
||||
|
||||
48
decort/utility.go
Normal file
48
decort/utility.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright (c) 2019-2021 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 (
|
||||
|
||||
"strings"
|
||||
|
||||
)
|
||||
|
||||
func Jo2JSON(arg_str string) string {
|
||||
// DECS API historically returns response in the form of Python dictionary, which generally
|
||||
// looks like JSON, but does not comply with JSON syntax.
|
||||
// For Golang JSON Unmarshal to work properly we need to pre-process API response as follows:
|
||||
ret_string := strings.Replace(string(arg_str), "u'", "\"", -1)
|
||||
ret_string = strings.Replace(ret_string, "'", "\"", -1)
|
||||
ret_string = strings.Replace(ret_string, ": False", ": false", -1)
|
||||
ret_string = strings.Replace(ret_string, ": True", ": true", -1)
|
||||
ret_string = strings.Replace(ret_string, "null", "\"\"", -1)
|
||||
ret_string = strings.Replace(ret_string, "None", "\"\"", -1)
|
||||
|
||||
// fix for incorrect handling of usage info
|
||||
// ret_string = strings.Replace(ret_string, "<", "\"", -1)
|
||||
// ret_string = strings.Replace(ret_string, ">", "\"", -1)
|
||||
return ret_string
|
||||
}
|
||||
146
decort/utility_resgroup.go
Normal file
146
decort/utility_resgroup.go
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
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 (ctrl *ControllerCfg) utilityResgroupConfigGet(rgid int) (*ResgroupConfig, error) {
|
||||
url_values := &url.Values{}
|
||||
url_values.Add("cloudspaceId", fmt.Sprintf("%d", rgid))
|
||||
resgroup_facts, err := ctrl.decortAPICall("POST", CloudspacesGetAPI, url_values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("utilityResgroupConfigGet: ready to unmarshal string %q", resgroup_facts)
|
||||
model := CloudspacesGetResp{}
|
||||
err = json.Unmarshal([]byte(resgroup_facts), &model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &ResgroupConfig{}
|
||||
ret.TenantID = model.TenantID
|
||||
ret.Location = model.Location
|
||||
ret.Name = model.Name
|
||||
ret.ID = rgid
|
||||
ret.GridID = model.GridID
|
||||
ret.ExtIP = model.ExtIP // legacy field for VDC - this will eventually become obsoleted by true Resource Groups
|
||||
// Quota ResgroupQuotaConfig
|
||||
// Network NetworkConfig
|
||||
log.Printf("utilityResgroupConfigGet: tenant ID %d, GridID %d, ExtIP %q",
|
||||
model.TenantID, model.GridID, model.ExtIP)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func utilityResgroupCheckPresence(d *schema.ResourceData, m interface{}) (string, error) {
|
||||
// This function tries to locate resource group by its name and tenant name.
|
||||
// If succeeded, it returns non empty string that contains JSON formatted facts about the
|
||||
// resource group as returned by cloudspaces/get API call.
|
||||
// Otherwise it returns empty string and meaningful error.
|
||||
//
|
||||
// This function does not modify its ResourceData argument, so it is safe to use it as core
|
||||
// method for the resource's Exists method.
|
||||
//
|
||||
name := d.Get("name").(string)
|
||||
tenant_name := d.Get("tenant").(string)
|
||||
|
||||
controller := m.(*ControllerCfg)
|
||||
url_values := &url.Values{}
|
||||
url_values.Add("includedeleted", "false")
|
||||
body_string, err := controller.decortAPICall("POST", CloudspacesListAPI, url_values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Printf("%s", body_string)
|
||||
log.Printf("utilityResgroupCheckPresence: ready to decode response body from %q", CloudspacesListAPI)
|
||||
model := CloudspacesListResp{}
|
||||
err = json.Unmarshal([]byte(body_string), &model)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Printf("utilityResgroupCheckPresence: traversing decoded Json of length %d", len(model))
|
||||
for index, item := range model {
|
||||
// need to match VDC by name & tenant name
|
||||
if item.Name == name && item.TenantName == tenant_name {
|
||||
log.Printf("utilityResgroupCheckPresence: match ResGroup name %q / ID %d, tenant %q at index %d",
|
||||
item.Name, item.ID, item.TenantName, index)
|
||||
|
||||
// not all required information is returned by cloudspaces/list API, so we need to initiate one more
|
||||
// call to cloudspaces/get to obtain extra data to complete Resource population.
|
||||
// Namely, we need to extract resource quota settings
|
||||
req_values := &url.Values{}
|
||||
req_values.Add("cloudspaceId", fmt.Sprintf("%d", item.ID))
|
||||
body_string, err := controller.decortAPICall("POST", CloudspacesGetAPI, req_values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return body_string, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Cannot find resource group name %q owned by tenant %q", name, tenant_name)
|
||||
}
|
||||
|
||||
func utilityGetTenantIdByName(tenant_name string, m interface{}) (int, error) {
|
||||
controller := m.(*ControllerCfg)
|
||||
url_values := &url.Values{}
|
||||
body_string, err := controller.decortAPICall("POST", TenantsListAPI, url_values)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
model := TenantsListResp{}
|
||||
err = json.Unmarshal([]byte(body_string), &model)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
log.Printf("utilityGetTenantIdByName: traversing decoded Json of length %d", len(model))
|
||||
for index, item := range model {
|
||||
// need to match Tenant by name
|
||||
if item.Name == tenant_name {
|
||||
log.Printf("utilityGetTenantIdByName: match Tenant name %q / ID %d at index %d",
|
||||
item.Name, item.ID, index)
|
||||
return item.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("Cannot find tenant %q for the current user. Check tenant value and your access rights", tenant_name)
|
||||
}
|
||||
159
decort/utility_vm.go
Normal file
159
decort/utility_vm.go
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
Copyright (c) 2019-2021 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"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
// "github.com/hashicorp/terraform/helper/validation"
|
||||
)
|
||||
|
||||
func (ctrl *ControllerCfg) utilityVmDisksProvision(mcfg *MachineConfig) error {
|
||||
for index, disk := range mcfg.DataDisks {
|
||||
url_values := &url.Values{}
|
||||
// url_values.Add("machineId", fmt.Sprintf("%d", mcfg.ID))
|
||||
url_values.Add("accountId", fmt.Sprintf("%d", mcfg.TenantID))
|
||||
url_values.Add("gid", fmt.Sprintf("%d", mcfg.GridID))
|
||||
url_values.Add("name", fmt.Sprintf("%s", disk.Label))
|
||||
url_values.Add("description", fmt.Sprintf("Data disk for VM ID %d / VM Name: %s", mcfg.ID, mcfg.Name))
|
||||
url_values.Add("size", fmt.Sprintf("%d", disk.Size))
|
||||
url_values.Add("type", "D")
|
||||
// url_values.Add("iops", )
|
||||
|
||||
disk_id_resp, err := ctrl.decortAPICall("POST", DiskCreateAPI, url_values)
|
||||
if err != nil {
|
||||
// failed to create disk - partial resource update
|
||||
return err
|
||||
}
|
||||
// disk created - API call returns disk ID as a string - use it to update
|
||||
// disk ID in the corresponding MachineConfig.DiskConfig record
|
||||
|
||||
mcfg.DataDisks[index].ID, err = strconv.Atoi(disk_id_resp)
|
||||
if err != nil {
|
||||
// failed to convert disk ID into proper integer value - partial resource update
|
||||
return err
|
||||
}
|
||||
|
||||
// now that we have disk created and stored its ID in the mcfg.DataDisks[index].ID
|
||||
// we can attempt attaching the disk to the VM
|
||||
url_values = &url.Values{}
|
||||
// url_values.Add("machineId", fmt.Sprintf("%d", mcfg.ID))
|
||||
url_values.Add("machineId", fmt.Sprintf("%d", mcfg.ID))
|
||||
url_values.Add("diskId", disk_id_resp)
|
||||
_, err = ctrl.decortAPICall("POST", DiskAttachAPI, url_values)
|
||||
if err != nil {
|
||||
// failed to attach disk - partial resource update
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (ctrl *ControllerCfg) utilityVmPortforwardsProvision(mcfg *MachineConfig) error {
|
||||
for _, rule := range mcfg.PortForwards {
|
||||
url_values := &url.Values{}
|
||||
url_values.Add("machineId", fmt.Sprintf("%d", mcfg.ID))
|
||||
url_values.Add("cloudspaceId", fmt.Sprintf("%d", mcfg.ResGroupID))
|
||||
url_values.Add("publicIp", mcfg.ExtIP) // this may be obsoleted by Resource group implementation
|
||||
url_values.Add("publicPort", fmt.Sprintf("%d", rule.ExtPort))
|
||||
url_values.Add("localPort", fmt.Sprintf("%d", rule.IntPort))
|
||||
url_values.Add("protocol", rule.Proto)
|
||||
_, err := ctrl.decortAPICall("POST", PortforwardingCreateAPI, url_values)
|
||||
if err != nil {
|
||||
// failed to create port forward rule - partial resource update
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctrl *ControllerCfg) utilityVmNetworksProvision(mcfg *MachineConfig) error {
|
||||
for _, net := range mcfg.Networks {
|
||||
url_values := &url.Values{}
|
||||
url_values.Add("machineId", fmt.Sprintf("%d", mcfg.ID))
|
||||
url_values.Add("externalNetworkId", fmt.Sprintf("%d", net.NetworkID))
|
||||
_, err := ctrl.decortAPICall("POST", AttachExternalNetworkAPI, url_values)
|
||||
if err != nil {
|
||||
// failed to attach network - partial resource update
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func utilityVmCheckPresence(d *schema.ResourceData, m interface{}) (string, error) {
|
||||
// This function tries to locate VM by its name and resource group ID
|
||||
// if succeeded, it returns non empty string that contains JSON formatted facts about the VM
|
||||
// as returned by machines/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.
|
||||
//
|
||||
name := d.Get("name").(string)
|
||||
rgid := d.Get("rgid").(int)
|
||||
|
||||
controller := m.(*ControllerCfg)
|
||||
list_url_values := &url.Values{}
|
||||
list_url_values.Add("cloudspaceId", fmt.Sprintf("%d",rgid))
|
||||
body_string, err := controller.decortAPICall("POST", MachinesListAPI, list_url_values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// log.Printf("%s", body_string)
|
||||
// log.Printf("dataSourceVmRead: ready to decode mashines/list response body")
|
||||
vm_list := MachinesListResp{}
|
||||
err = json.Unmarshal([]byte(body_string), &vm_list)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// log.Printf("%#v", vm_list)
|
||||
// log.Printf("dataSourceVmRead: traversing decoded JSON of length %d", len(vm_list))
|
||||
for _, item := range vm_list {
|
||||
// need to match VM by name, skip VMs with the same name in DESTROYED satus
|
||||
if item.Name == name && item.Status != "DESTROYED" {
|
||||
// log.Printf("dataSourceVmRead: index %d, matched name %q", index, item.Name)
|
||||
// we found the VM we need - not get detailed information via API call to cloudapi/machines/get
|
||||
get_url_values := &url.Values{}
|
||||
get_url_values.Add("machineId", fmt.Sprintf("%d", item.ID))
|
||||
body_string, err = controller.decortAPICall("POST", MachinesGetAPI, get_url_values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return body_string, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil // there should be no error if VM does not exist
|
||||
// return "", fmt.Errorf("Cannot find VM name %q in resource group ID %d", name, rgid)
|
||||
}
|
||||
Reference in New Issue
Block a user