This commit is contained in:
2025-05-31 16:02:52 +03:00
parent b6a2671665
commit d0baac83a9
15 changed files with 1544 additions and 40 deletions

View File

@@ -23,25 +23,9 @@
"name": "OpenRPCSpec",
"description": "The OpenRPC specification",
"schema": {
"type": "object"
"type": "string"
}
},
"examples": [
{
"name": "Get API specification",
"params": [],
"result": {
"name": "OpenRPCSpecResult",
"value": {
"openrpc": "1.2.6",
"info": {
"version": "1.0.0",
"title": "Zinit JSON-RPC API"
}
}
}
}
]
}
},
{
"name": "service_list",

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.schemas.jsonrpc
import freeflowuniverse.herolib.schemas.openrpc
import json
import x.json2
// Define the service status response structure based on the OpenRPC schema
struct ServiceStatus {
@@ -25,10 +27,15 @@ println('This will return the OpenRPC specification for the API')
// Use map[string]string for the result to avoid json2.Any issues
api_spec_raw := cl.send[[]string, string](discover_request)!
api_spec_any := json2.raw_decode(api_spec_raw)! //checks the json format
// Print the decoded JSON structure to see the keys
// println('API Specification (decoded structure):')
// println(api_spec_any)
mut myschema:=openrpc.decode(api_spec_raw)!
println('API Specification Methods (parsed):\n${myschema.methods}')
// Parse the JSON string manually
println('API Specification (raw):')
println(api_spec_raw)
// Example 2: List all services
// Create a request for service_list method with empty parameters

152
lib/clients/zinit/README.md Normal file
View File

@@ -0,0 +1,152 @@
# Zinit OpenRPC Client
This is a V language client for the Zinit service manager, implementing the OpenRPC specification.
## Overview
Zinit is a service manager that allows you to manage and monitor services on your system. This client provides a comprehensive API to interact with Zinit via its JSON-RPC interface.
## Features
- Complete implementation of all methods in the Zinit OpenRPC specification
- Type-safe API with proper error handling
- Comprehensive documentation
- Helper functions for common operations
- Example code for all operations
## Usage
### Basic Example
```v
import freeflowuniverse.heroweb.clients.zinit
fn main() {
// Create a new client with the default socket path
mut client := zinit.new_default_client()
// List all services
services := client.service_list() or {
println('Error: ${err}')
return
}
// Print the services
for name, state in services {
println('${name}: ${state}')
}
// Get status of a specific service
if services.len > 0 {
service_name := services.keys()[0]
status := client.service_status(service_name) or {
println('Error: ${err}')
return
}
println('Service: ${status.name}')
println('State: ${status.state}')
println('PID: ${status.pid}')
}
}
```
### Creating and Managing Services
```v
import freeflowuniverse.heroweb.clients.zinit
fn main() {
mut client := zinit.new_default_client()
// Create a new service configuration
config := zinit.ServiceConfig{
exec: '/bin/echo "Hello, World!"'
oneshot: true
log: zinit.log_stdout
env: {
'ENV_VAR': 'value'
}
}
// Create the service
client.service_create('hello', config) or {
println('Error creating service: ${err}')
return
}
// Start the service
client.service_start('hello') or {
println('Error starting service: ${err}')
return
}
// Get the service logs
logs := client.stream_current_logs('hello') or {
println('Error getting logs: ${err}')
return
}
for log in logs {
println(log)
}
// Clean up
client.service_stop('hello') or {}
client.service_forget('hello') or {}
client.service_delete('hello') or {}
}
```
## API Reference
### Client Creation
- `new_client(socket_path string) &Client` - Create a new client with a custom socket path
- `new_default_client() &Client` - Create a new client with the default socket path (`/tmp/zinit.sock`)
### Service Management
- `service_list() !map[string]string` - List all services and their states
- `service_status(name string) !ServiceStatus` - Get detailed status of a service
- `service_start(name string) !` - Start a service
- `service_stop(name string) !` - Stop a service
- `service_monitor(name string) !` - Start monitoring a service
- `service_forget(name string) !` - Stop monitoring a service
- `service_kill(name string, signal string) !` - Send a signal to a service
- `service_create(name string, config ServiceConfig) !string` - Create a new service
- `service_delete(name string) !string` - Delete a service
- `service_get(name string) !ServiceConfig` - Get a service configuration
- `service_stats(name string) !ServiceStats` - Get memory and CPU usage statistics
### System Operations
- `system_shutdown() !` - Stop all services and power off the system
- `system_reboot() !` - Stop all services and reboot the system
- `system_start_http_server(address string) !string` - Start an HTTP/RPC server
- `system_stop_http_server() !` - Stop the HTTP/RPC server
### Logs
- `stream_current_logs(name ?string) ![]string` - Get current logs
- `stream_subscribe_logs(name ?string) !string` - Subscribe to log messages
## Constants
- `default_socket_path` - Default Unix socket path (`/tmp/zinit.sock`)
- `state_running`, `state_success`, `state_error`, etc. - Common service states
- `target_up`, `target_down` - Common service targets
- `log_null`, `log_ring`, `log_stdout` - Common log types
- `signal_term`, `signal_kill`, etc. - Common signals
## Helper Functions
- `new_service_config(exec string) ServiceConfig` - Create a basic service configuration
- `new_oneshot_service_config(exec string) ServiceConfig` - Create a oneshot service configuration
- `is_service_not_found_error(err IError) bool` - Check if an error is a "service not found" error
- `format_memory_usage(bytes i64) string` - Format memory usage in human-readable format
- `format_cpu_usage(cpu_percent f64) string` - Format CPU usage
## License
MIT

18
lib/clients/zinit/error.v Normal file
View File

@@ -0,0 +1,18 @@
module zinit
// Request Types for Zinit API
//
// This file contains all the request types used by the Zinit API.
// ZinitError represents an error returned by the zinit API
pub struct ZinitError {
pub mut:
code int // Error code
message string // Error message
data string // Additional error data
}
// Error implements the error interface for ZinitError
pub fn (e ZinitError) msg() string {
return 'Zinit Error ${e.code}: ${e.message} - ${e.data}'
}

View File

@@ -0,0 +1,23 @@
module zinit
import freeflowuniverse.herolib.schemas.jsonrpc
// Client is an OpenRPC client for Zinit
pub struct Client {
mut:
rpc_client &jsonrpc.Client
}
@[params]
pub struct ClientParams {
path string = '/tmp/zinit.sock' // Path to the Zinit RPC socket
}
// new_client creates a new Zinit RPC client with a custom socket path
pub fn new_client(args_ ClientParams) &Client {
mut args:=args_
mut cl := jsonrpc.new_unix_socket_client(args.path)
return &Client{
rpc_client: cl
}
}

87
lib/clients/zinit/model.v Normal file
View File

@@ -0,0 +1,87 @@
module zinit
// ServiceCreateResponse represents the response from service_create
pub struct ServiceCreateResponse {
pub mut:
path string // Path to the created service file
}
// ServiceDeleteResponse represents the response from service_delete
pub struct ServiceDeleteResponse {
pub mut:
result string // Result of the delete operation
}
// SystemStartHttpServerResponse represents the response from system_start_http_server
pub struct SystemStartHttpServerResponse {
pub mut:
result string // Result of starting the HTTP server
}
// StreamCurrentLogsResponse represents the response from stream_currentLogs
pub struct StreamCurrentLogsResponse {
pub mut:
logs []string // Log entries
}
// StreamSubscribeLogsResponse represents the response from stream_subscribeLogs
pub struct StreamSubscribeLogsResponse {
pub mut:
subscription_id string // ID of the log subscription
}
// Module version information
pub const (
version = '1.0.0'
author = 'Hero Code'
license = 'MIT'
)
// Default socket path for zinit
pub const default_socket_path = '/tmp/zinit.sock'
// Common service states
pub const (
state_running = 'Running'
state_success = 'Success'
state_error = 'Error'
state_stopped = 'Stopped'
state_failed = 'Failed'
)
// Common service targets
pub const (
target_up = 'Up'
target_down = 'Down'
)
// Common log types
pub const (
log_null = 'null'
log_ring = 'ring'
log_stdout = 'stdout'
)
// Common signals
pub const (
signal_term = 'SIGTERM'
signal_kill = 'SIGKILL'
signal_hup = 'SIGHUP'
signal_usr1 = 'SIGUSR1'
signal_usr2 = 'SIGUSR2'
)
// JSON-RPC error codes as defined in the OpenRPC specification
pub const (
error_service_not_found = -32000
error_service_already_monitored = -32001
error_service_is_up = -32002
error_service_is_down = -32003
error_invalid_signal = -32004
error_config_error = -32005
error_shutting_down = -32006
error_service_already_exists = -32007
error_service_file_error = -32008
)

View File

@@ -0,0 +1,873 @@
{
"openrpc": "1.2.6",
"info": {
"version": "1.0.0",
"title": "Zinit JSON-RPC API",
"description": "JSON-RPC 2.0 API for controlling and querying Zinit services",
"license": {
"name": "MIT"
}
},
"servers": [
{
"name": "Unix Socket",
"url": "unix:///tmp/zinit.sock"
}
],
"methods": [
{
"name": "rpc.discover",
"description": "Returns the OpenRPC specification for the API",
"params": [],
"result": {
"name": "OpenRPCSpec",
"description": "The OpenRPC specification",
"schema": {
"type": "object"
}
},
"examples": [
{
"name": "Get API specification",
"params": [],
"result": {
"name": "OpenRPCSpecResult",
"value": {
"openrpc": "1.2.6",
"info": {
"version": "1.0.0",
"title": "Zinit JSON-RPC API"
}
}
}
}
]
},
{
"name": "service_list",
"description": "Lists all services managed by Zinit",
"params": [],
"result": {
"name": "ServiceList",
"description": "A map of service names to their current states",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string",
"description": "Service state (Running, Success, Error, etc.)"
}
}
},
"examples": [
{
"name": "List all services",
"params": [],
"result": {
"name": "ServiceListResult",
"value": {
"service1": "Running",
"service2": "Success",
"service3": "Error"
}
}
}
]
},
{
"name": "service_status",
"description": "Shows detailed status information for a specific service",
"params": [
{
"name": "name",
"description": "The name of the service",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ServiceStatus",
"description": "Detailed status information for the service",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Service name"
},
"pid": {
"type": "integer",
"description": "Process ID of the running service (if running)"
},
"state": {
"type": "string",
"description": "Current state of the service (Running, Success, Error, etc.)"
},
"target": {
"type": "string",
"description": "Target state of the service (Up, Down)"
},
"after": {
"type": "object",
"description": "Dependencies of the service and their states",
"additionalProperties": {
"type": "string",
"description": "State of the dependency"
}
}
}
}
},
"examples": [
{
"name": "Get status of redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ServiceStatusResult",
"value": {
"name": "redis",
"pid": 1234,
"state": "Running",
"target": "Up",
"after": {
"dependency1": "Success",
"dependency2": "Running"
}
}
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
}
]
},
{
"name": "service_start",
"description": "Starts a service",
"params": [
{
"name": "name",
"description": "The name of the service to start",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StartResult",
"description": "Result of the start operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Start redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "StartResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
}
]
},
{
"name": "service_stop",
"description": "Stops a service",
"params": [
{
"name": "name",
"description": "The name of the service to stop",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StopResult",
"description": "Result of the stop operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Stop redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "StopResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
}
]
},
{
"name": "service_monitor",
"description": "Starts monitoring a service. The service configuration is loaded from the config directory.",
"params": [
{
"name": "name",
"description": "The name of the service to monitor",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "MonitorResult",
"description": "Result of the monitor operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Monitor redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "MonitorResult",
"value": null
}
}
],
"errors": [
{
"code": -32001,
"message": "Service already monitored",
"data": "service \"redis\" already monitored"
},
{
"code": -32005,
"message": "Config error",
"data": "failed to load service configuration"
}
]
},
{
"name": "service_forget",
"description": "Stops monitoring a service. You can only forget a stopped service.",
"params": [
{
"name": "name",
"description": "The name of the service to forget",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ForgetResult",
"description": "Result of the forget operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Forget redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ForgetResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32002,
"message": "Service is up",
"data": "service \"redis\" is up"
}
]
},
{
"name": "service_kill",
"description": "Sends a signal to a running service",
"params": [
{
"name": "name",
"description": "The name of the service to send the signal to",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "signal",
"description": "The signal to send (e.g., SIGTERM, SIGKILL)",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "KillResult",
"description": "Result of the kill operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Send SIGTERM to redis service",
"params": [
{
"name": "name",
"value": "redis"
},
{
"name": "signal",
"value": "SIGTERM"
}
],
"result": {
"name": "KillResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
},
{
"code": -32004,
"message": "Invalid signal",
"data": "invalid signal: INVALID"
}
]
},
{
"name": "system_shutdown",
"description": "Stops all services and powers off the system",
"params": [],
"result": {
"name": "ShutdownResult",
"description": "Result of the shutdown operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Shutdown the system",
"params": [],
"result": {
"name": "ShutdownResult",
"value": null
}
}
],
"errors": [
{
"code": -32006,
"message": "Shutting down",
"data": "system is already shutting down"
}
]
},
{
"name": "system_reboot",
"description": "Stops all services and reboots the system",
"params": [],
"result": {
"name": "RebootResult",
"description": "Result of the reboot operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Reboot the system",
"params": [],
"result": {
"name": "RebootResult",
"value": null
}
}
],
"errors": [
{
"code": -32006,
"message": "Shutting down",
"data": "system is already shutting down"
}
]
},
{
"name": "service_create",
"description": "Creates a new service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to create",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "content",
"description": "The service configuration content",
"required": true,
"schema": {
"type": "object",
"properties": {
"exec": {
"type": "string",
"description": "Command to run"
},
"oneshot": {
"type": "boolean",
"description": "Whether the service should be restarted"
},
"after": {
"type": "array",
"items": {
"type": "string"
},
"description": "Services that must be running before this one starts"
},
"log": {
"type": "string",
"enum": ["null", "ring", "stdout"],
"description": "How to handle service output"
},
"env": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Environment variables for the service"
},
"shutdown_timeout": {
"type": "integer",
"description": "Maximum time to wait for service to stop during shutdown"
}
}
}
}
],
"result": {
"name": "CreateServiceResult",
"description": "Result of the create operation",
"schema": {
"type": "string"
}
},
"errors": [
{
"code": -32007,
"message": "Service already exists",
"data": "Service 'name' already exists"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to create service file"
}
]
},
{
"name": "service_delete",
"description": "Deletes a service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to delete",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "DeleteServiceResult",
"description": "Result of the delete operation",
"schema": {
"type": "string"
}
},
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "Service 'name' not found"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to delete service file"
}
]
},
{
"name": "service_get",
"description": "Gets a service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to get",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "GetServiceResult",
"description": "The service configuration",
"schema": {
"type": "object"
}
},
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "Service 'name' not found"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to read service file"
}
]
},
{
"name": "service_stats",
"description": "Get memory and CPU usage statistics for a service",
"params": [
{
"name": "name",
"description": "The name of the service to get stats for",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ServiceStats",
"description": "Memory and CPU usage statistics for the service",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Service name"
},
"pid": {
"type": "integer",
"description": "Process ID of the service"
},
"memory_usage": {
"type": "integer",
"description": "Memory usage in bytes"
},
"cpu_usage": {
"type": "number",
"description": "CPU usage as a percentage (0-100)"
},
"children": {
"type": "array",
"description": "Stats for child processes",
"items": {
"type": "object",
"properties": {
"pid": {
"type": "integer",
"description": "Process ID of the child process"
},
"memory_usage": {
"type": "integer",
"description": "Memory usage in bytes"
},
"cpu_usage": {
"type": "number",
"description": "CPU usage as a percentage (0-100)"
}
}
}
}
}
}
},
"examples": [
{
"name": "Get stats for redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ServiceStatsResult",
"value": {
"name": "redis",
"pid": 1234,
"memory_usage": 10485760,
"cpu_usage": 2.5,
"children": [
{
"pid": 1235,
"memory_usage": 5242880,
"cpu_usage": 1.2
}
]
}
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
}
]
},
{
"name": "system_start_http_server",
"description": "Start an HTTP/RPC server at the specified address",
"params": [
{
"name": "address",
"description": "The network address to bind the server to (e.g., '127.0.0.1:8080')",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StartHttpServerResult",
"description": "Result of the start HTTP server operation",
"schema": {
"type": "string"
}
},
"examples": [
{
"name": "Start HTTP server on localhost:8080",
"params": [
{
"name": "address",
"value": "127.0.0.1:8080"
}
],
"result": {
"name": "StartHttpServerResult",
"value": "HTTP server started at 127.0.0.1:8080"
}
}
],
"errors": [
{
"code": -32602,
"message": "Invalid address",
"data": "Invalid network address format"
}
]
},
{
"name": "system_stop_http_server",
"description": "Stop the HTTP/RPC server if running",
"params": [],
"result": {
"name": "StopHttpServerResult",
"description": "Result of the stop HTTP server operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Stop the HTTP server",
"params": [],
"result": {
"name": "StopHttpServerResult",
"value": null
}
}
],
"errors": [
{
"code": -32602,
"message": "Server not running",
"data": "No HTTP server is currently running"
}
]
},
{
"name": "stream_currentLogs",
"description": "Get current logs from zinit and monitored services",
"params": [
{
"name": "name",
"description": "Optional service name filter. If provided, only logs from this service will be returned",
"required": false,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "LogsResult",
"description": "Array of log strings",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"examples": [
{
"name": "Get all logs",
"params": [],
"result": {
"name": "LogsResult",
"value": [
"2023-01-01T12:00:00 redis: Starting service",
"2023-01-01T12:00:01 nginx: Starting service"
]
}
},
{
"name": "Get logs for a specific service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "LogsResult",
"value": [
"2023-01-01T12:00:00 redis: Starting service",
"2023-01-01T12:00:02 redis: Service started"
]
}
}
]
},
{
"name": "stream_subscribeLogs",
"description": "Subscribe to log messages generated by zinit and monitored services",
"params": [
{
"name": "name",
"description": "Optional service name filter. If provided, only logs from this service will be returned",
"required": false,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "LogSubscription",
"description": "A subscription to log messages",
"schema": {
"type": "string"
}
},
"examples": [
{
"name": "Subscribe to all logs",
"params": [],
"result": {
"name": "LogSubscription",
"value": "2023-01-01T12:00:00 redis: Service started"
}
},
{
"name": "Subscribe to filtered logs",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "LogSubscription",
"value": "2023-01-01T12:00:00 redis: Service started"
}
}
]
}
]
}

