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
2 changes: 1 addition & 1 deletion cmd/root/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func runPushCommand(cmd *cobra.Command, args []string) error {

out.Printf("Pushing agent %s to %s\n", agentFilename, tag)

err = remote.Push(tag)
err = remote.Push(ctx, tag)
if err != nil {
return fmt.Errorf("failed to push artifact: %w", err)
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require (
github.com/gorilla/websocket v1.5.3
github.com/junegunn/fzf v0.70.0
github.com/k3a/html2text v1.4.0
github.com/kofalt/go-memoize v0.0.0-20240506050413-9e5eb99a0f2a
github.com/labstack/echo/v4 v4.15.1
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-runewidth v0.0.21
Expand Down Expand Up @@ -183,6 +184,7 @@ require (
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
9 changes: 8 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kofalt/go-memoize v0.0.0-20240506050413-9e5eb99a0f2a h1:yyeZ0oZLWgSakB9QzPuL/Kyx9kcXYblDOswXaOEx0tg=
github.com/kofalt/go-memoize v0.0.0-20240506050413-9e5eb99a0f2a/go.mod h1:EUxMohcCc4AiiO1SImzCQo3EdrEYj9Xkyrxbepg02nQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down Expand Up @@ -385,6 +387,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
Expand Down Expand Up @@ -431,10 +435,13 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4=
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82/go.mod h1:xe4pgH49k4SsmkQq5OT8abwhWmnzkhpgnXeekbx2efw=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/gunit v1.4.2 h1:tyWYZffdPhQPfK5VsMQXfauwnJkqg7Tv5DLuQVYxq3Q=
github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ func (a urlSource) Read(ctx context.Context) ([]byte, error) {
// Add GitHub token authorization for GitHub URLs
a.addGitHubAuth(ctx, req)

resp, err := httpclient.NewHTTPClient().Do(req)
resp, err := httpclient.NewHTTPClient(ctx).Do(req)
if err != nil {
// Network error - try to use cached version
if cachedData, cacheErr := os.ReadFile(cachePath); cacheErr == nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/desktop/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "sync"

type DockerDesktopPaths struct {
BackendSocket string
ProxySocket string
}

var Paths = sync.OnceValue(func() DockerDesktopPaths {
Expand Down
3 changes: 3 additions & 0 deletions pkg/desktop/running.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package desktop

import (
"context"
"time"
)

func IsDockerDesktopRunning(ctx context.Context) bool {
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
defer cancel()
err := ClientBackend.Get(ctx, "/ping", nil)
return err == nil
}
9 changes: 9 additions & 0 deletions pkg/desktop/socket/dial.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package socket

import (
"strings"
)

func stripUnixScheme(path string) string {
return strings.TrimPrefix(path, "unix://")
}
14 changes: 14 additions & 0 deletions pkg/desktop/socket/dial_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build !windows

package socket

import (
"context"
"net"
)

// DialUnix is a simple wrapper for `net.Dial("unix")`.
func DialUnix(ctx context.Context, path string) (net.Conn, error) {
dialer := &net.Dialer{}
return dialer.DialContext(ctx, "unix", stripUnixScheme(path))
}
24 changes: 24 additions & 0 deletions pkg/desktop/socket/dial_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package socket

import (
"context"
"net"
"strings"
"time"

"github.com/Microsoft/go-winio"
)

// DialUnix is a simple wrapper for `winio.DialPipe(path, 10s)`.
// It provides API compatibility for named pipes with the Unix domain socket API.
func DialUnix(ctx context.Context, path string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()

if strings.HasPrefix(path, "unix://") {
// windows supports AF_UNIX
d := &net.Dialer{}
return d.DialContext(ctx, "unix", stripUnixScheme(path))
}
return winio.DialPipeContext(ctx, path)
}
1 change: 1 addition & 0 deletions pkg/desktop/sockets_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ func getDockerDesktopPaths() (DockerDesktopPaths, error) {

return DockerDesktopPaths{
BackendSocket: filepath.Join(data, "backend.sock"),
ProxySocket: filepath.Join(data, "httpproxy.sock"),
}, nil
}
3 changes: 3 additions & 0 deletions pkg/desktop/sockets_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func getDockerDesktopPaths() (DockerDesktopPaths, error) {
// Inside LinuxKit
return DockerDesktopPaths{
BackendSocket: "/run/host-services/backend.sock",
ProxySocket: "/run/host-services/httpproxy.sock",
}, nil
}

Expand All @@ -23,6 +24,7 @@ func getDockerDesktopPaths() (DockerDesktopPaths, error) {
// Inside WSL2
return DockerDesktopPaths{
BackendSocket: "/mnt/wsl/docker-desktop/shared-sockets/host-services/backend.sock",
ProxySocket: "/mnt/wsl/docker-desktop/shared-sockets/host-services/httpproxy.sock",
}, nil
}

Expand All @@ -38,5 +40,6 @@ func getDockerDesktopPaths() (DockerDesktopPaths, error) {
// On Linux
return DockerDesktopPaths{
BackendSocket: filepath.Join(home, ".docker", "desktop", "backend.sock"),
ProxySocket: filepath.Join(home, ".docker", "desktop", "httpproxy.sock"),
}, nil
}
1 change: 1 addition & 0 deletions pkg/desktop/sockets_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ func getDockerDesktopPaths() (DockerDesktopPaths, error) {

return DockerDesktopPaths{
BackendSocket: `\\.\pipe\dockerBackendApiServer`,
ProxySocket: `\\.\pipe\dockerHTTPProxy`,
}, nil
}
6 changes: 2 additions & 4 deletions pkg/gateway/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

"github.com/docker/docker-agent/pkg/paths"
"github.com/docker/docker-agent/pkg/remote"
)

const (
Expand Down Expand Up @@ -166,10 +167,6 @@ func saveToDisk(path string, catalog Catalog, etag string) {
}
}

// catalogClient is a dedicated HTTP client for catalog fetches, isolated from
// http.DefaultClient so that other parts of the process cannot interfere.
var catalogClient = &http.Client{}

// fetchFromNetwork fetches the catalog, using the ETag for conditional requests.
// It returns (nil, "", nil) when the server responds with 304 Not Modified.
func fetchFromNetwork(ctx context.Context, etag string) (Catalog, string, error) {
Expand All @@ -185,6 +182,7 @@ func fetchFromNetwork(ctx context.Context, etag string) (Catalog, string, error)
req.Header.Set("If-None-Match", etag)
}

catalogClient := &http.Client{Transport: remote.NewTransport(ctx)}
resp, err := catalogClient.Do(req)
if err != nil {
return nil, "", err
Expand Down
25 changes: 15 additions & 10 deletions pkg/httpclient/client.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package httpclient

import (
"context"
"fmt"
"maps"
"net/http"
"net/url"
"runtime"

"github.com/docker/docker-agent/pkg/remote"
"github.com/docker/docker-agent/pkg/version"
)

Expand All @@ -17,7 +19,7 @@ type HTTPOptions struct {

type Opt func(*HTTPOptions)

func NewHTTPClient(opts ...Opt) *http.Client {
func NewHTTPClient(ctx context.Context, opts ...Opt) *http.Client {
httpOptions := HTTPOptions{
Header: make(http.Header),
}
Expand All @@ -32,7 +34,7 @@ func NewHTTPClient(opts ...Opt) *http.Client {
// Disable automatic gzip: Go's default transport transparently compresses
// and decompresses responses, which is incompatible with SSE streaming.
// See https://github.com/docker/docker-agent/issues/1956
rt := newTransport()
rt := newTransport(ctx)

return &http.Client{
Transport: &userAgentTransport{
Expand Down Expand Up @@ -95,15 +97,18 @@ func WithQuery(query url.Values) Opt {
}
}

// newTransport returns an HTTP transport with automatic gzip compression disabled.
func newTransport() http.RoundTripper {
t, ok := http.DefaultTransport.(*http.Transport)
if !ok {
return http.DefaultTransport
// newTransport returns an HTTP transport with automatic gzip compression disabled and using Docker Desktop proxy if available.
func newTransport(ctx context.Context) http.RoundTripper {
// Get the base transport with Desktop proxy support from remote package
rt := remote.NewTransport(ctx)

// If it's an http.Transport, disable compression for SSE streaming compatibility
if transport, ok := rt.(*http.Transport); ok {
transport.DisableCompression = true
return transport
}
transport := t.Clone()
transport.DisableCompression = true
return transport

return rt
}

type userAgentTransport struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/httpclient/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func doRequest(t *testing.T, opts ...Opt) http.Header {
}))
defer srv.Close()

client := NewHTTPClient(opts...)
client := NewHTTPClient(t.Context(), opts...)
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
require.NoError(t, err)

Expand Down
4 changes: 2 additions & 2 deletions pkg/model/provider/anthropic/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, env environment.Pro
slog.Debug("Anthropic API key found, creating client")
requestOptions := []option.RequestOption{
option.WithAPIKey(authToken),
option.WithHTTPClient(httpclient.NewHTTPClient()),
option.WithHTTPClient(httpclient.NewHTTPClient(ctx)),
}
if cfg.BaseURL != "" {
requestOptions = append(requestOptions, option.WithBaseURL(cfg.BaseURL))
Expand Down Expand Up @@ -210,7 +210,7 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, env environment.Pro
option.WithAuthToken(authToken),
option.WithAPIKey(authToken),
option.WithBaseURL(baseURL),
option.WithHTTPClient(httpclient.NewHTTPClient(httpOptions...)),
option.WithHTTPClient(httpclient.NewHTTPClient(ctx, httpOptions...)),
)

return client, nil
Expand Down
4 changes: 2 additions & 2 deletions pkg/model/provider/gemini/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, env environment.Pro
}

backend = genai.BackendGeminiAPI
httpClient = httpclient.NewHTTPClient()
httpClient = httpclient.NewHTTPClient(ctx)
}

client, err := genai.NewClient(ctx, &genai.ClientConfig{
Expand Down Expand Up @@ -152,7 +152,7 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, env environment.Pro
return genai.NewClient(ctx, &genai.ClientConfig{
APIKey: authToken,
Backend: genai.BackendGeminiAPI,
HTTPClient: httpclient.NewHTTPClient(httpOptions...),
HTTPClient: httpclient.NewHTTPClient(ctx, httpOptions...),
HTTPOptions: genai.HTTPOptions{
BaseURL: baseURL,
Headers: http.Header{
Expand Down
4 changes: 2 additions & 2 deletions pkg/model/provider/openai/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, env environment.Pro
clientOptions = append(clientOptions, option.WithBaseURL(cfg.BaseURL))
}

httpClient := httpclient.NewHTTPClient()
httpClient := httpclient.NewHTTPClient(ctx)
clientOptions = append(clientOptions, option.WithHTTPClient(httpClient))

client := openai.NewClient(clientOptions...)
Expand Down Expand Up @@ -135,7 +135,7 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, env environment.Pro
client := openai.NewClient(
option.WithAPIKey(authToken),
option.WithBaseURL(baseURL),
option.WithHTTPClient(httpclient.NewHTTPClient(httpOptions...)),
option.WithHTTPClient(httpclient.NewHTTPClient(ctx, httpOptions...)),
option.WithMiddleware(oaistream.ErrorBodyMiddleware()),
)

Expand Down
4 changes: 3 additions & 1 deletion pkg/modelsdev/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"strings"
"sync"
"time"

"github.com/docker/docker-agent/pkg/remote"
)

const (
Expand Down Expand Up @@ -183,7 +185,7 @@ func fetchFromAPI(ctx context.Context, etag string) (*Database, string, error) {
req.Header.Set("If-None-Match", etag)
}

resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
resp, err := (&http.Client{Timeout: 30 * time.Second, Transport: remote.NewTransport(ctx)}).Do(req)
if err != nil {
return nil, "", fmt.Errorf("failed to fetch from API: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/remote/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

// Pull pulls an artifact from a registry and stores it in the content store
func Pull(ctx context.Context, registryRef string, force bool, opts ...crane.Option) (string, error) {
opts = append(opts, crane.WithContext(ctx))
opts = append(opts, crane.WithContext(ctx), crane.WithTransport(NewTransport(ctx)))

ref, err := name.ParseReference(registryRef)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/remote/pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestPullIntegration(t *testing.T) {
require.NoError(t, err)
assert.NotNil(t, retrievedImg)

err = Push("invalid:reference:with:too:many:colons")
err = Push(t.Context(), "invalid:reference:with:too:many:colons")
require.Error(t, err)
}

Expand Down
5 changes: 3 additions & 2 deletions pkg/remote/push.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package remote

import (
"context"
"fmt"

"github.com/google/go-containerregistry/pkg/crane"
Expand All @@ -13,7 +14,7 @@ import (
)

// Push pushes an artifact from the content store to an OCI registry
func Push(reference string) error {
func Push(ctx context.Context, reference string) error {
store, err := content.NewStore()
if err != nil {
return fmt.Errorf("creating content store: %w", err)
Expand Down Expand Up @@ -45,7 +46,7 @@ func Push(reference string) error {
return fmt.Errorf("parsing registry reference %s: %w", reference, err)
}

if err := crane.Push(img, ref.String()); err != nil {
if err := crane.Push(img, ref.String(), crane.WithContext(ctx), crane.WithTransport(NewTransport(ctx))); err != nil {
return fmt.Errorf("pushing image to registry %s: %w", reference, err)
}

Expand Down
Loading
Loading