terraform/builtin/providers/clc/provider.go

229 lines
6.0 KiB
Go

package clc
import (
"fmt"
"log"
"strconv"
clc "github.com/CenturyLinkCloud/clc-sdk"
"github.com/CenturyLinkCloud/clc-sdk/api"
"github.com/CenturyLinkCloud/clc-sdk/group"
"github.com/CenturyLinkCloud/clc-sdk/server"
"github.com/CenturyLinkCloud/clc-sdk/status"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
// Provider implements ResourceProvider for CLC
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"username": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("CLC_USERNAME", nil),
Description: "Your CLC username",
},
"password": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("CLC_PASSWORD", nil),
Description: "Your CLC password",
},
"account": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("CLC_ACCOUNT", ""),
Description: "Account alias override",
},
},
ResourcesMap: map[string]*schema.Resource{
"clc_server": resourceCLCServer(),
"clc_group": resourceCLCGroup(),
"clc_public_ip": resourceCLCPublicIP(),
"clc_load_balancer": resourceCLCLoadBalancer(),
"clc_load_balancer_pool": resourceCLCLoadBalancerPool(),
},
ConfigureFunc: providerConfigure,
}
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
un := d.Get("username").(string)
pw := d.Get("password").(string)
config, err := api.NewConfig(un, pw)
if err != nil {
return nil, fmt.Errorf("Failed to create CLC config with provided details: %v", err)
}
config.UserAgent = fmt.Sprintf("terraform-clc terraform/%s", terraform.Version)
// user requested alias override or sub-account
if al := d.Get("account").(string); al != "" {
config.Alias = al
}
client := clc.New(config)
if err := client.Authenticate(); err != nil {
return nil, fmt.Errorf("Failed authenticated with provided credentials: %v", err)
}
alerts, err := client.Alert.GetAll()
if err != nil {
return nil, fmt.Errorf("Failed to connect to the CLC api because %s", err)
}
for _, a := range alerts.Items {
log.Printf("[WARN] Received alert: %v", a)
}
return client, nil
}
// package utility functions
func waitStatus(client *clc.Client, id string) error {
// block until queue is processed and server is up
poll := make(chan *status.Response, 1)
err := client.Status.Poll(id, poll)
if err != nil {
return nil
}
status := <-poll
log.Printf("[DEBUG] status %v", status)
if status.Failed() {
return fmt.Errorf("unsuccessful job %v failed with status: %v", id, status.Status)
}
return nil
}
func dcGroups(dcname string, client *clc.Client) (map[string]string, error) {
dc, _ := client.DC.Get(dcname)
_, id := dc.Links.GetID("group")
m := map[string]string{}
resp, _ := client.Group.Get(id)
m[resp.Name] = resp.ID // top
m[resp.ID] = resp.ID
for _, x := range resp.Groups {
deepGroups(x, &m)
}
return m, nil
}
func deepGroups(g group.Groups, m *map[string]string) {
(*m)[g.Name] = g.ID
(*m)[g.ID] = g.ID
for _, sg := range g.Groups {
deepGroups(sg, m)
}
}
// resolveGroupByNameOrId takes a reference to a group (either name or guid)
// and returns the guid of the group
func resolveGroupByNameOrId(ref, dc string, client *clc.Client) (string, error) {
m, err := dcGroups(dc, client)
if err != nil {
return "", fmt.Errorf("Failed pulling groups in location %v - %v", dc, err)
}
if id, ok := m[ref]; ok {
return id, nil
}
return "", fmt.Errorf("Failed resolving group '%v' in location %v", ref, dc)
}
func stateFromString(st string) server.PowerState {
switch st {
case "on", "started":
return server.On
case "off", "stopped":
return server.Off
case "pause", "paused":
return server.Pause
case "reboot":
return server.Reboot
case "reset":
return server.Reset
case "shutdown":
return server.ShutDown
case "start_maintenance":
return server.StartMaintenance
case "stop_maintenance":
return server.StopMaintenance
}
return -1
}
func parseCustomFields(d *schema.ResourceData) ([]api.Customfields, error) {
var fields []api.Customfields
if v := d.Get("custom_fields"); v != nil {
for _, v := range v.([]interface{}) {
m := v.(map[string]interface{})
f := api.Customfields{
ID: m["id"].(string),
Value: m["value"].(string),
}
fields = append(fields, f)
}
}
return fields, nil
}
func parseAdditionalDisks(d *schema.ResourceData) ([]server.Disk, error) {
// some complexity here: create has a different format than update
// on-create: { path, sizeGB, type }
// on-update: { diskId, sizeGB, (path), (type=partitioned) }
var disks []server.Disk
if v := d.Get("additional_disks"); v != nil {
for _, v := range v.([]interface{}) {
m := v.(map[string]interface{})
ty := m["type"].(string)
var pa string
if nil != m["path"] {
pa = m["path"].(string)
}
sz, err := strconv.Atoi(m["size_gb"].(string))
if err != nil {
log.Printf("[WARN] Failed parsing size '%v'. skipping", m["size_gb"])
return nil, fmt.Errorf("Unable to parse %v as int", m["size_gb"])
}
if ty != "raw" && ty != "partitioned" {
return nil, fmt.Errorf("Expected type of { raw | partitioned }. received %v", ty)
}
if ty == "raw" && pa != "" {
return nil, fmt.Errorf("Path can not be specified for raw disks")
}
disk := server.Disk{
SizeGB: sz,
Type: ty,
}
if pa != "" {
disk.Path = pa
}
disks = append(disks, disk)
}
}
return disks, nil
}
func parsePackages(d *schema.ResourceData) ([]server.Package, error) {
var pkgs []server.Package
if e := d.Get("packages"); e != nil {
for _, e := range e.([]interface{}) {
m := e.(map[string]interface{})
id := m["id"].(string)
delete(m, "id")
ms := make(map[string]string)
for k, v := range m {
if s, ok := v.(string); ok {
ms[k] = s
}
}
p := server.Package{
ID: id,
Params: ms,
}
pkgs = append(pkgs, p)
}
}
return pkgs, nil
}