Compare commits

...

11 Commits

Author SHA1 Message Date
kjubybot
95cd7cb820 fixed pfw deletion 2022-02-07 12:19:21 +03:00
kjubybot
06c0578fa6 Merge branch 'rc-1.25' into 1.26 2022-02-07 12:14:31 +03:00
kjubybot
1bf17c23c8 updated id computation for pfw 2022-02-07 12:14:16 +03:00
kjubybot
668d57cd3b updated docs for pfw and kvmvm 2022-02-07 11:50:42 +03:00
kjubybot
fef6040cc6 k8s, k8s_wg resources 2022-02-03 15:20:12 +03:00
kjubybot
d0165918c3 Merge branch 'rc-1.25' into 1.26 2022-02-02 16:30:14 +03:00
kjubybot
dfeb9a9165 compute os_users parsing fix; driver field fix in compute datasource 2022-02-02 16:14:32 +03:00
kjubybot
e0dcd053c5 kubernetes resource (currently broken) 2022-02-02 15:58:20 +03:00
kjubybot
ab070b73cb docs and timeouts for pfw 2022-01-31 11:32:25 +03:00
kjubybot
80a4b70db8 added port forwarding resource 2022-01-27 17:51:44 +03:00
kjubybot
28db919ffa Fixed JSON parsing 2022-01-14 18:04:31 +03:00
17 changed files with 1279 additions and 91 deletions

View File

@@ -16,16 +16,15 @@ limitations under the License.
*/
/*
This file is part of Terraform (by Hashicorp) provider for Digital Energy Cloud Orchestration
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.
Visit https://github.com/rudecs/terraform-provider-decort for full source code package and updates.
*/
package decort
import (
"bytes"
"crypto/tls"
"fmt"
@@ -34,6 +33,7 @@ import (
"net/url"
"strconv"
"strings"
// "time"
log "github.com/sirupsen/logrus"
@@ -42,42 +42,41 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
// "github.com/hashicorp/terraform-plugin-sdk/terraform"
)
// enumerated constants that define authentication modes
// 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
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 checks successfully passed
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 checks successfully passed
}
func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
// This function first will check that all required provider parameters for the
// 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
// 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,
// 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{
@@ -90,7 +89,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
app_id: d.Get("app_id").(string),
app_secret: d.Get("app_secret").(string),
oauth2_url: d.Get("oauth2_url").(string),
decort_username: "",
decort_username: "",
}
var allow_unverified_ssl bool
@@ -137,10 +136,10 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
if allow_unverified_ssl {
log.Warn("ControllerConfigure: allow_unverified_ssl is set - will not check certificates!")
transCfg := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},}
transCfg := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
ret_config.cc_client = &http.Client{
Transport: transCfg,
Timeout: Timeout180s,
Timeout: Timeout180s,
}
} else {
ret_config.cc_client = &http.Client{
@@ -150,7 +149,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
switch ret_config.auth_mode_code {
case MODE_LEGACY:
ok, err := ret_config.validateLegacyUser()
ok, err := ret_config.validateLegacyUser()
if !ok {
return nil, err
}
@@ -162,13 +161,13 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
return nil, err
}
case MODE_OAUTH2:
// on success getOAuth2JWT will set config.jwt to the obtained JWT, so there is no
// 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
// 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{}
@@ -181,7 +180,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
tbuf.WriteString(claims["username"].(string))
tbuf.WriteString("@")
tbuf.WriteString(claims["iss"].(string))
ret_config.decort_username = tbuf.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.")
}
@@ -195,7 +194,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
return ret_config, nil
}
func (config *ControllerCfg) getDecortUsername() (string) {
func (config *ControllerCfg) getDecortUsername() string {
return config.decort_username
}
@@ -216,14 +215,14 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
params.Add("validity", "3600")
params_str := params.Encode()
req, err := http.NewRequest("POST", config.oauth2_url + "/v1/oauth/access_token", strings.NewReader(params_str))
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)
resp, err := config.cc_client.Do(req)
if err != nil {
return "", err
}
@@ -231,16 +230,16 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
// 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)
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
}
if err != nil {
return "", err
}
// validation successful - store JWT in the corresponding field of the ControllerCfg structure
config.jwt = strings.TrimSpace(string(responseData))
@@ -249,10 +248,10 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
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.
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 == "" {
@@ -265,7 +264,7 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
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)
req, err := http.NewRequest("POST", config.controller_url+"/restmachine/cloudapi/accounts/list", nil)
if err != nil {
return false, err
}
@@ -273,14 +272,14 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
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)
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)
return false, fmt.Errorf("validateJWT: unexpected status code %d when validating JWT against %q.",
resp.StatusCode, req.URL)
}
defer resp.Body.Close()
@@ -289,10 +288,10 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
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.
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.")
@@ -306,7 +305,7 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
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))
req, err := http.NewRequest("POST", config.controller_url+"/restmachine/cloudapi/users/authenticate", strings.NewReader(params_str))
if err != nil {
return false, err
}
@@ -314,21 +313,21 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
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)
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)
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)
}
if err != nil {
log.Fatal(err)
}
// validation successful - keep session ID for future use
config.legacy_sid = string(responseData)
@@ -336,16 +335,16 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
}
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
// 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
// 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))
//
@@ -367,42 +366,40 @@ func (config *ControllerCfg) decortAPICall(method string, api_name string, url_v
}
params_str := url_values.Encode()
req, err := http.NewRequest(method, config.controller_url + api_name, strings.NewReader(params_str))
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)))
req.Header.Set("Accept", "application/json")
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.Debugf("decortAPICall: %s %s\n %s", method, api_name, json_resp)
return json_resp, nil
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
log.Debugf("decortAPICall: %s %s\n %s", method, api_name, body)
if resp.StatusCode == http.StatusOK {
return string(body), nil
} else {
return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %q with request Body %q",
resp.StatusCode, req.URL, params_str)
return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %q with request Body %q. Respone:\n%s",
resp.StatusCode, req.URL, params_str, body)
}
/*
if resp.StatusCode == StatusServiceUnavailable {
return nil, fmt.Errorf("decortAPICall method called for incompatible authorization mode %q.", config.auth_mode_txt)
}
if resp.StatusCode == StatusServiceUnavailable {
return nil, fmt.Errorf("decortAPICall method called for incompatible authorization mode %q.", config.auth_mode_txt)
}
*/
return "", err
}

