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 }