Skip to content

Commit e2f8679

Browse files
authored
Merge pull request #1748 from gtardif/agent_cli_plugin
Allow to use cagent binary as a docker cli plugin docker-agent. No functional change for cagent command.
2 parents cf377fc + c313c26 commit e2f8679

4 files changed

Lines changed: 194 additions & 26 deletions

File tree

Taskfile.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ version: "3"
22

33
vars:
44
BINARY_NAME: cagent{{if eq OS "windows"}}.exe{{end}}
5+
CLI_PLUGIN_BINARY_NAME: docker-agent{{if eq OS "windows"}}.exe{{end}}
56
MAIN_PKG: ./main.go
67
BUILD_DIR: ./bin
78
GIT_TAG:
@@ -35,6 +36,12 @@ tasks:
3536
generates:
3637
- "{{.BUILD_DIR}}/{{.BINARY_NAME}}"
3738

39+
deploy-local:
40+
desc: Deploy the docker agent cli-plugin
41+
deps: ["build"]
42+
cmds:
43+
- cp "{{.BUILD_DIR}}/{{.BINARY_NAME}}" ~/.docker/cli-plugins/{{.CLI_PLUGIN_BINARY_NAME}}
44+
3845
lint:
3946
desc: Run golangci-lint
4047
cmds:

cmd/root/root.go

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ import (
99
"log/slog"
1010
"os"
1111
"path/filepath"
12+
"runtime"
1213
"strings"
1314

15+
"github.com/docker/cli/cli-plugins/metadata"
16+
"github.com/docker/cli/cli-plugins/plugin"
17+
"github.com/docker/cli/cli/command"
1418
"github.com/spf13/cobra"
1519

1620
"github.com/docker/cagent/pkg/environment"
@@ -28,6 +32,14 @@ type rootFlags struct {
2832
logFile io.Closer
2933
}
3034

35+
func isCliPLugin() bool {
36+
cliPluginBinary := "docker-agent"
37+
if runtime.GOOS == "windows" {
38+
cliPluginBinary += ".exe"
39+
}
40+
return len(os.Args) > 0 && strings.HasSuffix(os.Args[0], cliPluginBinary)
41+
}
42+
3143
func NewRootCmd() *cobra.Command {
3244
var flags rootFlags
3345

@@ -107,6 +119,14 @@ func NewRootCmd() *cobra.Command {
107119
cmd.AddGroup(&cobra.Group{ID: "advanced", Title: "Advanced Commands:"})
108120
cmd.AddGroup(&cobra.Group{ID: "server", Title: "Server Commands:"})
109121

122+
if isCliPLugin() {
123+
cmd.Use = "agent"
124+
cmd.Short = "create or run AI agents"
125+
cmd.Long = "create or run AI agents"
126+
cmd.Example = ` docker agent run ./agent.yaml
127+
docker agent run agentcatalog/pirate`
128+
}
129+
110130
return cmd
111131
}
112132

@@ -141,33 +161,60 @@ We collect anonymous usage data to help improve cagent. To disable:
141161
rootCmd.SetOut(stdout)
142162
rootCmd.SetErr(stderr)
143163

144-
if err := rootCmd.ExecuteContext(ctx); err != nil {
145-
if ctx.Err() != nil {
146-
return ctx.Err()
147-
} else if envErr, ok := errors.AsType[*environment.RequiredEnvError](err); ok {
148-
fmt.Fprintln(stderr, "The following environment variables must be set:")
149-
for _, v := range envErr.Missing {
150-
fmt.Fprintf(stderr, " - %s\n", v)
151-
}
152-
fmt.Fprintln(stderr, "\nEither:\n - Set those environment variables before running cagent\n - Run cagent with --env-from-file\n - Store those secrets using one of the built-in environment variable providers.")
153-
} else if _, ok := errors.AsType[RuntimeError](err); ok {
154-
// Runtime errors have already been printed by the command itself
155-
// Don't print them again or show usage
156-
} else {
157-
// Command line usage errors - show the error and usage
158-
fmt.Fprintln(stderr, err)
159-
fmt.Fprintln(stderr)
160-
if strings.HasPrefix(err.Error(), "unknown command ") || strings.HasPrefix(err.Error(), "accepts ") {
161-
_ = rootCmd.Usage()
164+
if isCliPLugin() {
165+
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
166+
originalPreRun := rootCmd.PersistentPreRunE
167+
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
168+
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
169+
return err
170+
}
171+
if originalPreRun != nil {
172+
if err := originalPreRun(cmd, args); err != nil {
173+
return processErr(ctx, err, stderr, rootCmd)
174+
}
175+
}
176+
return nil
162177
}
178+
rootCmd.SetContext(ctx)
179+
return rootCmd
180+
}, metadata.Metadata{
181+
SchemaVersion: "0.1.0",
182+
Vendor: "Docker Inc.",
183+
Version: version.Version,
184+
})
185+
} else {
186+
if err := rootCmd.ExecuteContext(ctx); err != nil {
187+
return processErr(ctx, err, stderr, rootCmd)
163188
}
164-
165-
return err
166189
}
167190

168191
return nil
169192
}
170193