View File

@@ -263,7 +263,7 @@ func flattenCompute(d *schema.ResourceData, compFacts string) error {
d.Set("rg_name", model.RgName)
d.Set("account_id", model.AccountID)
d.Set("account_name", model.AccountName)
d.Set("arch", model.Arch)
d.Set("driver", model.Driver)
d.Set("cpu", model.Cpu)
d.Set("ram", model.Ram)
// d.Set("boot_disk_size", model.BootDiskSize) - bootdiskSize key in API compute/get is always zero, so we set boot_disk_size in another way
@@ -367,7 +367,7 @@ func dataSourceCompute() *schema.Resource {
Description: "Name of the account this compute instance belongs to.",
},
"arch": {
"driver": {
Type: schema.TypeString,
Computed: true,
Description: "Hardware architecture of this compute instance.",

View File

@@ -25,6 +25,8 @@ Visit https://github.com/rudecs/terraform-provider-decort for full source code p
package decort
import (
"bytes"
"strconv"
"time"
)
@@ -356,6 +358,7 @@ type ComputeGetResp struct {
Cpu int `json:"cpus"`
Desc string `json:"desc"`
Disks []DiskRecord `json:"disks"`
Driver string `json:"driver"`
GridID int `json:"gid"`
ID uint `json:"id"`
ImageID int `json:"imageId"`
@@ -572,6 +575,76 @@ const VinsExtNetDisconnectAPI = "/restmachine/cloudapi/vins/extNetDisconnect"
const VinsDeleteAPI = "/restmachine/cloudapi/vins/delete"
//
// K8s structures
//
//K8sNodeRecord represents a worker/master group
type K8sNodeRecord struct {
ID int `json:"id"`
Disk int `json:"disk"`
Cpu int `json:"cpu"`
Num int `json:"num"`
Ram int `json:"ram"`
}
//K8sRecord represents k8s instance
type K8sRecord struct {
AccountID int `json:"accountId"`
AccountName string `json:"accountName"`
CI int `json:"ciId"`
ID int `json:"id"`
Groups struct {
Masters K8sNodeRecord `json:"masters"`
Workers []K8sNodeRecord `json:"workers"`
} `json:"k8sGroups"`
Name string `json:"name"`
RgID int `json:"rgId"`
RgName string `json:"rgName"`
}
const K8sCreateAPI = "/restmachine/cloudapi/k8s/create"
const K8sGetAPI = "/restmachine/cloudapi/k8s/get"
const K8sUpdateAPI = "/restmachine/cloudapi/k8s/update"
const K8sDeleteAPI = "/restmachine/cloudapi/k8s/delete"
const K8sWgCreateAPI = "/restmachine/cloudapi/k8s/workersGroupAdd"
const K8sWgDeleteAPI = "/restmachine/cloudapi/k8s/workersGroupDelete"
//Blasphemous workaround for parsing Result value
type TaskResult int
func (r *TaskResult) UnmarshalJSON(b []byte) error {
b = bytes.Trim(b, `"`)
if len(b) == 0 {
*r = 0
return nil
}
n, err := strconv.Atoi(string(b))
if err != nil {
return err
}
*r = TaskResult(n)
return nil
}
//AsyncTask represents a long task completion status
type AsyncTask struct {
AuditID string `json:"auditId"`
Completed bool `json:"completed"`
Error string `json:"error"`
Log []string `json:"log"`
Result TaskResult `json:"result"`
Stage string `json:"stage"`
Status string `json:"status"`
UpdateTime uint64 `json:"updateTime"`
UpdatedTime uint64 `json:"updatedTime"`
}
const AsyncTaskGetAPI = "/restmachine/cloudapi/tasks/get"
//
// Grid ID structures
//

100
decort/node_subresource.go Normal file
View File

@@ -0,0 +1,100 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Petr Krutov, <petr.krutov@digitalenergy.online>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
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 "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
func nodeMasterDefault() K8sNodeRecord {
return K8sNodeRecord{
Num: 1,
Cpu: 2,
Ram: 2048,
Disk: 0,
}
}
func nodeWorkerDefault() K8sNodeRecord {
return K8sNodeRecord{
Num: 1,
Cpu: 1,
Ram: 1024,
Disk: 0,
}
}
func parseNode(nodeList []interface{}) K8sNodeRecord {
node := nodeList[0].(map[string]interface{})
return K8sNodeRecord{
Num: node["num"].(int),
Cpu: node["cpu"].(int),
Ram: node["ram"].(int),
Disk: node["disk"].(int),
}
}
func nodeToResource(node K8sNodeRecord) []interface{} {
mp := make(map[string]interface{})
mp["id"] = node.ID
mp["num"] = node.Num
mp["cpu"] = node.Cpu
mp["ram"] = node.Ram
mp["disk"] = node.Disk
return []interface{}{mp}
}
func nodeK8sSubresourceSchemaMake() map[string]*schema.Schema {
return map[string]*schema.Schema{
"num": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
Description: "Number of nodes to create.",
},
"cpu": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
Description: "Node CPU count.",
},
"ram": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
Description: "Node RAM in MB.",
},
"disk": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
Description: "Node boot disk size in GB.",
},
}
}

