From 0a2d1bd711c860d328306d4366261640e9c46d7c Mon Sep 17 00:00:00 2001 From: JesusPerez Date: Sat, 28 Aug 2021 14:45:05 +0100 Subject: [PATCH] chore: adding code --- LICENSE | 21 ++ Makefile | 33 +++ bin/get_hosts_pub_ips.sh | 4 + bin/make_kenv.sh | 23 ++ bin/on_cloud.sh | 22 ++ bin/update_storage.sh | 54 ++++ floatip.go | 126 +++++++++ inventory.go | 131 ++++++++++ main.go | 552 +++++++++++++++++++++++++++++++++++++++ networks.go | 158 +++++++++++ prices.go | 279 ++++++++++++++++++++ run.go | 447 +++++++++++++++++++++++++++++++ servers.go | 306 ++++++++++++++++++++++ ssh.go | 110 ++++++++ storage.go | 281 ++++++++++++++++++++ tags.go | 56 ++++ 16 files changed, 2603 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100755 bin/get_hosts_pub_ips.sh create mode 100755 bin/make_kenv.sh create mode 100755 bin/on_cloud.sh create mode 100755 bin/update_storage.sh create mode 100644 floatip.go create mode 100644 inventory.go create mode 100644 main.go create mode 100644 networks.go create mode 100644 prices.go create mode 100644 run.go create mode 100644 servers.go create mode 100644 ssh.go create mode 100644 storage.go create mode 100644 tags.go 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