feat: implement wallet-based SSH authentication using Ed25519 keys

- Added documentation for wallet-based SSH authentication in WALLET_SSH_AUTH.md.
- Introduced SSH key derivation and management in rootwallet core and CLI.
- Created commands for generating, loading, and unloading SSH keys in the CLI.
- Updated Orama network to support SSH key authentication.
- Added migration steps for nodes to transition from password-based to key-based authentication.

feat: add serverless function management commands

- Implemented function command structure in CLI for managing serverless functions.
- Added commands for initializing, building, deploying, invoking, deleting, and listing functions.
- Created helper functions for handling function configuration and API requests.
- Integrated TinyGo for building functions to WASM.
- Added logging and version management for deployed functions.
This commit is contained in:
anonpenguin23 2026-02-19 10:51:03 +02:00
parent 40600c3557
commit 106c2df4d2
13 changed files with 955 additions and 0 deletions

View File

@ -12,6 +12,7 @@ import (
"github.com/DeBrosOfficial/network/pkg/cli/cmd/dbcmd" "github.com/DeBrosOfficial/network/pkg/cli/cmd/dbcmd"
deploycmd "github.com/DeBrosOfficial/network/pkg/cli/cmd/deploy" deploycmd "github.com/DeBrosOfficial/network/pkg/cli/cmd/deploy"
"github.com/DeBrosOfficial/network/pkg/cli/cmd/envcmd" "github.com/DeBrosOfficial/network/pkg/cli/cmd/envcmd"
"github.com/DeBrosOfficial/network/pkg/cli/cmd/functioncmd"
"github.com/DeBrosOfficial/network/pkg/cli/cmd/inspectcmd" "github.com/DeBrosOfficial/network/pkg/cli/cmd/inspectcmd"
"github.com/DeBrosOfficial/network/pkg/cli/cmd/monitorcmd" "github.com/DeBrosOfficial/network/pkg/cli/cmd/monitorcmd"
"github.com/DeBrosOfficial/network/pkg/cli/cmd/namespacecmd" "github.com/DeBrosOfficial/network/pkg/cli/cmd/namespacecmd"
@ -79,6 +80,9 @@ and interacting with the Orama distributed network.`,
// Monitor command // Monitor command
rootCmd.AddCommand(monitorcmd.Cmd) rootCmd.AddCommand(monitorcmd.Cmd)
// Serverless function commands
rootCmd.AddCommand(functioncmd.Cmd)
return rootCmd return rootCmd
} }

View File

@ -0,0 +1,36 @@
package functioncmd
import (
"github.com/DeBrosOfficial/network/pkg/cli/functions"
"github.com/spf13/cobra"
)
// Cmd is the top-level function command.
var Cmd = &cobra.Command{
Use: "function",
Short: "Manage serverless functions",
Long: `Deploy, invoke, and manage serverless functions on the Orama Network.
A function is a folder containing:
function.go your handler code (uses the fn SDK)
function.yaml configuration (name, memory, timeout, etc.)
Quick start:
orama function init my-function
cd my-function
orama function build
orama function deploy
orama function invoke my-function --data '{"name": "World"}'`,
}
func init() {
Cmd.AddCommand(functions.InitCmd)
Cmd.AddCommand(functions.BuildCmd)
Cmd.AddCommand(functions.DeployCmd)
Cmd.AddCommand(functions.InvokeCmd)
Cmd.AddCommand(functions.ListCmd)
Cmd.AddCommand(functions.GetCmd)
Cmd.AddCommand(functions.DeleteCmd)
Cmd.AddCommand(functions.LogsCmd)
Cmd.AddCommand(functions.VersionsCmd)
}

View File

@ -0,0 +1,79 @@
package functions
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/spf13/cobra"
)
// BuildCmd compiles a function to WASM using TinyGo.
var BuildCmd = &cobra.Command{
Use: "build [directory]",
Short: "Build a function to WASM using TinyGo",
Long: `Compiles function.go in the given directory (or current directory) to a WASM binary.
Requires TinyGo to be installed (https://tinygo.org/getting-started/install/).`,
Args: cobra.MaximumNArgs(1),
RunE: runBuild,
}
func runBuild(cmd *cobra.Command, args []string) error {
dir := ""
if len(args) > 0 {
dir = args[0]
}
_, err := buildFunction(dir)
return err
}
// buildFunction compiles the function in dir and returns the path to the WASM output.
func buildFunction(dir string) (string, error) {
absDir, err := ResolveFunctionDir(dir)
if err != nil {
return "", err
}
// Verify function.go exists
goFile := filepath.Join(absDir, "function.go")
if _, err := os.Stat(goFile); os.IsNotExist(err) {
return "", fmt.Errorf("function.go not found in %s", absDir)
}
// Verify function.yaml exists
if _, err := os.Stat(filepath.Join(absDir, "function.yaml")); os.IsNotExist(err) {
return "", fmt.Errorf("function.yaml not found in %s", absDir)
}
// Check TinyGo is installed
tinygoPath, err := exec.LookPath("tinygo")
if err != nil {
return "", fmt.Errorf("tinygo not found in PATH. Install it: https://tinygo.org/getting-started/install/")
}
outputPath := filepath.Join(absDir, "function.wasm")
fmt.Printf("Building %s...\n", absDir)
// Run tinygo build
buildCmd := exec.Command(tinygoPath, "build", "-o", outputPath, "-target", "wasi", ".")
buildCmd.Dir = absDir
buildCmd.Stdout = os.Stdout
buildCmd.Stderr = os.Stderr
if err := buildCmd.Run(); err != nil {
return "", fmt.Errorf("tinygo build failed: %w", err)
}
// Validate output
if err := ValidateWASMFile(outputPath); err != nil {
os.Remove(outputPath)
return "", fmt.Errorf("build produced invalid WASM: %w", err)
}
info, _ := os.Stat(outputPath)
fmt.Printf("Built %s (%d bytes)\n", outputPath, info.Size())
return outputPath, nil
}

View File

@ -0,0 +1,53 @@
package functions
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
)
var deleteForce bool
// DeleteCmd deletes a deployed function.
var DeleteCmd = &cobra.Command{
Use: "delete <name>",
Short: "Delete a deployed function",
Long: "Deletes a function from the Orama Network. This action cannot be undone.",
Args: cobra.ExactArgs(1),
RunE: runDelete,
}
func init() {
DeleteCmd.Flags().BoolVarP(&deleteForce, "force", "f", false, "Skip confirmation prompt")
}
func runDelete(cmd *cobra.Command, args []string) error {
name := args[0]
if !deleteForce {
fmt.Printf("Are you sure you want to delete function %q? This cannot be undone. [y/N] ", name)
reader := bufio.NewReader(os.Stdin)
answer, _ := reader.ReadString('\n')
answer = strings.TrimSpace(strings.ToLower(answer))
if answer != "y" && answer != "yes" {
fmt.Println("Cancelled.")
return nil
}
}
result, err := apiDelete("/v1/functions/" + name)
if err != nil {
return err
}
if msg, ok := result["message"]; ok {
fmt.Println(msg)
} else {
fmt.Printf("Function %q deleted.\n", name)
}
return nil
}

View File

@ -0,0 +1,89 @@
package functions
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
)
// DeployCmd deploys a function to the Orama Network.
var DeployCmd = &cobra.Command{
Use: "deploy [directory]",
Short: "Deploy a function to the Orama Network",
Long: `Deploys the function in the given directory (or current directory).
If no .wasm file exists, it will be built automatically using TinyGo.
Reads configuration from function.yaml.`,
Args: cobra.MaximumNArgs(1),
RunE: runDeploy,
}
func runDeploy(cmd *cobra.Command, args []string) error {
dir := ""
if len(args) > 0 {
dir = args[0]
}
absDir, err := ResolveFunctionDir(dir)
if err != nil {
return err
}
// Load configuration
cfg, err := LoadConfig(absDir)
if err != nil {
return err
}
wasmPath := filepath.Join(absDir, "function.wasm")
// Auto-build if no WASM file exists
if _, err := os.Stat(wasmPath); os.IsNotExist(err) {
fmt.Printf("No function.wasm found, building...\n\n")
built, err := buildFunction(dir)
if err != nil {
return err
}
wasmPath = built
fmt.Println()
} else {
// Validate existing WASM
if err := ValidateWASMFile(wasmPath); err != nil {
return fmt.Errorf("existing function.wasm is invalid: %w\nRun 'orama function build' to rebuild", err)
}
}
fmt.Printf("Deploying function %q...\n", cfg.Name)
result, err := uploadWASMFunction(wasmPath, cfg)
if err != nil {
return err
}
fmt.Printf("\nFunction deployed successfully!\n\n")
if msg, ok := result["message"]; ok {
fmt.Printf(" %s\n", msg)
}
if fn, ok := result["function"].(map[string]interface{}); ok {
if id, ok := fn["id"]; ok {
fmt.Printf(" ID: %s\n", id)
}
fmt.Printf(" Name: %s\n", cfg.Name)
if v, ok := fn["version"]; ok {
fmt.Printf(" Version: %v\n", v)
}
if wc, ok := fn["wasm_cid"]; ok {
fmt.Printf(" WASM CID: %s\n", wc)
}
if st, ok := fn["status"]; ok {
fmt.Printf(" Status: %s\n", st)
}
}
fmt.Printf("\nInvoke with:\n")
fmt.Printf(" orama function invoke %s --data '{\"name\": \"World\"}'\n", cfg.Name)
return nil
}

