builder #
Builder Module
The Builder module is a powerful system automation and remote execution framework that provides a unified interface for executing commands and managing files across both local and remote systems.
Overview
The Builder module consists of several key components:- BuilderFactory: Creates and manages builder instances
- Node: Represents a target system (local or remote) with its properties and state
- Executor: Interface for command execution and file operations (SSH or Local)
- NodeDB: Key-value store at the node level for persistent state
Getting Started
Basic Initialization
import freeflowuniverse.herolib.builder
// Create a new builder instance
mut b := builder.new()!
// Create a node for remote execution
mut n := b.node_new(ipaddr: 'root@195.192.213.2:2222')!
// Or create a local node
mut local_node := builder.node_local()!
Node Configuration
Nodes can be configured with various properties:
// Full node configuration
mut n := b.node_new(
name: 'myserver', // Optional name for the node
ipaddr: 'root@server.example.com:22', // SSH connection string
platform: .ubuntu, // Target platform type
debug: true // Enable debug output
)!
Node Properties
Each node maintains information about:- Platform type (OSX, Ubuntu, Alpine, Arch)
- CPU architecture (Intel, ARM)
- Environment variables
- System state
- Execution history
The node automatically detects and caches system information for better performance.
Executor Interface
The executor provides a unified interface for both local and remote operations:
Command Execution
// Execute command and get output
result := n.exec('ls -la')!
// Execute silently (no output)
n.exec_silent('mkdir -p /tmp/test')!
// Interactive shell
n.shell('bash')!
File Operations
// Write file
n.file_write('/path/to/file', 'content')!
// Read file
content := n.file_read('/path/to/file')!
// Check existence
exists := n.file_exists('/path/to/file')
// Delete file/directory
n.delete('/path/to/delete')!
// List directory contents
files := n.list('/path/to/dir')!
// File transfers
n.download('http://example.com/file', '/local/path')!
n.upload('/local/file', '/remote/path')!
Environment Management
// Get all environment variables
env := n.environ_get()!
// Get node information
info := n.info()
Node Database (NodeDB)
The NodeDB provides persistent key-value storage at the node level:
// Store a value
n.done['key'] = 'value'
n.save()!
// Load stored values
n.load()!
value := n.done['key']
This is useful for:- Caching system information
- Storing configuration state
- Tracking execution history
- Maintaining persistent data between sessions
Best Practices
Error Handling: Always use the
!
operator for methods that can fail and handle errors appropriately.Resource Management: Close connections and clean up resources when done:
defer {
n.close()
}
- Debug Mode: Enable debug mode when troubleshooting:
n.debug_on() // Enable debug output
n.debug_off() // Disable debug output
- Platform Awareness: Check platform compatibility before executing commands:
if n.platform == .ubuntu {
// Ubuntu-specific commands
} else if n.platform == .osx {
// macOS-specific commands
}
Examples
See complete examples in:- Simple usage: examples/builder/simple.vsh
- Remote execution:
examples/builder/remote_executor/
- Platform-specific examples:
- IPv4:
examples/builder/simple_ip4.vsh
- IPv6:
examples/builder/simple_ip6.vsh
Implementation Details
The Builder module uses:- Redis for caching node information
- SSH for secure remote execution
- MD5 hashing for unique node identification
- JSON for data serialization
- Environment detection for platform-specific behavior
For more detailed implementation information, refer to the source code in the lib/builder/
directory.
fn bootstrapper #
fn bootstrapper() BootStrapper
to use do something like: export NODES="195.192.213.3" .
fn new #
fn new() !BuilderFactory
fn node_local #
fn node_local(args NodeLocalArgs) !&Node
fn portforward_to_local #
fn portforward_to_local(args_ ForwardArgsToLocal) !
forward a remote port on ssh host to a local port
fn this_remote_exec #
fn this_remote_exec(args_ ThisRemoteArgs) !bool
to use do something like: export NODES="195.192.213.3" .
enum CPUType #
enum CPUType {
unknown
intel
arm
intel32
arm32
}
enum PlatformType #
enum PlatformType {
unknown
osx
ubuntu
alpine
arch
}
struct BootStrapper #
struct BootStrapper {
pub mut:
embedded_files map[string]embed_file.EmbedFileData @[skip; str: skip]
}
fn (BootStrapper) run #
fn (mut bs BootStrapper) run(args_ BootstrapperArgs) !
struct BootstrapperArgs #
struct BootstrapperArgs {
pub mut:
name string = 'bootstrap'
addr string // format: root@something:33, 192.168.7.7:222, 192.168.7.7, despiegk@something
reset bool
debug bool
}
struct BuilderFactory #
struct BuilderFactory {
}
fn (BuilderFactory) node_local #
fn (mut bldr BuilderFactory) node_local() !&Node
get node connection to local machine
fn (BuilderFactory) node_new #
fn (mut bldr BuilderFactory) node_new(args_ NodeArguments) !&Node
the factory which returns an node, based on the arguments will chose ssh executor or the local one .
- format ipaddr: localhost:7777
- format ipaddr: myuser@192.168.6.6:7777
- format ipaddr: 192.168.6.6
- format ipaddr: any ipv6 addr
- if only name used then is localhost with localhost executor
its possible to put a user as user@ .. in front of an ip addr . .
pub struct NodeArguments {
ipaddr string
name string //if not filled in will come from ipaddr
user string = "root"
debug bool
reset bool
}
struct EnvGetParams #
struct EnvGetParams {
pub mut:
reload bool
}
struct ExecArgs #
struct ExecArgs {
pub mut:
cmd string
stdout bool = true
}
struct ExecRetryArgs #
struct ExecRetryArgs {
pub:
cmd string
retrymax int = 10 // how may times maximum to retry
period_milli int = 100 // sleep in between retry in milliseconds
timeout int = 2 // timeout for al the tries together
stdout bool = true
}
pub fn (mut node Node) exec_file(args_ ExecFileArgs) !string { mut args:=args_ if args.path == '' { now := ourtime.now() args.path="/tmp/myexec_${now.key()}.sh" } if args.cmd == '' { return error('need to specify cmd') } args.cmd = texttools.dedent(args.cmd) node.file_write(args.path, args.cmd)! return node.exec_silent('chmod +x ${args.path} && bash ${args.path} && rm -f ${args.path}')! // }
struct ExecutorLocal #
struct ExecutorLocal {
retry int = 1 // nr of times something will be retried before failing, need to check also what error is, only things which should be retried need to be done, default 1 because is local
pub mut:
debug bool
}
fn (ExecutorLocal) exec #
fn (mut executor ExecutorLocal) exec(args ExecArgs) !string
fn (ExecutorLocal) exec_interactive #
fn (mut executor ExecutorLocal) exec_interactive(args ExecArgs) !
fn (ExecutorLocal) file_write #
fn (mut executor ExecutorLocal) file_write(path string, text string) !
fn (ExecutorLocal) file_read #
fn (mut executor ExecutorLocal) file_read(path string) !string
fn (ExecutorLocal) file_exists #
fn (mut executor ExecutorLocal) file_exists(path string) bool
fn (ExecutorLocal) debug_on #
fn (mut executor ExecutorLocal) debug_on()
fn (ExecutorLocal) debug_off #
fn (mut executor ExecutorLocal) debug_off()
fn (ExecutorLocal) delete #
fn (mut executor ExecutorLocal) delete(path string) !
carefull removes everything
fn (ExecutorLocal) environ_get #
fn (mut executor ExecutorLocal) environ_get() !map[string]string
get environment variables from the executor
fn (ExecutorLocal) info #
fn (mut executor ExecutorLocal) info() map[string]string
fn (ExecutorLocal) upload #
fn (mut executor ExecutorLocal) upload(args SyncArgs) !
upload from local FS to executor FS
fn (ExecutorLocal) download #
fn (mut executor ExecutorLocal) download(args SyncArgs) !
download from executor FS to local FS
fn (ExecutorLocal) shell #
fn (mut executor ExecutorLocal) shell(cmd string) !
fn (ExecutorLocal) list #
fn (mut executor ExecutorLocal) list(path string) ![]string
fn (ExecutorLocal) dir_exists #
fn (mut executor ExecutorLocal) dir_exists(path string) bool
struct ExecutorNewArguments #
struct ExecutorNewArguments {
pub mut:
local bool // if this set then will always be the local machine
ipaddr string
user string = 'root'
debug bool
}
struct ExecutorSSH #
struct ExecutorSSH {
pub mut:
ipaddr ipaddress.IPAddress
sshkey string
user string = 'root' // default will be root
initialized bool
retry int = 1 // nr of times something will be retried before failing, need to check also what error is, only things which should be retried need to be done
debug bool = true
}
fn (ExecutorSSH) debug_on #
fn (mut executor ExecutorSSH) debug_on()
fn (ExecutorSSH) debug_off #
fn (mut executor ExecutorSSH) debug_off()
fn (ExecutorSSH) exec #
fn (mut executor ExecutorSSH) exec(args_ ExecArgs) !string
fn (ExecutorSSH) exec_interactive #
fn (mut executor ExecutorSSH) exec_interactive(args_ ExecArgs) !
fn (ExecutorSSH) file_write #
fn (mut executor ExecutorSSH) file_write(path string, text string) !
fn (ExecutorSSH) file_read #
fn (mut executor ExecutorSSH) file_read(path string) !string
fn (ExecutorSSH) file_exists #
fn (mut executor ExecutorSSH) file_exists(path string) bool
fn (ExecutorSSH) delete #
fn (mut executor ExecutorSSH) delete(path string) !
carefull removes everything
fn (ExecutorSSH) download #
fn (mut executor ExecutorSSH) download(args SyncArgs) !
upload from local FS to executor FS
fn (ExecutorSSH) upload #
fn (mut executor ExecutorSSH) upload(args SyncArgs) !
download from executor FS to local FS
fn (ExecutorSSH) environ_get #
fn (mut executor ExecutorSSH) environ_get() !map[string]string
get environment variables from the executor
fn (ExecutorSSH) info #
fn (mut executor ExecutorSSH) info() map[string]string
fn (ExecutorSSH) shell #
fn (mut executor ExecutorSSH) shell(cmd string) !
ssh shell on the node default ssh port, or any custom port that may be forwarding ssh traffic to certain container
fn (ExecutorSSH) list #
fn (mut executor ExecutorSSH) list(path string) ![]string
fn (ExecutorSSH) dir_exists #
fn (mut executor ExecutorSSH) dir_exists(path string) bool
struct ForwardArgsToLocal #
struct ForwardArgsToLocal {
pub mut:
name string @[required]
address string @[required]
remote_port int @[required]
local_port int
user string = 'root'
}
struct HeroInstallArgs #
struct HeroInstallArgs {
pub mut:
reset bool
}
struct HeroUpdateArgs #
struct HeroUpdateArgs {
pub mut:
sync_from_local bool // will sync local hero lib to the remote, then cannot use git
sync_full bool // sync the full herolib repo
sync_fast bool = true // don't hash the files, there is small chance on error
git_reset bool // will get the code from github at remote and reset changes
git_pull bool // will pull the code but not reset, will give error if it can't reset
branch string
}
' console.print_debug('executing cmd ${cmd}') node.exec_cmd(cmd: cmd)! }
struct Node #
struct Node {
mut:
factory &BuilderFactory @[skip; str: skip]
pub mut:
name string = 'unknown'
executor Executor @[skip; str: skip]
platform PlatformType
cputype CPUType
done map[string]string
environment map[string]string
params Params
hostname string
}
fn (Node) cmd_exists #
fn (mut node Node) cmd_exists(cmd string) bool
check command exists on the platform, knows how to deal with different platforms
fn (Node) command_exists #
fn (mut node Node) command_exists(cmd string) bool
checks if given executable exists in node
fn (Node) dagu_install #
fn (mut node Node) dagu_install() !
fn (Node) debug_off #
fn (mut node Node) debug_off()
fn (Node) debug_on #
fn (mut node Node) debug_on()
fn (Node) delete #
fn (mut node Node) delete(path string) !
fn (Node) dir_exists #
fn (mut node Node) dir_exists(path string) bool
fn (Node) done_exists #
fn (mut node Node) done_exists(key string) bool
fn (Node) done_get #
fn (mut node Node) done_get(key string) ?string
fn (Node) done_get_int #
fn (mut node Node) done_get_int(key string) int
will return 0 if it doesnt exist
fn (Node) done_get_str #
fn (mut node Node) done_get_str(key string) string
will return empty string if it doesnt exist
fn (Node) done_print #
fn (mut node Node) done_print()
fn (Node) done_reset #
fn (mut node Node) done_reset() !
fn (Node) done_set #
fn (mut node Node) done_set(key string, val string) !
fn (Node) download #
fn (mut node Node) download(args_ SyncArgs) !
download files using rsync (can be ssh or local) . args: .
source string
dest string
delete bool //do we want to delete the destination
ignore []string //arguments to ignore e.g. ['*.pyc','*.bak']
ignore_default bool = true //if set will ignore a common set
stdout bool = true
.
fn (Node) environ_get #
fn (mut node Node) environ_get(args EnvGetParams) !map[string]string
fn (Node) exec #
fn (mut node Node) exec(args ExecArgs) !string
exec(cmd string) !string exec_silent(cmd string) !string file_write(path string, text string) ! file_read(path string) !string file_exists(path string) bool delete(path string) !
fn (Node) exec_cmd #
fn (mut node Node) exec_cmd(args_ NodeExecCmd) !string
cmd: cmd to execute . period in sec, e.g. if 3600, means will only execute this if not done yet within the hour . . ARGS: .
struct NodeExecCmd{
cmd string
period int //period in which we check when this was done last, if 0 then period is indefinite
reset bool = true
description string
checkkey string //if used will use this one in stead of hash of cmd, to check if was executed already
}
fn (Node) exec_interactive #
fn (mut node Node) exec_interactive(cmd_ string) !
fn (Node) exec_ok #
fn (mut node Node) exec_ok(cmd string) bool
check if we can execute and there is not errorcode
fn (Node) exec_retry #
fn (mut node Node) exec_retry(args ExecRetryArgs) !string
a cool way to execute something until it succeeds params: cmd string retrymax int = 10 //how may times maximum to retry period_milli int = 100 //sleep in between retry in milliseconds timeout int = 2 //timeout for al the tries together
fn (Node) exec_silent #
fn (mut node Node) exec_silent(cmd string) !string
silently execute a command
fn (Node) file_exists #
fn (mut node Node) file_exists(path string) bool
fn (Node) file_read #
fn (mut node Node) file_read(path string) !string
fn (Node) file_write #
fn (mut node Node) file_write(path string, text string) !
fn (Node) hero_compile #
fn (mut node Node) hero_compile() !
fn (Node) hero_compile_sync #
fn (mut node Node) hero_compile_sync() !
sync local hero code to rmote and then compile hero
fn (Node) hero_install #
fn (mut node Node) hero_install() !
fn (Node) hero_update #
fn (mut node Node) hero_update(args_ HeroUpdateArgs) !
execute vscript on remote node
fn (Node) info #
fn (mut node Node) info() map[string]string
fn (Node) ipaddr_pub_get #
fn (mut node Node) ipaddr_pub_get() !string
return the ipaddress as known on the public side is using resolver4.opendns.com
fn (Node) key #
fn (mut node Node) key() string
get unique key for the node, as used in caching environment
fn (Node) list #
fn (mut node Node) list(path string) ![]string
list(path string) ![]string dir_exists(path string) bool debug_off() debug_on()
fn (Node) load #
fn (mut node Node) load() !bool
load the node from redis cache, if not there will load from system . return true if the data was in redis (cache)
fn (Node) package_install #
fn (mut node Node) package_install(package Package) !
fn (Node) package_refresh #
fn (mut node Node) package_refresh() !
fn (Node) readfromsystem #
fn (mut node Node) readfromsystem() !
get remote environment arguments in memory
fn (Node) save #
fn (mut node Node) save() !
get remote environment arguments in memory
fn (Node) shell #
fn (mut node Node) shell(cmd string) !
fn (Node) sync_code #
fn (mut node Node) sync_code(name string, src_ string, dest string, fast_rsync bool) !
fn (Node) upgrade #
fn (mut node Node) upgrade() !
fn (Node) upload #
fn (mut node Node) upload(args_ SyncArgs) !
upload files using rsync (can be ssh or local) args: .
source string
dest string
delete bool //do we want to delete the destination
ignore []string //arguments to ignore e.g. ['*.pyc','*.bak']
ignore_default bool = true //if set will ignore a common set
stdout bool = true
.
fn (Node) vscript #
fn (mut node Node) vscript(args_ VScriptArgs) !
struct NodeArguments #
struct NodeArguments {
pub mut:
ipaddr string
name string
user string = 'root'
debug bool
reload bool
}
format ipaddr: localhost:7777 . format ipaddr: 192.168.6.6:7777 . format ipaddr: 192.168.6.6 . format ipaddr: any ipv6 addr . format ipaddr: if only name used then is localhost .
struct NodeExecCmd #
struct NodeExecCmd {
pub mut:
name string = 'default'
cmd string
period int // period in which we check when this was done last, if 0 then period is indefinite
reset bool = true // means do again or not
remove_installer bool = true // delete the installer
description string
stdout bool = true
checkkey string // if used will use this one in stead of hash of cmd, to check if was executed already
tmpdir string
ignore_error_codes []int
}
struct NodeLocalArgs #
struct NodeLocalArgs {
pub:
reload bool
}
struct Package #
struct Package {
name string
description string
version string
aliases []PackageAlias
}
is e.g. an ubuntu packagedapp, it needs to be packaged by the package maintainers !
fn (Package) name_get #
fn (mut package Package) name_get(platformtype PlatformType) string
get the right name depending the platform type
fn (Package) version_get #
fn (mut package Package) version_get(platformtype PlatformType) string
get the right name depending the platform type
struct PackageAlias #
struct PackageAlias {
name string
platformtype PlatformType
version string
}
if there is an exception of how package needs to be installed (alias) e.g. on ubuntu something is called myapp but on alpine its my_app
struct SyncArgs #
struct SyncArgs {
pub mut:
source string
dest string
delete bool // do we want to delete the destination
ipaddr string // e.g. root@192.168.5.5:33 (can be without root@ or :port)
ignore []string // arguments to ignore e.g. ['*.pyc','*.bak']
ignore_default bool = true // if set will ignore a common set
stdout bool = true
fast_rsync bool = true
}
struct ThisRemoteArgs #
struct ThisRemoteArgs {
pub mut:
name string = 'remote'
nodes string
script string
sync_from_local bool
}
struct VScriptArgs #
struct VScriptArgs {
pub mut:
path string
sync_from_local bool // will sync local hero lib to the remote
git_reset bool // will get the code from github at remote and reset changes
git_pull bool // will pull the code but not reset, will give error if it can't reset
branch string // can only be used when git used
}
- README
- fn bootstrapper
- fn new
- fn node_local
- fn portforward_to_local
- fn this_remote_exec
- enum CPUType
- enum PlatformType
- struct BootStrapper
- struct BootstrapperArgs
- struct BuilderFactory
- struct EnvGetParams
- struct ExecArgs
- struct ExecRetryArgs
- struct ExecutorLocal
- struct ExecutorNewArguments
- struct ExecutorSSH
- struct ForwardArgsToLocal
- struct HeroInstallArgs
- struct HeroUpdateArgs
- struct Node
- fn cmd_exists
- fn command_exists
- fn dagu_install
- fn debug_off
- fn debug_on
- fn delete
- fn dir_exists
- fn done_exists
- fn done_get
- fn done_get_int
- fn done_get_str
- fn done_print
- fn done_reset
- fn done_set
- fn download
- fn environ_get
- fn exec
- fn exec_cmd
- fn exec_interactive
- fn exec_ok
- fn exec_retry
- fn exec_silent
- fn file_exists
- fn file_read
- fn file_write
- fn hero_compile
- fn hero_compile_sync
- fn hero_install
- fn hero_update
- fn info
- fn ipaddr_pub_get
- fn key
- fn list
- fn load
- fn package_install
- fn package_refresh
- fn readfromsystem
- fn save
- fn shell
- fn sync_code
- fn upgrade
- fn upload
- fn vscript
- struct NodeArguments
- struct NodeExecCmd
- struct NodeLocalArgs
- struct Package
- struct PackageAlias
- struct SyncArgs
- struct ThisRemoteArgs
- struct VScriptArgs