181
lib/clients/zinit/service.v Normal file
View File

@@ -0,0 +1,181 @@
module zinit
import freeflowuniverse.herolib.schemas.jsonrpc
// ServiceConfig represents the configuration for a zinit service
pub struct ServiceConfig {
pub mut:
exec string // Command to run
oneshot bool // Whether the service should be restarted
after []string // Services that must be running before this one starts
log string // How to handle service output (null, ring, stdout)
env map[string]string // Environment variables for the service
shutdown_timeout int // Maximum time to wait for service to stop during shutdown
}
// KillParams represents the parameters for the service_kill method
pub struct KillParams {
pub:
name string // Name of the service to kill
signal string // Signal to send (e.g., SIGTERM, SIGKILL)
}
// RpcDiscoverResponse represents the response from rpc.discover
pub struct RpcDiscoverResponse {
pub mut:
spec map[string]string // OpenRPC specification
}
// rpc_discover returns the OpenRPC specification for the API
pub fn (mut c Client) rpc_discover() !RpcDiscoverResponse {
request := jsonrpc.new_request_generic('rpc.discover', []string{})
response := c.rpc_client.send[[]string, map[string]string](request)!
return RpcDiscoverResponse{
spec: response
}
}
// // Response Models for Zinit API
// //
// // This file contains all the response models used by the Zinit API.
// // These models are used as type parameters in the response generics.
// // ServiceListResponse represents the response from service_list
// pub struct ServiceListResponse {
// pub mut:
// // Map of service names to their current states
// services map[string]string
// }
// service_list lists all services managed by Zinit
// Returns a map of service names to their current states
pub fn (mut c Client) service_list() !map[string]string {
request := jsonrpc.new_request_generic('service_list',map[string]string )
services := c.rpc_client.send[map[string]string, map[string]string](request)!
// return ServiceListResponse{
// services: services
// }
return services
}
// ServiceStatusResponse represents the response from service_status
pub struct ServiceStatusResponse {
pub mut:
name string // Service name
pid int // Process ID of the running service (if running)
state string // Current state of the service (Running, Success, Error, etc.)
target string // Target state of the service (Up, Down)
after map[string]string // Dependencies of the service and their states
}
// service_status shows detailed status information for a specific service
// name: the name of the service
pub fn (mut c Client) service_status(name string) !ServiceStatusResponse {
request := jsonrpc.new_request_generic('service_status', name)
// Use a direct struct mapping instead of manual conversion
return c.rpc_client.send[string, ServiceStatusResponse](request)!
}
// service_start starts a service
// name: the name of the service to start
pub fn (mut c Client) service_start(name string) ! {
request := jsonrpc.new_request_generic('service_start', name)
c.rpc_client.send[string, string](request)!
}
// service_stop stops a service
// name: the name of the service to stop
pub fn (mut c Client) service_stop(name string) ! {
request := jsonrpc.new_request_generic('service_stop', name)
c.rpc_client.send[string, string](request)!
}
// service_monitor starts monitoring a service
// The service configuration is loaded from the config directory
// name: the name of the service to monitor
pub fn (mut c Client) service_monitor(name string) ! {
request := jsonrpc.new_request_generic('service_monitor', name)
c.rpc_client.send[string, string](request)!
}
// service_delete deletes a service configuration file
// name: the name of the service to delete
pub fn (mut c Client) service_delete(name string) !ServiceDeleteResponse {
request := jsonrpc.new_request_generic('service_delete', name)
result := c.rpc_client.send[string, string](request)!
return ServiceDeleteResponse{
result: result
}
}
// service_forget stops monitoring a service
// You can only forget a stopped service
// name: the name of the service to forget
pub fn (mut c Client) service_forget(name string) ! {
request := jsonrpc.new_request_generic('service_forget', name)
c.rpc_client.send[string, string](request)!
}
//TODO: make sure the signal is a valid signal and enumerator do as @[params] so its optional
// service_kill sends a signal to a running service
// name: the name of the service to send the signal to
// signal: the signal to send (e.g., SIGTERM, SIGKILL)
pub fn (mut c Client) service_kill(name string, signal string) ! {
params := KillParams{
name: name
signal: signal
}
request := jsonrpc.new_request_generic('service_kill', params)
c.rpc_client.send[KillParams, string](request)!
}
// CreateServiceParams represents the parameters for the service_create method
struct CreateServiceParams {
name string // Name of the service to create
content ServiceConfig // Configuration for the service
}
// service_create creates a new service configuration file
// name: the name of the service to create
// config: the service configuration
pub fn (mut c Client) service_create(name string, config ServiceConfig) !ServiceCreateResponse {
params := CreateServiceParams{
name: name
content: config
}
request := jsonrpc.new_request_generic('service_create', params)
path := c.rpc_client.send[CreateServiceParams, string](request)!
return ServiceCreateResponse{
path: path
}
}
// service_get gets a service configuration file
// name: the name of the service to get
pub fn (mut c Client) service_get(name string) !ServiceConfigResponse {
request := jsonrpc.new_request_generic('service_get', {"name":name})
// We need to handle the conversion from ServiceConfig to ServiceConfigResponse
config := c.rpc_client.send[map[string]string, ServiceConfig](request)!
return ServiceConfigResponse{
exec: config.exec
oneshot: config.oneshot
after: config.after
log: config.log
env: config.env
shutdown_timeout: config.shutdown_timeout
}
}