194+
func processErr(ctx context.Context, err error, stderr io.Writer, rootCmd *cobra.Command) error {
195+
if ctx.Err() != nil {
196+
return ctx.Err()
197+
} else if envErr, ok := errors.AsType[*environment.RequiredEnvError](err); ok {
198+
fmt.Fprintln(stderr, "The following environment variables must be set:")
199+
for _, v := range envErr.Missing {
200+
fmt.Fprintf(stderr, " - %s\n", v)
201+
}
202+
fmt.Fprintln(stderr, "\nEither:\n - Set those environment variables before running cagent\n - Run cagent with --env-from-file\n - Store those secrets using one of the built-in environment variable providers.")
203+
} else if _, ok := errors.AsType[RuntimeError](err); ok {
204+
// Runtime errors have already been printed by the command itself
205+
// Don't print them again or show usage
206+
} else {
207+
// Command line usage errors - show the error and usage
208+
fmt.Fprintln(stderr, err)
209+
fmt.Fprintln(stderr)
210+
if strings.HasPrefix(err.Error(), "unknown command ") || strings.HasPrefix(err.Error(), "accepts ") {
211+
_ = rootCmd.Usage()
212+
}
213+
}
214+
215+
return err
216+
}
217+
171218
// setupLogging configures slog logging behavior.
172219
// When --debug is enabled, logs are written to a rotating file <dataDir>/cagent.debug.log,
173220
// or to the file specified by --log-file. Log files are rotated when they exceed 10MB,

go.mod

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ require (
2929
github.com/clipperhouse/displaywidth v0.10.0
3030
github.com/clipperhouse/uax29/v2 v2.6.0
3131
github.com/coder/acp-go-sdk v0.6.3
32+
github.com/docker/cli v29.0.3+incompatible
3233
github.com/docker/go-units v0.5.0
3334
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3
3435
github.com/fatih/color v1.18.0
@@ -79,6 +80,7 @@ require (
7980
cloud.google.com/go/auth v0.17.0 // indirect
8081
cloud.google.com/go/compute/metadata v0.9.0 // indirect
8182
dario.cat/mergo v1.0.2 // indirect
83+
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
8284
github.com/JohannesKaufmann/dom v0.2.0 // indirect
8385
github.com/ProtonMail/go-crypto v1.1.6 // indirect
8486
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
@@ -123,16 +125,21 @@ require (
123125
github.com/charmbracelet/x/termios v0.1.1 // indirect
124126
github.com/charmbracelet/x/windows v0.2.2 // indirect
125127
github.com/cloudflare/circl v1.6.1 // indirect
128+
github.com/containerd/errdefs v1.0.0 // indirect
129+
github.com/containerd/errdefs/pkg v0.3.0 // indirect
126130
github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect
127131
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
128132
github.com/davecgh/go-spew v1.1.1 // indirect
133+
github.com/distribution/reference v0.6.0 // indirect
129134
github.com/dlclark/regexp2 v1.11.5 // indirect
130-
github.com/docker/cli v29.0.3+incompatible // indirect
131135
github.com/docker/distribution v2.8.3+incompatible // indirect
132136
github.com/docker/docker-credential-helpers v0.9.3 // indirect
137+
github.com/docker/go-connections v0.6.0 // indirect
138+
github.com/docker/go-metrics v0.0.1 // indirect
133139
github.com/dustin/go-humanize v1.0.1 // indirect
134140
github.com/emirpasic/gods v1.18.1 // indirect
135141
github.com/felixge/httpsnoop v1.0.4 // indirect
142+
github.com/fvbommel/sortorder v1.1.0 // indirect
136143
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
137144
github.com/go-git/go-billy/v5 v5.6.2 // indirect
138145
github.com/go-logr/logr v1.4.3 // indirect
@@ -153,7 +160,7 @@ require (
153160
github.com/inconshreveable/mousetrap v1.1.0 // indirect
154161
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
155162
github.com/josharian/intern v1.0.0 // indirect
156-
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede // indirect
163+
github.com/json-iterator/go v1.1.7 // indirect
157164
github.com/kevinburke/ssh_config v1.2.0 // indirect
158165
github.com/klauspost/compress v1.18.1 // indirect
159166
github.com/labstack/gommon v0.4.2 // indirect
@@ -162,7 +169,16 @@ require (
162169
github.com/mattn/go-colorable v0.1.14 // indirect
163170
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
164171
github.com/mitchellh/go-homedir v1.1.0 // indirect
172+
github.com/moby/docker-image-spec v1.3.1 // indirect
173+
github.com/moby/moby/api v1.53.0 // indirect
174+
github.com/moby/moby/client v0.2.2 // indirect
175+
github.com/moby/sys/atomicwriter v0.1.0 // indirect
176+
github.com/moby/sys/sequential v0.6.0 // indirect
177+
github.com/moby/term v0.5.2 // indirect
178+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
179+
github.com/modern-go/reflect2 v1.0.1 // indirect
165180
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
181+
github.com/morikuni/aec v1.0.0 // indirect
166182
github.com/mschoch/smat v0.2.0 // indirect
167183
github.com/muesli/cancelreader v0.2.2 // indirect
168184
github.com/ncruces/go-strftime v1.0.0 // indirect
@@ -195,8 +211,11 @@ require (
195211
go.etcd.io/bbolt v1.4.0 // indirect
196212
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
197213
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
214+
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect
198215
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect
216+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect
199217
go.opentelemetry.io/otel/metric v1.40.0 // indirect
218+
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
200219
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
201220
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
202221
golang.org/x/crypto v0.48.0 // indirect

0 commit comments

Comments
 (0)