Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ func (r *DevWorkspaceRoutingReconciler) Reconcile(ctx context.Context, req ctrl.
return reconcile.Result{}, r.markRoutingFailed(instance, "DevWorkspaceRouting requires field routingClass to be set")
}

solver, err := r.SolverGetter.GetSolver(r.Client, instance.Spec.RoutingClass)
solver, err := r.SolverGetter.GetSolver(r.Client, reqLogger, instance.Spec.RoutingClass)
if err != nil {
if errors.Is(err, solvers.RoutingNotSupported) {
reqLogger.Info("Routing class not supported by this controller, skipping reconciliation", "routingClass", instance.Spec.RoutingClass)
return reconcile.Result{}, nil
}
return reconcile.Result{}, r.markRoutingFailed(instance, fmt.Sprintf("Invalid routingClass for DevWorkspace: %s", err))
Expand Down Expand Up @@ -125,9 +126,10 @@ func (r *DevWorkspaceRoutingReconciler) Reconcile(ctx context.Context, req ctrl.
}

workspaceMeta := solvers.DevWorkspaceMetadata{
DevWorkspaceId: instance.Spec.DevWorkspaceId,
Namespace: instance.Namespace,
PodSelector: instance.Spec.PodSelector,
DevWorkspaceId: instance.Spec.DevWorkspaceId,
DevWorkspaceName: instance.Name,
Namespace: instance.Namespace,
PodSelector: instance.Spec.PodSelector,
}

restrictedAccess, setRestrictedAccess := instance.Annotations[constants.DevWorkspaceRestrictedAccessAnnotation]
Expand All @@ -149,6 +151,12 @@ func (r *DevWorkspaceRoutingReconciler) Reconcile(ctx context.Context, req ctrl.
return reconcile.Result{}, r.markRoutingFailed(instance, fmt.Sprintf("Unable to provision networking for DevWorkspace: %s", invalid))
}

var conflict *solvers.ServiceConflictError
if errors.As(err, &conflict) {
reqLogger.Error(conflict, "Routing controller detected a service conflict", "endpointName", conflict.EndpointName, "workspaceName", conflict.WorkspaceName)
return reconcile.Result{}, r.markRoutingFailed(instance, fmt.Sprintf("Unable to provision networking for DevWorkspace: %s", conflict))
}

// generic error, just fail the reconciliation
return reconcile.Result{}, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"github.com/devfile/devworkspace-operator/pkg/config"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var routeAnnotations = func(endpointName string, endpointAnnotations map[string]string) map[string]string {
Expand Down Expand Up @@ -47,10 +49,21 @@ var nginxIngressAnnotations = func(endpointName string, endpointAnnotations map[
// According to the current cluster there is different behavior:
// Kubernetes: use Ingresses without TLS
// OpenShift: use Routes with TLS enabled
type BasicSolver struct{}
type BasicSolver struct {
client client.Client
logger logr.Logger
}

var _ RoutingSolver = (*BasicSolver)(nil)

// NewBasicSolver creates a new BasicSolver with the provided dependencies
func NewBasicSolver(client client.Client, logger logr.Logger) *BasicSolver {
return &BasicSolver{
client: client,
logger: logger,
}
}

func (s *BasicSolver) FinalizerRequired(*controllerv1alpha1.DevWorkspaceRouting) bool {
return false
}
Expand All @@ -70,7 +83,11 @@ func (s *BasicSolver) GetSpecObjects(routing *controllerv1alpha1.DevWorkspaceRou

spec := routing.Spec
services := getServicesForEndpoints(spec.Endpoints, workspaceMeta)
services = append(services, GetDiscoverableServicesForEndpoints(spec.Endpoints, workspaceMeta)...)
discoverableServices, err := GetDiscoverableServicesForEndpoints(spec.Endpoints, workspaceMeta, s.client, s.logger)
if err != nil {
return RoutingObjects{}, err
}
services = append(services, discoverableServices...)
routingObjects.Services = services
if infrastructure.IsOpenShift() {
routingObjects.Routes = getRoutesForSpec(routingSuffix, spec.Endpoints, workspaceMeta)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,31 @@ import (
corev1 "k8s.io/api/core/v1"

controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
serviceServingCertAnnot = "service.beta.openshift.io/serving-cert-secret-name"
)

type ClusterSolver struct {
TLS bool
TLS bool
client client.Client
logger logr.Logger
}

var _ RoutingSolver = (*ClusterSolver)(nil)

// NewClusterSolver creates a new ClusterSolver with the provided dependencies
func NewClusterSolver(client client.Client, logger logr.Logger, tls bool) *ClusterSolver {
return &ClusterSolver{
TLS: tls,
client: client,
logger: logger,
}
}

func (s *ClusterSolver) FinalizerRequired(*controllerv1alpha1.DevWorkspaceRouting) bool {
return false
}
Expand All @@ -47,6 +60,11 @@ func (s *ClusterSolver) Finalize(*controllerv1alpha1.DevWorkspaceRouting) error
func (s *ClusterSolver) GetSpecObjects(routing *controllerv1alpha1.DevWorkspaceRouting, workspaceMeta DevWorkspaceMetadata) (RoutingObjects, error) {
spec := routing.Spec
services := getServicesForEndpoints(spec.Endpoints, workspaceMeta)
discoverableServices, err := GetDiscoverableServicesForEndpoints(spec.Endpoints, workspaceMeta, s.client, s.logger)
if err != nil {
return RoutingObjects{}, err
}
services = append(services, discoverableServices...)
podAdditions := &controllerv1alpha1.PodAdditions{}
if s.TLS {
readOnlyMode := int32(420)
Expand Down
43 changes: 32 additions & 11 deletions controllers/controller/devworkspacerouting/solvers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@
package solvers

import (
"context"

controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/common"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"

routeV1 "github.com/openshift/api/route/v1"
corev1 "k8s.io/api/core/v1"
Expand All @@ -29,14 +34,15 @@ import (
)

type DevWorkspaceMetadata struct {
DevWorkspaceId string
Namespace string
PodSelector map[string]string
DevWorkspaceId string
DevWorkspaceName string
Namespace string
PodSelector map[string]string
}

// GetDiscoverableServicesForEndpoints converts the endpoint list into a set of services, each corresponding to a single discoverable
// endpoint from the list. Endpoints with the NoneEndpointExposure are ignored.
func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1.EndpointList, meta DevWorkspaceMetadata) []corev1.Service {
func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1.EndpointList, meta DevWorkspaceMetadata, cl client.Client, log logr.Logger) ([]corev1.Service, error) {
var services []corev1.Service
for _, machineEndpoints := range endpoints {
for _, endpoint := range machineEndpoints {
Expand All @@ -45,21 +51,36 @@ func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1
}

if endpoint.Attributes.GetBoolean(string(controllerv1alpha1.DiscoverableAttribute), nil) {
// Create service with name matching endpoint
// TODO: This could cause a reconcile conflict if multiple workspaces define the same discoverable endpoint
// Also endpoint names may not be valid as service names
serviceName := common.EndpointName(endpoint.Name)
existingService := &corev1.Service{}
err := cl.Get(context.TODO(), client.ObjectKey{Name: serviceName, Namespace: meta.Namespace}, existingService)
if err != nil {
if !errors.IsNotFound(err) {
log.Error(err, "Failed to get service from cluster", "serviceName", serviceName)
return nil, err
}
} else {
if existingService.Labels[constants.DevWorkspaceIDLabel] != meta.DevWorkspaceId {
return nil, &ServiceConflictError{
EndpointName: endpoint.Name,
WorkspaceName: existingService.Labels[constants.DevWorkspaceNameLabel],
}
}
}

servicePort := corev1.ServicePort{
Name: common.EndpointName(endpoint.Name),
Name: serviceName,
Protocol: corev1.ProtocolTCP,
Port: int32(endpoint.TargetPort),
TargetPort: intstr.FromInt(endpoint.TargetPort),
}
services = append(services, corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: common.EndpointName(endpoint.Name),
Name: serviceName,
Namespace: meta.Namespace,
Labels: map[string]string{
constants.DevWorkspaceIDLabel: meta.DevWorkspaceId,
constants.DevWorkspaceIDLabel: meta.DevWorkspaceId,
constants.DevWorkspaceNameLabel: meta.DevWorkspaceName,
},
Annotations: map[string]string{
constants.DevWorkspaceDiscoverableServiceAnnotation: "true",
Expand All @@ -74,7 +95,7 @@ func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1
}
}
}
return services
return services, nil
}

// GetServiceForEndpoints returns a single service that exposes all endpoints of given exposure types, possibly also including the discoverable types.
Expand Down
Loading
Loading