185 lines
5.3 KiB
V
185 lines
5.3 KiB
V
module tfrobot
|
|
|
|
import incubaid.herolib.core.redisclient
|
|
import json
|
|
import os
|
|
import incubaid.herolib.ui.console
|
|
import incubaid.herolib.core.pathlib
|
|
import incubaid.herolib.osal.core as osal
|
|
import incubaid.herolib.osal.sshagent
|
|
|
|
const tfrobot_dir = '${os.home_dir()}/hero/tfrobot' // path to tfrobot dir in fs
|
|
|
|
pub struct DeployConfig {
|
|
pub mut:
|
|
name string
|
|
mnemonic string
|
|
network Network = .main
|
|
node_groups []NodeGroup @[required]
|
|
vms []VMConfig @[required]
|
|
ssh_keys map[string]string
|
|
debug bool
|
|
}
|
|
|
|
pub struct NodeGroup {
|
|
name string
|
|
nodes_count int @[required]
|
|
free_cpu int @[required] // number of logical cores
|
|
free_mru int @[required] // amount of memory in GB
|
|
free_ssd int // amount of ssd storage in GB
|
|
free_hdd int // amount of hdd storage in GB
|
|
dedicated bool // are nodes dedicated
|
|
public_ip4 bool
|
|
public_ip6 bool
|
|
certified bool // should the nodes be certified(if false the nodes could be certified of diyed)
|
|
region string // region could be the name of the continents the nodes are located in (africa, americas, antarctic, antarctic ocean, asia, europe, oceania, polar)
|
|
}
|
|
|
|
pub struct VMConfig {
|
|
pub mut:
|
|
name string @[required]
|
|
vms_count int = 1 @[required]
|
|
node_group string
|
|
cpu int = 4 @[required]
|
|
mem int = 4 @[required] // in GB
|
|
public_ip4 bool
|
|
public_ip6 bool
|
|
ygg_ip bool = true
|
|
mycelium_ip bool = true
|
|
flist string @[required]
|
|
entry_point string @[required]
|
|
root_size int = 20
|
|
ssh_key string
|
|
env_vars map[string]string
|
|
}
|
|
|
|
pub struct DeployResult {
|
|
pub:
|
|
ok map[string][]VMOutput
|
|
error map[string]string
|
|
}
|
|
|
|
pub struct VMOutput {
|
|
pub mut:
|
|
name string @[json: 'Name'; required]
|
|
network_name string @[json: 'NetworkName'; required]
|
|
node_group string
|
|
deployment_name string
|
|
public_ip4 string @[json: 'PublicIP4'; required]
|
|
public_ip6 string @[json: 'PublicIP6'; required]
|
|
yggdrasil_ip string @[json: 'YggIP'; required]
|
|
mycelium_ip string @[json: 'MyceliumIP'; required]
|
|
ip string @[json: 'IP'; required]
|
|
mounts []Mount @[json: 'Mounts'; required]
|
|
node_id u32 @[json: 'NodeID']
|
|
contract_id u64 @[json: 'ContractID']
|
|
}
|
|
|
|
pub struct Mount {
|
|
pub:
|
|
disk_name string
|
|
mount_point string
|
|
}
|
|
|
|
// get all keys from ssh_agent and add to the config
|
|
pub fn sshagent_keys_add(mut config DeployConfig) ! {
|
|
mut ssha := sshagent.new()!
|
|
if ssha.keys.len == 0 {
|
|
return error('no ssh-keys found in ssh-agent, cannot add to tfrobot deploy config.')
|
|
}
|
|
for mut key in ssha.keys_loaded()! {
|
|
config.ssh_keys[key.name] = key.keypub()!.trim('\n')
|
|
}
|
|
}
|
|
|
|
pub fn (mut robot TFRobot[Config]) deploy(config_ DeployConfig) !DeployResult {
|
|
mut config := config_
|
|
cfg := robot.config()!
|
|
if config.mnemonic == '' {
|
|
config.mnemonic = cfg.mnemonics
|
|
}
|
|
config.network = Network.from(cfg.network)!
|
|
|
|
if config.ssh_keys.len == 0 {
|
|
return error('no ssh-keys found in config')
|
|
}
|
|
|
|
if config.node_groups.len == 0 {
|
|
return error('there are no node requirement groups defined')
|
|
}
|
|
|
|
node_group := config.node_groups.first().name
|
|
|
|
for mut vm in config.vms {
|
|
if vm.ssh_key.len == 0 {
|
|
vm.ssh_key = config.ssh_keys.keys().first() // first one of the dict
|
|
}
|
|
if vm.ssh_key !in config.ssh_keys {
|
|
return error('Could not find specified sshkey: ${vm.ssh_key} in known sshkeys.\n${config.ssh_keys.values()}')
|
|
}
|
|
if vm.node_group == '' {
|
|
vm.node_group = node_group
|
|
}
|
|
}
|
|
|
|
check_deploy_config(config)!
|
|
|
|
mut config_file := pathlib.get_file(
|
|
path: '${tfrobot_dir}/deployments/${config.name}_config.json'
|
|
create: true
|
|
)!
|
|
mut output_file := pathlib.get_file(
|
|
path: '${tfrobot_dir}/deployments/${config.name}_output.json'
|
|
create: false
|
|
)!
|
|
config_json := json.encode(config)
|
|
config_file.write(config_json)!
|
|
cmd := 'tfrobot deploy -c ${config_file.path} -o ${output_file.path}'
|
|
if config.debug {
|
|
console.print_debug(config.str())
|
|
console.print_debug(cmd)
|
|
}
|
|
_ := osal.exec(
|
|
cmd: cmd
|
|
stdout: true
|
|
) or { return error('TFRobot command ${cmd} failed:\n${err}') }
|
|
output := output_file.read()!
|
|
mut res := json.decode(DeployResult, output)!
|
|
|
|
if res.ok.len == 0 {
|
|
return error('No vm was deployed, empty result')
|
|
}
|
|
|
|
mut redis := redisclient.core_get()!
|
|
|
|
redis.hset('tfrobot:${config.name}', 'config', config_json)!
|
|
for groupname, mut vms in res.ok {
|
|
for mut vm in vms {
|
|
if config.debug {
|
|
console.print_header('vm deployed: ${vm.name}')
|
|
console.print_debug(vm.str())
|
|
}
|
|
vm.node_group = groupname // remember the groupname
|
|
vm.deployment_name = config.name
|
|
vm_json := json.encode(vm)
|
|
redis.hset('tfrobot:${config.name}', vm.name, vm_json)!
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
fn check_deploy_config(config DeployConfig) ! {
|
|
// Checking if configuration is valid. For instance that there is no ssh_key key that isnt defined,
|
|
// or that the specified node group of a vm configuration exists
|
|
vms := config.vms.filter(it.node_group !in config.node_groups.map(it.name))
|
|
if vms.len > 0 {
|
|
error_msgs := vms.map('Node group: `${it.node_group}` for VM: `${it.name}`')
|
|
return error('${error_msgs.join(',')} not found.')
|
|
}
|
|
|
|
unknown_keys := config.vms.filter(it.ssh_key !in config.ssh_keys).map(it.ssh_key)
|
|
if unknown_keys.len > 0 {
|
|
return error('SSH Keys [${unknown_keys.join(',')}] not found.')
|
|
}
|
|
}
|