Skip to content
2 changes: 1 addition & 1 deletion controllers/backupcronjob/backupcronjob_controller.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2025 Red Hat, Inc.
// Copyright (c) 2019-2026 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2025 Red Hat, Inc.
// Copyright (c) 2019-2026 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2025 Red Hat, Inc.
// Copyright (c) 2019-2026 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down
17 changes: 6 additions & 11 deletions controllers/workspace/devworkspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ import (
"strings"
"time"

"github.com/devfile/devworkspace-operator/pkg/library/initcontainers"
"github.com/devfile/devworkspace-operator/pkg/library/overrides"
"github.com/devfile/devworkspace-operator/pkg/library/ssh"

dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
devfilevalidation "github.com/devfile/api/v2/pkg/validation"
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
Expand All @@ -35,15 +31,19 @@ import (
wkspConfig "github.com/devfile/devworkspace-operator/pkg/config"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/dwerrors"
"github.com/devfile/devworkspace-operator/pkg/httpfactory"
"github.com/devfile/devworkspace-operator/pkg/library/annotate"
containerlib "github.com/devfile/devworkspace-operator/pkg/library/container"
wsDefaults "github.com/devfile/devworkspace-operator/pkg/library/defaults"
"github.com/devfile/devworkspace-operator/pkg/library/env"
"github.com/devfile/devworkspace-operator/pkg/library/flatten"
"github.com/devfile/devworkspace-operator/pkg/library/home"
"github.com/devfile/devworkspace-operator/pkg/library/initcontainers"
kubesync "github.com/devfile/devworkspace-operator/pkg/library/kubernetes"
"github.com/devfile/devworkspace-operator/pkg/library/overrides/restrictions"
"github.com/devfile/devworkspace-operator/pkg/library/projects"
"github.com/devfile/devworkspace-operator/pkg/library/restore"
"github.com/devfile/devworkspace-operator/pkg/library/ssh"
"github.com/devfile/devworkspace-operator/pkg/library/status"
"github.com/devfile/devworkspace-operator/pkg/provision/automount"
"github.com/devfile/devworkspace-operator/pkg/provision/metadata"
Expand Down Expand Up @@ -258,7 +258,7 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
return reconcile.Result{Requeue: true}, err
}

httpClient := httpClientsFactory.GetHttpClient(ctx, config.Routing)
httpClient := httpfactory.HttpFactory.GetHttpClient(ctx, config.Routing)

flattenHelpers := flatten.ResolverTools{
WorkspaceNamespace: workspace.Namespace,
Expand Down Expand Up @@ -339,7 +339,7 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
workspace.Config.Workspace.DefaultContainerResources,
workspace.Config.Workspace.ContainerResourceCaps,
workspace.Config.Workspace.PostStartTimeout,
overrides.GetRestrictedContainerOverrideFields(workspace),
restrictions.GetRestrictedContainerFields(workspace),
postStartDebugTrapSleepDuration,
)
if err != nil {
Expand Down Expand Up @@ -789,11 +789,6 @@ func (r *DevWorkspaceReconciler) getWorkspaceId(ctx context.Context, workspace *
}

func (r *DevWorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
err := SetupHttpClientsFactory(mgr.GetClient(), mgr.GetLogger())
if err != nil {
return err
}

maxConcurrentReconciles, err := wkspConfig.GetMaxConcurrentReconciles()
if err != nil {
return err
Expand Down
32 changes: 16 additions & 16 deletions controllers/workspace/devworkspace_controller_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019-2025 Red Hat, Inc.
// Copyright (c) 2019-2026 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand All @@ -22,12 +22,12 @@ import (

dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
workspacecontroller "github.com/devfile/devworkspace-operator/controllers/workspace"
"github.com/devfile/devworkspace-operator/controllers/workspace/internal/testutil"
"github.com/devfile/devworkspace-operator/pkg/common"
"github.com/devfile/devworkspace-operator/pkg/conditions"
"github.com/devfile/devworkspace-operator/pkg/config"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/httpfactory"
"github.com/devfile/devworkspace-operator/pkg/library/projects"
"github.com/devfile/devworkspace-operator/pkg/library/restore"
. "github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -184,7 +184,7 @@ var _ = Describe("DevWorkspace Controller", func() {

AfterEach(func() {
deleteDevWorkspace(devWorkspaceName)
workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient())
httpfactory.SetupHttpClientsForTesting(getBasicTestHttpClient())
})

It("Creates roles and rolebindings", func() {
Expand Down Expand Up @@ -317,7 +317,7 @@ var _ = Describe("DevWorkspace Controller", func() {
devworkspace := getExistingDevWorkspace(devWorkspaceName)
workspaceID := devworkspace.Status.DevWorkspaceId

workspacecontroller.SetupHttpClientsForTesting(&http.Client{
httpfactory.SetupHttpClientsForTesting(&http.Client{
Transport: &testutil.TestRoundTripper{
Data: map[string]testutil.TestResponse{
"test-url/healthz": {
Expand Down Expand Up @@ -355,7 +355,7 @@ var _ = Describe("DevWorkspace Controller", func() {
const testURL = "test-url"

BeforeEach(func() {
workspacecontroller.SetupHttpClientsForTesting(&http.Client{
httpfactory.SetupHttpClientsForTesting(&http.Client{
Transport: &testutil.TestRoundTripper{
Data: map[string]testutil.TestResponse{
fmt.Sprintf("%s/healthz", testURL): {
Expand All @@ -368,7 +368,7 @@ var _ = Describe("DevWorkspace Controller", func() {

AfterEach(func() {
deleteDevWorkspace(devWorkspaceName)
workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient())
httpfactory.SetupHttpClientsForTesting(getBasicTestHttpClient())
})

It("Mounts image pull secrets to the DevWorkspace Deployment", func() {
Expand Down Expand Up @@ -954,7 +954,7 @@ var _ = Describe("DevWorkspace Controller", func() {
const testURL = "test-url"

BeforeEach(func() {
workspacecontroller.SetupHttpClientsForTesting(&http.Client{
httpfactory.SetupHttpClientsForTesting(&http.Client{
Transport: &testutil.TestRoundTripper{
Data: map[string]testutil.TestResponse{
fmt.Sprintf("%s/healthz", testURL): {
Expand All @@ -967,7 +967,7 @@ var _ = Describe("DevWorkspace Controller", func() {

AfterEach(func() {
deleteDevWorkspace(devWorkspaceName)
workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient())
httpfactory.SetupHttpClientsForTesting(getBasicTestHttpClient())
})

It("Sets the runtimeClassName from the DWOC", func() {
Expand Down Expand Up @@ -1033,7 +1033,7 @@ var _ = Describe("DevWorkspace Controller", func() {
const testURL = "test-url"

BeforeEach(func() {
workspacecontroller.SetupHttpClientsForTesting(&http.Client{
httpfactory.SetupHttpClientsForTesting(&http.Client{
Transport: &testutil.TestRoundTripper{
Data: map[string]testutil.TestResponse{
fmt.Sprintf("%s/healthz", testURL): {
Expand All @@ -1047,7 +1047,7 @@ var _ = Describe("DevWorkspace Controller", func() {

AfterEach(func() {
deleteDevWorkspace(devWorkspaceName)
workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient())
httpfactory.SetupHttpClientsForTesting(getBasicTestHttpClient())
})

It("Stops workspaces and scales deployment to zero", func() {
Expand Down Expand Up @@ -1224,7 +1224,7 @@ var _ = Describe("DevWorkspace Controller", func() {

BeforeEach(func() {
By("Setting up HTTP client")
workspacecontroller.SetupHttpClientsForTesting(&http.Client{
httpfactory.SetupHttpClientsForTesting(&http.Client{
Transport: &testutil.TestRoundTripper{
Data: map[string]testutil.TestResponse{
fmt.Sprintf("%s/healthz", testURL): {
Expand All @@ -1241,7 +1241,7 @@ var _ = Describe("DevWorkspace Controller", func() {
deleteDevWorkspace(altDevWorkspaceName)
cleanupPVC("claim-devworkspace")
By("Resetting HTTP client")
workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient())
httpfactory.SetupHttpClientsForTesting(getBasicTestHttpClient())
})

It("Cleans up workspace PVC storage when other workspaces exist", func() {
Expand Down Expand Up @@ -1361,7 +1361,7 @@ var _ = Describe("DevWorkspace Controller", func() {
const testURL = "test-url"

BeforeEach(func() {
workspacecontroller.SetupHttpClientsForTesting(&http.Client{
httpfactory.SetupHttpClientsForTesting(&http.Client{
Transport: &testutil.TestRoundTripper{
Data: map[string]testutil.TestResponse{
fmt.Sprintf("%s/healthz", testURL): {
Expand All @@ -1374,7 +1374,7 @@ var _ = Describe("DevWorkspace Controller", func() {

AfterEach(func() {
deleteDevWorkspace(devWorkspaceName)
workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient())
httpfactory.SetupHttpClientsForTesting(getBasicTestHttpClient())
})

It("Restores workspace from backup with common PVC", func() {
Expand Down Expand Up @@ -1563,7 +1563,7 @@ var _ = Describe("DevWorkspace Controller", func() {
devworkspace := getExistingDevWorkspace(devWorkspaceName)
workspaceID := devworkspace.Status.DevWorkspaceId

workspacecontroller.SetupHttpClientsForTesting(&http.Client{
httpfactory.SetupHttpClientsForTesting(&http.Client{
Transport: &testutil.TestRoundTripper{
Data: map[string]testutil.TestResponse{
"test-url/healthz": {
Expand Down Expand Up @@ -1595,7 +1595,7 @@ var _ = Describe("DevWorkspace Controller", func() {
Expect(runningCondition.Status).Should(Equal(corev1.ConditionTrue))

// Clean up
workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient())
httpfactory.SetupHttpClientsForTesting(getBasicTestHttpClient())
})

It("Ensures preStart initContainers are run after project-clone", func() {
Expand Down
3 changes: 2 additions & 1 deletion controllers/workspace/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/devworkspace-operator/pkg/common"
"github.com/devfile/devworkspace-operator/pkg/dwerrors"
"github.com/devfile/devworkspace-operator/pkg/httpfactory"
"github.com/devfile/devworkspace-operator/pkg/provision/sync"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -210,7 +211,7 @@ func checkServerStatus(workspace *common.DevWorkspaceWithConfig) (ok bool, respo
}
healthz.Path = path.Join(healthz.Path, "healthz")

healthCheckHttpClient := httpClientsFactory.GetHealthCheckHttpClient()
healthCheckHttpClient := httpfactory.HttpFactory.GetHealthCheckHttpClient()
resp, err := healthCheckHttpClient.Get(healthz.String())
if err != nil {
return false, nil, &dwerrors.RetryError{Err: err, Message: "Failed to check server status", RequeueAfter: 1 * time.Second}
Expand Down
5 changes: 3 additions & 2 deletions controllers/workspace/suite_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019-2025 Red Hat, Inc.
// Copyright (c) 2019-2026 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand All @@ -22,6 +22,7 @@ import (
"runtime"
"testing"

"github.com/devfile/devworkspace-operator/pkg/httpfactory"
"sigs.k8s.io/controller-runtime/pkg/webhook"

dwv1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha1"
Expand Down Expand Up @@ -147,7 +148,7 @@ var _ = BeforeSuite(func() {
Expect(err).NotTo(HaveOccurred())

// Set HTTP client to fail all requests by default; tests that require HTTP must set this up directly
workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient())
httpfactory.SetupHttpClientsForTesting(getBasicTestHttpClient())

// Skip trying to set up / test webhooks for now

Expand Down
9 changes: 8 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2025 Red Hat, Inc.
// Copyright (c) 2019-2026 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand All @@ -22,6 +22,7 @@ import (
"os"
"runtime"

"github.com/devfile/devworkspace-operator/pkg/httpfactory"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"

"github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting"
Expand Down Expand Up @@ -146,6 +147,12 @@ func main() {
os.Exit(1)
}

err = httpfactory.SetupHttpClientsFactory(mgr.GetClient(), mgr.GetLogger())
if err != nil {
setupLog.Error(err, "Failed to setup Http clients factory")
os.Exit(1)
}

nonCachingClient, err := client.New(mgr.GetConfig(), client.Options{Scheme: scheme})
if err != nil {
setupLog.Error(err, "unable to initialize non-caching client")
Expand Down
12 changes: 10 additions & 2 deletions pkg/constants/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ const (
// can either be specified as a decimal (e.g. '416') or as an octal by prefixing the number with zero (e.g. '0640')
DevWorkspaceMountAccessModeAnnotation = "controller.devfile.io/mount-access-mode"

// DevWorkspaceValidatedK8sResourcesAnnotation is the annotation key used to store the list of
// Kubernetes resources that have been validated by the mutating webhook via SubjectAccessReview.
// The controller uses this to determine which objects the workspace creator is authorized to create.
DevWorkspaceValidatedK8sResourcesAnnotation = "controller.devfile.io/validated-kubernetes-resources"
// DevWorkspaceValidatedSCCAnnotation is the annotation key used to store the SecurityContextConstraints
// that have been validated by the mutating webhook via SubjectAccessReview.
DevWorkspaceValidatedSCCAnnotation = "controller.devfile.io/validated-scc"

// DevWorkspaceGitCredentialLabel is the label key to specify if the secret is a git credential. All secrets who
// specify this label in a namespace will consolidate into one secret before mounting into a devworkspace.
// Only secret data with the credentials key will be used and credentials must be the base64 encoded version
Expand Down Expand Up @@ -161,10 +169,10 @@ const (
// fails to start (i.e. enters the "Failed" phase), its deployment will not be scaled down in order to allow viewing logs, etc.
DevWorkspaceDebugStartAnnotation = "controller.devfile.io/debug-start"

// WebhookRestartedAtAnnotation holds the the time (unixnano) of when the webhook server was forced to restart by controller
// WebhookRestartedAtAnnotation holds the time (unixnano) of when the webhook server was forced to restart by controller
WebhookRestartedAtAnnotation = "controller.devfile.io/restarted-at"

// DevWorkspaceStartedAtAnnotation holds the the time (unixnano) of when the devworkspace was started
// DevWorkspaceStartedAtAnnotation holds the time (unixnano) of when the devworkspace was started
DevWorkspaceStartedAtAnnotation = "controller.devfile.io/started-at"

// RoutingAnnotationInfix is the infix of the annotations of DevWorkspace that are passed down as annotation to the DevWorkspaceRouting objects.
Expand Down
6 changes: 3 additions & 3 deletions controllers/workspace/http.go → pkg/httpfactory/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package controllers
package httpfactory

import (
"context"
Expand All @@ -35,7 +35,7 @@ import (
"golang.org/x/net/http/httpproxy"
)

var httpClientsFactory HttpClientsFactory
var HttpFactory HttpClientsFactory

type HttpClientsFactory interface {
// GetHttpClient returns an HTTP client configured with proxy, TLS, and custom CA certificates
Expand Down Expand Up @@ -84,7 +84,7 @@ func SetupHttpClientsFactory(k8s client.Client, logger logr.Logger) error {
InsecureSkipVerify: true,
}

httpClientsFactory = &DefaultHttpClientsFactory{
HttpFactory = &DefaultHttpClientsFactory{
k8s: k8s,
logger: logger,
systemCertPool: systemCertPool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package controllers
package httpfactory

import (
"context"
Expand Down Expand Up @@ -40,26 +40,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)

type TestHttpClientsFactory struct {
client *http.Client
healthCheckHttpClient *http.Client
}

func (t *TestHttpClientsFactory) GetHttpClient(_ context.Context, _ *controller.RoutingConfig) *http.Client {
return t.client
}

func (t *TestHttpClientsFactory) GetHealthCheckHttpClient() *http.Client {
return t.healthCheckHttpClient
}

func SetupHttpClientsForTesting(client *http.Client) {
httpClientsFactory = &TestHttpClientsFactory{
client: client,
healthCheckHttpClient: client,
}
}

func TestHealthCheckHttpClient(t *testing.T) {
t.Run("returns non-nil client", func(t *testing.T) {
factory := newTestFactory(t)
Expand Down
Loading
Loading