Skip to content

fix: preserve default proxy settings#1656

Open
tolusha wants to merge 5 commits into
mainfrom
fixproxyconfig
Open

fix: preserve default proxy settings#1656
tolusha wants to merge 5 commits into
mainfrom
fixproxyconfig

Conversation

@tolusha

@tolusha tolusha commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Preserve default proxy settings

What issues does this PR fix or reference?

{"level":"info","ts":"2026-06-23T07:31:51Z","logger":"controllers.DevWorkspace","msg":"Failed to check server status: Get \"https://eclipse-che.apps.che-airgap-v20.crw-qe.com/happypath-dev/quarkus-api-example-wgx6/3100/healthz\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)","Request.Namespace":"happypath-dev-che","Request.Name":"quarkus-api-example-wgx6","devworkspace_id":"workspace5e9f8620bd9f4773"}

fix #1650

Is it tested? How?

Manually on disconnected cluster

PR Checklist

  • E2E tests pass (when PR is ready, comment /test v8-devworkspace-operator-e2e, v8-che-happy-path to trigger)
    • v8-devworkspace-operator-e2e: DevWorkspace e2e test
    • v8-che-happy-path: Happy path for verification integration with Che

Summary by CodeRabbit

Bug Fixes

  • Fixed HTTP proxy handling so proxy settings are applied consistently from the global configuration, without unintentionally overriding environment/default proxy behavior when no explicit proxy is configured.
  • Improved health-check reliability by using a stable health-check HTTP client that is no longer affected by per-workspace routing changes.

Tests

  • Updated and expanded HTTP client tests to cover TLS behavior, client caching, proxy-change rebuilds, and concurrent access.

tolusha added 3 commits June 23, 2026 10:55
Signed-off-by: Anatolii Bazko <abazko@redhat.com>
Signed-off-by: Anatolii Bazko <abazko@redhat.com>
Signed-off-by: Anatolii Bazko <abazko@redhat.com>
@openshift-ci

openshift-ci Bot commented Jun 23, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: tolusha
Once this PR has been reviewed and has the lgtm label, please assign dkwon17 for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

HTTP client factory is refactored to decouple proxy configuration from per-workspace routing. Proxy behavior is now computed once globally during setup and stored on the factory; both regular and health-check clients use this single stored proxy function. The GetHealthCheckHttpClient method is simplified to take no parameters. Conditional proxy assignment is applied in client creation only when the stored proxy function is non-nil, allowing transports to fall back to default proxy behavior. Tests are updated to match the new parameterless API and expanded to validate TLS handling. The status check call site is updated to use the simplified API.

Changes

Proxy decoupling from routing config

Layer / File(s) Summary
Factory interface and global proxy setup
controllers/workspace/http.go
GetHealthCheckHttpClient interface method removes RoutingConfig parameter. SetupHttpClientsFactory computes a single proxyFunc from global config, conditionally applies it to the health-check transport, and stores both proxyFunc and healthCheckHttpClient on the factory.
Client creation using stored proxy function
controllers/workspace/http.go
GetHttpClient and createHttpClient use the factory's stored proxyFunc. Proxy is assigned to transports only when non-nil; GetHealthCheckHttpClient() returns the cached instance without routing config. New parameterless getProxyFunc() builds from global settings and returns nil when unconfigured.
Test suite updates for new factory API
controllers/workspace/http_test.go
GetHealthCheckHttpClient becomes parameterless in test factory. TestGetHttpClient refactors into inline subtests validating creation, caching, and rebuild on proxy/TLS changes. newTestFactory explicitly constructs healthCheckHttpClient with TLS config. Concurrent access test validates TLS and RootCAs.
Status check call site update
controllers/workspace/status.go
checkServerStatus updates health-check client call from routing-aware GetHealthCheckHttpClient(workspace.Config.Routing) to parameterless GetHealthCheckHttpClient().

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Sequence Diagram

sequenceDiagram
  participant Init as Controller Init
  participant Factory as HttpClientsFactory
  participant GlobalConfig as Global Config
  participant Transport as HTTP Transport
  
  Init->>Factory: SetupHttpClientsFactory()
  Factory->>GlobalConfig: Read routing proxy settings
  GlobalConfig-->>Factory: HttpProxy, HttpsProxy values
  Factory->>Factory: Compute proxyFunc from global config
  alt proxyFunc is non-nil
    Factory->>Transport: Set Proxy field with proxyFunc
  else proxyFunc is nil
    Factory->>Transport: Leave Proxy field as default
  end
  Factory->>Factory: Create and cache healthCheckHttpClient
  
  Note over Factory: Later: Client requests
  participant Client as Caller
  Client->>Factory: GetHttpClient(routingConfig)
  Factory->>Factory: createHttpClient(cert only)
  Factory->>Transport: Use cached proxyFunc
  Transport-->>Client: Return configured client