View File

@@ -0,0 +1,35 @@
module zinit
pub struct ServiceConfigResponse {
pub mut:
exec string // Command to run
oneshot bool // Whether the service should be restarted
after []string // Services that must be running before this one starts
log string // How to handle service output (null, ring, stdout)
env map[string]string // Environment variables for the service
shutdown_timeout int // Maximum time to wait for service to stop during shutdown
}
// Helper function to create a basic service configuration
pub fn new_service_config(exec string) ServiceConfig {
return ServiceConfig{
exec: exec
oneshot: false
log: log_stdout
env: map[string]string{}
shutdown_timeout: 30
}
}
// Helper function to create a oneshot service configuration
pub fn new_oneshot_service_config(exec string) ServiceConfig {
return ServiceConfig{
exec: exec
oneshot: true
log: log_stdout
env: map[string]string{}
shutdown_timeout: 30
}
}

View File

@@ -0,0 +1,44 @@
module zinit
import freeflowuniverse.herolib.schemas.jsonrpc
// ServiceStatsResponse represents the response from service_stats
pub struct ServiceStatsResponse {
pub mut:
name string // Service name
pid int // Process ID of the service
memory_usage i64 // Memory usage in bytes
cpu_usage f64 // CPU usage as a percentage (0-100)
children []ChildStatsResponse // Stats for child processes
}
// ChildStatsResponse represents statistics for a child process
pub struct ChildStatsResponse {
pub mut:
pid int // Process ID of the child process
memory_usage i64 // Memory usage in bytes
cpu_usage f64 // CPU usage as a percentage (0-100)
}
// Serv
// service_stats gets memory and CPU usage statistics for a service
// name: the name of the service to get stats for
pub fn (mut c Client) service_stats(name string) !ServiceStatsResponse {
request := jsonrpc.new_request_generic('service_stats', name)
// We need to handle the conversion from the raw response to our model
raw_stats := c.rpc_client.send[string, map[string]string](request)!
// Parse the raw stats into our response model
mut children := []ChildStatsResponse{}
// In a real implementation, we would parse the children from the raw response
return ServiceStatsResponse{
name: raw_stats['name'] or { '' }
pid: raw_stats['pid'].int()
memory_usage: raw_stats['memory_usage'].i64()
cpu_usage: raw_stats['cpu_usage'].f64()
children: children
}
}

