Skip to content
Draft
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
55 changes: 50 additions & 5 deletions cmd/cli/pkg/standalone/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
gpupkg "github.com/docker/model-runner/cmd/cli/pkg/gpu"
Expand Down Expand Up @@ -267,6 +268,48 @@ func tryGetBindAscendMounts(printer StatusPrinter) []mount.Mount {
return newMounts
}

// getProxySettings retrieves proxy settings from Docker daemon configuration
// and environment variables. Docker daemon settings take precedence over
// environment variables. Returns a map of proxy environment variable names to values.
func getProxySettings(ctx context.Context, dockerClient *client.Client) map[string]string {
proxySettings := make(map[string]string)

// First, check environment variables as fallback
proxyEnvVars := []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"}
for _, proxyVar := range proxyEnvVars {
if value, ok := os.LookupEnv(proxyVar); ok {
proxySettings[proxyVar] = value
}
}

// Try to get proxy settings from Docker daemon configuration
// These settings take precedence over environment variables
info, err := dockerClient.Info(ctx)
if err == nil {
mergeDockerProxySettings(proxySettings, info)
}

return proxySettings
}

// mergeDockerProxySettings merges Docker daemon proxy settings into the provided map.
// Docker daemon settings take precedence over existing values.
func mergeDockerProxySettings(proxySettings map[string]string, info system.Info) {
// Map Docker daemon proxy settings to both uppercase and lowercase variants
if info.HTTPProxy != "" {
proxySettings["HTTP_PROXY"] = info.HTTPProxy
proxySettings["http_proxy"] = info.HTTPProxy
}
if info.HTTPSProxy != "" {
proxySettings["HTTPS_PROXY"] = info.HTTPSProxy
proxySettings["https_proxy"] = info.HTTPSProxy
}
if info.NoProxy != "" {
proxySettings["NO_PROXY"] = info.NoProxy
proxySettings["no_proxy"] = info.NoProxy
}
}