Loading

Possibly related PRs

  • devfile/devworkspace-operator#1650: Both PRs modify HttpClientsFactory health-check client initialization and require corresponding updates to call sites, so the main PR's API/signature refactor is directly entangled with the factory setup changes.

Suggested labels

lgtm

Suggested reviewers

  • ibuziuk
  • akurinnoy
  • btjd
  • dkwon17
  • rohanKanojia

Poem

🐇 Through routing's tangled proxy vines,
I found a path of cleaner lines.
One config global, stored with care,
All clients breathe the same fresh air.
A simpler call, no routing passed—
The proxy's reign has breathed its last! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: preserve default proxy settings' directly aligns with the main change: decoupling proxy computation from per-workspace routing config to preserve default proxy behavior instead of resetting it.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fixproxyconfig

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tolusha

tolusha commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Hi! I'm che-ai-assistant — I help with your pull requests.

Available commands:

  • /che-ai-assistant generate-che-doc — Generate a documentation PR based on this PR's changes
  • /che-ai-assistant ok-pr-review — Run a comprehensive PR review (summary, code review, deep review, impact analysis)
  • /che-ai-assistant help — Show this help message

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controllers/workspace/http.go`:
- Around line 232-234: The condition checking ProxyConfig.HttpProxy and
ProxyConfig.HttpsProxy unconditionally dereferences these pointer fields without
first verifying they are not nil. Since both HttpProxy and HttpsProxy are
*string pointer fields with omitempty tag, they can legitimately be nil. Add
explicit nil-checks for both pointers before dereferencing them in the
condition, ensuring each pointer is not nil before accessing its value. Follow
the same pattern used in the existing code below at lines 237 and 240 which
already properly nil-check before dereferencing.
- Around line 232-234: In the proxy configuration validation check within the
ProxyConfig initialization, change the logical operator from `||` (OR) to `&&`
(AND) in the condition that evaluates *routingConfig.ProxyConfig.HttpProxy and
*routingConfig.ProxyConfig.HttpsProxy. This ensures the function only returns
nil when both proxy fields are empty, allowing configurations with at least one
proxy type set to be processed correctly instead of being skipped.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32ca01cb-c20c-44cf-be9d-63106cd185a6

📥 Commits

Reviewing files that changed from the base of the PR and between 3bbd19a and b090a16.

📒 Files selected for processing (1)
  • controllers/workspace/http.go

Comment thread controllers/workspace/http.go Outdated
Signed-off-by: Anatolii Bazko <abazko@redhat.com>
@tolusha

tolusha commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

/che-ai-assistant ok-pr-review

tolusha

This comment was marked as outdated.

Signed-off-by: Anatolii Bazko <abazko@redhat.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controllers/workspace/http_test.go`:
- Around line 81-100: The test "rebuilds client when proxy changes" is not
actually exercising the proxyFunc field that createHttpClient now uses in
production, instead it relies on routingConfig.ProxyConfig which is ignored. To
fix this, either seed the proxyFunc field in newTestFactory with a test proxy
function that can be verified in assertions, or replace the assert.NotNil checks
on client.Transport.(*http.Transport).Proxy with assertions that verify the
actual proxy URL being returned by the transport matches what was configured in
the routingConfig passed to GetHttpClient.
- Around line 166-173: The require.NotNil assertions on the client and tlsConfig
variables are being called from within a worker goroutine, which is unsafe
because require.NotNil calls FailNow() and must run on the test goroutine only.
Create a channel to collect validation results or errors from the worker
goroutine, then move these require.NotNil assertions outside the goroutine to
execute after wg.Wait() completes so test failures are reported reliably on the
main test goroutine.