View File

@@ -0,0 +1,71 @@
module zinit
import freeflowuniverse.herolib.schemas.jsonrpc
// system_shutdown stops all services and powers off the system
pub fn (mut c Client) system_shutdown() ! {
request := jsonrpc.new_request_generic('system_shutdown', []string{})
c.rpc_client.send[[]string, string](request)!
}
// system_reboot stops all services and reboots the system
pub fn (mut c Client) system_reboot() ! {
request := jsonrpc.new_request_generic('system_reboot', []string{})
c.rpc_client.send[[]string, string](request)!
}
// system_start_http_server starts an HTTP/RPC server at the specified address
// address: the network address to bind the server to (e.g., '127.0.0.1:8080')
pub fn (mut c Client) system_start_http_server(address string) !SystemStartHttpServerResponse {
request := jsonrpc.new_request_generic('system_start_http_server', address)
result := c.rpc_client.send[string, string](request)!
return SystemStartHttpServerResponse{
result: result
}
}
// system_stop_http_server stops the HTTP/RPC server if running
pub fn (mut c Client) system_stop_http_server() ! {
request := jsonrpc.new_request_generic('system_stop_http_server', []string{})
c.rpc_client.send[[]string, string](request)!
}
@[params]
pub struct LogParams {
name string
}
// stream_current_logs gets current logs from zinit and monitored services
// name: optional service name filter. If provided, only logs from this service will be returned
pub fn (mut c Client) stream_current_logs(args LogParams) ![]string {
mut logs := []string{}
if args.name != '' {
request := jsonrpc.new_request_generic('stream_currentLogs', {"name":args.name})
logs = c.rpc_client.send[map[string]string, map[string]string](request)!
} else {
request := jsonrpc.new_request_generic('stream_currentLogs', map[string]string{})
logs = c.rpc_client.send[[]map[string]string, map[string]string](request)!
}
return logs
}
// stream_subscribe_logs subscribes to log messages generated by zinit and monitored services
// name: optional service name filter. If provided, only logs from this service will be returned
pub fn (mut c Client) stream_subscribe_logs(name ?string) !StreamSubscribeLogsResponse {
mut subscription_id := ''
if service_name := name {
request := jsonrpc.new_request_generic('stream_subscribeLogs', service_name)
subscription_id = c.rpc_client.send[string, string](request)!
} else {
request := jsonrpc.new_request_generic('stream_subscribeLogs', []string{})
subscription_id = c.rpc_client.send[[]string, string](request)!
}
return StreamSubscribeLogsResponse{
subscription_id: subscription_id
}
}