// CreateControllerContainer creates and starts a controller container.
func CreateControllerContainer(ctx context.Context, dockerClient *client.Client, port uint16, host string, environment string, doNotTrack bool, gpu gpupkg.GPUSupport, backend string, modelStorageVolume string, printer StatusPrinter, engineKind types.ModelRunnerEngineKind) error {
imageName := controllerImageName(gpu, backend)
Expand All @@ -281,11 +324,13 @@ func CreateControllerContainer(ctx context.Context, dockerClient *client.Client,
env = append(env, "DO_NOT_TRACK=1")
}

// Pass proxy environment variables to the container if they are set
proxyEnvVars := []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"}
for _, proxyVar := range proxyEnvVars {
if value, ok := os.LookupEnv(proxyVar); ok {
env = append(env, proxyVar+"="+value)
// Pass proxy environment variables to the container
// First, try to get proxy settings from Docker daemon configuration
// If not available, fall back to environment variables
proxySettings := getProxySettings(ctx, dockerClient)
for key, value := range proxySettings {
if value != "" {
env = append(env, key+"="+value)
}
}
config := &container.Config{
Expand Down
171 changes: 171 additions & 0 deletions cmd/cli/pkg/standalone/containers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package standalone

import (
"os"
"testing"

"github.com/docker/docker/api/types/system"
)

func TestMergeDockerProxySettings_FromDaemonConfig(t *testing.T) {
proxySettings := make(map[string]string)

info := system.Info{
HTTPProxy: "http://proxy.example.com:8080",
HTTPSProxy: "https://proxy.example.com:8443",
NoProxy: "localhost,127.0.0.1",
}

mergeDockerProxySettings(proxySettings, info)

// Verify both uppercase and lowercase variants are set
if proxySettings["HTTP_PROXY"] != "http://proxy.example.com:8080" {
t.Errorf("Expected HTTP_PROXY to be http://proxy.example.com:8080, got %s", proxySettings["HTTP_PROXY"])
}
if proxySettings["http_proxy"] != "http://proxy.example.com:8080" {
t.Errorf("Expected http_proxy to be http://proxy.example.com:8080, got %s", proxySettings["http_proxy"])
}
if proxySettings["HTTPS_PROXY"] != "https://proxy.example.com:8443" {
t.Errorf("Expected HTTPS_PROXY to be https://proxy.example.com:8443, got %s", proxySettings["HTTPS_PROXY"])
}
if proxySettings["https_proxy"] != "https://proxy.example.com:8443" {
t.Errorf("Expected https_proxy to be https://proxy.example.com:8443, got %s", proxySettings["https_proxy"])
}
if proxySettings["NO_PROXY"] != "localhost,127.0.0.1" {
t.Errorf("Expected NO_PROXY to be localhost,127.0.0.1, got %s", proxySettings["NO_PROXY"])
}
if proxySettings["no_proxy"] != "localhost,127.0.0.1" {
t.Errorf("Expected no_proxy to be localhost,127.0.0.1, got %s", proxySettings["no_proxy"])
}
}

func TestMergeDockerProxySettings_OverridesExisting(t *testing.T) {
// Start with environment-based settings
proxySettings := map[string]string{
"HTTP_PROXY": "http://env.proxy.com:3128",
"http_proxy": "http://env.proxy.com:3128",
"HTTPS_PROXY": "https://env.proxy.com:3129",
"https_proxy": "https://env.proxy.com:3129",
"NO_PROXY": "localhost",
"no_proxy": "localhost",
}

info := system.Info{
HTTPProxy: "http://daemon.proxy.com:8080",
HTTPSProxy: "https://daemon.proxy.com:8443",
NoProxy: "localhost,127.0.0.1,*.local",
}

mergeDockerProxySettings(proxySettings, info)

// Verify daemon settings take precedence
if proxySettings["HTTP_PROXY"] != "http://daemon.proxy.com:8080" {
t.Errorf("Expected HTTP_PROXY from daemon to override env, got %s", proxySettings["HTTP_PROXY"])
}
if proxySettings["http_proxy"] != "http://daemon.proxy.com:8080" {
t.Errorf("Expected http_proxy from daemon to override env, got %s", proxySettings["http_proxy"])
}
if proxySettings["HTTPS_PROXY"] != "https://daemon.proxy.com:8443" {
t.Errorf("Expected HTTPS_PROXY from daemon to override env, got %s", proxySettings["HTTPS_PROXY"])
}
if proxySettings["https_proxy"] != "https://daemon.proxy.com:8443" {
t.Errorf("Expected https_proxy from daemon to override env, got %s", proxySettings["https_proxy"])
}
if proxySettings["NO_PROXY"] != "localhost,127.0.0.1,*.local" {
t.Errorf("Expected NO_PROXY from daemon to override env, got %s", proxySettings["NO_PROXY"])
}
if proxySettings["no_proxy"] != "localhost,127.0.0.1,*.local" {
t.Errorf("Expected no_proxy from daemon to override env, got %s", proxySettings["no_proxy"])
}
}

func TestMergeDockerProxySettings_EmptyDoesNotOverride(t *testing.T) {
// Start with environment-based settings
proxySettings := map[string]string{
"HTTP_PROXY": "http://env.proxy.com:3128",
"http_proxy": "http://env.proxy.com:3128",
"HTTPS_PROXY": "https://env.proxy.com:3129",
"https_proxy": "https://env.proxy.com:3129",
}

// Empty daemon info should not override existing values
info := system.Info{}

mergeDockerProxySettings(proxySettings, info)

// Verify environment settings are preserved
if proxySettings["HTTP_PROXY"] != "http://env.proxy.com:3128" {
t.Errorf("Expected HTTP_PROXY to remain from env, got %s", proxySettings["HTTP_PROXY"])
}
if proxySettings["http_proxy"] != "http://env.proxy.com:3128" {
t.Errorf("Expected http_proxy to remain from env, got %s", proxySettings["http_proxy"])
}
if proxySettings["HTTPS_PROXY"] != "https://env.proxy.com:3129" {
t.Errorf("Expected HTTPS_PROXY to remain from env, got %s", proxySettings["HTTPS_PROXY"])
}
if proxySettings["https_proxy"] != "https://env.proxy.com:3129" {
t.Errorf("Expected https_proxy to remain from env, got %s", proxySettings["https_proxy"])
}
}

func TestMergeDockerProxySettings_PartialSettings(t *testing.T) {
proxySettings := make(map[string]string)

// Only HTTP proxy is set in daemon
info := system.Info{
HTTPProxy: "http://proxy.example.com:8080",
}

mergeDockerProxySettings(proxySettings, info)

// Verify only HTTP proxy is set
if proxySettings["HTTP_PROXY"] != "http://proxy.example.com:8080" {
t.Errorf("Expected HTTP_PROXY to be http://proxy.example.com:8080, got %s", proxySettings["HTTP_PROXY"])
}
if proxySettings["http_proxy"] != "http://proxy.example.com:8080" {
t.Errorf("Expected http_proxy to be http://proxy.example.com:8080, got %s", proxySettings["http_proxy"])
}

// Verify other proxies are not set
if val, ok := proxySettings["HTTPS_PROXY"]; ok && val != "" {
t.Errorf("Expected HTTPS_PROXY to be empty, got %s", val)
}
if val, ok := proxySettings["NO_PROXY"]; ok && val != "" {
t.Errorf("Expected NO_PROXY to be empty, got %s", val)
}
}

func TestGetProxySettings_EnvironmentOnly(t *testing.T) {
// Save current env vars
savedEnv := map[string]string{
"HTTP_PROXY": os.Getenv("HTTP_PROXY"),
"HTTPS_PROXY": os.Getenv("HTTPS_PROXY"),
"NO_PROXY": os.Getenv("NO_PROXY"),
"http_proxy": os.Getenv("http_proxy"),
"https_proxy": os.Getenv("https_proxy"),
"no_proxy": os.Getenv("no_proxy"),
}
defer func() {
for k, v := range savedEnv {
if v != "" {
os.Setenv(k, v)
} else {
os.Unsetenv(k)
}
}
}()

// Clear all proxy env vars first
for k := range savedEnv {
os.Unsetenv(k)
}

// Set test environment variables
os.Setenv("HTTP_PROXY", "http://env.proxy.com:3128")
os.Setenv("https_proxy", "https://env.proxy.com:3129")
os.Setenv("NO_PROXY", "localhost")

// Test that environment variables are picked up
// Note: We can't easily test getProxySettings without a real Docker client,
// but the mergeDockerProxySettings tests above cover the core logic
}