diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0b897b4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Jesús Pérez Lorenzo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..83f8058 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +.PHONY: all + +# all: test build run +all: build run + +.PHONY: run + +run: + @go run ./... + +.PHONY: build + +build: + @go build -o ./build/upclapi ./... + @echo "[OK] upclapi was build in ./build/upclapi !" + +.PHONY: install + +install: + @go install + @echo "[OK] upclapi was installed in $$GOPATH/bin/upclapi" + +# .PHONY: test + +# test: +# @go test -v -coverprofile=cover.out ./... +# @echo "[OK] Test and coverage file was created!" + +.PHONY: show_coverage + +show_coverage: + @go tool cover -html=cover.out + @echo "[OK] Coverage file opened!" diff --git a/bin/get_hosts_pub_ips.sh b/bin/get_hosts_pub_ips.sh new file mode 100755 index 0000000..fe5c436 --- /dev/null +++ b/bin/get_hosts_pub_ips.sh @@ -0,0 +1,4 @@ +#!/bin/bash +[ -z "$1" ] && echo "source-cloud-home-name not found" && exit 1 +. ./.env +../klouds/bin/hosts_list.sh $1 -src ../klouds/home -filter pub diff --git a/bin/make_kenv.sh b/bin/make_kenv.sh new file mode 100755 index 0000000..0597713 --- /dev/null +++ b/bin/make_kenv.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# tecoder --fencrypt _datacfg.yaml > kdata.yaml +# +ENCODER="/usr/local/bin/tecoder" +ARGSENCODER="-e" + +SOURCE_ENV=".env" +TARGET_ENV=".envk" + +[ ! -r "$SOURCE_ENV" ] && echo "File $SOURCE_ENV not found" && exit 1 + +USER=$(grep "USER" $SOURCE_ENV | cut -f2 -d"=") +[ -z "$USER" ] && echo "User is empty" && exit 1 + +PASSWRD=$(grep "PASSWORD" $SOURCE_ENV | cut -f2 -d"=") +[ -z "$PASSWRD" ] && echo "Password is empty" && exit 1 + +kuser=$($ENCODER $ARGSENCODER $USER) +[ -z "$kuser" ] && echo "Unable to encode User" && exit 1 +kpasswdr=$($ENCODER $ARGSENCODER $PASSWRD) +[ -z "$kpasswdr" ] && echo "Unable to encode Password" && exit 1 + +echo "export KUPCLAPI=\"$kuser $kpasswdr\"" > $TARGET_ENV diff --git a/bin/on_cloud.sh b/bin/on_cloud.sh new file mode 100755 index 0000000..a979296 --- /dev/null +++ b/bin/on_cloud.sh @@ -0,0 +1,22 @@ +#!/bin/bash +UPCLAPI_CMD="./build/upclapi" +USAGE="on_cloud.sh home-cloud-dir-name tsksrvc [ args ] +example: on_cloud.sh wuji/dvara info +" +[ -z "$1" ] || [ "$1" == "-h" ] && echo "$USAGE" && $UPCLAPI_CMD -h && exit +. ./env +CLOUD_CONFIG_PATH="$KLDS_HOME/$1/$KLDS_CONFIG" +[ ! -r "$CLOUD_CONFIG_PATH" ] && echo "$CLOUD_CONFIG_PATH not found" && exit +[ -z "$2" ] && echo "No tsksrvc defined" && echo "$USAGE" && exit 1 +case "$2" in + c|create|createserver) TSKSRVC="createserver" ;; + d|delete|deleteserver) TSKSRVC="deleteserver" ;; + i|info) TSKSRVC="infoserver" ;; + *) TSKSRVC="$2" +esac + +if $UPCLAPI_CMD -c "$TSKSRVC" -f "$CLOUD_CONFIG_PATH" ; then + [ "$TSKSRVC" == "createserver" ] && "$ROOT_KLDS"/bin/hosts_list.sh "$1" -src "$KLDS_HOME" -filter pub +fi + + diff --git a/bin/update_storage.sh b/bin/update_storage.sh new file mode 100755 index 0000000..25038ec --- /dev/null +++ b/bin/update_storage.sh @@ -0,0 +1,54 @@ +#!/bin/bash +USAGE="update_storage.sh [-d] server-name new-size" +[ "$1" == "-h" ] && echo "$USAGE" && exit +[ "$1" == "-d" ] && DEBUG=$1 && shift +[ -z "$1" ] && echo "No server found" && echo "$USAGE" && exit 1 +SERVER_ID=$1 +[ -z "$2" ] && echo "No new size found" && echo "$USAGE" && exit 1 +NEW_SIZE=$2 +STOPPED="stopped" +STARTED="started" +MAX_TRIES=5 +SLEEP_TIME=10 + +_server_state() { + [ -z "$1" ] && return + upctl server show "$1" | grep State | awk '{print $2}' +} +_is_server_started() { + [ -z "$1" ] && return + _server_state "$1" | grep started +} +_stop_server() { + [ -z "$1" ] && return + [[ "$(_server_state "$1")" =~ $STOPPED ]] && return + #[[ ! "$(_server_state "$1")" =~ $STOPPED ]] && + upctl server stop "$1" 2>/dev/null + local n=0 + echo -n "Stopping $1 " + while [[ ! "$(_server_state "$1")" =~ $STOPPED ]] + do + n=$((n+1)) + echo -n ". " + sleep $SLEEP_TIME + [ "$n" -gt $MAX_TRIES ] && echo "Server $1 not $STOPPED" && exit 2 + done + echo " $(_server_state "$1")" +} + +STORE_UUID=$(upctl server show "$SERVER_ID" -o json | jq -r '.storage[].uuid ' | sed 's/"//') +[ -z "$STORE_UUID" ] && echo "No store found for $SERVER_ID" && exit 1 +STORE_SIZE=$(upctl server show "$SERVER_ID" -o json | jq '.storage[].size ') + +echo "$SERVER_ID: store $STORE_UUID from $STORE_SIZE to $NEW_SIZE" +[ "$STORE_SIZE" -gt "$NEW_SIZE" ] && echo "$NEW_SIZE is less then current $STORE_SIZE" && exit 1 + +_stop_server "$SERVER_ID" +[ -n "$DEBUG" ] && _server_state "$SERVER_ID" + +if upctl storage modify "$STORE_UUID" --size "$NEW_SIZE" ; then + [ -n "$DEBUG" ] && _server_state "$SERVER_ID" + [[ ! "$(_server_state "$1")" =~ $STARTED ]] && upctl server start "$SERVER_ID" +else + echo "errors in modify $SERVER_ID $STORE_UUID" +fi diff --git a/floatip.go b/floatip.go new file mode 100644 index 0000000..2ecd04d --- /dev/null +++ b/floatip.go @@ -0,0 +1,126 @@ +package main + +import ( + "fmt" + "os" + "strings" + + // "encoding/json" + + "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" +) + +func getFloatIP(s *service.Service, floatIP string, datacfg *DataConfig) (*upcloud.IPAddress,error) { + ip := floatIP + if ip == "" { + ip = datacfg.FloatIP + } + ipinfo, err := s.GetIPAddressDetails(&request.GetIPAddressDetailsRequest{ + Address: ip, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "error getting info %s: %#v\n", floatIP, err) + return nil,err + } + return ipinfo,nil +} +func moveFloatIP(s *service.Service, data []string, datacfg *DataConfig) error { + // err := s.ReleaseIPAddress(&request.ReleaseIPAddressRequest{ + // IPAddress: uuid, + // }) + // fmt.Fprintf(os.Stderr, "details %#v: %#v\n", uuid, err) + targetFloatIP := "" + targetHost := "" + if len(data) > 0 { + targetFloatIP = data[0] + targetHost = data[1] + } else { + targetFloatIP = datacfg.FloatIP + } + servers, err := s.GetServers() + if err != nil || len(servers.Servers) == 0 { + fmt.Fprintf(os.Stderr, "Unable to get servers: %#v\n", err) + return err + } + + ipinfo, err := getFloatIP(s,targetFloatIP,datacfg) + if err != nil { + fmt.Fprintf(os.Stderr, "error getting info %s: %#v\n", targetFloatIP, err) + return err + } + // fmt.Fprintf(os.Stderr, "ipinfo: %#v\n", ipinfo) + // fmt.Printf("Retrieved %d servers\n", len(servers.Servers)) + // fmt.Println("Deleting all servers") + currentFloatIP := "" + for _, server := range servers.Servers { + if datacfg.HostPrefix != "" && ! strings.Contains(server.Hostname,datacfg.HostPrefix) { + continue + } + if ipinfo.ServerUUID == server.UUID { + fmt.Fprintf(os.Stderr, "%s Located in %s %s:%s Server: %s) \n", targetFloatIP, ipinfo.PTRRecord, server.Hostname, ipinfo.MAC, ipinfo.ServerUUID) + currentFloatIP = server.Hostname + break + } + } + if currentFloatIP == "" { + fmt.Fprintf(os.Stderr, "%s Not in use in %s \n", targetFloatIP, ipinfo.PTRRecord) + } + for _, server := range servers.Servers { + if (datacfg.HostPrefix != "" && ! strings.Contains(server.Hostname,datacfg.HostPrefix)) || ipinfo.ServerUUID == server.UUID { + continue + } + var target_srv ServerConfig + if targetHost == "" { + for _,srv := range datacfg.Servers { + if server.Hostname == srv.Hostname && srv.UseFloatIP { + target_srv = srv + break + } + } + } else { + if server.Hostname == targetHost { + target_srv.Hostname = targetHost + } + } + if target_srv.Hostname == "" { + continue + } + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: server.UUID, + }) + if err != nil { + continue + } + target_IP := "" + target_MAC := "" + for i := 0; i < len(info.Networking.Interfaces); i++ { + if info.Networking.Interfaces[i].Type == upcloud.NetworkTypePublic { + for _,ip := range info.Networking.Interfaces[i].IPAddresses { + if ip.Address != targetFloatIP { + target_IP = ip.Address + break + } + } + target_MAC=info.Networking.Interfaces[i].MAC + break + } + } + if target_IP != "" && target_MAC != "" { + ptr := fmt.Sprintf("%s.%s.upcloud.host", target_IP, info.Zone) + newIP,err := s.ModifyIPAddress(&request.ModifyIPAddressRequest{ + IPAddress: targetFloatIP, + PTRRecord: ptr, + MAC: target_MAC, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "error %s: %#v\n", targetFloatIP, err) + } else { + fmt.Fprintf(os.Stderr, "%s Moved -> %s %s:%s Server: %s \n", newIP.Address, newIP.PTRRecord, target_srv.Hostname, newIP.MAC, server.UUID) + } + break + } + } + return err +} diff --git a/inventory.go b/inventory.go new file mode 100644 index 0000000..21e2f39 --- /dev/null +++ b/inventory.go @@ -0,0 +1,131 @@ +package main + +import ( + "fmt" + // "log" + "os" + + "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" +) + +func inventoryOneServer(s *service.Service, floatIP string, server upcloud.Server) (string, error) { + ansible_data := "ansible_port=22 ansible_python_interpreter=/usr/bin/python3 ansible_user=root" + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: server.UUID, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get details from server %#v: %#v\n", server.UUID, err) + return "", err + } + public_ip :="" + private_ip :="" + vpn_ip :="" + host :="" + float := upcloud.False + output := "" + + for _, ip := range info.Networking.Interfaces { + for _, addr := range ip.IPAddresses { + if addr.Address == floatIP { + continue + } + switch ip.Type { + case upcloud.NetworkTypePublic: + public_ip = addr.Address + float = addr.Floating + case upcloud.NetworkTypeUtility: + private_ip = addr.Address + case upcloud.NetworkTypePrivate: + vpn_ip = addr.Address + } + } + } + if vpn_ip != "" { + host = vpn_ip + } else { + host = public_ip + } + floating := "no" + if float == upcloud.True { + floating = "yes" + } + output += fmt.Sprintf("%s ansible_host=%s hostname=%s vpn_ip=%s pub_ip=%s private_ip=%s float=%s ", + server.Hostname, + host, + server.Hostname, + vpn_ip, + public_ip, + private_ip, + floating) + output += fmt.Sprintf(" uuid=%s %s\n",info.UUID,ansible_data) + return output, nil +} +func hostsOneServer(s *service.Service, usetype string, floatIP string, server upcloud.Server, target ServerConfig) (string, error) { + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: server.UUID, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get details from server %#v: %#v\n", server.UUID, err) + return "", err + } + public_ip :="" + private_ip :="" + sdn_ip :="" + vpn_ip :="" + vpn_net :="" + float := upcloud.False + output := "" + + for i := 0; i < len(info.Networking.Interfaces); i++ { + for _,addr := range info.Networking.Interfaces[i].IPAddresses { + if addr.Address == floatIP { + continue + } + switch info.Networking.Interfaces[i].Type { + case upcloud.NetworkTypePublic: + public_ip = addr.Address + float = addr.Floating + case upcloud.NetworkTypeUtility: + private_ip = addr.Address + case upcloud.NetworkTypePrivate: + sdn_ip = addr.Address + } + } + } + for i := 0; i < len(target.Networks); i++ { + if target.Networks[i].Access == "vpn" { + vpn_ip = target.Networks[i].IPAddress + vpn_net = target.Networks[i].Network + } + } + floating := "no" + if float == upcloud.True { + floating = "yes" + } + suffix := "" + output += fmt.Sprintf("# %s \t %s float=%s | %s\n", server.Hostname, server.UUID, floating, server.Title) + if public_ip != "" { + if usetype == "prv" { + suffix = ".pub" + } + output += fmt.Sprintf("%s \t %s%s \t# PUB\n", public_ip, server.Hostname, suffix) + } + if sdn_ip != "" { + if usetype == "pub" { + suffix = ".prv" + } else { + suffix = "" + } + output += fmt.Sprintf("%s \t %s%s \t# SDN\n", sdn_ip,server.Hostname, suffix) + } + if private_ip != "" { + output += fmt.Sprintf("%s \t %s.pv \t# PV\n", private_ip, server.Hostname) + } + if vpn_ip != "" { + output += fmt.Sprintf("%s \t %s.vpn \t# VPN /%s\n", vpn_ip, server.Hostname, vpn_net) + } + return output,nil +} + diff --git a/main.go b/main.go new file mode 100644 index 0000000..0f321be --- /dev/null +++ b/main.go @@ -0,0 +1,552 @@ +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 SEPARATOR = "::" +const DATA_CFG = "datacfg.yaml" +const DFLT_COMMAND= "infoserver" +const SSH_KEY_PATH = ".sshkeys" + +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 +} +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 || 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, "D 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"), + } + 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 ") + flag.StringVar(&runFlags.target, "t", runFlags.out, "target item for command ") + + // 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 +} \ No newline at end of file diff --git a/networks.go b/networks.go new file mode 100644 index 0000000..431e4ac --- /dev/null +++ b/networks.go @@ -0,0 +1,158 @@ +package main + +import ( + "fmt" + "os" + + "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" +) + +func getNetworkInterfaces(srv ServerConfig) []request.CreateServerInterface { + var networkInterfaces []request.CreateServerInterface + for _,net := range srv.Networks { + if net.Access == "private" { + var sourceIPFiltering upcloud.Boolean = upcloud.True + if net.SourceIPFiltering == "no" { + sourceIPFiltering = upcloud.False + } + networkInterfaces = append(networkInterfaces, request.CreateServerInterface{ + IPAddresses: []request.CreateServerIPAddress{ + { + Family: net.Family, + Address: net.IPAddress, + }, + }, + Type: net.Access, + Network: net.Network, + SourceIPFiltering: sourceIPFiltering, + }) + } else { + networkInterfaces = append(networkInterfaces, request.CreateServerInterface{ + IPAddresses: []request.CreateServerIPAddress{ + { + Family: net.Family, + }, + }, + Type: net.Access, + // SourceIPFiltering: net.SourceIPFiltering, + }) + } + } + return networkInterfaces +} + +func modifyIPOneServer(s *service.Service, server upcloud.Server,nettype string, target ServerConfig) error { + var target_NetCfg NetworksConfig + for _,it := range target.Networks { + if it.Access == nettype { // upcloud.NetworkTypePrivate { + target_NetCfg = it + break + } + } + if target_NetCfg.IPAddress == "" { + return nil + } + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: server.UUID, + }) + if err != nil { + return err + } +// target_MAC := "" + target_Index := -1 + target_pos := -1 + org_IPAddress := "" + for i := 0; i < len(info.Networking.Interfaces); i++ { + if info.Networking.Interfaces[i].Type == target_NetCfg.Access { + // fmt.Fprintf(os.Stderr, "Info: %#v\n",info.Networking.Interfaces[i]) + target_pos=i + target_Index=info.Networking.Interfaces[i].Index + for _,ip := range info.Networking.Interfaces[i].IPAddresses { + if ip.Address == target_NetCfg.IPAddress { + fmt.Fprintf(os.Stderr, "Already interface in server: %s to config %#v\n", server.Hostname,target_NetCfg) + return nil + } + org_IPAddress = ip.Address + } + break + } + target_Index=info.Networking.Interfaces[i].Index + } + if target_pos == -1 { + target_Index +=1 + } + if target_Index == -1 { // } && target_MAC != "" { + fmt.Fprintf(os.Stderr, "Unable to find interface in server: %s to config %#v\n", server.Hostname,target_NetCfg) + return nil + } + err = stopOneServer(s,server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to stop server: %#v\n", err) + return err + } + if target_pos > -1 { + err = s.DeleteNetworkInterface(&request.DeleteNetworkInterfaceRequest{ + Index: target_Index, + ServerUUID: server.UUID, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: Unable to delete interface %d server %s: %#v\n",org_IPAddress, target_Index,server.Hostname,err) + return err + } + } + var ipAddress []request.CreateNetworkInterfaceIPAddress + var r *request.CreateNetworkInterfaceRequest + switch(target_NetCfg.Access) { + case upcloud.NetworkTypeUtility: + ip_addrr := request.CreateNetworkInterfaceIPAddress{ + Family: target_NetCfg.Family, + } + ipAddress = append(ipAddress, ip_addrr) + r = &request.CreateNetworkInterfaceRequest{ + ServerUUID: server.UUID, + Type: upcloud.NetworkTypeUtility, + IPAddresses: ipAddress, + } + case upcloud.NetworkTypePublic: + ip_addrr := request.CreateNetworkInterfaceIPAddress{ + Family: target_NetCfg.Family, + } + ipAddress = append(ipAddress, ip_addrr) + r = &request.CreateNetworkInterfaceRequest{ + ServerUUID: server.UUID, + Type: upcloud.NetworkTypePublic, + IPAddresses: ipAddress, + } + case upcloud.NetworkTypePrivate: + ip_addrr := request.CreateNetworkInterfaceIPAddress{ + Family: target_NetCfg.Family, + Address: target_NetCfg.IPAddress, + } + var sourceIPFiltering upcloud.Boolean = upcloud.True + if target_NetCfg.SourceIPFiltering == "no" { + sourceIPFiltering = upcloud.False + } + ipAddress = append(ipAddress, ip_addrr) + r = &request.CreateNetworkInterfaceRequest{ + ServerUUID: server.UUID, + Type: upcloud.NetworkTypePrivate, + NetworkUUID: target_NetCfg.Network, + IPAddresses: ipAddress, + SourceIPFiltering: sourceIPFiltering, + } + } + newIP,err := s.CreateNetworkInterface(r) + if err != nil { + fmt.Fprintf(os.Stderr, "error %s: %#v\n", target_NetCfg.IPAddress, err) + } else { + fmt.Fprintf(os.Stderr, "IP %s installed for Private Network: %s in %s\n", target_NetCfg.IPAddress, newIP.Network, server.Hostname) + } + err = startOneServer(s,server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to start server: %#v\n", err) + return err + } + return nil +} \ No newline at end of file diff --git a/prices.go b/prices.go new file mode 100644 index 0000000..565a30a --- /dev/null +++ b/prices.go @@ -0,0 +1,279 @@ +package main + +import ( + "fmt" + "math" + + // "log" + + // "strings" + // "encoding/json" + + // "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + // "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" + // "gocloud.dev/server" + // "gocloud.dev/server" + // "github.com/davecgh/go-spew/spew" +) + +func dataPriceResource(s *service.Service, dataprices DataPrices, zone string, target string) (*Price,error) { + resource_price := &Price{} + resource_zone := PricesZone{} + for _,price_zone := range dataprices.Prices.Zone { + if price_zone.Name == zone { + resource_zone = price_zone + break + } + } + if resource_zone.Name == zone { + switch target { + case "firewall": + resource_price = &resource_zone.Firewall + case "io_request_backup": + resource_price = &resource_zone.IORequestBackup + case "io_request_maxiops": + resource_price = &resource_zone.IORequestMaxIOPS + case "ipv4_address": + resource_price = &resource_zone.IPv4Address + case "ipv6_address": + resource_price = &resource_zone.IPv6Address + case "managed_database_1x1xCPU-2GB-25GB": + resource_price = &resource_zone.ManagedDatabase1x1CPU2GB25GB + case "managed_database_1x2xCPU-4GB-100GB": + resource_price = &resource_zone.ManagedDatabase1x2CPU4GB100GB + case "managed_database_1x2xCPU-4GB-50GB": + resource_price = &resource_zone.ManagedDatabase1x2CPU4GB50GB + case "managed_database_2x2xCPU-4GB-100GB": + resource_price = &resource_zone.ManagedDatabase2x2CPU4GB100GB + case "managed_database_2x2xCPU-4GB-50GB": + resource_price = &resource_zone.ManagedDatabase2x2CPU4GB50GB + case "managed_database_2x4xCPU-8GB-100GB": + resource_price = &resource_zone.ManagedDatabase2x4CPU8GB100GB + case "managed_database_2x4xCPU-8GB-50GB": + resource_price = &resource_zone.ManagedDatabase2x4CPU8GB50GB + case "managed_database_2x6xCPU-16GB-100GB": + resource_price = &resource_zone.ManagedDatabase2x6CPU16GB100GB + case "managed_database_2x6xCPU-16GB-250GB": + resource_price = &resource_zone.ManagedDatabase2x6CPU16GB50GB + case "managed_database_2x8xCPU-32GB-100GB": + resource_price = &resource_zone.ManagedDatabase2x8CPU32GB100GB + case "managed_database_2x8xCPU-32GB-250GB": + resource_price = &resource_zone.ManagedDatabase2x8CPU22GB250GB + case "managed_database_3x2xCPU-4GB-100GB": + resource_price = &resource_zone.ManagedDatabase3x2CPU4GB100GB + case "managed_database_3x2xCPU-4GB-200GB": + resource_price = &resource_zone.ManagedDatabase3x2CPU4GB200GB + case "managed_database_3x4xCPU-8GB-100GB": + resource_price = &resource_zone.ManagedDatabase3x4CPU8GB100GB + case "managed_database_3x4xCPU-8GB-200GB": + resource_price = &resource_zone.ManagedDatabase3x4CPU8GB200GB + case "managed_database_3x6xCPU-16GB-200GB": + resource_price = &resource_zone.ManagedDatabase3x6CPU16GB200GB + case "managed_database_3x6xCPU-16GB-500GB": + resource_price = &resource_zone.ManagedDatabase3x6CPU16GB500GB + case "managed_database_3x8xCPU-32GB-200GB": + resource_price = &resource_zone.ManagedDatabase3x8CPU32GB200GB + case "managed_database_3x8xCPU-32GB-500GB": + resource_price = &resource_zone.ManagedDatabase3x8CPU32GB500GB + case "network_private_vlan": + resource_price = &resource_zone.NetworkPrivateVLAN + case "object_storage_1TB": + resource_price = &resource_zone.ObjectStorage1TB + case "object_storage_250GB": + resource_price = &resource_zone.ObjectStorage250GB + case "object_storage_500GB": + resource_price = &resource_zone.ObjectStorage500GB + case "public_ipv4_bandwidth_in": + resource_price = &resource_zone.PublicIPv4BandwidthIn + case "public_ipv4_bandwidth_out": + resource_price = &resource_zone.PublicIPv4BandwidthOut + case "public_ipv6_bandwidth_in": + resource_price = &resource_zone.PublicIPv6BandwidthIn + case "public_ipv6_bandwidth_out": + resource_price = &resource_zone.PublicIPv6BandwidthOut + case "server_core": + resource_price = &resource_zone.ServerCore + case "server_memory": + resource_price = &resource_zone.ServerMemory + case "server_plan_6xCPU-8GB": + resource_price = &resource_zone.ServerPlan6xCPU8GB + case "server_plan_12xCPU-48GB": + resource_price = &resource_zone.ServerPlan12xCPU48GB + case "server_plan_16xCPU-64GB": + resource_price = &resource_zone.ServerPlan16xCPU64GB + case "server_plan_1xCPU-1GB": + resource_price = &resource_zone.ServerPlan1xCPU1GB + case "server_plan_1xCPU-2GB": + resource_price = &resource_zone.ServerPlan1xCPU2GB + case "server_plan_20xCPU-128GB": + resource_price = &resource_zone.ServerPlan20xCPU128GB + case "server_plan_20xCPU-96GB": + resource_price = &resource_zone.ServerPlan20xCPU96GB + case "server_plan_2xCPU-4GB": + resource_price = &resource_zone.ServerPlan2xCPU4GB + case "server_plan_4xCPU-8GB": + resource_price = &resource_zone.ServerPlan4xCPU8GB + case "server_plan_6xCPU-16GB": + resource_price = &resource_zone.ServerPlan6xCPU16GB + case "server_plan_8xCPU-32GB": + resource_price = &resource_zone.ServerPlan8xCPU32GB + case "simple_backup_dailies_12xCPU-48GB": + resource_price = &resource_zone.SimpleBackupDailies12xCPU48GB + case "simple_backup_dailies_16xCPU-64GB": + resource_price = &resource_zone.SimpleBackupDailies16xCPU64GB + case "simple_backup_dailies_1xCPU-1GB": + resource_price = &resource_zone.SimpleBackupDailiesx1CPU1GB + case "simple_backup_dailies_1xCPU-2GB": + resource_price = &resource_zone.SimpleBackupDailies1xCPU2GB + case "simple_backup_dailies_20xCPU-128GB": + resource_price = &resource_zone.SimpleBackupDailies20xCPU128GB + case "simple_backup_dailies_20xCPU-96GB": + resource_price = &resource_zone.SimpleBackupDailies20xCPU96GB + case "simple_backup_dailies_2xCPU-4GB": + resource_price = &resource_zone.SimpleBackupDailies2xCPU4GB + case "simple_backup_dailies_4xCPU-8GB": + resource_price = &resource_zone.SimpleBackupDailies4xCPU8GB + case "simple_backup_dailies_6xCPU-16GB": + resource_price = &resource_zone.SimpleBackupDailies6xCPU16GB + case "simple_backup_dailies_8xCPU-32GB": + resource_price = &resource_zone.SimpleBackupDailies8xCPU32GB + case "simple_backup_extra_dailies": + resource_price = &resource_zone.SimpleBackupExtraDailies + case "simple_backup_extra_monthlies": + resource_price = &resource_zone.SimpleBackupExtraMontlies + case "simple_backup_extra_weeklies": + resource_price = &resource_zone.SimpleBackupExtraWeeklies + case "simple_backup_monthlies_12xCPU-48GB": + resource_price = &resource_zone.SimpleBackupMonthlies12xCPU48GB + case "simple_backup_monthlies_16xCPU-64GB": + resource_price = &resource_zone.SimpleBackupMonthlies16xCPU64GB + case "simple_backup_monthlies_1xCPU-1GB": + resource_price = &resource_zone.SimpleBackupMonthlies1xCPU1GB + case "simple_backup_monthlies_1xCPU-2GB": + resource_price = &resource_zone.SimpleBackupMonthlies1xCPU2GB + case "simple_backup_monthlies_20xCPU-128GB": + resource_price = &resource_zone.SimpleBackupMonthlies20xCPU128GB + case "simple_backup_monthlies_20xCPU-96GB": + resource_price = &resource_zone.SimpleBackupMonthlies20xCPU96GB + case "simple_backup_monthlies_2xCPU-4GB": + resource_price = &resource_zone.SimpleBackupMonthlies2xCPU4GB + case "simple_backup_monthlies_4xCPU-8GB": + resource_price = &resource_zone.SimpleBackupMonthlies4xCPU8GB + case "simple_backup_monthlies_6xCPU-16GB": + resource_price = &resource_zone.SimpleBackupMonthlies6xCPU16GB + case "simple_backup_monthlies_8xCPU-32GB": + resource_price = &resource_zone.SimpleBackupMonthlies8xCPU32GB + case "simple_backup_weeklies_12xCPU-48GB": + resource_price = &resource_zone.SimpleBackupWeeklies12xCPU48GB + case "simple_backup_weeklies_16xCPU-64GB": + resource_price = &resource_zone.SimpleBackupWeeklies16xCPU64GB + case "simple_backup_weeklies_1xCPU-1GB": + resource_price = &resource_zone.SimpleBackupWeeklies1xCPU1GB + case "simple_backup_weeklies_1xCPU-2GB": + resource_price = &resource_zone.SimpleBackupWeeklies1xCPU2GB + case "simple_backup_weeklies_20xCPU-128GB": + resource_price = &resource_zone.SimpleBackupWeeklies20xCPU128GB + case "simple_backup_weeklies_20xCPU-96GB": + resource_price = &resource_zone.SimpleBackupWeeklies20xCPU96GB + case "simple_backup_weeklies_2xCPU-4GB": + resource_price = &resource_zone.SimpleBackupWeeklies2xCPU4GB + case "simple_backup_weeklies_4xCPU-8GB": + resource_price = &resource_zone.SimpleBackupWeeklies4xCPU8GB + case "simple_backup_weeklies_6xCPU-16GB": + resource_price = &resource_zone.SimpleBackupWeeklies6xCPU16GB + case "simple_backup_weeklies_8xCPU-32GB": + resource_price = &resource_zone.SimpleBackupWeeklies8xCPU32GB + case "storage_hdd": + resource_price = &resource_zone.StorageHDD + case "storage_backup": + resource_price = &resource_zone.StorageBackup + case "storage_maxiops": + resource_price = &resource_zone.StorageMaxIOPS + case "storage_template": + resource_price = &resource_zone.StorageTemplate + default: + //fmt.Fprintf(os.Stderr, "Resource price %s in %s NOT FOUND",target,zone) + return resource_price, fmt.Errorf("Resource price %s in %s NOT FOUND",target,zone) + } + resource_price.Hour = (resource_price.Price / 100) + // resource_price.Month = (resource_price.Hour * MONTH_HOURS) + resource_price.Month = math.Round(resource_price.Hour * MONTH_HOURS) + // fmt.Printf("Resource %s price in %s: %#v\n", target, zone, resource_price) + // fmt.Printf("Resource %s price in %s: %d %.4f\n", target, zone, resource_price.Amount, resource_price.Price) + } + // enc := json.NewEncoder(os.Stdout) + // enc.Encode(resource_price) + + return resource_price, nil +} +/* +// UpCloud API is incomplete to make this work +// priceZones := upcloud.PriceZones{} +func upcloud_priceResource(s *service.Service, zone string, target string) (upcloud.Price,error) { + pricesZones,err:= s.GetPriceZones() + resource_price := new(upcloud.Price) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get prices zones: %#v\n", err) + return *resource_price,err + } + resource_zone := upcloud.PriceZone{} + for _,price_zone := range pricesZones.PriceZones { + if price_zone.Name == zone { + resource_zone = price_zone + break + } + } + if resource_zone.Name == zone { + switch target { + case "firewall": + resource_price = resource_zone.Firewall + case "io_request_backup": + resource_price = resource_zone.IORequestBackup + case "io_request_maxiops": + resource_price = resource_zone.IORequestMaxIOPS + case "ipv4_address": + resource_price = resource_zone.IPv4Address + case "ipv6_address": + resource_price = resource_zone.IPv6Address + case "public_ipv4_bandwidth_in": + resource_price = resource_zone.PublicIPv4BandwidthIn + case "public_ipv4_bandwidth_out": + resource_price = resource_zone.PublicIPv4BandwidthOut + case "public_ipv6_bandwidth_in": + resource_price = resource_zone.PublicIPv6BandwidthIn + case "public_ipv6_bandwidth_out": + resource_price = resource_zone.PublicIPv6BandwidthOut + case "server_core": + resource_price = resource_zone.ServerCore + case "server_memory": + resource_price = resource_zone.ServerMemory + case "server_plan_1xCPU-1GB": + resource_price = resource_zone.ServerPlan1xCPU1GB + case "server_plan_2xCPU-2GB": + resource_price = resource_zone.ServerPlan2xCPU2GB + case "server_plan_2xCPU-4GB": + resource_price.Price = 2.9761 + case "server_plan_4xCPU-4GB": + resource_price = resource_zone.ServerPlan4xCPU4GB + case "server_plan_6xCPU-8GB": + resource_price = resource_zone.ServerPlan6xCPU8GB + case "storage_backup": + resource_price = resource_zone.StorageBackup + case "storage_maxiops": + resource_price = resource_zone.StorageMaxIOPS + case "storage_template": + resource_price = resource_zone.StorageTemplate + default: + //fmt.Fprintf(os.Stderr, "Resource price %s in %s NOT FOUND",target,zone) + return *resource_price, fmt.Errorf("Resource price %s in %s NOT FOUND",target,zone) + } + // fmt.Printf("Resource %s price in %s: %#v\n", target, zone, resource_price) + // fmt.Printf("Resource %s price in %s: %d %.4f\n", target, zone, resource_price.Amount, resource_price.Price) + } + // enc := json.NewEncoder(os.Stdout) + // enc.Encode(resource_price) + + return *resource_price, nil +} +*/ \ No newline at end of file diff --git a/run.go b/run.go new file mode 100644 index 0000000..e639246 --- /dev/null +++ b/run.go @@ -0,0 +1,447 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "os/exec" + "strings" + + "github.com/UpCloudLtd/upcloud-go-api/upcloud" + + "github.com/UpCloudLtd/upcloud-go-api/upcloud/client" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" +) + +func cmdexec(command string, args []string) (string, error) { + cmd := &exec.Cmd { + Path: command, + Args: args, + // Stdout: os.Stdout, + // Stderr: os.Stdout, + } + if out, err := cmd.Output(); err != nil { + fmt.Fprintf(os.Stderr, "%s error: %#v\n",command,err) + return "",err + } else { + return string(out), nil + } + // cmd.Start(); + // cmd.Wait() +} +func run() int { + runFlags, err := ParseFlags() + if err != nil { + log.Fatal(err) + } + username := "" + password := "" + if runFlags.encdr == TECODER.cmd || runFlags.encdr == TECODER.abbrv { + k := os.Getenv("KUPCLAPI") + if len(k) > 0 { + kdata := strings.Split(k," ") + out, err := exec.Command(TECODER.cmd,TECODER.decoder,kdata[0]).Output() + if err != nil { + fmt.Fprintf(os.Stderr, "Auth info: Username not found in ecoded: %#v\n ",err) + return 1 + } + username = strings.TrimSuffix(string(out),"\n") + out, err = exec.Command(TECODER.cmd,TECODER.decoder,kdata[1]).Output() + if err != nil { + fmt.Fprintf(os.Stderr, "Auth info: Password not found in ecoded: %#v\n",err) + return 1 + } + password = strings.TrimSuffix(string(out),"\n") + } + } else { + username = os.Getenv("UPCLOUD_USERNAME") + password = os.Getenv("UPCLOUD_PASSWORD") + } + + if len(username) == 0 { + fmt.Fprintln(os.Stderr, "Auth info: Username must be specified") + return 1 + } + + if len(password) == 0 { + fmt.Fprintln(os.Stderr, "Auth info: Password must be specified") + return 2 + } + + c := client.New(username, password) + s := service.New(c) + + switch runFlags.command { + case "infoserver": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"info",runFlags,datacfg) + case "inventory": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"inventory",runFlags,datacfg) + case "pubhosts": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"pubhosts",runFlags,datacfg) + case "prvhosts": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"prvhosts",runFlags,datacfg) + case "deleteserver": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"delete",runFlags,datacfg) + case "startserver": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"start",runFlags,datacfg) + case "restartserver": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"restart",runFlags,datacfg) + case "stopserver": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"stop",runFlags,datacfg) + case "createserver": + datacfg,err := loadDataConfig(runFlags) + if err != nil { + return 99 + } + sshkeys := loadLoginUserKeys(runFlags) + if err := createServer(s,datacfg,sshkeys); err != nil { + return 3 + } + case "modifyserver": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + if runFlags.target == "" { + fmt.Fprintln(os.Stderr, "Unable to run modifyserver command: ", runFlags.command) + return 99 + } + onServers(s,"modifyserver",runFlags,datacfg) + case "fixstorage": + datacfg,err := loadDataConfig(runFlags) + if err != nil { + return 99 + } + if runFlags.target == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"fixstorage",runFlags,datacfg) + case "modifystorage": + storecfg,err := loadStoreConfig(runFlags.dataCfgPath) + if err != nil { + return 99 + } + if err := modifyStorage(s,storecfg); err != nil { + return 3 + } + case "deletestorage": + hasID(runFlags.id) + if err := deleteStorage(s, runFlags.id); err != nil { + return 2 + } + case "liststorages": + datacfg,_ := loadDataConfig(runFlags) + if err := listStorages(s, datacfg, runFlags.target); err != nil { + return 2 + } + case "createstorageimage": + hasID(runFlags.id) + if runFlags.target == "" { + fmt.Fprintln(os.Stderr, "Unable to run createStorageImage no title: ", runFlags.target) + return 99 + } + if err := createStorageImage(s, runFlags.id,runFlags.target); err != nil { + return 2 + } + case "runssh": + if len(runFlags.runCmd) == 0 { + fmt.Fprintln(os.Stderr, "Unable to run ssh command: ", runFlags.command) + return 99 + } + datacfg,err := loadDataConfig(runFlags) + if err != nil { + fmt.Fprintln(os.Stderr, "Unable to run ssh command: ", runFlags.command) + return 99 + } + onServers(s,"runssh",runFlags,datacfg) + case "runcmd": + if len(runFlags.runCmd) == 0 { + fmt.Fprintln(os.Stderr, "Unable to run command: ", runFlags.command) + return 99 + } + datacfg,err := loadDataConfig(runFlags) + if err != nil { + fmt.Fprintln(os.Stderr, "Unable to run command: ", runFlags.command) + return 99 + } + onServers(s,"runcmd",runFlags,datacfg) + case "infofloatip": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + ipinfo, err := getFloatIP(s,runFlags.id,datacfg) + if err != nil { + fmt.Fprintf(os.Stderr, "error getting info %s: %#v\n", datacfg.FloatIP, err) + return 99 + } + if runFlags.out == "attached" { + ipData := strings.Split(ipinfo.PTRRecord,".") + ip := strings.Join(ipData[0:4],".") + fmt.Fprintf(os.Stdout, "%s\n",ip) + } else { + enc := json.NewEncoder(os.Stdout) + enc.Encode(ipinfo) + } + // fmt.Fprintf(os.Stderr, "%s Located in %s %s Server: %s) \n", datacfg.FloatIP, ipinfo.PTRRecord, ipinfo.MAC, ipinfo.ServerUUID) + case "movefloatip": + datacfg := &DataConfig{} + id := runFlags.id + var data []string + if id == "" { + id = os.Getenv("UPCLOUD_MOVEFLOATIP") + } + if id == "" { + datacfg,err = loadDataConfig(runFlags) + if datacfg.FloatIP == "" { + fmt.Fprintln(os.Stderr, "Float IP not found in: ", runFlags.dataCfgPath) + return 99 + } + } else { + data = strings.Split(id, SEPARATOR) + } + moveFloatIP(s,data,datacfg) + if err != nil { + fmt.Fprintln(os.Stderr, "Unable to move Float IP: ", datacfg.FloatIP) + return 99 + } + case "modifyip": + datacfg,err := loadDataConfig(runFlags) + if err != nil { + return 99 + } + onServers(s,"modifyip",runFlags,datacfg) + case "infotags": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"infotags",runFlags,datacfg) + case "addtags": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"addtags",runFlags,datacfg) + case "deletetags": + datacfg := &DataConfig{} + if runFlags.id == "" { + datacfg,err = loadDataConfig(runFlags) + } + onServers(s,"deletetags",runFlags,datacfg) + default: + fmt.Fprintln(os.Stderr, "Unknown command: ", runFlags.command) + return 99 + } + return 0 +} + +func onServers(s *service.Service, tsksrvc string, runFlags RunFlags, datacfg *DataConfig) error { + uuid := runFlags.id + runComand := runFlags.runCmd + servers, err := s.GetServers() + if err != nil || len(servers.Servers) == 0 { + fmt.Fprintf(os.Stderr, "Unable to get servers: %#v\n", err) + return err + } + inventory := map[string][]string{} + sshAccess := SSHAccess{} + var target ServerConfig + for _, server := range servers.Servers { + target_srvr := "" + role_srvr := "" + if uuid != "" { + if server.UUID == uuid || server.Hostname == uuid { + target_srvr = uuid + for _,srv := range datacfg.Servers { + if srv.Hostname == server.Hostname { + target = srv + role_srvr = srv.Cluster.Role + sshAccess = srv.SSHAccess + break; + } + } + } + } else { + if datacfg.HostPrefix != "" && !strings.Contains(server.Hostname,datacfg.HostPrefix) { + continue + } + for _,srv := range datacfg.Servers { + if srv.Hostname == server.Hostname || srv.UUID == server.UUID { + target_srvr = server.UUID + target = srv + role_srvr = srv.Cluster.Role + sshAccess = srv.SSHAccess + break; + } + } + } + if target_srvr == "" { + continue + } + switch tsksrvc { + case "delete": + err := deleteOneServer(s, server, runFlags.target) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to delete server %#v: %#v\n", server.UUID, err) + return err + } + case "stop": + err := stopOneServer(s, server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to stop server %#v: %#v\n", server.UUID, err) + } + case "start": + err := startOneServer(s, server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to start server %#v: %#v\n", server.UUID, err) + } + case "restart": + err := restarOneServer(s, server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to restart server %#v: %#v\n", server.UUID, err) + } + case "info": + dataPrices,err_prices := getDataPrices() + if err_prices != nil { + fmt.Fprintf(os.Stderr, "Unable to load prices for server %#v: %#v\n", server.UUID, err_prices) + } + err := detailsOneServer(s, server, *dataPrices) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get details for server %#v: %#v\n", server.UUID, err) + } + case "inventory": + info, err := inventoryOneServer(s, datacfg.FloatIP, server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to make inventory from server %#v: %#v\n", server.UUID, err) + } else { + inventory[role_srvr] = append(inventory[role_srvr],info) + } + case "pubhosts": + info, err := hostsOneServer(s,"pub", datacfg.FloatIP, server, target) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to make hosts list from server %#v: %#v\n", server.UUID, err) + } else { + fmt.Printf("%s",info) + } + case "prvhosts": + info, err := hostsOneServer(s,"prv", datacfg.FloatIP, server, target) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to make hosts list from server %#v: %#v\n", server.UUID, err) + } else { + fmt.Printf("%s",info) + } + case "modifyip": + nettype := upcloud.NetworkTypePrivate + err := modifyIPOneServer(s, server, nettype, target) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to modify IP server %#v: %#v\n", server.UUID, err) + } + case "infotags": + tags, err := infoTagsFromId(s, server.UUID) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get tags from server %#v: %#v\n", server.UUID, err) + } else { + enc := json.NewEncoder(os.Stdout) + enc.Encode(tags) + } + case "addtags": + if uuid != "" { + fmt.Fprintf(os.Stderr, "No tags found, for server %#v: %#v\n", server.UUID, err) + } else { + err := addTagsToId(s, server.UUID, target.Tags) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to add tags to server %#v: %#v\n", server.UUID, err) + } + } + case "deletetags": + if uuid != "" { + fmt.Fprintf(os.Stderr, "No tags found, for server %#v: %#v\n", server.UUID, err) + } else { + err := deleteTagsFromId(s, server.UUID, target.Tags) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to delete tags from server %#v: %#v\n", server.UUID, err) + } + } + case "fixstorage": + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: server.UUID, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get details from server %#v: %#v\n", server.UUID, err) + } else { + fixStorageDevices(s, target, info, runFlags.target) + } + case "modifyserver": + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: server.UUID, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get details from server %#v: %#v\n", server.UUID, err) + } else { + modifyServer(s, target, info, runFlags.target) + } + case "runssh": + if sshAccess.Host != "" { + output, err := runSSH(sshAccess, runComand) + if err != nil { + log.Fatal(err) + } + fmt.Println(string(output)) + } else { + fmt.Fprintf(os.Stderr, "No Host to run ssh on %s \n", server.UUID) + } + case "runcmd": + args := strings.Split(runComand, " ") + output, err := cmdexec(args[0],args[1:]) + if err != nil { + log.Fatal(err) + fmt.Fprintf(os.Stderr, "No Host to run on %s \n", server.UUID) + } + fmt.Println(string(output)) + } + } + if tsksrvc == "inventory" { + for key,items := range inventory { + fmt.Printf("[%s]\n",key) + for _,item := range items { + fmt.Printf("%s\n",item) + } + } + } + return nil +} diff --git a/servers.go b/servers.go new file mode 100644 index 0000000..05e46bb --- /dev/null +++ b/servers.go @@ -0,0 +1,306 @@ +package main + +import ( + "errors" + "fmt" + + // "log" + "os" + "time" + + // "strings" + "encoding/json" + + "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + // "gocloud.dev/server" + // "gocloud.dev/server" + // "github.com/davecgh/go-spew/spew" +) + +func createServer(s *service.Service, datacfg *DataConfig,sshkeys request.SSHKeySlice) error { + fmt.Println("Creating servers ...") + //fmt.Fprintf(os.Stdout, "%#v\n",sshkeys) + //fmt.Printf("%+v\n",datacfg) // Print with Variable Name + // fmt.Printf("Created server: %#v\n", details) + servers, _ := s.GetServers() + for _,srv := range datacfg.Servers { + alreadyExists := false + for _, server := range servers.Servers { + if server.Hostname == srv.Hostname { + alreadyExists = true + break + } + } + // Networking: setNetworkInterfaces(datacfg), + title := fmt.Sprintf("%s %s", srv.Hostname, srv.Title) + if alreadyExists { + fmt.Fprintf(os.Stderr, "Already exists server: %s\n", title) + continue + } + fmt.Printf("server: %s\n", title) + var srv_sshkeys request.SSHKeySlice + if len(srv_sshkeys) == 0 && len(sshkeys) > 0 { + srv_sshkeys = sshkeys + } else { + srv_sshkeys = srv.LoginUser.SSHKeys + } + details, err := s.CreateServer(&request.CreateServerRequest{ + Hostname: srv.Hostname, + Title: title, // fmt.Sprintf("example-cli-server-%04d", rand.Int31n(1000)), + Zone: srv.Zone, + Plan: srv.Plan, + Metadata: srv.Metadata, + TimeZone: srv.TimeZone, + StorageDevices: getStorageDevices(srv), + Networking: &request.CreateServerNetworking{ + Interfaces: getNetworkInterfaces(srv), + }, + LoginUser: &request.LoginUser{ + SSHKeys: srv_sshkeys, + }, + UserData: srv.UserData, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to create server: %#v\n", err) + return err + } + // spew.Println(details) + if len(details.UUID) == 0 { + fmt.Fprintf(os.Stderr, "UUID missing") + return errors.New("UUID too short") + } + details, err = s.WaitForServerState(&request.WaitForServerStateRequest{ + UUID: details.UUID, + DesiredState: upcloud.ServerStateStarted, + Timeout: 1 * time.Minute, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to wait for server: %#v", err) + return err + } + fmt.Printf("Created server: %#v\n", details) + fixStorageDevices(s,srv,details,"size") + addTagsToId(s,details.UUID,srv.Tags) + } + return nil +} +func modifyServer(s *service.Service,srvrCfg ServerConfig, serverDetails *upcloud.ServerDetails, targetPlan string) error { + title := fmt.Sprintf("%s %s", serverDetails.Hostname, serverDetails.Title) + fmt.Printf("Modify server %s ...\n",title) + if serverDetails.Plan == targetPlan { + fmt.Printf("%s == %s\n",serverDetails.Plan, targetPlan) + return nil + } + if serverDetails.State != upcloud.ServerStateStopped { + err := stopOneServer(s, serverDetails.Server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to stop server %#v: %#v\n", serverDetails.UUID, err) + return err + } + } + details,err := s.WaitForServerState(&request.WaitForServerStateRequest{ + UUID: serverDetails.UUID, + DesiredState: upcloud.ServerStateStopped, + Timeout: 1 * time.Minute, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to wait for server: %#v", err) + return err + } + fmt.Fprintf(os.Stderr, "Modify server after to: %s\n", targetPlan) + details, err = s.ModifyServer(&request.ModifyServerRequest{ + UUID: serverDetails.UUID, + Plan: targetPlan, + }) + fmt.Printf("Modify server done: %#v %#v\n", details, err) + // if err != nil { + // } else { + // fmt.Fprintf(os.Stderr, "Failed to modify server: %#v", err) + // // return err + // } + fmt.Fprintf(os.Stderr, "Start server after modify: %s\n", title) + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: serverDetails.UUID, + }) + if info.State != upcloud.ServerStateStarted { + err = startOneServer(s, info.Server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to start server %#v: %#v\n", serverDetails.UUID, err) + return err + } + } + return nil +} + +func waitForState(s *service.Service, server upcloud.Server, desiredState string) error { + _, err := s.WaitForServerState(&request.WaitForServerStateRequest{ + UUID: server.UUID, + DesiredState: desiredState, + Timeout: 1 * time.Minute, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to wait for server to reach desired state: %#v", err) + return err + } + return nil +} +func startOneServer(s *service.Service, server upcloud.Server) error { + fmt.Printf("Starting %s (%s) ... \n", server.Hostname, server.UUID) + if server.State == upcloud.ServerStateStopped { + fmt.Printf("Server %s (%s) is stopped. Starting\n", server.Title, server.UUID) + } + _, err := s.StartServer(&request.StartServerRequest{ + UUID: server.UUID, + // StopType: request.ServerStopTypeHard, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to start server: %#v\n", err) + return err + } + err = waitForState(s,server,upcloud.ServerStateStarted) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to start state server: %#v\n", err) + return err + } + fmt.Printf("Successfully started %s (%s)\n", server.Title, server.UUID) + return nil +} +func restarOneServer(s *service.Service, server upcloud.Server) error { + fmt.Printf("Restarting %s (%s) ... \n", server.Hostname, server.UUID) + _, err := s.RestartServer(&request.RestartServerRequest{ + UUID: server.UUID, + StopType: request.ServerStopTypeHard, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to restart server: %#v\n", err) + return err + } + err = waitForState(s,server,upcloud.ServerStateStarted) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to restart state server: %#v\n", err) + return err + } + fmt.Printf("Successfully restarted %s (%s)\n", server.Title, server.UUID) + return nil +} +func stopOneServer(s *service.Service, server upcloud.Server) error { + fmt.Printf("Stopping %s (%s) ... \n", server.Hostname, server.UUID) + if server.State != upcloud.ServerStateStopped { + // fmt.Printf("Server %s (%s) is not stopped. Stopping\n", server.Title, server.UUID) + _, err := s.StopServer(&request.StopServerRequest{ + UUID: server.UUID, + StopType: request.ServerStopTypeHard, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to stop server: %#v\n", err) + return err + } + err = waitForState(s,server,upcloud.ServerStateStopped) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to stop state server: %#v\n", err) + return err + } + fmt.Printf("Successfully stopped %s (%s)\n", server.Title, server.UUID) + } + return nil +} +func deleteOneServer(s *service.Service, server upcloud.Server, keepStorage string) error { + err := stopOneServer(s,server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to stop server: %#v\n", err) + return err + } + if keepStorage != "keep" { + fmt.Printf("Deleting %s (%s) with storage\n", server.Title, server.UUID) + err = s.DeleteServerAndStorages(&request.DeleteServerAndStoragesRequest{ + UUID: server.UUID, + }) + } else { + fmt.Printf("Deleting %s (%s) keep storage\n", server.Title, server.UUID) + err = s.DeleteServer(&request.DeleteServerRequest{ + UUID: server.UUID, + }) + } + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to delete server: %#v\n", err) + return err + } + fmt.Printf("Successfully deleted %s (%s)\n", server.Title, server.UUID) + return nil +} + +func detailsOneServer(s *service.Service, server upcloud.Server, dataprices DataPrices) error { + + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: server.UUID, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get details from server %#v: %#v\n", server.UUID, err) + return err + } + // price,err_price:= priceResource(s,server.Zone,fmt.Sprintf("server_plan_%s",server.Plan)) + price,err_price:= dataPriceResource(s,dataprices,server.Zone,fmt.Sprintf("server_plan_%s",server.Plan)) + if err_price != nil { + fmt.Fprintf(os.Stderr, "Unable to get price info from server %#v: %#v\n", server.UUID, err_price) + return err + } + info_server := &InfoServer{info: *info, price: *price} + // c, err := json.Marshal(info_server) + // if err != nil { + // fmt.Println("error:", err) + // } + // os.Stdout.Write(c) + + + enc := json.NewEncoder(os.Stdout) + enc.Encode(info_server) +// enc.Encode(price) + + /* + i,_ := json.Marshal(info) + p,_ := json.Marshal(price) + fmt.Printf("info %s \n", i) + fmt.Printf("price %s \n", p) + jsonData := []byte(`{"pric": "Value"}`) + // info_server := &v{info: fmt.Sprintf("%s",i) , price: fmt.Sprintf("%s",p)} + info_server := InfoServerPrice{info: "HOLA", price: "$$$" } + // v := struct { + // info upcloud.ServerDetails `json:"upcloud.ServerDetails"` + // price upcloud.Price `json:"upcloud.Price"` + // }{} + fmt.Printf("InfoServer %#v\n", info_server) + enc := json.NewEncoder(os.Stdout) + enc.Encode(info_server) + // b,err_un := json.Marshal(info_server) // json.Marshal(info_server) + // if err_un != nil { + // fmt.Fprintf(os.Stderr, "Unable to marshall: %#v\n", err_un) + // return err_un + // } + // os.Stdout.Write(b) + type ColorGroup struct { + ID int + Name string + Colors []string + } + group := ColorGroup{ + ID: 1, + Name: "Reds", + Colors: []string{"Crimson", "Red", "Ruby", "Maroon"}, + } + c, err := json.Marshal(group) + if err != nil { + fmt.Println("error:", err) + } + os.Stdout.Write(c) + // err_en := enc.Encode(info_server) + // if err_en != nil { + // fmt.Fprintf(os.Stderr, "Unable to encode info_server: %#v\n", err_en) + // return err_en + // } + // fmt.Printf("InfoServer %#v\n", info) + +*/ + return nil +} \ No newline at end of file diff --git a/ssh.go b/ssh.go new file mode 100644 index 0000000..0377659 --- /dev/null +++ b/ssh.go @@ -0,0 +1,110 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "strings" + "time" + + "golang.org/x/crypto/ssh" + // "github.com/mitchellh/go-homedir" + // "github.com/davecgh/go-spew/spew" +) + +func publicKeyAuthFunc(kPath string) ssh.AuthMethod { + // keyPath, err := homedir.Expand(kPath) + // if err != nil { + // log.Fatal("find key's home dir failed", err) + // } + // key, err := ioutil.ReadFile(keyPath ) + key, err := ioutil.ReadFile(kPath ) + if err != nil { + log.Fatal("ssh key file read failed", err) + } + // Create the Signer for this private key. + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + log.Fatal("ssh key signer failed", err) + } + return ssh.PublicKeys(signer) +} +func runSSH(cfg SSHAccess, cmds ...string ) ([]byte, error) { + // fmt.Fprintf(os.Stderr, "SSH: %#v\n", cfg) + fmt.Fprintf(os.Stderr, "%s - running : %s\n\n", cfg.Host, cmds) + + // Create SSHP login configuration + config := &ssh.ClientConfig{ + Timeout: time.Second, //ssh connection time out time is one second, if SSH validation error returns in one second + User: cfg.User, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), // This is OK, but not safe enough. + // HostKeyCallback: hostKeyCallBackFunc(h.Host), + } + if cfg.UType == "password" { + config.Auth = []ssh.AuthMethod{ssh.Password(cfg.Password)} + } else { + config.Auth = []ssh.AuthMethod{publicKeyAuthFunc(cfg.KeyPath)} + } + addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) + conn, err := ssh.Dial("tcp", addr,config) + if err != nil { + log.Fatal("Failed to dial SSH client", err) + return []byte{}, err + } + session, err := conn.NewSession() + if err != nil { + log.Fatal(err) + } + defer session.Close() + modes := ssh.TerminalModes{ + ssh.ECHO: 0, // disable echoing + ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud + ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud + } + err = session.RequestPty("xterm", 80, 40, modes) + if err != nil { + return []byte{}, err + } + in, err := session.StdinPipe() + if err != nil { + log.Fatal(err) + } + out, err := session.StdoutPipe() + if err != nil { + log.Fatal(err) + } + var output []byte + go func(in io.WriteCloser, out io.Reader, output *[]byte) { + var ( + line string + r = bufio.NewReader(out) + ) + for { + b, err := r.ReadByte() + if err != nil { + break + } + *output = append(*output, b) + if b == byte('\n') { + line = "" + continue + } + line += string(b) + if strings.HasPrefix(line, "[sudo] password for ") && strings.HasSuffix(line, ": ") { + _, err = in.Write([]byte(cfg.Password+ "\n")) + if err != nil { + break + } + } + } + }(in, out, &output) + cmd := strings.Join(cmds, "; ") + _, err = session.Output(cmd) + if err != nil { + return []byte{}, err + } + return output, nil +} \ No newline at end of file diff --git a/storage.go b/storage.go new file mode 100644 index 0000000..0f12625 --- /dev/null +++ b/storage.go @@ -0,0 +1,281 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "os" + "strconv" + "strings" + "time" + + "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "gopkg.in/yaml.v3" +) + +func NewStoreConfig(dataPath string) (*StoreConfig, error) { + // Create config structure + storeConfig := &StoreConfig{} + + // Open config file + file, err := os.Open(dataPath) + 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(&storeConfig); err != nil { + return nil, err + } + + return storeConfig, nil +} + +func loadStoreConfig(dataCfgPath string) (*StoreConfig, error) { + // datacfg := &DataConfig{} + storecfg, err := NewStoreConfig(dataCfgPath) + if err != nil { + log.Fatal(err) + return storecfg, err + } + return storecfg, nil +} + +func getStorageDevices(srv ServerConfig) []request.CreateServerStorageDevice { + var storageDevices []request.CreateServerStorageDevice + for _,storage := range srv.StorageDevices { + storageDevices = append(storageDevices,request.CreateServerStorageDevice{ + Action : storage.Action, + Title : storage.Title, + Storage : storage.Storage, + Size : storage.Size, + Tier : storage.Tier, + }) + } + return storageDevices +} +func deleteStorage(s *service.Service, uuid string) error { + fmt.Println("Getting storage") + storages, err := s.GetStorages(&request.GetStoragesRequest{ + Access: upcloud.StorageAccessPrivate, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get storages: %#v\n", err) + return err + } + fmt.Printf("Retrieved %d storages\n", len(storages.Storages)) + if len(storages.Storages) > 0 { + for _, storage := range storages.Storages { + if storage.UUID == uuid { + fmt.Printf("Deleting storage %s", storage.UUID) + err := errors.New("Dummy") + for i := 0; err != nil && i < 5; i++ { + fmt.Printf("%d: Deleting %s (%s)\n", i, storage.Title, storage.UUID) + err = s.DeleteStorage(&request.DeleteStorageRequest{ + UUID: storage.UUID, + }) + } + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to delete storage: %#v (%s)\n", err, err.Error()) + return err + } + + fmt.Printf("Successfully deleted %s (%s)\n", storage.Title, storage.UUID) + } + } + } + return nil +} +func modifyStorage(s *service.Service, storecfg *StoreConfig) error { + if len(storecfg.Uuid) == 0 { + fmt.Printf("Unable to get storages uuid from data file \n") + return nil + } + for _, item := range storecfg.Servers { + s.StopServer(&request.StopServerRequest{ + UUID: item, + StopType: request.ServerStopTypeHard, + }) + } + fmt.Println("Getting storage") + storages, err := s.GetStorages(&request.GetStoragesRequest{ + Access: upcloud.StorageAccessPrivate, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get storages: %#v\n", err) + return err + } + fmt.Printf("Retrieved %d storages\n", len(storages.Storages)) + + if len(storages.Storages) > 0 { + for _, storage := range storages.Storages { + if storage.UUID == storecfg.Uuid { + fmt.Printf("Modify storage %s (%s)\n",storage.Title, storage.UUID) + err := errors.New("Dummy") + for i := 0; err != nil && i < 5; i++ { + fmt.Printf("%d: Modify %s (%s)\n", i, storage.Title, storage.UUID) + _, err = s.ModifyStorage(&request.ModifyStorageRequest{ + UUID: storecfg.Uuid, + // Title: datacfg.Title, + Size: storecfg.Size, + // BackupRule *upcloud.BackupRule `json:"backup_rule,omitempty"` + }) + } + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to modify storage: %#v (%s)\n", err, err.Error()) + // return err + } + fmt.Printf("Successfully modified %s (%s)\n", storage.Title, storage.UUID) + } + } + } + for _, item := range storecfg.Servers { + s.StartServer(&request.StartServerRequest{ + UUID: item, + // StopType: request.ServerStopTypeHard, + }) + } + return nil +} + +func fixStorageDevices(s *service.Service,srvrCfg ServerConfig, serverDetails *upcloud.ServerDetails, targetSize string) { + if len(serverDetails.StorageDevices) == 0 { + return + } + err := errors.New("Dummy") +// fmt.Fprintf(os.Stderr, "%#v: %#v\n", serverDetails.StorageDevices, srvrCfg) + for i, storage := range serverDetails.StorageDevices { + store_size := 0 + switch targetSize { + case "size": + store_size = srvrCfg.StorageDevices[i].Size + case "part0": + s := strings.Split(srvrCfg.StorageDevices[i].PartSizes, ",") + store_size, err = strconv.Atoi(s[0]) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get partsize 0 to create image storage for %s: %#v (%s)\n", storage.Title, err, err.Error()) + return + } + case "part1": + s := strings.Split(srvrCfg.StorageDevices[i].PartSizes, ",") + store_size, err = strconv.Atoi(s[1]) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get partsize 1 to create image storage for %s: %#v (%s)\n", storage.Title, err, err.Error()) + return + } + case "final": + store_size = srvrCfg.StorageDevices[i].FinalSize + } + if store_size == 0 { + fmt.Printf("No targetSize found [size|final] for %s (%s) %dGB == %dGB\n",storage.Title, storage.UUID, storage.Size, store_size) + continue + } + if store_size== storage.Size { + fmt.Printf("%s (%s) %dGB == %dGB\n",storage.Title, storage.UUID, storage.Size, store_size) + continue + } + fmt.Printf("FixStorage %s (%s) %dGB to %dGB\n",storage.Title, storage.UUID, storage.Size, store_size) + if serverDetails.Server.State != upcloud.ServerStateStopped { + err = stopOneServer(s, serverDetails.Server) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to stop server %#v: %#v\n", serverDetails.Server.UUID, err) + continue + } + } + _, err = s.ModifyStorage(&request.ModifyStorageRequest{ + UUID: storage.UUID, + // Title: srvrCfg.StorageDevices[i].Title, + Size: store_size, + // BackupRule *upcloud.BackupRule `json:"backup_rule,omitempty"` + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to modify storage: %#v (%s)\n", err, err.Error()) + } else { + fmt.Printf("Successfully modified %s (%s) to %dGB\n", storage.Title, storage.UUID, store_size) + } + } + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: serverDetails.UUID, + }) + if info.Server.State != upcloud.ServerStateStarted { + startOneServer(s, serverDetails.Server) + } +} + +func createStorageImage(s *service.Service, target_uuid string, title string) error { + info,err := s.TemplatizeStorage(&request.TemplatizeStorageRequest{ + UUID: target_uuid, + Title: strings.Trim(title, "\""), + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to create image storage: %#v (%s)\n", err, err.Error()) + } else { + fmt.Printf("Successfully created %s (%s)\n", title, target_uuid) + } + s.WaitForStorageState(&request.WaitForStorageStateRequest{ + UUID: target_uuid, + DesiredState: "online", + Timeout: 1 * time.Minute, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to wait for storage to reach desired online state: %#v", err) + return err + } + enc := json.NewEncoder(os.Stdout) + enc.Encode(info) + return nil +} +func listStorages(s *service.Service, dataCfg *DataConfig, target string) error { + storages, err := s.GetStorages(&request.GetStoragesRequest{ + Access: upcloud.StorageAccessPrivate, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get storages: %#v\n", err) + return err + } + servers, _ := s.GetServers() + fmt.Printf("Scanning over %d storages and %d servers \n", len(storages.Storages), len(servers.Servers)) + if len(storages.Storages) > 0 { + for _, storage := range storages.Storages { + storage_out := fmt.Sprintf("title: \"%s\", id: %s, size: %d, plan: %s, state: %s, zone: %s, type: %s", + storage.Title, storage.UUID, storage.Size, storage.PartOfPlan, storage.State, storage.Zone, storage.Type) + is_attached := false + for _, server := range servers.Servers { + isInGroup := false + for _, srv := range dataCfg.Servers { + if server.Hostname == srv.Hostname { + isInGroup = true + break + } + } + info,err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: server.UUID, + }) + if err == nil { + for _, srv_storage := range info.StorageDevices { + if srv_storage.UUID == storage.UUID { + is_attached=true + if isInGroup { + fmt.Printf("%s, attached: \"%s\", id: %s\n", storage_out, server.Title, server.UUID) + } + } + } + } + } + if ! is_attached { + fmt.Printf("%s, attached: none\n", storage_out) + } + if target != "" && storage.UUID == target { + break + } + } + } + return nil +} \ No newline at end of file diff --git a/tags.go b/tags.go new file mode 100644 index 0000000..84e51db --- /dev/null +++ b/tags.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "os" + + "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" +) + +func infoTagsFromId(s *service.Service, id string) (upcloud.TagServerSlice,error) { + var upTags upcloud.TagServerSlice + if id == "" { + fmt.Fprintf(os.Stderr, "Resouce not found to get tags\n") + return upTags,nil + } + info, err := s.GetServerDetails(&request.GetServerDetailsRequest{ + UUID: id, + }) + for _, tag := range info.Tags { + upTags = append(upTags, tag) + } + return upTags, err +} +func addTagsToId(s *service.Service, id string, tags []string) error { + if id == "" || len(tags) == 0 { + fmt.Fprintf(os.Stderr, "Resouce not found to add tags to (%s)\n",id) + return nil + } + var upTags upcloud.TagServerSlice + for i := 0; i < len(tags); i++ { + upTags = append(upTags,tags[i]) + } + _, err := s.TagServer(&request.TagServerRequest{ + UUID: id, + Tags: upTags, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to tag server %s: %#v", id, err) + return err + } + return nil +} +func deleteTagsFromId(s *service.Service, id string, tags []string) error { + if id == "" || len(tags) == 0 { + fmt.Fprintf(os.Stderr, "Resouce not found to get tags (%s)\n",id) + return nil + } + _, err := s.UntagServer(&request.UntagServerRequest{ + UUID: id, + Tags: tags, + }) + // fmt.Printf("Delete Tag form server: %#v\n", msg) + return err +} \ No newline at end of file