Upclapi/main.go

564 lines
19 KiB
Go

package main
import (
"flag"
"fmt"
"path/filepath"
"github.com/UpCloudLtd/upcloud-go-api/upcloud"
"github.com/UpCloudLtd/upcloud-go-api/upcloud/request"
"encoding/json"
"log"
"math/rand"
"os"
"time"
"gopkg.in/yaml.v3"
// https://abhinavg.net/posts/flexible-yaml/
)
const VERSION = "0.1.1"
const SEPARATOR = "::"
const DATA_CFG = "datacfg.yaml"
const DFLT_COMMAND= "infoserver"
const SSH_KEY_PATH = ".sshkeys"
const DFLT_SSH_USER = "kloudsadm"
const MONTH_HOURS = 672
var username string
var password string
var TECODER = CoderConfig {
cmd: "tecoder",
abbrv: "tc",
decoder: "-d",
fdecoder: "--fdecrypt",
encoder: "-e",
fencoder: "--fencrypt",
}
type RunFlags struct {
command string
dataCfgPath string
id string
runCmd string
encdr string
out string
target string
sid string
version bool
}
type CoderConfig struct {
cmd string
abbrv string
decoder string
fdecoder string
encoder string
fencoder string
}
type StoreConfig struct {
Title string `yaml:"title"`
Uuid string `yaml:"uuid"`
Size int `yaml:"size"`
Servers []string `yaml:"servers"`
}
type DataStorageDevices struct {
Action string `yaml:"action"`
Title string `yaml:"title"`
Storage string `yaml:"storage"`
Size int `yaml:"size"`
FinalSize int `yaml:"finalSize"`
PartSizes string `yaml:"partSizes"`
MakeFs string `yaml:"makefs"`
Tier string `yaml:"tier"`
Source string `yaml:"source"`
// Type: string `yaml:"type"`
// Address string `json:"address,omitempty"`
}
type NetworksConfig struct {
Access string `yaml:"access"`
Family string `yaml:"family"`
Network string `yaml:"network"`
IPAddress string `yaml:"ipaddress"`
SourceIPFiltering string `yaml:"source_ip_filtering"`
// SourceIPFiltering upcloud.Boolean `yaml:"source_ip_filtering"`
}
type ClusterRole struct {
Role string `yaml:"role"`
}
type TskSrvc struct {
name string `yaml:"name"`
path string `yaml:"path"`
target string `yaml:"target"`
req string `yaml:"req"`
liveness string `yaml:"liveness"`
}
type App struct {
name string `yaml:"name"`
path string `yaml:"path"`
target string `yaml:"target"`
req string `yaml:"req"`
liveness string `yaml:"liveness"`
}
type SSHAccess struct {
Host string `yaml:"host"`
User string `yaml:"user"`
Password string `yaml:"password"`
UType string `yaml:"utype"`
KeyPath string `yaml:"keyPath"`
Port int `yaml:"port"`
}
type LoginUserConfig struct {
// CreatePassword string `yaml:"createPassword"`
// Username string `yaml:"username"`
SSHKeys []string `yaml:"sshKeys"`
}
type Cntrllrs struct {
host string `yaml:"host"`
sshaccess SSHAccess `yaml:"sshaccess"`
cldPath string `yaml:"cldPath"`
masterPath string `yaml:"masterPath"`
}
// https://abhinavg.net/posts/flexible-yaml/
type ServerConfig struct {
Hostname string `yaml:"hostname"`
Title string `yaml:"title"`
UserData string `yaml:"userData"`
Zone string `yaml:"zone"`
UUID string `yaml:"uuid"`
Host string `yaml:"host"`
Tags []string `yaml:"tags"`
Cluster ClusterRole `yaml:"cluster"`
SSHAccess SSHAccess `yaml:"sshAccess"`
LoginUser LoginUserConfig `yaml:"loginUser"`
Plan string `yaml:"plan"`
Metadata upcloud.Boolean `yaml:"metadata"`
TimeZone string `yaml:"timeZone"`
Networks []NetworksConfig `yaml:"networks"`
StorageDevices []DataStorageDevices `yaml:"storages"`
UseFloatIP bool `yaml:"useFloatIP"`
TskSrvcs []TskSrvc `yaml:"tsksrvcs"`
Apps []App `yaml:"apps"`
Clients []string `yaml:"clients"`
Rules []string `yaml:"rules"`
Backup []string `yaml:"backup"`
}
type DataConfig struct {
Servers []ServerConfig `yaml:"servers"`
FloatIP string `yaml:"floatIP"`
Group string `yaml:"group"`
GroupPath string `yaml:"group_path"`
HostPrefix string `yaml:"hostPrefix"`
MainName string `yaml:"main"`
domainName string `yaml:"domainName"`
cntrllrs []Cntrllrs `yaml:"cntrllrs"`
}
type InfoServer struct {
info upcloud.ServerDetails `json:"ServerDetails"`
price Price `json:"Price"`
// price upcloud.Price `json:"Price"`
}
/*
type CloudGroup struct {
name string `yaml:"name"`
path string `yaml:"path"`
info string `yaml:"info"`
resources ConfigResources `yaml:"resources"`
}
type KloudHome struct {
name string `yaml:"name"`
title string `yaml:"title"`
dflts string `yaml:"dflts"`
provider []ProviderName `yaml:"provider"`
netips []IpDef `yaml:"netips"`
cntrllrs []Cntrllrs `yaml:"cntrllrs"`
groups []CloudGroup `yaml:"groups"`
}
*/
// type DDataConfig struct {
// // Servers []ServerConfig `yaml:"servers"`
// floatIP string `yaml:"floatIP"`
// hostPrefix string `yaml:"hostPrefix"`
// mainName string `yaml:"main"`
// }
// Price represents a price
type Price struct {
Amount int `yam:"amount"`
Price float64 `yaml:"price"`
Hour float64 `yaml:"price_hour"`
Month float64 `yaml:"price_month"`
}
type DataPrices struct {
Prices PricesZones `yaml:"prices"`
}
type PricesZones struct {
Zone []PricesZone `yaml:"zone"`
}
type PricesZone struct {
Name string `yaml:"name"`
Firewall Price `yaml:"firewall"`
IORequestBackup Price `yaml:"io_request_backup"`
IORequestHDD Price `yaml:"io_request_hdd"`
IORequestMaxIOPS Price `yaml:"io_request_maxiops"`
IPv4Address Price `yaml:"ipv4_address"`
IPv6Address Price `yaml:"ipv6_address"`
ManagedDatabase1x1CPU2GB25GB Price `yaml:"managed_database_1x1xCPU-2GB-25GB"`
ManagedDatabase1x2CPU4GB100GB Price `yaml:"managed_database_1x2xCPU-4GB-100GB"`
ManagedDatabase1x2CPU4GB50GB Price `yaml:"managed_database_1x2xCPU-4GB-50GB"`
ManagedDatabase2x2CPU4GB100GB Price `yaml:"managed_database_2x2xCPU-4GB-100GB"`
ManagedDatabase2x2CPU4GB50GB Price `yaml:"managed_database_2x2xCPU-4GB-50GB"`
ManagedDatabase2x4CPU8GB100GB Price `yaml:"managed_database_2x4xCPU-8GB-100GB"`
ManagedDatabase2x4CPU8GB50GB Price `yaml:"managed_database_2x4xCPU-8GB-50GB"`
ManagedDatabase2x6CPU16GB100GB Price `yaml:"managed_database_2x6xCPU-16GB-100GB"`
ManagedDatabase2x6CPU16GB50GB Price `yaml:"managed_database_2x6xCPU-16GB-250GB"`
ManagedDatabase2x8CPU32GB100GB Price `yaml:"managed_database_2x8xCPU-32GB-100GB"`
ManagedDatabase2x8CPU22GB250GB Price `yaml:"managed_database_2x8xCPU-32GB-250GB"`
ManagedDatabase3x2CPU4GB100GB Price `yaml:"managed_database_3x2xCPU-4GB-100GB"`
ManagedDatabase3x2CPU4GB200GB Price `yaml:"managed_database_3x2xCPU-4GB-200GB"`
ManagedDatabase3x4CPU8GB100GB Price `yaml:"managed_database_3x4xCPU-8GB-100GB"`
ManagedDatabase3x4CPU8GB200GB Price `yaml:"managed_database_3x4xCPU-8GB-200GB"`
ManagedDatabase3x6CPU16GB200GB Price `yaml:"managed_database_3x6xCPU-16GB-200GB"`
ManagedDatabase3x6CPU16GB500GB Price `yaml:"managed_database_3x6xCPU-16GB-500GB"`
ManagedDatabase3x8CPU32GB200GB Price `yaml:"managed_database_3x8xCPU-32GB-200GB"`
ManagedDatabase3x8CPU32GB500GB Price `yaml:"managed_database_3x8xCPU-32GB-500GB"`
NetworkPrivateVLAN Price `yaml:"network_private_vlan"`
ObjectStorage1TB Price `yaml:"object_storage_1TB"`
ObjectStorage250GB Price `yaml:"object_storage_250GB"`
ObjectStorage500GB Price `yaml:"object_storage_500GB"`
PublicIPv4BandwidthIn Price `yaml:"public_ipv4_bandwidth_in"`
PublicIPv4BandwidthOut Price `yaml:"public_ipv4_bandwidth_out"`
PublicIPv6BandwidthIn Price `yaml:"public_ipv6_bandwidth_in"`
PublicIPv6BandwidthOut Price `yaml:"public_ipv6_bandwidth_out"`
ServerCore Price `yaml:"server_core"`
ServerMemory Price `yaml:"server_memory"`
ServerPlan6xCPU8GB Price `yaml:"server_plan_6xCPU-8GB"`
ServerPlan12xCPU48GB Price `yaml:"server_plan_12xCPU-48GB"`
ServerPlan16xCPU64GB Price `yaml:"server_plan_16xCPU-64GB"`
ServerPlan1xCPU1GB Price `yaml:"server_plan_1xCPU-1GB"`
ServerPlan1xCPU2GB Price `yaml:"server_plan_1xCPU-2GB"`
ServerPlan20xCPU128GB Price `yaml:"server_plan_20xCPU-128GB"`
ServerPlan20xCPU96GB Price `yaml:"server_plan_20xCPU-96GB"`
ServerPlan2xCPU4GB Price `yaml:"server_plan_2xCPU-4GB"`
ServerPlan4xCPU8GB Price `yaml:"server_plan_4xCPU-8GB"`
ServerPlan6xCPU16GB Price `yaml:"server_plan_6xCPU-16GB"`
ServerPlan8xCPU32GB Price `yaml:"server_plan_8xCPU-32GB"`
SimpleBackupDailies12xCPU48GB Price `yaml:"simple_backup_dailies_12xCPU-48GB"`
SimpleBackupDailies16xCPU64GB Price `yaml:"simple_backup_dailies_16xCPU-64GB"`
SimpleBackupDailiesx1CPU1GB Price `yaml:"simple_backup_dailies_1xCPU-1GB"`
SimpleBackupDailies1xCPU2GB Price `yaml:"simple_backup_dailies_1xCPU-2GB"`
SimpleBackupDailies20xCPU128GB Price `yaml:"simple_backup_dailies_20xCPU-128GB"`
SimpleBackupDailies20xCPU96GB Price `yaml:"simple_backup_dailies_20xCPU-96GB"`
SimpleBackupDailies2xCPU4GB Price `yaml:"simple_backup_dailies_2xCPU-4GB"`
SimpleBackupDailies4xCPU8GB Price `yaml:"simple_backup_dailies_4xCPU-8GB"`
SimpleBackupDailies6xCPU16GB Price `yaml:"simple_backup_dailies_6xCPU-16GB"`
SimpleBackupDailies8xCPU32GB Price `yaml:"simple_backup_dailies_8xCPU-32GB"`
SimpleBackupExtraDailies Price `yaml:"simple_backup_extra_dailies"`
SimpleBackupExtraMontlies Price `yaml:"simple_backup_extra_monthlies"`
SimpleBackupExtraWeeklies Price `yaml:"simple_backup_extra_weeklies"`
SimpleBackupMonthlies12xCPU48GB Price `yaml:"simple_backup_monthlies_12xCPU-48GB"`
SimpleBackupMonthlies16xCPU64GB Price `yaml:"simple_backup_monthlies_16xCPU-64GB"`
SimpleBackupMonthlies1xCPU1GB Price `yaml:"simple_backup_monthlies_1xCPU-1GB"`
SimpleBackupMonthlies1xCPU2GB Price `yaml:"simple_backup_monthlies_1xCPU-2GB"`
SimpleBackupMonthlies20xCPU128GB Price `yaml:"simple_backup_monthlies_20xCPU-128GB"`
SimpleBackupMonthlies20xCPU96GB Price `yaml:"simple_backup_monthlies_20xCPU-96GB"`
SimpleBackupMonthlies2xCPU4GB Price `yaml:"simple_backup_monthlies_2xCPU-4GB"`
SimpleBackupMonthlies4xCPU8GB Price `yaml:"simple_backup_monthlies_4xCPU-8GB"`
SimpleBackupMonthlies6xCPU16GB Price `yaml:"simple_backup_monthlies_6xCPU-16GB"`
SimpleBackupMonthlies8xCPU32GB Price `yaml:"simple_backup_monthlies_8xCPU-32GB"`
SimpleBackupWeeklies12xCPU48GB Price `yaml:"simple_backup_weeklies_12xCPU-48GB"`
SimpleBackupWeeklies16xCPU64GB Price `yaml:"simple_backup_weeklies_16xCPU-64GB"`
SimpleBackupWeeklies1xCPU1GB Price `yaml:"simple_backup_weeklies_1xCPU-1GB"`
SimpleBackupWeeklies1xCPU2GB Price `yaml:"simple_backup_weeklies_1xCPU-2GB"`
SimpleBackupWeeklies20xCPU128GB Price `yaml:"simple_backup_weeklies_20xCPU-128GB"`
SimpleBackupWeeklies20xCPU96GB Price `yaml:"simple_backup_weeklies_20xCPU-96GB"`
SimpleBackupWeeklies2xCPU4GB Price `yaml:"simple_backup_weeklies_2xCPU-4GB"`
SimpleBackupWeeklies4xCPU8GB Price `yaml:"simple_backup_weeklies_4xCPU-8GB"`
SimpleBackupWeeklies6xCPU16GB Price `yaml:"simple_backup_weeklies_6xCPU-16GB"`
SimpleBackupWeeklies8xCPU32GB Price `yaml:"simple_backup_weeklies_8xCPU-32GB"`
StorageHDD Price `yaml:"storage_hdd"`
StorageBackup Price `yaml:"storage_backup"`
StorageMaxIOPS Price `yaml:"storage_maxiops"`
StorageTemplate Price `yaml:"storage_template"`
}
func (m *InfoServer) MarshalJSON() ([]byte, error) {
// meta := `"Id":` + strconv.Itoa(m.Meta.Id)
info, err_info := json.Marshal(m.info)
if err_info != nil {
return nil, err_info
}
price, err_price := json.Marshal(m.price)
if err_price != nil {
return nil, err_price
}
// fmt.Printf("price %s \n", price)
// Stitching it all together
// return []byte(`{` + meta + `,"Contents":` + string(cont) + `}`), nil
return []byte(`{"info":` + string(info) + `,"price":` + string(price) + `}`), nil
}
//func (s *InfoServer) UnmarshalJSON(b []byte) error {
// type serverWrapper struct {
// PriceZones []PriceZone `json:"zone"`
// }
// v := struct {
// PriceZones serverWrapper `json:"prices"`
// }{}
// err := json.Unmarshal(b, &v)
// if err != nil {
// return err
// }
// s.PriceZones = v.PriceZones.PriceZones
// return nil
// }
func getDataPrices() (*DataPrices, error) {
dataPrices := &DataPrices{}
if len(os.Getenv("UPCLOUD_PRICES")) == 0 {
return dataPrices,nil
}
file, err := os.Open(os.Getenv("UPCLOUD_PRICES"))
if err != nil {
return dataPrices,err
}
defer file.Close()
// Init new YAML decode
d := yaml.NewDecoder(file)
// Start YAML decoding from file
if err := d.Decode(&dataPrices); err != nil {
return dataPrices,err
}
// fmt.Printf("Loaded prices: %#v\n", dataPrices)
return dataPrices,nil
}
// NewDataConfig returns a new decoded DataConfig struct
func NewDataConfig(runFlags RunFlags) (*DataConfig, error) {
// Load config structure from YAML
dataConfig := &DataConfig{}
// fmt.Printf("Loaded config: %#v\n", runFlags.dataCfgPath)
/*
if runFlags.encdr == TECODER.cmd || runFlags.encdr == TECODER.abbrv {
// content, err := ioutil.ReadFile(runFlags.dataCfgPath)
// if err != nil {
// log.Fatal(err)
// return nil,err
// }
//out, err := exec.Command(CODER,ARGS_DECODER,string(content)).Output()
out, err := exec.Command(TECODER.cmd,TECODER.fdecoder,runFlags.dataCfgPath).Output()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to decode %s error: %#v\n ",runFlags.dataCfgPath,err)
return nil, err
}
err = yaml.Unmarshal(out,&dataConfig)
if err != nil {
return nil, err
}
} else {
/*
ddataConfig := &DDataConfig{}
bytes, err := ioutil.ReadFile(runFlags.dataCfgPath)
if err != nil {
panic(err)
}
err = dhall.Unmarshal(bytes, &ddataConfig)
if err != nil {
panic(err)
}
fmt.Printf("Loaded config: %#v\n", ddataConfig)
*/
// Open config file
file, err := os.Open(runFlags.dataCfgPath)
if err != nil {
return nil, err
}
defer file.Close()
// Init new YAML decode
d := yaml.NewDecoder(file)
// Start YAML decoding from file
if err := d.Decode(&dataConfig); err != nil {
return nil, err
}
// fmt.Printf("Loaded config: %#v\n", dataConfig)
// }
return dataConfig, nil
}
func getAuthKeysData(path string) string {
pathAuth := fmt.Sprintf("%s/%s", path,SSH_KEY_PATH)
// fmt.Fprintf(os.Stdout, "%#v\n",pathAuth)
if _, err := os.Stat(pathAuth); err == nil {
file, err := os.Open(pathAuth)
if err != nil {
return ""
}
defer file.Close()
}
return pathAuth
}
func loadLoginUserKeys(runFlags RunFlags) request.SSHKeySlice {
dirAuth := filepath.Dir(runFlags.dataCfgPath)
keysPath := getAuthKeysData(dirAuth)
for i := 0; i < 3; i++ {
if keysPath == "" {
break
}
dirAuth = filepath.Dir(dirAuth)
keysPath = getAuthKeysData(dirAuth)
}
if keysPath == "" {
return nil
}
file, err := os.Open(keysPath)
if err != nil {
return nil
}
defer file.Close()
// fmt.Fprintf(os.Stdout, "%#v\n",keysPath)
var sshKeys request.SSHKeySlice
d := yaml.NewDecoder(file)
if err := d.Decode(&sshKeys); err != nil {
fmt.Fprintf(os.Stdout, "%#v\n",err)
return nil
}
// fmt.Fprintf(os.Stdout, "%#v\n",sshKeys)
return sshKeys
}
func init() {
rand.Seed(time.Now().Unix())
}
func main() {
if len(os.Args) > 1 && (os.Args[1] == "-v" || os.Args[1] == "version") {
fmt.Fprintf(os.Stdout, "{...} UpClapi version:%s\n",VERSION)
os.Exit(0)
}
if len(os.Args) > 1 || len(os.Getenv("UPCLAPI_COMMAND")) > 0 {
os.Exit(run())
} else {
fmt.Fprintln(os.Stderr, "Use --help to see options")
}
}
func hasID(id string) bool {
if len(id) == 0 {
fmt.Fprintln(os.Stderr, "ID must be specified")
os.Exit(2)
}
return true
}
func loadDataConfig(runFlags RunFlags) (*DataConfig, error) {
// datacfg := &DataConfig{}
datacfg, err := NewDataConfig(runFlags)
if err != nil {
log.Fatal(err)
return datacfg, err
}
return datacfg, nil
}
// ValidateDataPath just makes sure, that the path provided is a file,
// that can be read
func ValidateDataPath(path string) error {
s, err := os.Stat(path); os.IsNotExist(err)
if err != nil {
return fmt.Errorf("'%s' config file not found", path)
}
if s.IsDir() {
return fmt.Errorf("'%s' is a directory, not a normal file", path)
}
return nil
}
// ParseFlags will create and parse the CLI flags
// and return the path to be used elsewhere
func ParseFlags() (RunFlags,error) {
// target := flag.Arg(1)
// fmt.Printf("%+v ...\n",target) // Print with Variable Name
// String that contains the configured configuration path
runFlags := RunFlags {
command: os.Getenv("UPCLAPI_COMMAND"),
dataCfgPath: os.Getenv("UPCLAPI_DATACFG"),
id: os.Getenv("UPCLAPI_ID"),
runCmd: os.Getenv("UPCLAPI_RUNCMD"),
encdr: os.Getenv("UPCLAPI_ENCODER"),
out: os.Getenv("UPCLAPI_OUT"),
target: os.Getenv("UPCLAPI_TARGET"),
sid: os.Getenv("UPCLAPI_SID"),
}
if runFlags.command == "" {
runFlags.command = DFLT_COMMAND
}
if runFlags.dataCfgPath == "" {
runFlags.dataCfgPath = DATA_CFG
}
// var command string
// var dataCfgPath string
// var id string
// var cmd string
// var encdr string
// Set up a CLI flag called "-config" to allow users
// to supply the configuration file
commandInfo := fmt.Sprintf("command to run [\n%s\n%s\n%s\n%s\n%s\n%s\n]",
"createserver, infoserver, restartserver, startserver, stopserver, modifyserver, deleteserver,",
"infofloatip, movefloatip, modifyip,",
"infotags, addtags, deletetags (use -t keep to keep storages)",
"liststorages, fixstorage (-t size | part0 | part1 | final), deletestorage, modifystorage, createstorageimage (-t title),",
"inventory, pubhosts, prvhosts",
"runssh, runcmd")
flag.StringVar(&runFlags.command, "c", runFlags.command, commandInfo)
flag.StringVar(&runFlags.dataCfgPath, "f", runFlags.dataCfgPath, "path to data file")
flag.StringVar(&runFlags.id, "id", runFlags.id, "resource name or uuid")
flag.StringVar(&runFlags.runCmd, "cmd", runFlags.runCmd, "run [ssh] command")
flag.StringVar(&runFlags.encdr, "kdr", runFlags.encdr, "use coder ")
flag.StringVar(&runFlags.out, "o", runFlags.out, "output format (attached for infofloatip)")
flag.StringVar(&runFlags.target, "t", runFlags.target, "target item for command ")
flag.StringVar(&runFlags.sid, "sid", runFlags.sid, "ssh id for ssh commands (or defaul id_rsa)")
flag.BoolVar(&runFlags.version,"v",false,"version")
// Actually parse the flags
flag.Parse()
if len(runFlags.id) == 0 {
if _, err := os.Stat(runFlags.dataCfgPath); os.IsNotExist(err) {
msg := fmt.Errorf("'%s' config file not found", DATA_CFG)
if len(os.Getenv("UPCLAPI_DATACFG")) == 0 {
err = fmt.Errorf("%s\nUPCLAPI_DATACFG environment value not set\n",msg)
return runFlags,err
}
}
// fmt.Printf("dataCfgPath: %+v\n",dataCfgPath) // Print with Variable Name
// Validate the path first
// if err := ValidateDataPath(runFlags.dataCfgPath); err != nil {
// return runFlags,err
// }
} else {
if runFlags.command != os.Getenv("UPCLAPI_COMMAND") && runFlags.id == os.Getenv("UPCLAPI_ID") {
fmt.Fprintf(os.Stdout, "Warning: Resource ID is set in UPCLAPI_ID enviroment to: %s\n",runFlags.id)
}
}
// Return the configuration path
return runFlags,nil
}