Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,28 @@ jobs:
echo "Testing: breeder update"
$BINARY_PATH --hostname=localhost --port=4010 breeder update --file=test_breeder_update.yml

# Test breeder purge
echo "Testing: breeder purge"
# Test breeder purge (safe mode)
echo "Testing: breeder purge (safe mode)"
$BINARY_PATH --hostname=localhost --port=4010 breeder purge --id=550e8400-e29b-41d4-a716-446655440000

# Test breeder stop
echo "Testing: breeder stop"
$BINARY_PATH --hostname=localhost --port=4010 breeder stop --id=550e8400-e29b-41d4-a716-446655440000

# Test breeder start
echo "Testing: breeder start"
$BINARY_PATH --hostname=localhost --port=4010 breeder start --id=550e8400-e29b-41d4-a716-446655440000

# Test breeder purge with force flag
echo "Testing: breeder purge --force"
$BINARY_PATH --hostname=localhost --port=4010 --force breeder purge --id=550e8400-e29b-41d4-a716-446655440000

# Test help shows new commands
echo "Testing: help shows stop/start/force commands"
$BINARY_PATH --help | grep -q "stop" && echo "✅ stop command documented" || echo "❌ stop command missing"
$BINARY_PATH --help | grep -q "start" && echo "✅ start command documented" || echo "❌ start command missing"
$BINARY_PATH --help | grep -q "force" && echo "✅ force flag documented" || echo "❌ force flag missing"

# Test help commands
echo "Testing: help commands"
$BINARY_PATH --help
Expand Down
25 changes: 23 additions & 2 deletions src/godon/breeder.nim
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,32 @@ proc updateBreederFromYaml*(client: GodonClient, yamlContent: string): ApiRespon
except CatchableError as e:
result = ApiResponse[Breeder](success: false, data: default(Breeder), error: e.msg)

proc deleteBreeder*(client: GodonClient, uuid: string): ApiResponse[JsonNode] =
proc deleteBreeder*(client: GodonClient, uuid: string, force: bool = false): ApiResponse[JsonNode] =
## Delete/purge a breeder by UUID
## Set force=true to cancel workers immediately (default: false for safe deletion)
try:
let url = client.baseUrl() & "/breeders/" & encodeUrl(uuid)
var url = client.baseUrl() & "/breeders/" & encodeUrl(uuid)
if force:
url = url & "?force=true"
let response = client.httpClient.delete(url)
result = handleResponse[JsonNode](client, response)
except CatchableError as e:
result = ApiResponse[JsonNode](success: false, data: nil, error: e.msg)

proc stopBreeder*(client: GodonClient, uuid: string): ApiResponse[JsonNode] =
## Stop a breeder (graceful shutdown)
try:
let url = client.baseUrl() & "/breeders/" & encodeUrl(uuid) & "/stop"
let response = client.httpClient.post(url)
result = handleResponse[JsonNode](client, response)
except CatchableError as e:
result = ApiResponse[JsonNode](success: false, data: nil, error: e.msg)

proc startBreeder*(client: GodonClient, uuid: string): ApiResponse[JsonNode] =
## Start/resume a stopped breeder
try:
let url = client.baseUrl() & "/breeders/" & encodeUrl(uuid) & "/start"
let response = client.httpClient.post(url)
result = handleResponse[JsonNode](client, response)
except CatchableError as e:
result = ApiResponse[JsonNode](success: false, data: nil, error: e.msg)
80 changes: 65 additions & 15 deletions src/godon_cli.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ Commands:
breeder create --name <name> --file <path> Create a breeder from file
breeder show --id <id> Show breeder details
breeder update --file <path> Update a breeder from file
breeder purge --id <id> Delete a breeder

breeder stop --id <id> Stop breeder workers (graceful shutdown)
breeder start --id <id> Start/resume a stopped breeder
breeder purge [--force] --id <id> Delete a breeder (use --force to cancel workers immediately)

credential list List all credentials
credential create --file <path> Create a credential from file
credential show --id <id> Show credential details (including content)
Expand All @@ -31,6 +33,7 @@ Global Options:
--port, -p <port> Godon port (default: 8080)
--api-version, -v <ver> API version (default: v0)
--output, -o <format> Output format: text, json, or yaml (default: text)
--force Force immediate deletion (skip graceful shutdown)
--insecure Skip SSL certificate verification (HTTPS only)
--help Show this help message

Expand All @@ -39,6 +42,8 @@ Examples:
godon_cli --hostname api.example.com --port 9090 breeder list
godon_cli breeder create --name my-breeder --file breeder-config.yaml
godon_cli breeder show --id 550e8400-e29b-41d4-a716-446655440000
godon_cli breeder stop --id 550e8400-e29b-41d4-a716-446655440000
godon_cli breeder purge --force --id 550e8400-e29b-41d4-a716-446655440000
godon_cli credential list
godon_cli credential create --file credential.yaml
godon_cli credential show --id 550e8400-e29b-41d4-a716-446655440001
Expand All @@ -48,26 +53,27 @@ proc writeError(message: string) =
stderr.writeLine("Error: " & message)
quit(1)