35
pkg/cli/functions/get.go Normal file
View File

@ -0,0 +1,35 @@
package functions
import (
"encoding/json"
"fmt"
"github.com/spf13/cobra"
)
// GetCmd shows details of a deployed function.
var GetCmd = &cobra.Command{
Use: "get <name>",
Short: "Get details of a deployed function",
Long: "Retrieves and displays detailed information about a specific function.",
Args: cobra.ExactArgs(1),
RunE: runGet,
}
func runGet(cmd *cobra.Command, args []string) error {
name := args[0]
result, err := apiGet("/v1/functions/" + name)
if err != nil {
return err
}
// Pretty-print the result
data, err := json.MarshalIndent(result, "", " ")
if err != nil {
return fmt.Errorf("failed to format response: %w", err)
}
fmt.Println(string(data))
return nil
}

View File

@ -0,0 +1,260 @@
package functions
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
"github.com/DeBrosOfficial/network/pkg/cli/shared"
"gopkg.in/yaml.v3"
)
// FunctionConfig represents the function.yaml configuration.
type FunctionConfig struct {
Name string `yaml:"name"`
Public bool `yaml:"public"`
Memory int `yaml:"memory"`
Timeout int `yaml:"timeout"`
Retry RetryConfig `yaml:"retry"`
Env map[string]string `yaml:"env"`
}
// RetryConfig holds retry settings.
type RetryConfig struct {
Count int `yaml:"count"`
Delay int `yaml:"delay"`
}
// wasmMagicBytes is the WASM binary magic number: \0asm
var wasmMagicBytes = []byte{0x00, 0x61, 0x73, 0x6d}
// validNameRegex validates function names (alphanumeric, hyphens, underscores).
var validNameRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`)
// LoadConfig reads and parses a function.yaml from the given directory.
func LoadConfig(dir string) (*FunctionConfig, error) {
path := filepath.Join(dir, "function.yaml")
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read function.yaml: %w", err)
}
var cfg FunctionConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to parse function.yaml: %w", err)
}
// Apply defaults
if cfg.Memory == 0 {
cfg.Memory = 64
}
if cfg.Timeout == 0 {
cfg.Timeout = 30
}
if cfg.Retry.Delay == 0 {
cfg.Retry.Delay = 5
}
// Validate
if cfg.Name == "" {
return nil, fmt.Errorf("function.yaml: 'name' is required")
}
if !validNameRegex.MatchString(cfg.Name) {
return nil, fmt.Errorf("function.yaml: 'name' must start with a letter and contain only letters, digits, hyphens, or underscores")
}
if cfg.Memory < 1 || cfg.Memory > 256 {
return nil, fmt.Errorf("function.yaml: 'memory' must be between 1 and 256 MB (got %d)", cfg.Memory)
}
if cfg.Timeout < 1 || cfg.Timeout > 300 {
return nil, fmt.Errorf("function.yaml: 'timeout' must be between 1 and 300 seconds (got %d)", cfg.Timeout)
}
return &cfg, nil
}
// ValidateWASM checks that the given bytes are a valid WASM binary (magic number check).
func ValidateWASM(data []byte) error {
if len(data) < 8 {
return fmt.Errorf("file too small to be a valid WASM binary (%d bytes)", len(data))
}
if !bytes.HasPrefix(data, wasmMagicBytes) {
return fmt.Errorf("file is not a valid WASM binary (bad magic bytes)")
}
return nil
}
// ValidateWASMFile checks that the file at the given path is a valid WASM binary.
func ValidateWASMFile(path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to open WASM file: %w", err)
}
defer f.Close()
header := make([]byte, 8)
n, err := f.Read(header)
if err != nil {
return fmt.Errorf("failed to read WASM file: %w", err)
}
return ValidateWASM(header[:n])
}
// apiRequest performs an authenticated HTTP request to the gateway API.
func apiRequest(method, endpoint string, body io.Reader, contentType string) (*http.Response, error) {
apiURL := shared.GetAPIURL()
url := apiURL + endpoint
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
token, err := shared.GetAuthToken()
if err != nil {
return nil, fmt.Errorf("authentication required: %w", err)
}
req.Header.Set("Authorization", "Bearer "+token)
return http.DefaultClient.Do(req)
}
// apiGet performs an authenticated GET request and returns the parsed JSON response.
func apiGet(endpoint string) (map[string]interface{}, error) {
resp, err := apiRequest("GET", endpoint, nil, "")
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error (%d): %s", resp.StatusCode, string(respBody))
}
var result map[string]interface{}
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
return result, nil
}
// apiDelete performs an authenticated DELETE request and returns the parsed JSON response.
func apiDelete(endpoint string) (map[string]interface{}, error) {
resp, err := apiRequest("DELETE", endpoint, nil, "")
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error (%d): %s", resp.StatusCode, string(respBody))
}
var result map[string]interface{}
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
return result, nil
}
// uploadWASMFunction uploads a WASM file to the deploy endpoint via multipart/form-data.
func uploadWASMFunction(wasmPath string, cfg *FunctionConfig) (map[string]interface{}, error) {
wasmFile, err := os.Open(wasmPath)
if err != nil {
return nil, fmt.Errorf("failed to open WASM file: %w", err)
}
defer wasmFile.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// Add form fields
writer.WriteField("name", cfg.Name)
writer.WriteField("is_public", strconv.FormatBool(cfg.Public))
writer.WriteField("memory_limit_mb", strconv.Itoa(cfg.Memory))
writer.WriteField("timeout_seconds", strconv.Itoa(cfg.Timeout))
writer.WriteField("retry_count", strconv.Itoa(cfg.Retry.Count))
writer.WriteField("retry_delay_seconds", strconv.Itoa(cfg.Retry.Delay))
// Add env vars as metadata JSON
if len(cfg.Env) > 0 {
metadata, _ := json.Marshal(map[string]interface{}{
"env_vars": cfg.Env,
})
writer.WriteField("metadata", string(metadata))
}
// Add WASM file
part, err := writer.CreateFormFile("wasm", filepath.Base(wasmPath))
if err != nil {
return nil, fmt.Errorf("failed to create form file: %w", err)
}
if _, err := io.Copy(part, wasmFile); err != nil {
return nil, fmt.Errorf("failed to write WASM data: %w", err)
}
writer.Close()
resp, err := apiRequest("POST", "/v1/functions", body, writer.FormDataContentType())
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("deploy failed (%d): %s", resp.StatusCode, string(respBody))
}
var result map[string]interface{}
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
return result, nil
}
// ResolveFunctionDir resolves and validates a function directory.
// If dir is empty, uses the current working directory.
func ResolveFunctionDir(dir string) (string, error) {
if dir == "" {
dir = "."
}
absDir, err := filepath.Abs(dir)
if err != nil {
return "", fmt.Errorf("failed to resolve path: %w", err)
}
info, err := os.Stat(absDir)
if err != nil {
return "", fmt.Errorf("directory does not exist: %w", err)
}
if !info.IsDir() {
return "", fmt.Errorf("%s is not a directory", absDir)
}
return absDir, nil
}

84
pkg/cli/functions/init.go Normal file
View File

@ -0,0 +1,84 @@
package functions
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
)
// InitCmd scaffolds a new function project.
var InitCmd = &cobra.Command{
Use: "init <name>",
Short: "Create a new serverless function project",
Long: "Scaffolds a new directory with function.go and function.yaml templates.",
Args: cobra.ExactArgs(1),
RunE: runInit,
}
func runInit(cmd *cobra.Command, args []string) error {
name := args[0]
if !validNameRegex.MatchString(name) {
return fmt.Errorf("invalid function name %q: must start with a letter and contain only letters, digits, hyphens, or underscores", name)
}
dir := filepath.Join(".", name)
if _, err := os.Stat(dir); err == nil {
return fmt.Errorf("directory %q already exists", name)
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
// Write function.yaml
yamlContent := fmt.Sprintf(`name: %s
public: false
memory: 64
timeout: 30
retry:
count: 0
delay: 5
`, name)
if err := os.WriteFile(filepath.Join(dir, "function.yaml"), []byte(yamlContent), 0o644); err != nil {
return fmt.Errorf("failed to write function.yaml: %w", err)
}
// Write function.go
goContent := fmt.Sprintf(`package main
import "github.com/DeBrosOfficial/network/sdk/fn"
func main() {
fn.Run(func(input []byte) ([]byte, error) {
var req struct {
Name string `+"`"+`json:"name"`+"`"+`
}
fn.ParseJSON(input, &req)
if req.Name == "" {
req.Name = "World"
}
return fn.JSON(map[string]string{
"greeting": "Hello, " + req.Name + "!",
})
})
}
`)
if err := os.WriteFile(filepath.Join(dir, "function.go"), []byte(goContent), 0o644); err != nil {
return fmt.Errorf("failed to write function.go: %w", err)
}
fmt.Printf("Created function project: %s/\n", name)
fmt.Printf(" %s/function.yaml — configuration\n", name)
fmt.Printf(" %s/function.go — handler code\n\n", name)
fmt.Printf("Next steps:\n")
fmt.Printf(" cd %s\n", name)
fmt.Printf(" orama function build\n")
fmt.Printf(" orama function deploy\n")
return nil
}

View File

@ -0,0 +1,58 @@
package functions
import (
"bytes"
"fmt"
"io"
"net/http"
"github.com/spf13/cobra"
)
var invokeData string
// InvokeCmd invokes a deployed function.
var InvokeCmd = &cobra.Command{
Use: "invoke <name>",
Short: "Invoke a deployed function",
Long: "Sends a request to invoke the named function with optional JSON payload.",
Args: cobra.ExactArgs(1),
RunE: runInvoke,
}
func init() {
InvokeCmd.Flags().StringVar(&invokeData, "data", "{}", "JSON payload to send to the function")
}
func runInvoke(cmd *cobra.Command, args []string) error {
name := args[0]
fmt.Printf("Invoking function %q...\n\n", name)
resp, err := apiRequest("POST", "/v1/functions/"+name+"/invoke", bytes.NewBufferString(invokeData), "application/json")
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
// Print timing info from headers
if reqID := resp.Header.Get("X-Request-ID"); reqID != "" {
fmt.Printf("Request ID: %s\n", reqID)
}
if dur := resp.Header.Get("X-Duration-Ms"); dur != "" {
fmt.Printf("Duration: %s ms\n", dur)
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("invocation failed (%d): %s", resp.StatusCode, string(respBody))
}
fmt.Printf("\nOutput:\n%s\n", string(respBody))
return nil
}

80
pkg/cli/functions/list.go Normal file
View File

@ -0,0 +1,80 @@
package functions
import (
"fmt"
"os"
"text/tabwriter"
"github.com/spf13/cobra"
)
// ListCmd lists all deployed functions.
var ListCmd = &cobra.Command{
Use: "list",
Short: "List deployed functions",
Long: "Lists all functions deployed in the current namespace.",
Args: cobra.NoArgs,
RunE: runList,
}
func runList(cmd *cobra.Command, args []string) error {
result, err := apiGet("/v1/functions")
if err != nil {
return err
}
functions, ok := result["functions"].([]interface{})
if !ok || len(functions) == 0 {
fmt.Println("No functions deployed.")
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "NAME\tVERSION\tSTATUS\tMEMORY\tTIMEOUT\tPUBLIC")
fmt.Fprintln(w, "----\t-------\t------\t------\t-------\t------")
for _, f := range functions {
fn, ok := f.(map[string]interface{})
if !ok {
continue
}
name := valStr(fn, "name")
version := valNum(fn, "version")
status := valStr(fn, "status")
memory := valNum(fn, "memory_limit_mb")
timeout := valNum(fn, "timeout_seconds")
public := valBool(fn, "is_public")
publicStr := "no"
if public {
publicStr = "yes"
}
fmt.Fprintf(w, "%s\t%d\t%s\t%dMB\t%ds\t%s\n", name, version, status, memory, timeout, publicStr)
}
w.Flush()
fmt.Printf("\nTotal: %d function(s)\n", len(functions))
return nil
}
func valStr(m map[string]interface{}, key string) string {
if v, ok := m[key]; ok {
return fmt.Sprintf("%v", v)
}
return ""
}
func valNum(m map[string]interface{}, key string) int {
if v, ok := m[key].(float64); ok {
return int(v)
}
return 0
}
func valBool(m map[string]interface{}, key string) bool {
if v, ok := m[key].(bool); ok {
return v
}
return false
}

57
pkg/cli/functions/logs.go Normal file
View File

@ -0,0 +1,57 @@
package functions
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
)
var logsLimit int
// LogsCmd retrieves function execution logs.
var LogsCmd = &cobra.Command{
Use: "logs <name>",
Short: "Get execution logs for a function",
Long: "Retrieves the most recent execution logs for a deployed function.",
Args: cobra.ExactArgs(1),
RunE: runLogs,
}
func init() {
LogsCmd.Flags().IntVar(&logsLimit, "limit", 50, "Maximum number of log entries to retrieve")
}
func runLogs(cmd *cobra.Command, args []string) error {
name := args[0]
endpoint := "/v1/functions/" + name + "/logs"
if logsLimit > 0 {
endpoint += "?limit=" + strconv.Itoa(logsLimit)
}
result, err := apiGet(endpoint)
if err != nil {
return err
}
logs, ok := result["logs"].([]interface{})
if !ok || len(logs) == 0 {
fmt.Printf("No logs found for function %q.\n", name)
return nil
}
for _, entry := range logs {
log, ok := entry.(map[string]interface{})
if !ok {
continue
}
ts := valStr(log, "timestamp")
level := valStr(log, "level")
msg := valStr(log, "message")
fmt.Printf("[%s] %s: %s\n", ts, level, msg)
}
fmt.Printf("\nShowing %d log(s)\n", len(logs))
return nil
}

View File

@ -0,0 +1,54 @@
package functions
import (
"fmt"
"os"
"text/tabwriter"
"github.com/spf13/cobra"
)
// VersionsCmd lists all versions of a function.
var VersionsCmd = &cobra.Command{
Use: "versions <name>",
Short: "List all versions of a function",
Long: "Shows all deployed versions of a specific function.",
Args: cobra.ExactArgs(1),
RunE: runVersions,
}
func runVersions(cmd *cobra.Command, args []string) error {
name := args[0]
result, err := apiGet("/v1/functions/" + name + "/versions")
if err != nil {
return err
}
versions, ok := result["versions"].([]interface{})
if !ok || len(versions) == 0 {
fmt.Printf("No versions found for function %q.\n", name)
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "VERSION\tWASM CID\tSTATUS\tCREATED")
fmt.Fprintln(w, "-------\t--------\t------\t-------")
for _, v := range versions {
ver, ok := v.(map[string]interface{})
if !ok {
continue
}
version := valNum(ver, "version")
wasmCID := valStr(ver, "wasm_cid")
status := valStr(ver, "status")
created := valStr(ver, "created_at")
fmt.Fprintf(w, "%d\t%s\t%s\t%s\n", version, wasmCID, status, created)
}
w.Flush()
fmt.Printf("\nTotal: %d version(s)\n", len(versions))
return nil
}

66
sdk/fn/fn.go Normal file
View File

@ -0,0 +1,66 @@
// Package fn provides a tiny, TinyGo-compatible SDK for writing Orama serverless functions.
//
// A function is a Go program that reads JSON input from stdin and writes JSON output to stdout.
// This package handles the boilerplate so you only write your handler logic.
//
// Example:
//
// package main
//
// import "github.com/DeBrosOfficial/network/sdk/fn"
//
// func main() {
// fn.Run(func(input []byte) ([]byte, error) {
// var req struct{ Name string `json:"name"` }
// fn.ParseJSON(input, &req)
// if req.Name == "" { req.Name = "World" }
// return fn.JSON(map[string]string{"greeting": "Hello, " + req.Name + "!"})
// })
// }
package fn
import (
"encoding/json"
"fmt"
"io"
"os"
)
// HandlerFunc is the signature for a serverless function handler.
// It receives the raw JSON input bytes and returns raw JSON output bytes.
type HandlerFunc func(input []byte) (output []byte, err error)
// Run reads input from stdin, calls the handler, and writes the output to stdout.
// If the handler returns an error, it writes a JSON error response to stdout and exits with code 1.
func Run(handler HandlerFunc) {
input, err := io.ReadAll(os.Stdin)
if err != nil {
writeError(fmt.Sprintf("failed to read input: %v", err))
os.Exit(1)
}
output, err := handler(input)
if err != nil {
writeError(err.Error())
os.Exit(1)
}
if output != nil {
os.Stdout.Write(output)
}
}
// JSON marshals a value to JSON bytes. Convenience wrapper around json.Marshal.
func JSON(v interface{}) ([]byte, error) {
return json.Marshal(v)
}
// ParseJSON unmarshals JSON bytes into a value. Convenience wrapper around json.Unmarshal.
func ParseJSON(data []byte, v interface{}) error {
return json.Unmarshal(data, v)
}
func writeError(msg string) {
resp, _ := json.Marshal(map[string]string{"error": msg})
os.Stdout.Write(resp)
}