In `@controllers/workspace/http.go`:
- Around line 188-209: The getProxyFunc function currently returns a proxy
closure built from an empty httpproxy.Config when ProxyConfig exists but both
HttpProxy and HttpsProxy fields are nil, which overrides the default proxy
behavior. Add a guard condition before returning the proxy function to check
that at least one of HttpProxy or HttpsProxy is non-nil; only return the proxy
closure when at least one explicit proxy field is configured, otherwise return
nil to preserve the default proxy behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e20ed13a-ee18-4abc-9bfc-8c380ebdbcb2

📥 Commits

Reviewing files that changed from the base of the PR and between b090a16 and 80de25d.

📒 Files selected for processing (3)
  • controllers/workspace/http.go
  • controllers/workspace/http_test.go
  • controllers/workspace/status.go

Comment on lines +81 to +100
t.Run("rebuilds client when proxy changes", func(t *testing.T) {
factory := newTestFactory(t)
routingConfig1 := routingConfigWithProxy("http://proxy:80", "", "")
routingConfig2 := routingConfigWithProxy("http://proxy:90", "", "")

client1 := factory.GetHttpClient(context.Background(), routingConfig1)

assert.NotNil(t, client1.Transport.(*http.Transport).Proxy)

client2 := factory.GetHttpClient(context.Background(), routingConfig2)

assert.NotNil(t, client2.Transport.(*http.Transport).Proxy)
assert.NotSame(t, client1, client2)

client3 := factory.GetHttpClient(context.Background(), nil)

assert.NotSame(t, client2, client3)

// Default proxy config is not nil as well
assert.NotNil(t, client3.Transport.(*http.Transport).Proxy)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the proxy test exercise proxyFunc, not routingConfig.ProxyConfig.

After the production change, createHttpClient ignores routingConfig.ProxyConfig and only reads factory.proxyFunc; newTestFactory leaves that field nil. The Proxy != nil assertions can pass just because http.DefaultTransport.Clone() keeps the default proxy function, so this test can miss the proxy-reset regression. Seed proxyFunc in the test factory or assert the actual proxy URL returned by the transport.

Also applies to: 217-225

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/workspace/http_test.go` around lines 81 - 100, The test "rebuilds
client when proxy changes" is not actually exercising the proxyFunc field that
createHttpClient now uses in production, instead it relies on
routingConfig.ProxyConfig which is ignored. To fix this, either seed the
proxyFunc field in newTestFactory with a test proxy function that can be
verified in assertions, or replace the assert.NotNil checks on
client.Transport.(*http.Transport).Proxy with assertions that verify the actual
proxy URL being returned by the transport matches what was configured in the
routingConfig passed to GetHttpClient.

Comment on lines +166 to +173
client := factory.GetHttpClient(context.Background(), routingConfig)

require.NotNil(t, client)

assert.NotNil(t, client)
if routingConfig != nil {
assert.NotNil(t, client.Transport.(*http.Transport).Proxy)
tlsConfig := client.Transport.(*http.Transport).TLSClientConfig
require.NotNil(t, tlsConfig)
if routingConfig != nil && routingConfig.TLSCertificateConfigmapRef != nil {
assert.NotNil(t, tlsConfig.RootCAs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect goroutine-local assertions in the concurrent test.
# Expected: no require/assert calls directly inside spawned goroutines after the fix.

sed -n '159,178p' controllers/workspace/http_test.go
rg -n -C4 'go func|require\.|assert\.' controllers/workspace/http_test.go

Repository: devfile/devworkspace-operator

Length of output: 4469


Move require assertions out of worker goroutines.

require.NotNil calls FailNow, which should run on the test goroutine. Collect failures from workers through a channel and assert after wg.Wait() so failures are reported reliably.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/workspace/http_test.go` around lines 166 - 173, The
require.NotNil assertions on the client and tlsConfig variables are being called
from within a worker goroutine, which is unsafe because require.NotNil calls
FailNow() and must run on the test goroutine only. Create a channel to collect
validation results or errors from the worker goroutine, then move these
require.NotNil assertions outside the goroutine to execute after wg.Wait()
completes so test failures are reported reliably on the main test goroutine.

Comment on lines 188 to +209
// getProxyFunc returns a proxy function based on the proxy settings in routingConfig.
// Returns nil if no proxy is configured; a nil proxy func causes the HTTP transport to
// use the default proxy settings from environment variables.
func (h *DefaultHttpClientsFactory) getProxyFunc(routingConfig *controller.RoutingConfig) func(*http.Request) (*url.URL, error) {
if routingConfig == nil || routingConfig.ProxyConfig == nil {
return nil
}
func getProxyFunc() func(*http.Request) (*url.URL, error) {
globalConfig := config.GetGlobalConfig()

proxyConfig := httpproxy.Config{}
if routingConfig.ProxyConfig.HttpProxy != nil {
proxyConfig.HTTPProxy = *routingConfig.ProxyConfig.HttpProxy
}
if routingConfig.ProxyConfig.HttpsProxy != nil {
proxyConfig.HTTPSProxy = *routingConfig.ProxyConfig.HttpsProxy
}
if routingConfig.ProxyConfig.NoProxy != nil {
proxyConfig.NoProxy = *routingConfig.ProxyConfig.NoProxy
}
if globalConfig.Routing != nil && globalConfig.Routing.ProxyConfig != nil {
proxyConf := httpproxy.Config{}
if globalConfig.Routing.ProxyConfig.HttpProxy != nil {
proxyConf.HTTPProxy = *globalConfig.Routing.ProxyConfig.HttpProxy
}
if globalConfig.Routing.ProxyConfig.HttpsProxy != nil {
proxyConf.HTTPSProxy = *globalConfig.Routing.ProxyConfig.HttpsProxy
}
if globalConfig.Routing.ProxyConfig.NoProxy != nil {
proxyConf.NoProxy = *globalConfig.Routing.ProxyConfig.NoProxy
}

return func(req *http.Request) (*url.URL, error) {
return proxyConfig.ProxyFunc()(req.URL)
return func(req *http.Request) (*url.URL, error) {
return proxyConf.ProxyFunc()(req.URL)
}
}

return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the nil/non-nil proxy function contract and proxy field optionality.
# Expected: getProxyFunc only returns non-nil when an explicit HTTP/HTTPS proxy override exists.

sed -n '77,96p;139,209p' controllers/workspace/http.go
rg -n -C3 'type Proxy struct|HttpProxy|HttpsProxy|NoProxy' apis/controller pkg/config controllers/workspace

Repository: devfile/devworkspace-operator

Length of output: 19601


Preserve defaults when ProxyConfig is present but no proxy field is set.

When ProxyConfig exists but all proxy fields (HttpProxy and HttpsProxy) are nil, the function returns a non-nil proxy closure built from an empty httpproxy.Config{}. This still overrides the cloned DefaultTransport.Proxy (lines 80–83 and 141–143), resetting the default proxy path that the PR aims to preserve. Only return a non-nil proxy function when at least one explicit HTTP or HTTPS proxy is configured; treat empty strings as explicit "disable" overrides but allow nil fields to preserve defaults.

Minimal guard
 	if globalConfig.Routing != nil && globalConfig.Routing.ProxyConfig != nil {
+		proxyConfig := globalConfig.Routing.ProxyConfig
+		if proxyConfig.HttpProxy == nil && proxyConfig.HttpsProxy == nil {
+			return nil
+		}
+
 		proxyConf := httpproxy.Config{}
-		if globalConfig.Routing.ProxyConfig.HttpProxy != nil {
-			proxyConf.HTTPProxy = *globalConfig.Routing.ProxyConfig.HttpProxy
+		if proxyConfig.HttpProxy != nil {
+			proxyConf.HTTPProxy = *proxyConfig.HttpProxy
 		}
-		if globalConfig.Routing.ProxyConfig.HttpsProxy != nil {
-			proxyConf.HTTPSProxy = *globalConfig.Routing.ProxyConfig.HttpsProxy
+		if proxyConfig.HttpsProxy != nil {
+			proxyConf.HTTPSProxy = *proxyConfig.HttpsProxy
 		}
-		if globalConfig.Routing.ProxyConfig.NoProxy != nil {
-			proxyConf.NoProxy = *globalConfig.Routing.ProxyConfig.NoProxy
+		if proxyConfig.NoProxy != nil {
+			proxyConf.NoProxy = *proxyConfig.NoProxy
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/workspace/http.go` around lines 188 - 209, The getProxyFunc
function currently returns a proxy closure built from an empty httpproxy.Config
when ProxyConfig exists but both HttpProxy and HttpsProxy fields are nil, which
overrides the default proxy behavior. Add a guard condition before returning the
proxy function to check that at least one of HttpProxy or HttpsProxy is non-nil;
only return the proxy closure when at least one explicit proxy field is
configured, otherwise return nil to preserve the default proxy behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant