Skip to content

Commit bb68cbc

Browse files
authored
Allow CLI Daemon Configuration (#84)
# Context Creates a daemon interface (alongside a default setup) allowing external implementors to set up their own daemon - or simply configure the default one. ## Changes - Add new daemon manager interface for implementing daemons - Add default daemon that's based on docker compose file & information -- easily configurable - Add `CliOptions` which can be extended here to allow implementors to define more - This right now is essentially just wrapping the daemon configuration, but can be used to add more configuration in the future (e.g. CLI flags we not meant to be user-configurable) --------- Signed-off-by: Fabian Gonzalez <fabian.gonzalez@solo.io>
1 parent f5e781c commit bb68cbc

5 files changed

Lines changed: 133 additions & 56 deletions

File tree

internal/daemon/daemon.go

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,7 @@ package daemon
22

33
import (
44
_ "embed"
5-
"fmt"
6-
"os"
7-
"os/exec"
8-
"strings"
9-
10-
"github.com/agentregistry-dev/agentregistry/internal/client"
11-
"github.com/agentregistry-dev/agentregistry/internal/version"
125
)
136

147
//go:embed docker-compose.yml
15-
var dockerComposeYaml string
16-
17-
func Start() error {
18-
fmt.Println("Starting agentregistry daemon...")
19-
// Pipe the docker-compose.yml via stdin to docker compose
20-
cmd := exec.Command("docker", "compose", "-p", "agentregistry", "-f", "-", "up", "-d", "--wait")
21-
cmd.Stdin = strings.NewReader(dockerComposeYaml)
22-
cmd.Env = append(os.Environ(), fmt.Sprintf("VERSION=%s", version.Version), fmt.Sprintf("DOCKER_REGISTRY=%s", version.DockerRegistry))
23-
if byt, err := cmd.CombinedOutput(); err != nil {
24-
fmt.Printf("failed to start docker compose: %v, output: %s", err, string(byt))
25-
return fmt.Errorf("failed to start docker compose: %w", err)
26-
}
27-
28-
fmt.Println("✓ Agentregistry daemon started successfully")
29-
30-
_, err := client.NewClientFromEnv()
31-
if err != nil {
32-
return fmt.Errorf("failed to connect to API: %w", err)
33-
}
34-
fmt.Println("✓ API connected successfully")
35-
return nil
36-
}
37-
38-
func IsRunning() bool {
39-
cmd := exec.Command("docker", "compose", "-p", "agentregistry", "-f", "-", "ps")
40-
cmd.Stdin = strings.NewReader(dockerComposeYaml)
41-
cmd.Env = append(os.Environ(), fmt.Sprintf("VERSION=%s", version.Version), fmt.Sprintf("DOCKER_REGISTRY=%s", version.DockerRegistry))
42-
output, err := cmd.CombinedOutput()
43-
if err != nil {
44-
fmt.Printf("failed to check if daemon is running: %v, output: %s", err, string(output))
45-
return false
46-
}
47-
return strings.Contains(string(output), "agentregistry-server")
48-
}
49-
50-
func IsDockerComposeAvailable() bool {
51-
cmd := exec.Command("docker", "compose", "version")
52-
output, err := cmd.CombinedOutput()
53-
if err != nil {
54-
fmt.Printf("docker compose is not available: %v, output: %s", err, string(output))
55-
return false
56-
}
57-
// Return true if the commands returns 0
58-
return true
59-
}
8+
var DockerComposeYaml string

internal/utils/utils.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package utils
33
import (
44
"fmt"
55
"net"
6+
"os/exec"
67
"strings"
78
)
89

@@ -63,3 +64,9 @@ func SanitizeVersion(version string) string {
6364
}
6465
return sanitized
6566
}
67+
68+
func IsDockerComposeAvailable() bool {
69+
cmd := exec.Command("docker", "compose", "version")
70+
_, err := cmd.CombinedOutput()
71+
return err == nil
72+
}

pkg/cli/root.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,45 @@ import (
1010
"github.com/agentregistry-dev/agentregistry/internal/cli/mcp"
1111
"github.com/agentregistry-dev/agentregistry/internal/cli/skill"
1212
"github.com/agentregistry-dev/agentregistry/internal/client"
13-
"github.com/agentregistry-dev/agentregistry/internal/daemon"
13+
"github.com/agentregistry-dev/agentregistry/internal/utils"
14+
"github.com/agentregistry-dev/agentregistry/pkg/daemon"
15+
"github.com/agentregistry-dev/agentregistry/pkg/types"
1416
"github.com/spf13/cobra"
1517
)
1618

19+
// CLIOptions configures the CLI behavior
20+
// We could extend this to include more extensibility options in the future (e.g. client factory)
21+
type CLIOptions struct {
22+
// DaemonManager handles daemon lifecycle. If nil, uses default.
23+
DaemonManager types.DaemonManager
24+
}
25+
26+
var cliOptions CLIOptions
27+
28+
// Configure applies options to the root command
29+
func Configure(opts CLIOptions) {
30+
cliOptions = opts
31+
}
32+
1733
var rootCmd = &cobra.Command{
1834
Use: "arctl",
1935
Short: "Agent Registry CLI",
2036
Long: `arctl is a CLI tool for managing agents, MCP servers and skills.`,
2137
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
38+
dm := cliOptions.DaemonManager
39+
if dm == nil {
40+
dm = daemon.NewDaemonManager(nil)
41+
}
42+
2243
// Check if docker compose is available
23-
if !daemon.IsDockerComposeAvailable() {
44+
if !utils.IsDockerComposeAvailable() {
2445
fmt.Println("Docker compose is not available. Please install docker compose and try again.")
2546
fmt.Println("See https://docs.docker.com/compose/install/ for installation instructions.")
2647
fmt.Println("agent registry uses docker compose to start the server and the agent gateway.")
2748
return fmt.Errorf("docker compose is not available")
2849
}
29-
if !daemon.IsRunning() {
30-
if err := daemon.Start(); err != nil {
50+
if !dm.IsRunning() {
51+
if err := dm.Start(); err != nil {
3152
return fmt.Errorf("failed to start daemon: %w", err)
3253
}
3354
}

pkg/daemon/daemon.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package daemon
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
10+
"github.com/agentregistry-dev/agentregistry/internal/daemon"
11+
"github.com/agentregistry-dev/agentregistry/internal/version"
12+
"github.com/agentregistry-dev/agentregistry/pkg/types"
13+
)
14+
15+
// DefaultConfig returns the default configuration for the daemon (AgentRegistry OSS daemon)
16+
func DefaultConfig() types.DaemonConfig {
17+
return types.DaemonConfig{
18+
ProjectName: "agentregistry",
19+
ContainerName: "agentregistry-server",
20+
ComposeYAML: daemon.DockerComposeYaml,
21+
DockerRegistry: version.DockerRegistry,
22+
Version: version.Version,
23+
}
24+
}
25+
26+
// DefaultDaemonManager implements types.DaemonManager with configurable options
27+
type DefaultDaemonManager struct {
28+
config types.DaemonConfig
29+
}
30+
31+
// Ensure DefaultDaemonManager implements types.DaemonManager
32+
var _ types.DaemonManager = (*DefaultDaemonManager)(nil)
33+
34+
func NewDaemonManager(config *types.DaemonConfig) *DefaultDaemonManager {
35+
cfg := DefaultConfig()
36+
if config != nil {
37+
if config.ProjectName != "" {
38+
cfg.ProjectName = config.ProjectName
39+
}
40+
if config.ContainerName != "" {
41+
cfg.ContainerName = config.ContainerName
42+
}
43+
if config.ComposeYAML != "" {
44+
cfg.ComposeYAML = config.ComposeYAML
45+
}
46+
if config.DockerRegistry != "" {
47+
cfg.DockerRegistry = config.DockerRegistry
48+
}
49+
if config.Version != "" {
50+
cfg.Version = config.Version
51+
}
52+
}
53+
return &DefaultDaemonManager{config: cfg}
54+
}
55+
56+
func (d *DefaultDaemonManager) Start() error {
57+
fmt.Printf("Starting %s daemon...\n", d.config.ProjectName)
58+
// Pipe the docker-compose.yml via stdin to docker compose
59+
cmd := exec.Command("docker", "compose", "-p", d.config.ProjectName, "-f", "-", "up", "-d", "--wait")
60+
cmd.Stdin = strings.NewReader(d.config.ComposeYAML)
61+
cmd.Env = append(os.Environ(), fmt.Sprintf("VERSION=%s", d.config.Version), fmt.Sprintf("DOCKER_REGISTRY=%s", d.config.DockerRegistry))
62+
if byt, err := cmd.CombinedOutput(); err != nil {
63+
fmt.Printf("failed to start docker compose: %v, output: %s", err, string(byt))
64+
return fmt.Errorf("failed to start docker compose: %w", err)
65+
}
66+
67+
fmt.Printf("✓ %s daemon started successfully\n", d.config.ProjectName)
68+
69+
return nil
70+
}
71+
72+
func (d *DefaultDaemonManager) IsRunning() bool {
73+
cmd := exec.Command("docker", "compose", "-p", d.config.ProjectName, "-f", "-", "ps")
74+
cmd.Stdin = strings.NewReader(d.config.ComposeYAML)
75+
cmd.Env = append(os.Environ(), fmt.Sprintf("VERSION=%s", d.config.Version), fmt.Sprintf("DOCKER_REGISTRY=%s", d.config.DockerRegistry))
76+
output, err := cmd.CombinedOutput()
77+
if err != nil {
78+
fmt.Printf("failed to check if daemon is running: %v, output: %s", err, string(output))
79+
return false
80+
}
81+
return strings.Contains(string(output), d.config.ContainerName)
82+
}

pkg/types/types.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,27 @@ type Server interface {
6060
Shutdown(ctx context.Context) error
6161
}
6262

63+
// DaemonManager defines the interface for managing the CLI's backend daemon.
64+
// External libraries can implement this to use their own orchestration.
65+
type DaemonManager interface {
66+
// IsRunning checks if the daemon is currently running
67+
IsRunning() bool
68+
// Start starts the daemon, blocking until it's ready
69+
Start() error
70+
}
71+
6372
// HTTPServerFactory is a function type that creates a server implementation that
6473
// adds new API routes and handlers.
6574
//
6675
// The factory receives a Server interface and should return a Server after
6776
// registering new routes using base.HumaAPI() or base.Mux().
6877
type HTTPServerFactory func(base Server) Server
78+
79+
// DaemonConfig allows customization of the default daemon manager
80+
type DaemonConfig struct {
81+
ProjectName string // docker compose project name (default: "agentregistry")
82+
ContainerName string // container name to check for running state (default: "agentregistry-server")
83+
ComposeYAML string // docker-compose.yml content (default: embedded)
84+
DockerRegistry string // image registry (default: version.DockerRegistry)
85+
Version string // image version (default: version.Version)
86+
}

0 commit comments

Comments
 (0)