proc parseArgs(): (string, string, int, string, bool, bool, OutputFormat, seq[string]) =
proc parseArgs(): (string, string, int, string, bool, bool, bool, OutputFormat, seq[string]) =
var command = ""
var hostname = "localhost"
var port = 8080
var apiVersion = "v0"
var insecure = false
var debug = false
var force = false
var outputFormat = OutputFormat.Text
var args: seq[string] = @[]

var p = initOptParser(commandLineParams())

for kind, key, val in p.getopt():
case kind
of cmdArgument:
if command.len == 0:
command = key
else:
args.add(key)

of cmdLongOption, cmdShortOption:
case key.normalize()
of "hostname":
Expand All @@ -90,6 +96,8 @@ proc parseArgs(): (string, string, int, string, bool, bool, OutputFormat, seq[st
of "id":
# Reconstruct as argument for subcommand parsing
args.add("--id=" & val)
of "force":
force = true
of "insecure":
insecure = true
of "debug":
Expand All @@ -111,7 +119,7 @@ proc parseArgs(): (string, string, int, string, bool, bool, OutputFormat, seq[st
quit(0)
else:
writeError("Unknown option: " & key)

of cmdEnd:
discard

Expand All @@ -123,7 +131,7 @@ proc parseArgs(): (string, string, int, string, bool, bool, OutputFormat, seq[st
if existsEnv("DEBUG"):
debug = true

(command, hostname, port, apiVersion, insecure, debug, outputFormat, args)
(command, hostname, port, apiVersion, insecure, debug, force, outputFormat, args)

proc formatOutput*[T](data: T, outputFormat: OutputFormat) =
## Format data according to output format preference
Expand All @@ -139,7 +147,7 @@ proc formatOutput*[T](data: T, outputFormat: OutputFormat) =
var dumper = blockOnlyDumper()
echo dumper.transform(jsonString)

proc handleBreederCommand(client: GodonClient, command: string, args: seq[string], outputFormat: OutputFormat) =
proc handleBreederCommand(client: GodonClient, command: string, args: seq[string], force: bool, outputFormat: OutputFormat) =
let subCommand = if args.len > 0: args[0] else: ""

case subCommand:
Expand Down Expand Up @@ -246,19 +254,61 @@ proc handleBreederCommand(client: GodonClient, command: string, args: seq[string
if arg.startsWith("--id="):
id = arg.split("=")[1]
break

if id.len == 0:
writeError("breeder purge requires --id <id>")

let response = client.deleteBreeder(id)
let response = client.deleteBreeder(id, force)
if response.success:
if outputFormat == OutputFormat.Text:
echo "Breeder deleted successfully: ", id
if force:
echo "Breeder force deleted (workers cancelled): ", id
else:
echo "Breeder deleted: ", id
else:
formatOutput(response.data, outputFormat)
else:
writeError(response.error)


of "stop":
var id = ""
for arg in args:
if arg.startsWith("--id="):
id = arg.split("=")[1]
break

if id.len == 0:
writeError("breeder stop requires --id <id>")

let response = client.stopBreeder(id)
if response.success:
if outputFormat == OutputFormat.Text:
echo "Breeder stop requested (graceful shutdown): ", id
echo "Workers will finish current trial before stopping."
else:
formatOutput(response.data, outputFormat)
else:
writeError(response.error)

of "start":
var id = ""
for arg in args:
if arg.startsWith("--id="):
id = arg.split("=")[1]
break

if id.len == 0:
writeError("breeder start requires --id <id>")

let response = client.startBreeder(id)
if response.success:
if outputFormat == OutputFormat.Text:
echo "Breeder started/resumed: ", id
else:
formatOutput(response.data, outputFormat)
else:
writeError(response.error)

else:
writeError("Unknown breeder command: " & subCommand)

Expand Down Expand Up @@ -361,15 +411,15 @@ proc handleCredentialCommand(client: GodonClient, command: string, args: seq[str
else:
writeError("Unknown credential command: " & subCommand)

let (command, hostname, port, apiVersion, insecure, debug, outputFormat, args) = parseArgs()
let (command, hostname, port, apiVersion, insecure, debug, force, outputFormat, args) = parseArgs()

let godonClient = newGodonClient(hostname, port, apiVersion, insecure, debug)

case command:
of "breeder":
if args.len == 0:
writeError("breeder command requires a subcommand (list, create, show, update, purge)")
handleBreederCommand(godonClient, command, args, outputFormat)
writeError("breeder command requires a subcommand (list, create, show, update, stop, start, purge)")
handleBreederCommand(godonClient, command, args, force, outputFormat)

of "credential":
if args.len == 0:
Expand Down
Loading