324 lines
8.1 KiB
Markdown
324 lines
8.1 KiB
Markdown
# Builder Module: System Automation and Remote Execution
|
|
|
|
The `builder` module in Herolib provides a powerful framework for automating system tasks and executing commands on both local and remote machines. It offers a unified interface to manage nodes, execute commands, perform file operations, and maintain persistent state.
|
|
|
|
## Key Components
|
|
|
|
- **`BuilderFactory`**: Responsible for creating and managing `Node` instances.
|
|
- **`Node`**: Represents a target system (local or remote). It encapsulates system properties (platform, CPU type, environment variables) and provides methods for interaction.
|
|
- **`Executor`**: An interface (implemented by `ExecutorLocal` and `ExecutorSSH`) that handles the actual command execution and file operations on the target system.
|
|
- **NodeDB (via `Node.done` map)**: A key-value store within each `Node` for persistent state, caching, and tracking execution history.
|
|
|
|
## Getting Started
|
|
|
|
### Initializing a Builder and Node
|
|
|
|
First, import the `builder` module and create a new `BuilderFactory` instance. Then, create a `Node` object, which can represent either the local machine or a remote server.
|
|
|
|
```v
|
|
import incubaid.herolib.builder
|
|
|
|
// Create a new builder factory
|
|
mut b := builder.new()!
|
|
|
|
// Create a node for the local machine
|
|
mut local_node := b.node_local()!
|
|
|
|
// Create a node for a remote server via SSH
|
|
// Format: "user@ip_address:port" or "ip_address:port" or "ip_address"
|
|
mut remote_node := b.node_new(ipaddr: "root@195.192.213.2:2222")!
|
|
|
|
// Node with custom name and debug enabled
|
|
mut named_debug_node := b.node_new(
|
|
name: "my_remote_server",
|
|
ipaddr: "user@server.example.com:22",
|
|
debug: true
|
|
)!
|
|
```
|
|
|
|
### `Node` Properties
|
|
|
|
A `Node` object automatically detects and caches system information. You can access these properties:
|
|
|
|
```v
|
|
// Get platform type (e.g., .osx, .ubuntu, .alpine, .arch)
|
|
println(node.platform)
|
|
|
|
// Get CPU architecture (e.g., .intel, .arm)
|
|
println(node.cputype)
|
|
|
|
// Get hostname
|
|
println(node.hostname)
|
|
|
|
// Get environment variables
|
|
env_vars := node.environ_get()!
|
|
println(env_vars['HOME'])
|
|
|
|
// Get node information (category, sshkey, user, ipaddress, port)
|
|
info := node.info()
|
|
println(info['category'])
|
|
```
|
|
|
|
## Command Execution
|
|
|
|
The `Node` object provides methods to execute commands on the target system.
|
|
|
|
### `node.exec(args ExecArgs) !string`
|
|
|
|
Executes a command and returns its standard output.
|
|
|
|
```v
|
|
import incubaid.herolib.builder { ExecArgs }
|
|
|
|
// Execute a command with stdout
|
|
result := node.exec(cmd: "ls -la /tmp", stdout: true)!
|
|
println(result)
|
|
|
|
// Execute silently (no stdout)
|
|
node.exec(cmd: "mkdir -p /tmp/my_dir", stdout: false)!
|
|
```
|
|
|
|
### `node.exec_silent(cmd string) !string`
|
|
|
|
Executes a command silently (no stdout) and returns its output.
|
|
|
|
```v
|
|
output := node.exec_silent("echo 'Hello from remote!'")!
|
|
println(output)
|
|
```
|
|
|
|
### `node.exec_interactive(cmd string) !`
|
|
|
|
Executes a command in an interactive shell.
|
|
|
|
```v
|
|
// This will open an interactive shell session
|
|
node.exec_interactive("bash")!
|
|
```
|
|
|
|
### `node.exec_cmd(args NodeExecCmd) !string`
|
|
|
|
A more advanced command execution method that supports caching, periodic execution, and temporary script handling.
|
|
|
|
```v
|
|
import incubaid.herolib.builder { NodeExecCmd }
|
|
|
|
// Execute a command, cache its result for 24 hours (48*3600 seconds)
|
|
// and provide a description for logging.
|
|
result := node.exec_cmd(
|
|
cmd: "apt-get update",
|
|
period: 48 * 3600,
|
|
description: "Update system packages"
|
|
)!
|
|
println(result)
|
|
|
|
// Execute a multi-line script
|
|
script_output := node.exec_cmd(
|
|
cmd: "
|
|
echo 'Starting script...'
|
|
ls -la /
|
|
echo 'Script finished.'
|
|
",
|
|
name: "my_custom_script",
|
|
stdout: true
|
|
)!
|
|
println(script_output)
|
|
```
|
|
|
|
### `node.exec_retry(args ExecRetryArgs) !string`
|
|
|
|
Executes a command with retries until it succeeds or a timeout is reached.
|
|
|
|
```v
|
|
import incubaid.herolib.builder { ExecRetryArgs }
|
|
|
|
// Try to connect to a service, retrying every 100ms for up to 10 seconds
|
|
result := node.exec_retry(
|
|
cmd: "curl --fail http://localhost:8080/health",
|
|
retrymax: 100, // 100 retries
|
|
period_milli: 100, // 100ms sleep between retries
|
|
timeout: 10 // 10 seconds total timeout
|
|
)!
|
|
println("Service is up: ${result}")
|
|
```
|
|
|
|
### `node.cmd_exists(cmd string) bool`
|
|
|
|
Checks if a command exists on the target system.
|
|
|
|
```v
|
|
if node.cmd_exists("docker") {
|
|
println("Docker is installed.")
|
|
} else {
|
|
println("Docker is not installed.")
|
|
}
|
|
```
|
|
|
|
## File System Operations
|
|
|
|
The `Node` object provides comprehensive file and directory management capabilities.
|
|
|
|
### `node.file_write(path string, text string) !`
|
|
|
|
Writes content to a file on the target system.
|
|
|
|
```v
|
|
node.file_write("/tmp/my_file.txt", "This is some content.")!
|
|
```
|
|
|
|
### `node.file_read(path string) !string`
|
|
|
|
Reads content from a file on the target system.
|
|
|
|
```v
|
|
content := node.file_read("/tmp/my_file.txt")!
|
|
println(content)
|
|
```
|
|
|
|
### `node.file_exists(path string) bool`
|
|
|
|
Checks if a file or directory exists on the target system.
|
|
|
|
```v
|
|
if node.file_exists("/tmp/my_file.txt") {
|
|
println("File exists.")
|
|
}
|
|
```
|
|
|
|
### `node.delete(path string) !`
|
|
|
|
Deletes a file or directory (recursively for directories) on the target system.
|
|
|
|
```v
|
|
node.delete("/tmp/my_dir")!
|
|
```
|
|
|
|
### `node.list(path string) ![]string`
|
|
|
|
Lists the contents of a directory on the target system.
|
|
|
|
```v
|
|
files := node.list("/home/user")!
|
|
for file in files {
|
|
println(file)
|
|
}
|
|
```
|
|
|
|
### `node.dir_exists(path string) bool`
|
|
|
|
Checks if a directory exists on the target system.
|
|
|
|
```v
|
|
if node.dir_exists("/var/log") {
|
|
println("Log directory exists.")
|
|
}
|
|
```
|
|
|
|
### File Transfers (`node.upload` and `node.download`)
|
|
|
|
Transfer files between the local machine and the target node using `rsync` or `scp`.
|
|
|
|
```v
|
|
import incubaid.herolib.builder { SyncArgs }
|
|
|
|
// Upload a local file to the remote node
|
|
node.upload(
|
|
source: "/local/path/to/my_script.sh",
|
|
dest: "/tmp/remote_script.sh",
|
|
stdout: true // Show rsync/scp output
|
|
)!
|
|
|
|
// Download a file from the remote node to the local machine
|
|
node.download(
|
|
source: "/var/log/syslog",
|
|
dest: "/tmp/local_syslog.log",
|
|
stdout: false
|
|
)!
|
|
|
|
// Upload a directory, ignoring .git and examples folders, and deleting extra files on destination
|
|
node.upload(
|
|
source: "/local/repo/",
|
|
dest: "~/code/my_project/",
|
|
ignore: [".git/*", "examples/"],
|
|
delete: true,
|
|
fast_rsync: true
|
|
)!
|
|
```
|
|
|
|
## Node Database (`node.done`)
|
|
|
|
The `node.done` map provides a simple key-value store for persistent data on the node. This data is cached in Redis.
|
|
|
|
```v
|
|
// Store a value
|
|
node.done_set("setup_complete", "true")!
|
|
|
|
// Retrieve a value
|
|
status := node.done_get("setup_complete") or { "false" }
|
|
println("Setup complete: ${status}")
|
|
|
|
// Check if a key exists
|
|
if node.done_exists("initial_config") {
|
|
println("Initial configuration done.")
|
|
}
|
|
|
|
// Print all stored 'done' items
|
|
node.done_print()
|
|
|
|
// Reset all stored 'done' items
|
|
node.done_reset()!
|
|
```
|
|
|
|
## Bootstrapping and Updates
|
|
|
|
The `bootstrapper` module provides functions for installing and updating Herolib components on nodes.
|
|
|
|
### `node.hero_install() !`
|
|
|
|
Installs the Herolib environment on the node.
|
|
|
|
```v
|
|
node.hero_install()!
|
|
```
|
|
|
|
### `node.hero_update(args HeroUpdateArgs) !`
|
|
|
|
Updates the Herolib code on the node, with options for syncing from local, git reset, or git pull.
|
|
|
|
```v
|
|
import incubaid.herolib.builder { HeroUpdateArgs }
|
|
|
|
// Sync local Herolib code to the remote node (full sync)
|
|
node.hero_update(sync_from_local: true, sync_full: true)!
|
|
|
|
// Reset git repository on the remote node and pull latest from 'dev' branch
|
|
node.hero_update(git_reset: true, branch: "dev")!
|
|
```
|
|
|
|
### `node.vscript(args VScriptArgs) !`
|
|
|
|
Uploads and executes a Vlang script (`.vsh` or `.v`) on the remote node.
|
|
|
|
```v
|
|
import incubaid.herolib.builder { VScriptArgs }
|
|
|
|
// Upload and execute a local V script on the remote node
|
|
node.vscript(path: "/local/path/to/my_script.vsh", sync_from_local: true)!
|
|
```
|
|
|
|
## Port Forwarding
|
|
|
|
The `portforward_to_local` function allows forwarding a remote port on an SSH host to a local port.
|
|
|
|
```v
|
|
import incubaid.herolib.builder { portforward_to_local, ForwardArgsToLocal }
|
|
|
|
// Forward remote port 8080 on 192.168.1.100 to local port 9000
|
|
portforward_to_local(
|
|
name: "my_app_forward",
|
|
address: "192.168.1.100",
|
|
remote_port: 8080,
|
|
local_port: 9000
|
|
)!
|
|
```
|