View File

@@ -27,9 +27,9 @@ import (
func parseOsUsers(logins []OsUserRecord) []interface{} {
var result = make([]interface{}, len(logins))
elem := make(map[string]interface{})
for index, value := range logins {
elem := make(map[string]interface{})
elem["guid"] = value.Guid
elem["login"] = value.Login
elem["password"] = value.Password

View File

@@ -103,7 +103,9 @@ func Provider() *schema.Provider {
"decort_kvmvm": resourceCompute(),
"decort_disk": resourceDisk(),
"decort_vins": resourceVins(),
// "decort_pfw": resourcePfw(),
"decort_pfw": resourcePfw(),
"decort_k8s": resourceK8s(),
"decort_k8s_wg": resourceK8sWg(),
},
DataSourcesMap: map[string]*schema.Resource{

289
decort/resource_k8s.go Normal file
View File

@@ -0,0 +1,289 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Petr Krutov, <petr.krutov@digitalenergy.online>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
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"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
log "github.com/sirupsen/logrus"
)
func resourceK8sCreate(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceK8sCreate: called with name %s, rg %d", d.Get("name").(string), d.Get("rg_id").(int))
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("name", d.Get("name").(string))
urlValues.Add("rgId", strconv.Itoa(d.Get("rg_id").(int)))
urlValues.Add("k8ciId", strconv.Itoa(d.Get("k8sci_id").(int)))
var masterNode K8sNodeRecord
if masters, ok := d.GetOk("masters"); ok {
masterNode = parseNode(masters.([]interface{}))
} else {
masterNode = nodeMasterDefault()
}
urlValues.Add("masterNum", strconv.Itoa(masterNode.Num))
urlValues.Add("masterCpu", strconv.Itoa(masterNode.Cpu))
urlValues.Add("masterRam", strconv.Itoa(masterNode.Ram))
urlValues.Add("masterDisk", strconv.Itoa(masterNode.Disk))
var workerNode K8sNodeRecord
if workers, ok := d.GetOk("workers"); ok {
workerNode = parseNode(workers.([]interface{}))
} else {
workerNode = nodeWorkerDefault()
}
urlValues.Add("workerNum", strconv.Itoa(workerNode.Num))
urlValues.Add("workerCpu", strconv.Itoa(workerNode.Cpu))
urlValues.Add("workerRam", strconv.Itoa(workerNode.Ram))
urlValues.Add("workerDisk", strconv.Itoa(workerNode.Disk))
//TODO find a way to avoid hardcoding these values
//if withLB, ok := d.GetOk("with_lb"); ok {
//urlValues.Add("withLB", strconv.FormatBool(withLB.(bool)))
//}
urlValues.Add("withLB", strconv.FormatBool(true))
//if extNet, ok := d.GetOk("extnet_id"); ok {
//urlValues.Add("extnetId", strconv.Itoa(extNet.(int)))
//}
urlValues.Add("extnetId", strconv.Itoa(0))
//if desc, ok := d.GetOk("desc"); ok {
//urlValues.Add("desc", desc.(string))
//}
resp, err := controller.decortAPICall("POST", K8sCreateAPI, urlValues)
if err != nil {
return err
}
urlValues = &url.Values{}
urlValues.Add("auditId", strings.Trim(resp, `"`))
for {
resp, err := controller.decortAPICall("POST", AsyncTaskGetAPI, urlValues)
if err != nil {
return err
}
task := AsyncTask{}
if err := json.Unmarshal([]byte(resp), &task); err != nil {
return err
}
log.Debugf("resourceK8sCreate: instance creating - %s", task.Stage)
if task.Completed {
if task.Error != "" {
return fmt.Errorf("cannot create k8s instance: %v", task.Error)
}
d.SetId(strconv.Itoa(int(task.Result)))
break
}
time.Sleep(time.Second * 10)
}
k8s, err := utilityK8sCheckPresence(d, m)
if err != nil {
return err
}
d.Set("default_wg_id", k8s.Groups.Workers[0].ID)
return nil
}
func resourceK8sRead(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceK8sRead: called with id %s, rg %d", d.Id(), d.Get("rg_id").(int))
k8s, err := utilityK8sCheckPresence(d, m)
if k8s == nil {
d.SetId("")
return err
}
d.Set("name", k8s.Name)
d.Set("rg_id", k8s.RgID)
d.Set("k8sci_id", k8s.CI)
d.Set("masters", nodeToResource(k8s.Groups.Masters))
d.Set("workers", nodeToResource(k8s.Groups.Workers[0]))
d.Set("default_wg_id", k8s.Groups.Workers[0].ID)
return nil
}
func resourceK8sUpdate(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceK8sUpdate: called with id %s, rg %d", d.Id(), d.Get("rg_id").(int))
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("k8sId", d.Id())
urlValues.Add("name", d.Get("name").(string))
_, err := controller.decortAPICall("POST", K8sUpdateAPI, urlValues)
if err != nil {
return err
}
return nil
}
func resourceK8sDelete(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceK8sDelete: called with id %s, rg %d", d.Id(), d.Get("rg_id").(int))
k8s, err := utilityK8sCheckPresence(d, m)
if k8s == nil {
if err != nil {
return err
}
return nil
}
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("k8sId", d.Id())
urlValues.Add("permanently", "true")
_, err = controller.decortAPICall("POST", K8sDeleteAPI, urlValues)
if err != nil {
return err
}
return nil
}
func resourceK8sExists(d *schema.ResourceData, m interface{}) (bool, error) {
log.Debugf("resourceK8sExists: called with id %s, rg %d", d.Id(), d.Get("rg_id").(int))
k8s, err := utilityK8sCheckPresence(d, m)
if k8s == nil {
return false, err
}
return true, nil
}
func resourceK8sSchemaMake() map[string]*schema.Schema {
return map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "Name of the cluster.",
},
"rg_id": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
Description: "Resource group ID that this instance belongs to.",
},
"k8sci_id": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
Description: "ID of the k8s catalog item to base this instance on.",
},
"masters": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: nodeK8sSubresourceSchemaMake(),
},
Description: "Master node(s) configuration.",
},
"workers": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: nodeK8sSubresourceSchemaMake(),
},
Description: "Worker node(s) configuration.",
},
//"with_lb": {
//Type: schema.TypeBool,
//Optional: true,
//ForceNew: true,
//Default: true,
//Description: "Create k8s with load balancer if true.",
//},
//"extnet_id": {
//Type: schema.TypeInt,
//Optional: true,
//ForceNew: true,
//Default: 0,
//Description: "ID of the external network to connect workers to.",
//},
//"desc": {
//Type: schema.TypeString,
//Optional: true,
//Description: "Text description of this instance.",
//},
"default_wg_id": {
Type: schema.TypeInt,
Computed: true,
Description: "ID of default workers group for this instace.",
},
}
}
func resourceK8s() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
Create: resourceK8sCreate,
Read: resourceK8sRead,
Update: resourceK8sUpdate,
Delete: resourceK8sDelete,
Exists: resourceK8sExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
//TODO timeouts
Schema: resourceK8sSchemaMake(),
}
}

