...
This commit is contained in:
@@ -1,40 +1,55 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.virt.hetzner
|
||||
import freeflowuniverse.herolib.virt.hetznermanager
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.builder
|
||||
import time
|
||||
import os
|
||||
|
||||
console.print_header('Hetzner login.')
|
||||
import freeflowuniverse.herolib.core.playcmds
|
||||
|
||||
// USE IF YOU WANT TO CONFIGURE THE HETZNER, ONLY DO THIS ONCE
|
||||
// hetzner.configure("test")!
|
||||
|
||||
mut cl := hetzner.get('test')!
|
||||
passwd:=os.environ()['HETZNER_PASSWORD'] or {
|
||||
println('HETZNER_PASSWORD not set')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
playcmds.run(
|
||||
heroscript: '
|
||||
!!hetznermanager.configure
|
||||
name:"main"
|
||||
user:"operations@threefold.io"
|
||||
whitelist:"2111181, 2392178"
|
||||
password:"${passwd}"
|
||||
'
|
||||
)!
|
||||
|
||||
console.print_header('Hetzner Test.')
|
||||
|
||||
mut cl := hetznermanager.get(name:'main')!
|
||||
|
||||
for i in 0 .. 5 {
|
||||
println('test cache, first time slow then fast')
|
||||
cl.servers_list()!
|
||||
}
|
||||
|
||||
println(cl.servers_list()!)
|
||||
// println(cl.servers_list()!)
|
||||
|
||||
mut serverinfo := cl.server_info_get(name: 'kristof2')!
|
||||
// mut serverinfo := cl.server_info_get(name: 'kristof2')!
|
||||
|
||||
println(serverinfo)
|
||||
// println(serverinfo)
|
||||
|
||||
// cl.server_reset(name:"kristof2",wait:true)!
|
||||
// // cl.server_reset(name:"kristof2",wait:true)!
|
||||
|
||||
// cl.server_rescue(name:"kristof2",wait:true)!
|
||||
// // cl.server_rescue(name:"kristof2",wait:true)!
|
||||
|
||||
console.print_header('SSH login')
|
||||
mut b := builder.new()!
|
||||
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
|
||||
// console.print_header('SSH login')
|
||||
// mut b := builder.new()!
|
||||
// mut n := b.node_new(ipaddr: serverinfo.server_ip)!
|
||||
|
||||
// n.hero_install()!
|
||||
// n.hero_compile_debug()!
|
||||
// // n.hero_install()!
|
||||
// // n.hero_compile_debug()!
|
||||
|
||||
// mut ks:=cl.keys_get()!
|
||||
// println(ks)
|
||||
// // mut ks:=cl.keys_get()!
|
||||
// // println(ks)
|
||||
|
||||
@@ -21,15 +21,7 @@ pub mut:
|
||||
|
||||
fn (mut executor ExecutorSSH) init() ! {
|
||||
if !executor.initialized {
|
||||
// if executor.ipaddr.port == 0 {
|
||||
// return error('port cannot be 0.\n${executor}')
|
||||
// }
|
||||
// TODO: need to call code from SSHAGENT do not reimplement here, not nicely done
|
||||
os.execute('pgrep -x ssh-agent || eval `ssh-agent -s`')
|
||||
|
||||
if executor.sshkey != '' {
|
||||
osal.exec(cmd: 'ssh-add ${executor.sshkey}')!
|
||||
}
|
||||
$dbg;
|
||||
mut addr := executor.ipaddr.addr
|
||||
if addr == '' {
|
||||
addr = 'localhost'
|
||||
|
||||
@@ -153,10 +153,11 @@ pub fn play(mut plbook PlayBook) ! {
|
||||
mut install_actions := plbook.find(filter: '${args.name}.configure')!
|
||||
if install_actions.len > 0 {
|
||||
@if args.hasconfig
|
||||
for install_action in install_actions {
|
||||
for mut install_action in install_actions {
|
||||
heroscript := install_action.heroscript()
|
||||
mut obj2 := heroscript_loads(heroscript)!
|
||||
set(obj2)!
|
||||
install_action.done = true
|
||||
}
|
||||
@else
|
||||
return error("can't configure ${args.name}, because no configuration allowed for this installer.")
|
||||
@@ -164,7 +165,7 @@ pub fn play(mut plbook PlayBook) ! {
|
||||
}
|
||||
@if args.cat == .installer
|
||||
mut other_actions := plbook.find(filter: '${args.name}.')!
|
||||
for other_action in other_actions {
|
||||
for mut other_action in other_actions {
|
||||
if other_action.name in ["destroy","install","build"]{
|
||||
mut p := other_action.params
|
||||
reset:=p.get_default_false("reset")
|
||||
@@ -198,6 +199,7 @@ pub fn play(mut plbook PlayBook) ! {
|
||||
}
|
||||
}
|
||||
@end
|
||||
other_action.done = true
|
||||
}
|
||||
@end
|
||||
}
|
||||
|
||||
@@ -71,11 +71,6 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result {
|
||||
}
|
||||
url := h.url(req)
|
||||
|
||||
// println("----")
|
||||
// println(url)
|
||||
// println(req.data)
|
||||
// println("----")
|
||||
|
||||
mut new_req := http.new_request(req.method, url, req.data)
|
||||
// joining the header from the HTTPConnection with the one from Request
|
||||
new_req.header = h.header()
|
||||
@@ -97,6 +92,9 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result {
|
||||
}
|
||||
|
||||
if req.debug {
|
||||
console.print_debug("----")
|
||||
console.print_debug(" ${url} ${req.method} url:${url}\n${req.data} \n${new_req.header}")
|
||||
console.print_debug("----")
|
||||
console.print_debug('http request:\n${new_req.str()}')
|
||||
}
|
||||
for _ in 0 .. h.retry {
|
||||
@@ -126,6 +124,8 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result {
|
||||
h.cache_invalidate(req)!
|
||||
}
|
||||
|
||||
$dbg;
|
||||
|
||||
// 5 - Return result
|
||||
return result
|
||||
}
|
||||
@@ -189,6 +189,9 @@ pub fn (mut h HTTPConnection) get(req_ Request) !string {
|
||||
req.debug
|
||||
req.method = .get
|
||||
result := h.send(req)!
|
||||
if !result.is_ok() {
|
||||
return error('Could not get ${req}\result:\n${result}')
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
@@ -197,6 +200,9 @@ pub fn (mut h HTTPConnection) delete(req_ Request) !string {
|
||||
mut req := req_
|
||||
req.method = .delete
|
||||
result := h.send(req)!
|
||||
if !result.is_ok() {
|
||||
return error('Could not delete ${req}\result:\n${result}')
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
@@ -207,5 +213,6 @@ pub fn (mut h HTTPConnection) post_multi_part(req Request, form http.PostMultipa
|
||||
header.set(http.CommonHeader.content_type, 'multipart/form-data')
|
||||
req_form.header = header
|
||||
url := h.url(req)
|
||||
//TODO: should that not be on line with above? seems to be other codepath.
|
||||
return http.post_multipart_form(url, req_form)!
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@ pub mut:
|
||||
header ?Header
|
||||
dict_key string // if the return is a dict, then will take the element out of the dict with the key and process further
|
||||
list_dict_key string // if the output is a list of dicts, then will process each element of the list to take the val with key out of that dict
|
||||
debug bool
|
||||
debug bool = true
|
||||
dataformat DataFormat
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import freeflowuniverse.herolib.data.doctree
|
||||
import freeflowuniverse.herolib.biz.bizmodel
|
||||
import freeflowuniverse.herolib.threefold.incatokens
|
||||
import freeflowuniverse.herolib.web.site
|
||||
import freeflowuniverse.herolib.virt.hetznermanager
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
import freeflowuniverse.herolib.clients.openai
|
||||
import freeflowuniverse.herolib.clients.giteaclient
|
||||
@@ -53,6 +54,7 @@ pub fn run(args_ PlayArgs) ! {
|
||||
incatokens.play(mut plbook)!
|
||||
|
||||
docusaurus.play(mut plbook)!
|
||||
hetznermanager.play(mut plbook)!
|
||||
|
||||
giteaclient.play(mut plbook)!
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ import freeflowuniverse.herolib.core.pathlib
|
||||
import os
|
||||
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
|
||||
if !plbook.exists(filter: 'incatokens.') {
|
||||
return
|
||||
}
|
||||
console.print_header('INCA Token Simulation')
|
||||
|
||||
// Collect all configurations first
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
module hetzner
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import time
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import freeflowuniverse.herolib.builder
|
||||
|
||||
/////////////////////////// LIST
|
||||
|
||||
pub struct ServerInfo {
|
||||
pub mut:
|
||||
server_ip string
|
||||
server_ipv6_net string
|
||||
server_number int
|
||||
server_name string
|
||||
product string
|
||||
dc string
|
||||
traffic string
|
||||
status string
|
||||
cancelled bool
|
||||
paid_until string
|
||||
ip []string
|
||||
subnet []Subnet
|
||||
}
|
||||
|
||||
pub struct ServerInfoDetailed {
|
||||
ServerInfo
|
||||
pub mut:
|
||||
reset bool
|
||||
rescue bool
|
||||
vnc bool
|
||||
windows bool
|
||||
plesk bool
|
||||
cpanel bool
|
||||
wol bool
|
||||
hot_swap bool
|
||||
// linked_storagebox int
|
||||
}
|
||||
|
||||
pub struct Subnet {
|
||||
pub mut:
|
||||
ip string
|
||||
mask string
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) servers_list() ![]ServerInfo {
|
||||
mut conn := h.connection()!
|
||||
return conn.get_json_list_generic[ServerInfo](
|
||||
method: .get
|
||||
prefix: 'server'
|
||||
list_dict_key: 'server'
|
||||
)!
|
||||
}
|
||||
|
||||
// ///////////////////////////GETID
|
||||
|
||||
pub struct ServerGetArgs {
|
||||
pub mut:
|
||||
id int
|
||||
name string
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) server_info_get(args_ ServerGetArgs) !ServerInfoDetailed {
|
||||
mut args := args_
|
||||
|
||||
args.name = texttools.name_fix(args.name)
|
||||
|
||||
l := h.servers_list()!
|
||||
|
||||
mut res := []ServerInfo{}
|
||||
|
||||
for item in l {
|
||||
if args.id > 0 && item.server_number != args.id {
|
||||
continue
|
||||
}
|
||||
server_name := texttools.name_fix(item.server_name)
|
||||
if args.name.len > 0 && server_name != args.name {
|
||||
continue
|
||||
}
|
||||
res << item
|
||||
}
|
||||
|
||||
if res.len > 1 {
|
||||
return error("Found too many servers with: '${args}'")
|
||||
}
|
||||
if res.len == 0 {
|
||||
return error("couldn't find server with: '${args}'")
|
||||
}
|
||||
|
||||
mut conn := h.connection()!
|
||||
return conn.get_json_generic[ServerInfoDetailed](
|
||||
method: .get
|
||||
prefix: 'server/${res[0].server_number}'
|
||||
dict_key: 'server'
|
||||
cache_disable: true
|
||||
)!
|
||||
}
|
||||
|
||||
// ///////////////////////////RESCUE
|
||||
|
||||
pub struct RescueInfo {
|
||||
pub mut:
|
||||
server_ip string
|
||||
server_ipv6_net string
|
||||
server_number int
|
||||
os string
|
||||
arch int
|
||||
active bool
|
||||
password string
|
||||
authorized_key []string
|
||||
host_key []string
|
||||
}
|
||||
|
||||
pub struct ServerRescueArgs {
|
||||
pub mut:
|
||||
id int
|
||||
name string
|
||||
wait bool = true
|
||||
hero_install bool
|
||||
hero_install bool
|
||||
sshkey_name string
|
||||
reset bool // ask to do reset/rescue even if its already in that state
|
||||
}
|
||||
|
||||
// put server in rescue mode, if sshkey_name not specified then will use the first one in the list
|
||||
pub fn (mut h HetznerManager) server_rescue(args ServerRescueArgs) !ServerInfoDetailed {
|
||||
mut serverinfo := h.server_info_get(id: args.id, name: args.name)!
|
||||
|
||||
console.print_header('server ${serverinfo.server_name} goes into rescue mode')
|
||||
|
||||
// only do it if its not in rescue yet
|
||||
if serverinfo.rescue == false || args.reset {
|
||||
mut key := h.keys_get()![0]
|
||||
if args.sshkey_name == '' {
|
||||
key = h.key_get(args.sshkey_name)!
|
||||
}
|
||||
|
||||
mut conn := h.connection()!
|
||||
rescue := conn.post_json_generic[RescueInfo](
|
||||
prefix: 'boot/${serverinfo.server_number}/rescue'
|
||||
params: {
|
||||
'os': 'linux'
|
||||
'authorized_key': key.fingerprint
|
||||
}
|
||||
dict_key: 'rescue'
|
||||
dataformat: .urlencoded
|
||||
)!
|
||||
|
||||
console.print_debug('hetzner rescue\n${rescue}')
|
||||
|
||||
h.server_reset(id: args.id, name: args.name, wait: args.wait)!
|
||||
}
|
||||
|
||||
if args.wait {
|
||||
// now we should check if ssh is responding
|
||||
// next will do that check
|
||||
builder.executor_new(ipaddr: serverinfo.server_ip, checkconnect: 60)!
|
||||
}
|
||||
|
||||
if args.hero_install {
|
||||
mut b := builder.new()!
|
||||
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
|
||||
n.hero_install()!
|
||||
}
|
||||
|
||||
if args.hero_install {
|
||||
mut b := builder.new()!
|
||||
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
|
||||
n.hero_install()!
|
||||
}
|
||||
|
||||
mut serverinfo2 := h.server_info_get(id: args.id, name: args.name)!
|
||||
|
||||
return serverinfo2
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) server_rescue_node(args ServerRescueArgs) !&builder.Node {
|
||||
mut serverinfo := h.server_rescue(args)!
|
||||
|
||||
mut b := builder.new()!
|
||||
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// /////////////////////////////////////RESET
|
||||
|
||||
struct ResetInfo {
|
||||
server_ip string
|
||||
server_ipv6_net string
|
||||
server_number int
|
||||
operating_status string
|
||||
}
|
||||
|
||||
pub struct ServerRebootArgs {
|
||||
pub mut:
|
||||
id int
|
||||
name string
|
||||
wait bool = true
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) server_reset(args ServerRebootArgs) !ResetInfo {
|
||||
mut serverinfo := h.server_info_get(id: args.id, name: args.name)!
|
||||
|
||||
console.print_header('server ${serverinfo.server_name} goes for reset')
|
||||
|
||||
mut serveractive := false
|
||||
if osal.ping(address: serverinfo.server_ip)! == .ok {
|
||||
serveractive = true
|
||||
console.print_debug('server ${serverinfo.server_name} is active')
|
||||
} else {
|
||||
console.print_debug('server ${serverinfo.server_name} is down')
|
||||
}
|
||||
|
||||
mut conn := h.connection()!
|
||||
o := conn.post_json_generic[ResetInfo](
|
||||
prefix: 'reset/${serverinfo.server_number}'
|
||||
params: {
|
||||
'type': 'hw'
|
||||
}
|
||||
dataformat: .urlencoded
|
||||
// dict_key:'reset'
|
||||
)!
|
||||
// now need to wait till it goes off
|
||||
if serveractive {
|
||||
for {
|
||||
console.print_debug('wait for server ${serverinfo.server_name} to go down.')
|
||||
if osal.ping(address: serverinfo.server_ip) != .ok {
|
||||
console.print_debug('server ${serverinfo.server_name} is now down, now waitig for reboot.')
|
||||
break
|
||||
}
|
||||
time.sleep(1000 * time.millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
mut x := 0
|
||||
if args.wait {
|
||||
for {
|
||||
time.sleep(1000 * time.millisecond)
|
||||
console.print_debug('wait for ${serverinfo.server_name}')
|
||||
if osal.ping(address: serverinfo.server_ip)! == .ok {
|
||||
console.print_debug('ping ok')
|
||||
osal.tcp_port_test(address: serverinfo.server_ip, port: 22, timeout: 3000)
|
||||
console.print_debug('ssh tcp port ok')
|
||||
console.print_header('server is rebooted: ${serverinfo.server_name}')
|
||||
break
|
||||
}
|
||||
x += 1
|
||||
if x > 60 * 5 {
|
||||
// 5 min
|
||||
return error('Could not reboot server ${serverinfo.server_name} in 5 min')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// /////////////////////////////////////BOOT
|
||||
|
||||
// struct BootRoot {
|
||||
// boot Boot
|
||||
// }
|
||||
|
||||
// struct Boot {
|
||||
// rescue RescueInfo
|
||||
// }
|
||||
|
||||
// pub fn (mut h HetznerManager) server_boot(id int) !RescueInfo {
|
||||
// mut conn := h.connection()!
|
||||
// boot := conn.get_json[BootRoot](prefix: 'boot/${id}')!
|
||||
// return boot.boot.rescue
|
||||
// }
|
||||
@@ -1,102 +0,0 @@
|
||||
module hetzner
|
||||
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook }
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
__global (
|
||||
hetzner_global map[string]&HetznerManager
|
||||
hetzner_default string
|
||||
)
|
||||
|
||||
/////////FACTORY
|
||||
|
||||
@[params]
|
||||
pub struct ArgsGet {
|
||||
pub mut:
|
||||
name string
|
||||
}
|
||||
|
||||
fn args_get(args_ ArgsGet) ArgsGet {
|
||||
mut args := args_
|
||||
if args.name == '' {
|
||||
args.name = 'default'
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
pub fn get(args_ ArgsGet) !&HetznerManager {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
mut obj := HetznerManager{
|
||||
name: args.name
|
||||
}
|
||||
if args.name !in hetzner_global {
|
||||
if !exists(args)! {
|
||||
set(obj)!
|
||||
} else {
|
||||
heroscript := context.hero_config_get('hetzner', args.name)!
|
||||
mut obj_ := heroscript_loads(heroscript)!
|
||||
set_in_mem(obj_)!
|
||||
}
|
||||
}
|
||||
return hetzner_global[args.name] or {
|
||||
println(hetzner_global)
|
||||
// bug if we get here because should be in globals
|
||||
panic('could not get config for hetzner with name, is bug:${args.name}')
|
||||
}
|
||||
}
|
||||
|
||||
// register the config for the future
|
||||
pub fn set(o HetznerManager) ! {
|
||||
set_in_mem(o)!
|
||||
mut context := base.context()!
|
||||
heroscript := heroscript_dumps(o)!
|
||||
context.hero_config_set('hetzner', o.name, heroscript)!
|
||||
}
|
||||
|
||||
// does the config exists?
|
||||
pub fn exists(args_ ArgsGet) !bool {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
return context.hero_config_exists('hetzner', args.name)
|
||||
}
|
||||
|
||||
pub fn delete(args_ ArgsGet) ! {
|
||||
mut args := args_get(args_)
|
||||
mut context := base.context()!
|
||||
context.hero_config_delete('hetzner', args.name)!
|
||||
if args.name in hetzner_global {
|
||||
// del hetzner_global[args.name]
|
||||
}
|
||||
}
|
||||
|
||||
// only sets in mem, does not set as config
|
||||
fn set_in_mem(o HetznerManager) ! {
|
||||
mut o2 := obj_init(o)!
|
||||
hetzner_global[o.name] = &o2
|
||||
hetzner_default = o.name
|
||||
}
|
||||
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
mut install_actions := plbook.find(filter: 'hetzner.configure')!
|
||||
if install_actions.len > 0 {
|
||||
for install_action in install_actions {
|
||||
heroscript := install_action.heroscript()
|
||||
mut obj2 := heroscript_loads(heroscript)!
|
||||
set(obj2)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// switch instance to be used for hetzner
|
||||
pub fn switch(name string) {
|
||||
hetzner_default = name
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
@[params]
|
||||
pub struct DefaultConfigArgs {
|
||||
instance string = 'default'
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
module hetzner
|
||||
|
||||
import freeflowuniverse.herolib.data.paramsparser
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
|
||||
pub const version = '1.14.3'
|
||||
const singleton = false
|
||||
const default = true
|
||||
|
||||
pub fn heroscript_default() !string {
|
||||
heroscript := "
|
||||
!!hetzner.configure
|
||||
name:'default'
|
||||
url:'https://robot-ws.your-server.de'
|
||||
user:''
|
||||
password:''
|
||||
whitelist:''
|
||||
"
|
||||
return heroscript
|
||||
}
|
||||
|
||||
// THIS IS THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
|
||||
|
||||
@[heap]
|
||||
pub struct HetznerManager {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
description string
|
||||
baseurl string
|
||||
whitelist string // comma separated list of servers we whitelist to work on
|
||||
user string
|
||||
password string
|
||||
conn ?&httpconnection.HTTPConnection
|
||||
}
|
||||
|
||||
fn cfg_play(p paramsparser.Params) !HetznerManager {
|
||||
mut mycfg := HetznerManager{
|
||||
name: p.get_default('name', 'default')!
|
||||
baseurl: p.get_default('url', 'https://robot-ws.your-server.de')!
|
||||
// TODO: whitelist
|
||||
user: p.get('user')!
|
||||
password: p.get('password')!
|
||||
}
|
||||
return mycfg
|
||||
}
|
||||
|
||||
fn obj_init(obj_ HetznerManager) !HetznerManager {
|
||||
// never call get here, only thing we can do here is work on object itself
|
||||
mut obj := obj_
|
||||
// Initialize connection with caching enabled
|
||||
return obj
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) connection() !&httpconnection.HTTPConnection {
|
||||
mut c := h.conn or {
|
||||
mut c2 := httpconnection.new(
|
||||
name: 'hetzner_${h.name}'
|
||||
url: h.baseurl
|
||||
cache: true
|
||||
retry: 3
|
||||
)!
|
||||
c2.basic_auth(h.user, h.password)
|
||||
c2
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
!!hero_code.generate_client
|
||||
name:'hetzner'
|
||||
name:'hetznermanager'
|
||||
classname:'HetznerManager'
|
||||
singleton:0
|
||||
default:1
|
||||
@@ -1,4 +1,4 @@
|
||||
module hetzner
|
||||
module hetznermanager
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
137
lib/virt/hetznermanager/hetznermanager_factory_.v
Normal file
137
lib/virt/hetznermanager/hetznermanager_factory_.v
Normal file
@@ -0,0 +1,137 @@
|
||||
module hetznermanager
|
||||
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook }
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import json
|
||||
|
||||
__global (
|
||||
hetznermanager_global map[string]&HetznerManager
|
||||
hetznermanager_default string
|
||||
)
|
||||
|
||||
/////////FACTORY
|
||||
|
||||
@[params]
|
||||
pub struct ArgsGet {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
fromdb bool // will load from filesystem
|
||||
create bool // default will not create if not exist
|
||||
}
|
||||
|
||||
pub fn new(args ArgsGet) !&HetznerManager {
|
||||
mut obj := HetznerManager{
|
||||
name: args.name
|
||||
}
|
||||
set(obj)!
|
||||
return get(name: args.name)!
|
||||
}
|
||||
|
||||
pub fn get(args ArgsGet) !&HetznerManager {
|
||||
mut context := base.context()!
|
||||
hetznermanager_default = args.name
|
||||
if args.fromdb || args.name !in hetznermanager_global {
|
||||
mut r := context.redis()!
|
||||
if r.hexists('context:hetznermanager', args.name)! {
|
||||
data := r.hget('context:hetznermanager', args.name)!
|
||||
if data.len == 0 {
|
||||
return error('HetznerManager with name: hetznermanager does not exist, prob bug.')
|
||||
}
|
||||
mut obj := json.decode(HetznerManager, data)!
|
||||
set_in_mem(obj)!
|
||||
} else {
|
||||
if args.create {
|
||||
new(args)!
|
||||
} else {
|
||||
return error("HetznerManager with name 'hetznermanager' does not exist")
|
||||
}
|
||||
}
|
||||
return get(name: args.name)! // no longer from db nor create
|
||||
}
|
||||
return hetznermanager_global[args.name] or {
|
||||
return error('could not get config for hetznermanager with name:hetznermanager')
|
||||
}
|
||||
}
|
||||
|
||||
// register the config for the future
|
||||
pub fn set(o HetznerManager) ! {
|
||||
mut o2 := set_in_mem(o)!
|
||||
hetznermanager_default = o2.name
|
||||
mut context := base.context()!
|
||||
mut r := context.redis()!
|
||||
r.hset('context:hetznermanager', o2.name, json.encode(o2))!
|
||||
}
|
||||
|
||||
// does the config exists?
|
||||
pub fn exists(args ArgsGet) !bool {
|
||||
mut context := base.context()!
|
||||
mut r := context.redis()!
|
||||
return r.hexists('context:hetznermanager', args.name)!
|
||||
}
|
||||
|
||||
pub fn delete(args ArgsGet) ! {
|
||||
mut context := base.context()!
|
||||
mut r := context.redis()!
|
||||
r.hdel('context:hetznermanager', args.name)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ArgsList {
|
||||
pub mut:
|
||||
fromdb bool // will load from filesystem
|
||||
}
|
||||
|
||||
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
|
||||
pub fn list(args ArgsList) ![]&HetznerManager {
|
||||
mut res := []&HetznerManager{}
|
||||
mut context := base.context()!
|
||||
if args.fromdb {
|
||||
// reset what is in mem
|
||||
hetznermanager_global = map[string]&HetznerManager{}
|
||||
hetznermanager_default = ''
|
||||
}
|
||||
if args.fromdb {
|
||||
mut r := context.redis()!
|
||||
mut l := r.hkeys('context:hetznermanager')!
|
||||
|
||||
for name in l {
|
||||
res << get(name: name, fromdb: true)!
|
||||
}
|
||||
return res
|
||||
} else {
|
||||
// load from memory
|
||||
for _, client in hetznermanager_global {
|
||||
res << client
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// only sets in mem, does not set as config
|
||||
fn set_in_mem(o HetznerManager) !HetznerManager {
|
||||
mut o2 := obj_init(o)!
|
||||
hetznermanager_global[o2.name] = &o2
|
||||
hetznermanager_default = o2.name
|
||||
return o2
|
||||
}
|
||||
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
if !plbook.exists(filter: 'hetznermanager.') {
|
||||
return
|
||||
}
|
||||
mut install_actions := plbook.find(filter: 'hetznermanager.configure')!
|
||||
if install_actions.len > 0 {
|
||||
for mut install_action in install_actions {
|
||||
heroscript := install_action.heroscript()
|
||||
mut obj2 := heroscript_loads(heroscript)!
|
||||
set(obj2)!
|
||||
install_action.done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// switch instance to be used for hetznermanager
|
||||
pub fn switch(name string) {
|
||||
hetznermanager_default = name
|
||||
}
|
||||
41
lib/virt/hetznermanager/hetznermanager_model.v
Normal file
41
lib/virt/hetznermanager/hetznermanager_model.v
Normal file
@@ -0,0 +1,41 @@
|
||||
module hetznermanager
|
||||
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
import freeflowuniverse.herolib.data.encoderhero
|
||||
|
||||
pub const version = '0.0.0'
|
||||
const singleton = false
|
||||
const default = true
|
||||
|
||||
@[heap]
|
||||
pub struct HetznerManager {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
description string
|
||||
baseurl string = 'https://robot-ws.your-server.de'
|
||||
whitelist []string // comma separated list of servers we whitelist to work on
|
||||
user string
|
||||
password string
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) connection() !&httpconnection.HTTPConnection {
|
||||
mut c2 := httpconnection.new(
|
||||
name: 'hetzner_${h.name}'
|
||||
url: h.baseurl
|
||||
cache: true
|
||||
retry: 3
|
||||
)!
|
||||
c2.basic_auth(h.user, h.password)
|
||||
println(c2)
|
||||
return c2
|
||||
}
|
||||
|
||||
fn obj_init(mycfg_ HetznerManager) !HetznerManager {
|
||||
mut mycfg := mycfg_
|
||||
return mycfg
|
||||
}
|
||||
|
||||
pub fn heroscript_loads(heroscript string) !HetznerManager {
|
||||
mut obj := encoderhero.decode[HetznerManager](heroscript)!
|
||||
return obj
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module hetzner
|
||||
module hetznermanager
|
||||
|
||||
// TODO: couldn't get ssh lib to work
|
||||
|
||||
@@ -4,22 +4,28 @@ This module provides a V client for interacting with Hetzner's Robot API, allowi
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create an account on [Hetzner Robot](https://robot.hetzner.com/preferences/index)
|
||||
1. Create an account on [Hetzner Robot](https://robot.hetznermanager.com/preferences/index)
|
||||
2. Configure the client using heroscript:
|
||||
3.
|
||||
```v
|
||||
import freeflowuniverse.herolib.virt.hetzner
|
||||
|
||||
heroscript := "
|
||||
!!hetzner.configure
|
||||
name:'my_instance'
|
||||
url:'https://robot-ws.your-server.de'
|
||||
user:'your-username'
|
||||
password:'your-password'
|
||||
whitelist:'' // comma separated list of servers to whitelist
|
||||
"
|
||||
import freeflowuniverse.herolib.core.playcmds
|
||||
|
||||
passwd:=os.environ()['HETZNER_PASSWORD'] or {
|
||||
println('HETZNER_PASSWORD not set')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
playcmds.run(
|
||||
heroscript: '
|
||||
!!hetznermanager.configure
|
||||
name:"main"
|
||||
user:"operations@threefold.io"
|
||||
whitelist:"2111181, 2392178"
|
||||
password:"${passwd}"
|
||||
'
|
||||
)!
|
||||
|
||||
// Apply the configuration (only needs to be done once)
|
||||
hetzner.play(heroscript: heroscript)!
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -27,7 +33,7 @@ hetzner.play(heroscript: heroscript)!
|
||||
### Initialize Client
|
||||
```v
|
||||
// Get a configured client instance
|
||||
mut cl := hetzner.get(name: 'my_instance')!
|
||||
mut cl := hetznermanager.get(name: 'main')!
|
||||
```
|
||||
|
||||
### Configuration Notes
|
||||
@@ -40,7 +46,7 @@ mut cl := hetzner.get(name: 'my_instance')!
|
||||
|
||||
### Examples
|
||||
|
||||
> see examples/virt/hetzner
|
||||
> see examples/virt/hetznermanager
|
||||
|
||||
### Available Operations
|
||||
|
||||
@@ -100,12 +106,12 @@ Here's a complete example showing common operations:
|
||||
```v
|
||||
#!/usr/bin/env -S v run
|
||||
|
||||
import freeflowuniverse.herolib.virt.hetzner
|
||||
import freeflowuniverse.herolib.virt.hetznermanager
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
fn main() {
|
||||
// Get client instance
|
||||
mut cl := hetzner.get('my_instance')!
|
||||
mut cl := hetznermanager.get('my_instance')!
|
||||
|
||||
// List all servers
|
||||
servers := cl.servers_list()!
|
||||
@@ -141,4 +147,4 @@ fn main() {
|
||||
- Server operations that include `wait: true` will monitor the server until the operation completes
|
||||
- Reset operations with `wait: true` will timeout after 5 minutes if the server doesn't come back online
|
||||
- The module automatically manages SSH keys for rescue mode operations
|
||||
- API description is on https://robot.hetzner.com/doc/webservice/en.html#preface
|
||||
- API description is on https://robot.hetznermanager.com/doc/webservice/en.html#preface
|
||||
97
lib/virt/hetznermanager/rescue.v
Normal file
97
lib/virt/hetznermanager/rescue.v
Normal file
@@ -0,0 +1,97 @@
|
||||
module hetznermanager
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import time
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import freeflowuniverse.herolib.builder
|
||||
|
||||
// ///////////////////////////RESCUE
|
||||
|
||||
pub struct RescueInfo {
|
||||
pub mut:
|
||||
server_ip string
|
||||
server_ipv6_net string
|
||||
server_number int
|
||||
os string
|
||||
arch int
|
||||
active bool
|
||||
password string
|
||||
authorized_key []string
|
||||
host_key []string
|
||||
}
|
||||
|
||||
pub struct ServerRescueArgs {
|
||||
pub mut:
|
||||
id int
|
||||
name string
|
||||
wait bool = true
|
||||
hero_install bool
|
||||
sshkey_name string
|
||||
reset bool // ask to do reset/rescue even if its already in that state
|
||||
}
|
||||
|
||||
// put server in rescue mode, if sshkey_name not specified then will use the first one in the list
|
||||
pub fn (mut h HetznerManager) server_rescue(args ServerRescueArgs) !ServerInfoDetailed {
|
||||
mut serverinfo := h.server_info_get(id: args.id, name: args.name)!
|
||||
|
||||
console.print_header('server ${serverinfo.server_name} goes into rescue mode')
|
||||
|
||||
// only do it if its not in rescue yet
|
||||
if serverinfo.rescue == false || args.reset {
|
||||
mut key := h.keys_get()![0]
|
||||
if args.sshkey_name == '' {
|
||||
key = h.key_get(args.sshkey_name)!
|
||||
}
|
||||
|
||||
mut conn := h.connection()!
|
||||
rescue := conn.post_json_generic[RescueInfo](
|
||||
prefix: 'boot/${serverinfo.server_number}/rescue'
|
||||
params: {
|
||||
'os': 'linux'
|
||||
'authorized_key': key.fingerprint
|
||||
}
|
||||
dict_key: 'rescue'
|
||||
dataformat: .urlencoded
|
||||
)!
|
||||
|
||||
console.print_debug('hetzner rescue\n${rescue}')
|
||||
|
||||
h.server_reset(id: args.id, name: args.name, wait: args.wait)!
|
||||
}
|
||||
|
||||
if args.wait {
|
||||
// now we should check if ssh is responding
|
||||
// next will do that check
|
||||
mut b := builder.new()!
|
||||
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
|
||||
println(n)
|
||||
$dbg;
|
||||
// builder.executor_new(ipaddr: serverinfo.server_ip, checkconnect: 60)!
|
||||
}
|
||||
|
||||
if args.hero_install {
|
||||
mut b := builder.new()!
|
||||
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
|
||||
n.hero_install()!
|
||||
}
|
||||
|
||||
if args.hero_install {
|
||||
mut b := builder.new()!
|
||||
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
|
||||
n.hero_install()!
|
||||
}
|
||||
|
||||
mut serverinfo2 := h.server_info_get(id: args.id, name: args.name)!
|
||||
|
||||
return serverinfo2
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) server_rescue_node(args ServerRescueArgs) !&builder.Node {
|
||||
mut serverinfo := h.server_rescue(args)!
|
||||
|
||||
mut b := builder.new()!
|
||||
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
|
||||
|
||||
return n
|
||||
}
|
||||
98
lib/virt/hetznermanager/reset.v
Normal file
98
lib/virt/hetznermanager/reset.v
Normal file
@@ -0,0 +1,98 @@
|
||||
module hetznermanager
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import time
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import freeflowuniverse.herolib.builder
|
||||
|
||||
// /////////////////////////////////////RESET
|
||||
|
||||
struct ResetInfo {
|
||||
server_ip string
|
||||
server_ipv6_net string
|
||||
server_number int
|
||||
operating_status string
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ServerRebootArgs {
|
||||
pub mut:
|
||||
id int
|
||||
name string
|
||||
wait bool = true
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) server_reset(args ServerRebootArgs) !ResetInfo {
|
||||
mut serverinfo := h.server_info_get(id: args.id, name: args.name)!
|
||||
|
||||
console.print_header('server ${serverinfo.server_name} goes for reset')
|
||||
|
||||
mut serveractive := false
|
||||
if osal.ping(address: serverinfo.server_ip)! == .ok {
|
||||
serveractive = true
|
||||
console.print_debug('server ${serverinfo.server_name} is active')
|
||||
} else {
|
||||
console.print_debug('server ${serverinfo.server_name} is down')
|
||||
}
|
||||
|
||||
mut conn := h.connection()!
|
||||
o := conn.post_json_generic[ResetInfo](
|
||||
prefix: 'reset/${serverinfo.server_number}'
|
||||
params: {
|
||||
'type': 'hw'
|
||||
}
|
||||
dataformat: .urlencoded
|
||||
// dict_key:'reset'
|
||||
)!
|
||||
// now need to wait till it goes off
|
||||
if serveractive {
|
||||
for {
|
||||
console.print_debug('wait for server ${serverinfo.server_name} to go down.')
|
||||
pingresult := osal.ping(address: serverinfo.server_ip)!
|
||||
if pingresult != .ok {
|
||||
console.print_debug('server ${serverinfo.server_name} is now down, now waitig for reboot.')
|
||||
break
|
||||
}
|
||||
time.sleep(1000 * time.millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
mut x := 0
|
||||
if args.wait {
|
||||
for {
|
||||
time.sleep(1000 * time.millisecond)
|
||||
console.print_debug('wait for ${serverinfo.server_name}')
|
||||
if osal.ping(address: serverinfo.server_ip)! == .ok {
|
||||
console.print_debug('ping ok')
|
||||
osal.tcp_port_test(address: serverinfo.server_ip, port: 22, timeout: 3000)
|
||||
console.print_debug('ssh tcp port ok')
|
||||
console.print_header('server is rebooted: ${serverinfo.server_name}')
|
||||
break
|
||||
}
|
||||
x += 1
|
||||
if x > 60 * 5 {
|
||||
// 5 min
|
||||
return error('Could not reboot server ${serverinfo.server_name} in 5 min')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// /////////////////////////////////////BOOT
|
||||
|
||||
// struct BootRoot {
|
||||
// boot Boot
|
||||
// }
|
||||
|
||||
// struct Boot {
|
||||
// rescue RescueInfo
|
||||
// }
|
||||
|
||||
// pub fn (mut h HetznerManager) server_boot(id int) !RescueInfo {
|
||||
// mut conn := h.connection()!
|
||||
// boot := conn.get_json[BootRoot](prefix: 'boot/${id}')!
|
||||
// return boot.boot.rescue
|
||||
// }
|
||||
98
lib/virt/hetznermanager/serverinfo.v
Normal file
98
lib/virt/hetznermanager/serverinfo.v
Normal file
@@ -0,0 +1,98 @@
|
||||
module hetznermanager
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import time
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import freeflowuniverse.herolib.builder
|
||||
|
||||
/////////////////////////// LIST
|
||||
|
||||
pub struct ServerInfo {
|
||||
pub mut:
|
||||
server_ip string
|
||||
server_ipv6_net string
|
||||
server_number int
|
||||
server_name string
|
||||
product string
|
||||
dc string
|
||||
traffic string
|
||||
status string
|
||||
cancelled bool
|
||||
paid_until string
|
||||
ip []string
|
||||
subnet []Subnet
|
||||
}
|
||||
|
||||
pub struct ServerInfoDetailed {
|
||||
ServerInfo
|
||||
pub mut:
|
||||
reset bool
|
||||
rescue bool
|
||||
vnc bool
|
||||
windows bool
|
||||
plesk bool
|
||||
cpanel bool
|
||||
wol bool
|
||||
hot_swap bool
|
||||
// linked_storagebox int
|
||||
}
|
||||
|
||||
pub struct Subnet {
|
||||
pub mut:
|
||||
ip string
|
||||
mask string
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) servers_list() ![]ServerInfo {
|
||||
mut conn := h.connection()!
|
||||
return conn.get_json_list_generic[ServerInfo](
|
||||
method: .get
|
||||
prefix: 'server'
|
||||
list_dict_key: 'server'
|
||||
)!
|
||||
}
|
||||
|
||||
// ///////////////////////////GETID
|
||||
|
||||
pub struct ServerGetArgs {
|
||||
pub mut:
|
||||
id int
|
||||
name string
|
||||
}
|
||||
|
||||
pub fn (mut h HetznerManager) server_info_get(args_ ServerGetArgs) !ServerInfoDetailed {
|
||||
mut args := args_
|
||||
|
||||
args.name = texttools.name_fix(args.name)
|
||||
|
||||
l := h.servers_list()!
|
||||
|
||||
mut res := []ServerInfo{}
|
||||
|
||||
for item in l {
|
||||
if args.id > 0 && item.server_number != args.id {
|
||||
continue
|
||||
}
|
||||
server_name := texttools.name_fix(item.server_name)
|
||||
if args.name.len > 0 && server_name != args.name {
|
||||
continue
|
||||
}
|
||||
res << item
|
||||
}
|
||||
|
||||
if res.len > 1 {
|
||||
return error("Found too many servers with: '${args}'")
|
||||
}
|
||||
if res.len == 0 {
|
||||
return error("couldn't find server with: '${args}'")
|
||||
}
|
||||
|
||||
mut conn := h.connection()!
|
||||
return conn.get_json_generic[ServerInfoDetailed](
|
||||
method: .get
|
||||
prefix: 'server/${res[0].server_number}'
|
||||
dict_key: 'server'
|
||||
cache_disable: true
|
||||
)!
|
||||
}
|
||||
Reference in New Issue
Block a user