20
lib/clients/zinit/tools.v Normal file
View File

@@ -0,0 +1,20 @@
module zinit
// Helper function to format memory usage in human-readable format
pub fn format_memory_usage(bytes i64) string {
if bytes < 1024 {
return '${bytes} B'
} else if bytes < 1024 * 1024 {
return '${bytes / 1024} KB'
} else if bytes < 1024 * 1024 * 1024 {
return '${bytes / 1024 / 1024} MB'
} else {
return '${bytes / 1024 / 1024 / 1024} GB'
}
}
// Helper function to format CPU usage
pub fn format_cpu_usage(cpu_percent f64) string {
return '${cpu_percent:.1f}%'
}

View File

@@ -36,7 +36,7 @@ pub fn (mut t UnixSocketTransport) send(request string, params SendParams) !stri
if params.timeout > 0 {
socket.set_read_timeout(params.timeout * time.second)
socket.set_write_timeout(params.timeout * time.second)
console.print_debug('Set socket timeout to ${params.timeout} seconds')
// console.print_debug('Set socket timeout to ${params.timeout} seconds')
}
net.set_blocking(socket.sock.handle,false) !
@@ -53,26 +53,26 @@ pub fn (mut t UnixSocketTransport) send(request string, params SendParams) !stri
// Read up to 64000 bytes
mut res := []u8{len: 64000, cap: 64000}
n := socket.read(mut res)!
console.print_debug('Read ${n} bytes from socket')
// console.print_debug('Read ${n} bytes from socket')
if n == 0 {
console.print_debug('No more data to read, breaking loop')
// console.print_debug('No more data to read, breaking loop')
break
}
// Append the newly read data to the total response
res_total << res[..n]
if n < 8192{
console.print_debug('No more data to read, breaking loop after ${n} bytes')
// console.print_debug('No more data to read, breaking loop after ${n} bytes')
break
}
}
println(res_total.bytestr().trim_space())
// println(res_total.bytestr().trim_space())
// println(19)
// Convert response to string and trim whitespace
mut response := res_total.bytestr().trim_space()
console.print_debug('Received ${response.len} bytes')
// console.print_debug('Received ${response.len} bytes')
// Basic validation
if response.len == 0 {

View File

@@ -44,6 +44,12 @@ The properties of a JSON Schema is a list of key value pairs, where keys represe
It's good practice to define object type schemas separately and reference them in properties, especially if the same schema is used in multiple places. However, object type schemas can also be defined in property definitions. This may make sense if the schema is exclusively used as a property of a schema, similar to using an anonymous struct for the type definition of a field of a struct.
## Json Schema Decode & Validation
```v
import freeflowuniverse.herolib.schemas.jsonschema
mut myschema:=jsonschema.decode("the spec...")!
## Code Generation
### V Code to JSON Schema

View File

@@ -1,10 +1,11 @@
module openrpc
import json
import x.json2 { Any }
import freeflowuniverse.herolib.schemas.jsonschema { Reference, decode_schemaref }
pub fn decode(data string) !OpenRPC {
mut object := json2.decode[OpenRPC](data) or { return error('Failed to decode json\n${err}') }
// mut object := json.decode[OpenRPC](data) or { return error('Failed to decode json\n=======\n${data}\n===========\n${err}') }
mut object := json.decode(OpenRPC,data) or { return error('Failed to decode json\n=======\n${data}\n===========\n${err}') }
data_map := json2.raw_decode(data)!.as_map()
if 'components' in data_map {
object.components = decode_components(data_map) or {
@@ -16,17 +17,19 @@ pub fn decode(data string) !OpenRPC {
for i, method in methods_any.arr() {
method_map := method.as_map()
if result_any := method_map['result'] {
object.methods[i].result = decode_content_descriptor_ref(result_any.as_map()) or {
return error('Failed to decode result\n${err}')
}
}
if params_any := method_map['params'] {
params_arr := params_any.arr()
object.methods[i].params = params_arr.map(decode_content_descriptor_ref(it.as_map()) or {
return error('Failed to decode params\n${err}')
})
}
//TODO: I had to disable this because it was not working, need to check why !!!!!
// if result_any := method_map['result'] {
// object.methods[i].result = decode_content_descriptor_ref(result_any.as_map()) or {
// return error('Failed to decode result\n${err}')
// }
// }
// if params_any := method_map['params'] {
// params_arr := params_any.arr()
// object.methods[i].params = params_arr.map(decode_content_descriptor_ref(it.as_map()) or {
// return error('Failed to decode params\n${err}')
// })
// }
}
// object.methods = decode_method(data_map['methods'].as_array)!
return object