207
decort/resource_k8s_wg.go Normal file
View File

@@ -0,0 +1,207 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Petr Krutov, <petr.krutov@digitalenergy.online>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
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 (
"net/url"
"strconv"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
log "github.com/sirupsen/logrus"
)
func resourceK8sWgCreate(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceK8sWgCreate: called with k8s id %d", d.Get("k8s_id").(int))
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("k8sId", strconv.Itoa(d.Get("k8s_id").(int)))
urlValues.Add("name", uuid.New().String())
urlValues.Add("workerNum", strconv.Itoa(d.Get("num").(int)))
urlValues.Add("workerCpu", strconv.Itoa(d.Get("cpu").(int)))
urlValues.Add("workerRam", strconv.Itoa(d.Get("ram").(int)))
urlValues.Add("workerDisk", strconv.Itoa(d.Get("disk").(int)))
resp, err := controller.decortAPICall("POST", K8sWgCreateAPI, urlValues)
if err != nil {
return err
}
d.SetId(resp)
//urlValues = &url.Values{}
//urlValues.Add("auditId", strings.Trim(resp, `"`))
//for {
//resp, err := controller.decortAPICall("POST", AsyncTaskGetAPI, urlValues)
//if err != nil {
//return err
//}
//task := AsyncTask{}
//if err := json.Unmarshal([]byte(resp), &task); err != nil {
//return err
//}
//log.Debugf("resourceK8sCreate: workers group creating - %s", task.Stage)
//if task.Completed {
//if task.Error != "" {
//return fmt.Errorf("cannot create workers group: %v", task.Error)
//}
//d.SetId(strconv.Itoa(int(task.Result)))
//break
//}
//time.Sleep(time.Second * 5)
//}
return nil
}
func resourceK8sWgRead(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceK8sWgRead: called with k8s id %d", d.Get("k8s_id").(int))
wg, err := utilityK8sWgCheckPresence(d, m)
if wg == nil {
d.SetId("")
return err
}
d.Set("num", wg.Num)
d.Set("cpu", wg.Cpu)
d.Set("ram", wg.Ram)
d.Set("disk", wg.Disk)
return nil
}
func resourceK8sWgDelete(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourceK8sWgDelete: called with k8s id %d", d.Get("k8s_id").(int))
wg, err := utilityK8sWgCheckPresence(d, m)
if wg == nil {
if err != nil {
return err
}
return nil
}
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("k8sId", strconv.Itoa(d.Get("k8s_id").(int)))
urlValues.Add("workersGroupId", strconv.Itoa(wg.ID))
_, err = controller.decortAPICall("POST", K8sWgDeleteAPI, urlValues)
if err != nil {
return err
}
return nil
}
func resourceK8sWgExists(d *schema.ResourceData, m interface{}) (bool, error) {
log.Debugf("resourceK8sWgExists: called with k8s id %d", d.Get("k8s_id").(int))
wg, err := utilityK8sWgCheckPresence(d, m)
if wg == nil {
if err != nil {
return false, err
}
return false, nil
}
return true, nil
}
func resourceK8sWgSchemaMake() map[string]*schema.Schema {
return map[string]*schema.Schema{
"k8s_id": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
Description: "ID of k8s instance.",
},
//Unused but required by creation API. Sending generated UUID each time
//"name": {
//Type: schema.TypeString,
//Required: true,
//ForceNew: true,
//Description: "Name of the worker group.",
//},
"num": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 1,
Description: "Number of worker nodes to create.",
},
"cpu": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 1,
Description: "Worker node CPU count.",
},
"ram": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 1024,
Description: "Worker node RAM in MB.",
},
"disk": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 0,
Description: "Worker node boot disk size. If unspecified or 0, size is defined by OS image size.",
},
}
}
func resourceK8sWg() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
Create: resourceK8sWgCreate,
Read: resourceK8sWgRead,
Delete: resourceK8sWgDelete,
Exists: resourceK8sWgExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
//TODO timeouts
Schema: resourceK8sWgSchemaMake(),
}
}

200
decort/resource_pfw.go Normal file
View File

@@ -0,0 +1,200 @@
/*
Copyright (c) 2019-2021 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Petr Krutov, <petr.krutov@digitalenergy.online>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
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"
"net/url"
"strconv"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
log "github.com/sirupsen/logrus"
)
func resourcePfwCreate(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourcePfwCreate: called for compute %d", d.Get("compute_id").(int))
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int)))
urlValues.Add("publicPortStart", strconv.Itoa(d.Get("public_port_start").(int)))
urlValues.Add("localBasePort", strconv.Itoa(d.Get("local_base_port").(int)))
urlValues.Add("proto", d.Get("proto").(string))
if portEnd, ok := d.GetOk("public_port_end"); ok {
urlValues.Add("publicPortEnd", strconv.Itoa(portEnd.(int)))
}
pfwId, err := controller.decortAPICall("POST", ComputePfwAddAPI, urlValues)
if err != nil {
return err
}
d.SetId(fmt.Sprintf("%d-%s", d.Get("compute_id").(int), pfwId))
pfw, err := utilityPfwCheckPresence(d, m)
if err != nil {
return err
}
d.Set("local_ip", pfw.LocalIP)
if _, ok := d.GetOk("public_port_end"); !ok {
d.Set("public_port_end", pfw.PublicPortEnd)
}
return nil
}
func resourcePfwRead(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourcePfwRead: called for compute %d, rule %s", d.Get("compute_id").(int), d.Id())
pfw, err := utilityPfwCheckPresence(d, m)
if pfw == nil {
d.SetId("")
return err
}
d.Set("compute_id", pfw.ComputeID)
d.Set("public_port_start", pfw.PublicPortStart)
d.Set("public_port_end", pfw.PublicPortEnd)
d.Set("local_ip", pfw.LocalIP)
d.Set("local_base_port", pfw.LocalPort)
d.Set("proto", pfw.Protocol)
return nil
}
func resourcePfwDelete(d *schema.ResourceData, m interface{}) error {
log.Debugf("resourcePfwDelete: called for compute %d, rule %s", d.Get("compute_id").(int), d.Id())
pfw, err := utilityPfwCheckPresence(d, m)
if pfw == nil {
if err != nil {
return err
}
return nil
}
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int)))
urlValues.Add("ruleId", strconv.Itoa(pfw.ID))
_, err = controller.decortAPICall("POST", ComputePfwDelAPI, urlValues)
if err != nil {
return err
}
return nil
}
func resourcePfwExists(d *schema.ResourceData, m interface{}) (bool, error) {
log.Debugf("resourcePfwExists: called for compute %d, rule %s", d.Get("compute_id").(int), d.Id())
pfw, err := utilityPfwCheckPresence(d, m)
if pfw == nil {
if err != nil {
return false, err
}
return false, nil
}
return true, nil
}
func resourcePfwSchemaMake() map[string]*schema.Schema {
return map[string]*schema.Schema{
"compute_id": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
Description: "ID of compute instance.",
},
"public_port_start": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
ValidateFunc: validation.IntBetween(1, 65535),
Description: "External start port number for the rule.",
},
"public_port_end": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
ValidateFunc: validation.IntBetween(1, 65535),
Description: "End port number (inclusive) for the ranged rule.",
},
"local_ip": {
Type: schema.TypeString,
Computed: true,
Description: "IP address of compute instance.",
},
"local_base_port": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
ValidateFunc: validation.IntBetween(1, 65535),
Description: "Internal base port number.",
},
"proto": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"tcp", "udp"}, false),
Description: "Network protocol, either 'tcp' or 'udp'.",
},
}
}
func resourcePfw() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
Create: resourcePfwCreate,
Read: resourcePfwRead,
Delete: resourcePfwDelete,
Exists: resourcePfwExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Timeouts: &schema.ResourceTimeout{
Create: &Timeout60s,
Read: &Timeout30s,
Update: &Timeout60s,
Delete: &Timeout60s,
Default: &Timeout60s,
},
Schema: resourcePfwSchemaMake(),
}
}

54
decort/utility_k8s.go Normal file
View File

@@ -0,0 +1,54 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Petr Krutov, <petr.krutov@digitalenergy.online>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
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"
"net/url"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)
func utilityK8sCheckPresence(d *schema.ResourceData, m interface{}) (*K8sRecord, error) {
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("k8sId", d.Id())
resp, err := controller.decortAPICall("POST", K8sGetAPI, urlValues)
if err != nil {
return nil, err
}
if resp == "" {
return nil, nil
}
var k8s K8sRecord
if err := json.Unmarshal([]byte(resp), &k8s); err != nil {
return nil, err
}
return &k8s, nil
}

66
decort/utility_k8s_wg.go Normal file
View File

@@ -0,0 +1,66 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Petr Krutov, <petr.krutov@digitalenergy.online>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
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"
"net/url"
"strconv"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)
func utilityK8sWgCheckPresence(d *schema.ResourceData, m interface{}) (*K8sNodeRecord, error) {
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("k8sId", strconv.Itoa(d.Get("k8s_id").(int)))
resp, err := controller.decortAPICall("POST", K8sGetAPI, urlValues)
if err != nil {
return nil, err
}
if resp == "" {
return nil, nil
}
var k8s K8sRecord
if err := json.Unmarshal([]byte(resp), &k8s); err != nil {
return nil, err
}
id, err := strconv.Atoi(d.Id())
if err != nil {
return nil, err
}
for _, wg := range k8s.Groups.Workers {
if wg.ID == id {
return &wg, nil
}
}
return nil, nil
}

68
decort/utility_pfw.go Normal file
View File

@@ -0,0 +1,68 @@
/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Author: Petr Krutov, <petr.krutov@digitalenergy.online>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
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"
"net/url"
"strconv"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)
func utilityPfwCheckPresence(d *schema.ResourceData, m interface{}) (*PfwRecord, error) {
controller := m.(*ControllerCfg)
urlValues := &url.Values{}
urlValues.Add("computeId", strconv.Itoa(d.Get("compute_id").(int)))
resp, err := controller.decortAPICall("POST", ComputePfwListAPI, urlValues)
if err != nil {
return nil, err
}
if resp == "" {
return nil, nil
}
idS := strings.Split(d.Id(), "-")[1]
id, err := strconv.Atoi(idS)
if err != nil {
return nil, err
}
var pfws []PfwRecord
if err := json.Unmarshal([]byte(resp), &pfws); err != nil {
return nil, err
}
for _, pfw := range pfws {
if pfw.ID == id {
return &pfw, nil
}
}
return nil, nil
}

View File

@@ -29,12 +29,12 @@ description: |-
- **account_id** (Number) ID of the account this compute instance belongs to.
- **account_name** (String) Name of the account this compute instance belongs to.
- **arch** (String) Hardware architecture of this compute instance.
- **boot_disk_id** (Number) This compute instance boot disk ID.
- **boot_disk_size** (Number) This compute instance boot disk size in GB.
- **cloud_init** (String) Placeholder for cloud_init parameters.
- **cpu** (Number) Number of CPUs allocated for this compute instance.
- **description** (String) User-defined text description of this compute instance.
- **driver** (String) Hardware architecture of this compute instance.
- **extra_disks** (Set of Number) IDs of the extra disk(s) attached to this compute.
- **image_id** (Number) ID of the OS image this compute instance is based on.
- **image_name** (String) Name of the OS image this compute instance is based on.

55
docs/resources/k8s.md Normal file
View File

@@ -0,0 +1,55 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "decort_k8s Resource - terraform-provider-decort"
subcategory: ""
description: |-
---
# decort_k8s (Resource)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **k8sci_id** (Number) ID of the k8s catalog item to base this instance on.
- **name** (String) Name of the cluster.
- **rg_id** (Number) Resource group ID that this instance belongs to.
### Optional
- **id** (String) The ID of this resource.
- **masters** (Block List, Max: 1) Master node(s) configuration. (see [below for nested schema](#nestedblock--masters))
- **workers** (Block List, Max: 1) Worker node(s) configuration. (see [below for nested schema](#nestedblock--workers))
### Read-Only
- **default_wg_id** (Number) ID of default workers group for this instace.
<a id="nestedblock--masters"></a>
### Nested Schema for `masters`
Required:
- **cpu** (Number) Node CPU count.
- **disk** (Number) Node boot disk size in GB.
- **num** (Number) Number of nodes to create.
- **ram** (Number) Node RAM in MB.
<a id="nestedblock--workers"></a>
### Nested Schema for `workers`
Required:
- **cpu** (Number) Node CPU count.
- **disk** (Number) Node boot disk size in GB.
- **num** (Number) Number of nodes to create.
- **ram** (Number) Node RAM in MB.

30
docs/resources/k8s_wg.md Normal file
View File

@@ -0,0 +1,30 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "decort_k8s_wg Resource - terraform-provider-decort"
subcategory: ""
description: |-
---
# decort_k8s_wg (Resource)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **k8s_id** (Number) ID of k8s instance.
### Optional
- **cpu** (Number) Worker node CPU count.
- **disk** (Number) Worker node boot disk size. If unspecified or 0, size is defined by OS image size.
- **id** (String) The ID of this resource.
- **num** (Number) Number of worker nodes to create.
- **ram** (Number) Worker node RAM in MB.

46
docs/resources/pfw.md Normal file
View File

@@ -0,0 +1,46 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "decort_pfw Resource - terraform-provider-decort"
subcategory: ""
description: |-
---
# decort_pfw (Resource)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **compute_id** (Number) ID of compute instance.
- **local_base_port** (Number) Internal base port number.
- **proto** (String) Network protocol, either 'tcp' or 'udp'.
- **public_port_start** (Number) External start port number for the rule.
### Optional
- **id** (String) The ID of this resource.
- **public_port_end** (Number) End port number (inclusive) for the ranged rule.
- **timeouts** (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
### Read-Only
- **local_ip** (String) IP address of compute instance.
<a id="nestedblock--timeouts"></a>
### Nested Schema for `timeouts`
Optional:
- **create** (String)
- **default** (String)
- **delete** (String)
- **read** (String)
- **update** (String)

1
go.mod
View File

@@ -4,6 +4,7 @@ go 1.15
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/google/uuid v1.1.2
github.com/hashicorp/terraform-plugin-docs v0.5.1
github.com/hashicorp/terraform-plugin-sdk v1.16.0
github.com/sirupsen/logrus v1.7.0