Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry pick for 0.15.1 #885

Merged
merged 11 commits into from
Jul 5, 2022
Merged
4 changes: 2 additions & 2 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#

# https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/ubi8/go-toolset
FROM registry.access.redhat.com/ubi8/go-toolset:1.17.7-13 as builder
FROM registry.access.redhat.com/ubi8/go-toolset:1.17.10-4 as builder
ENV GOPATH=/go/
USER root
WORKDIR /devworkspace-operator
Expand All @@ -34,7 +34,7 @@ RUN make compile-devworkspace-controller
RUN make compile-webhook-server

# https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/ubi8-minimal
FROM registry.access.redhat.com/ubi8-minimal:8.6-751
FROM registry.access.redhat.com/ubi8-minimal:8.6-854
RUN microdnf -y update && microdnf clean all && rm -rf /var/cache/yum && echo "Installed Packages" && rpm -qa | sort -V && echo "End Of Installed Packages"
WORKDIR /
COPY --from=builder /devworkspace-operator/_output/bin/devworkspace-controller /usr/local/bin/devworkspace-controller
Expand Down
64 changes: 51 additions & 13 deletions controllers/workspace/devworkspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,28 +631,65 @@ func (r *DevWorkspaceReconciler) getWorkspaceId(ctx context.Context, workspace *
}

// Mapping the pod to the devworkspace
func dwRelatedPodsHandler() handler.EventHandler {
podToDW := func(obj client.Object) []reconcile.Request {
labels := obj.GetLabels()
if _, ok := labels[constants.DevWorkspaceNameLabel]; !ok {
return nil
}
func dwRelatedPodsHandler(obj client.Object) []reconcile.Request {
labels := obj.GetLabels()
if _, ok := labels[constants.DevWorkspaceNameLabel]; !ok {
return []reconcile.Request{}
}

//If the dewworkspace label does not exist, do no reconcile
if _, ok := labels[constants.DevWorkspaceIDLabel]; !ok {
return nil
}
//If the dewworkspace label does not exist, do no reconcile
if _, ok := labels[constants.DevWorkspaceIDLabel]; !ok {
return []reconcile.Request{}
}

return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Name: labels[constants.DevWorkspaceNameLabel],
Namespace: obj.GetNamespace(),
},
},
}
}

func (r *DevWorkspaceReconciler) dwPVCHandler(obj client.Object) []reconcile.Request {
// Check if PVC is owned by a DevWorkspace (per-workspace storage case)
for _, ownerref := range obj.GetOwnerReferences() {
if ownerref.Kind != "DevWorkspace" {
continue
}
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Name: labels[constants.DevWorkspaceNameLabel],
Name: ownerref.Name,
Namespace: obj.GetNamespace(),
},
},
}
}
return handler.EnqueueRequestsFromMapFunc(podToDW)

// Otherwise, check if common PVC is deleted to make sure all DevWorkspaces see it happen
if obj.GetName() != config.Workspace.PVCName || obj.GetDeletionTimestamp() == nil {
// We're looking for a deleted common PVC
return []reconcile.Request{}
}
dwList := &dw.DevWorkspaceList{}
if err := r.Client.List(context.Background(), dwList); err != nil {
return []reconcile.Request{}
}
var reconciles []reconcile.Request
for _, workspace := range dwList.Items {
storageType := workspace.Spec.Template.Attributes.GetString(constants.DevWorkspaceStorageTypeAttribute, nil)
if storageType == constants.CommonStorageClassType || storageType == "" {
reconciles = append(reconciles, reconcile.Request{
NamespacedName: types.NamespacedName{
Name: workspace.GetName(),
Namespace: workspace.GetNamespace(),
},
})
}
}
return reconciles
}

func (r *DevWorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
Expand Down Expand Up @@ -683,7 +720,8 @@ func (r *DevWorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
Owns(&corev1.ConfigMap{}).
Owns(&corev1.Secret{}).
Owns(&corev1.ServiceAccount{}).
Watches(&source.Kind{Type: &corev1.Pod{}}, dwRelatedPodsHandler()).
Watches(&source.Kind{Type: &corev1.Pod{}}, handler.EnqueueRequestsFromMapFunc(dwRelatedPodsHandler)).
Watches(&source.Kind{Type: &corev1.PersistentVolumeClaim{}}, handler.EnqueueRequestsFromMapFunc(r.dwPVCHandler)).
Watches(&source.Kind{Type: &controllerv1alpha1.DevWorkspaceOperatorConfig{}}, handler.EnqueueRequestsFromMapFunc(emptyMapper), configWatcher).
WithEventFilter(predicates).
WithEventFilter(podPredicates).
Expand Down
64 changes: 37 additions & 27 deletions controllers/workspace/finalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controllers
import (
"context"

"github.com/devfile/devworkspace-operator/pkg/conditions"
"github.com/devfile/devworkspace-operator/pkg/constants"

dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
Expand All @@ -26,7 +27,6 @@ import (
"github.com/go-logr/logr"
coputil "github.com/redhat-cop/operator-utils/pkg/util"
corev1 "k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

Expand All @@ -46,28 +46,35 @@ func (r *DevWorkspaceReconciler) workspaceNeedsFinalize(workspace *dw.DevWorkspa
return false
}

func (r *DevWorkspaceReconciler) finalize(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace) (reconcile.Result, error) {
if workspace.Status.Phase != dw.DevWorkspaceStatusError {
workspace.Status.Message = "Cleaning up resources for deletion"
workspace.Status.Phase = devworkspacePhaseTerminating
err := r.Client.Status().Update(ctx, workspace)
if err != nil && !k8sErrors.IsConflict(err) {
return reconcile.Result{}, err
func (r *DevWorkspaceReconciler) finalize(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace) (finalizeResult reconcile.Result, finalizeErr error) {
// Tracked state for the finalize process; we update the workspace status in a deferred function (and pass the
// named return value for finalize()) to update the workspace's status with whatever is in finalizeStatus
// when this function returns.
finalizeStatus := &currentStatus{phase: devworkspacePhaseTerminating}
finalizeStatus.setConditionTrue(conditions.Started, "Cleaning up resources for deletion")
defer func() (reconcile.Result, error) {
if len(workspace.Finalizers) == 0 {
// If there are no finalizers on the workspace, the workspace may be garbage collected before we get to update
// its status. This avoids potentially logging a confusing error due to trying to set the status on a deleted
// workspace. This check has to be in the deferred function since updateWorkspaceStatus will be called after the
// client.Update() call that removes the last finalizer.
return finalizeResult, finalizeErr
}
return r.updateWorkspaceStatus(workspace, log, finalizeStatus, finalizeResult, finalizeErr)
}()

for _, finalizer := range workspace.Finalizers {
switch finalizer {
case constants.StorageCleanupFinalizer:
return r.finalizeStorage(ctx, log, workspace)
case constants.ServiceAccountCleanupFinalizer:
return r.finalizeServiceAccount(ctx, log, workspace)
}
for _, finalizer := range workspace.Finalizers {
switch finalizer {
case constants.StorageCleanupFinalizer:
return r.finalizeStorage(ctx, log, workspace, finalizeStatus)
case constants.ServiceAccountCleanupFinalizer:
return r.finalizeServiceAccount(ctx, log, workspace, finalizeStatus)
}
}
return reconcile.Result{}, nil
}

func (r *DevWorkspaceReconciler) finalizeStorage(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace) (reconcile.Result, error) {
func (r *DevWorkspaceReconciler) finalizeStorage(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace, finalizeStatus *currentStatus) (reconcile.Result, error) {
// Need to make sure Deployment is cleaned up before starting job to avoid mounting issues for RWO PVCs
wait, err := wsprovision.DeleteWorkspaceDeployment(ctx, workspace, r.Client)
if err != nil {
Expand All @@ -90,9 +97,9 @@ func (r *DevWorkspaceReconciler) finalizeStorage(ctx context.Context, log logr.L
storageProvisioner, err := storage.GetProvisioner(workspace)
if err != nil {
log.Error(err, "Failed to clean up DevWorkspace storage")
failedStatus := currentStatus{phase: dw.DevWorkspaceStatusError}
failedStatus.setConditionTrue(dw.DevWorkspaceError, err.Error())
return r.updateWorkspaceStatus(workspace, r.Log, &failedStatus, reconcile.Result{}, nil)
finalizeStatus.phase = dw.DevWorkspaceStatusError
finalizeStatus.setConditionTrue(dw.DevWorkspaceError, err.Error())
return reconcile.Result{}, nil
}
err = storageProvisioner.CleanupWorkspaceStorage(workspace, sync.ClusterAPI{
Ctx: ctx,
Expand All @@ -106,10 +113,13 @@ func (r *DevWorkspaceReconciler) finalizeStorage(ctx context.Context, log logr.L
log.Info(storageErr.Message)
return reconcile.Result{RequeueAfter: storageErr.RequeueAfter}, nil
case *storage.ProvisioningError:
log.Error(storageErr, "Failed to clean up DevWorkspace storage")
failedStatus := currentStatus{phase: dw.DevWorkspaceStatusError}
failedStatus.setConditionTrue(dw.DevWorkspaceError, err.Error())
return r.updateWorkspaceStatus(workspace, r.Log, &failedStatus, reconcile.Result{}, nil)
if workspace.Status.Phase != dw.DevWorkspaceStatusError {
// Avoid repeatedly logging error unless it's relevant
log.Error(storageErr, "Failed to clean up DevWorkspace storage")
}
finalizeStatus.phase = dw.DevWorkspaceStatusError
finalizeStatus.setConditionTrue(dw.DevWorkspaceError, err.Error())
return reconcile.Result{}, nil
default:
return reconcile.Result{}, storageErr
}
Expand All @@ -119,13 +129,13 @@ func (r *DevWorkspaceReconciler) finalizeStorage(ctx context.Context, log logr.L
return reconcile.Result{}, r.Update(ctx, workspace)
}

func (r *DevWorkspaceReconciler) finalizeServiceAccount(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace) (reconcile.Result, error) {
func (r *DevWorkspaceReconciler) finalizeServiceAccount(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace, finalizeStatus *currentStatus) (reconcile.Result, error) {
retry, err := wsprovision.FinalizeServiceAccount(workspace, ctx, r.NonCachingClient)
if err != nil {
log.Error(err, "Failed to finalize workspace ServiceAccount")
failedStatus := currentStatus{phase: dw.DevWorkspaceStatusError}
failedStatus.setConditionTrue(dw.DevWorkspaceError, err.Error())
return r.updateWorkspaceStatus(workspace, r.Log, &failedStatus, reconcile.Result{}, nil)
finalizeStatus.phase = dw.DevWorkspaceStatusError
finalizeStatus.setConditionTrue(dw.DevWorkspaceError, err.Error())
return reconcile.Result{}, nil
}
if retry {
return reconcile.Result{Requeue: true}, nil
Expand Down
4 changes: 1 addition & 3 deletions pkg/config/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ func SetupControllerConfig(client crclient.Client) error {
return err
}
defaultConfig.Routing.ProxyConfig = clusterProxy
if internalConfig.Routing.ProxyConfig == nil {
internalConfig.Routing.ProxyConfig = clusterProxy
}
internalConfig.Routing.ProxyConfig = proxy.MergeProxyConfigs(clusterProxy, internalConfig.Routing.ProxyConfig)

updatePublicConfig()
return nil
Expand Down
11 changes: 6 additions & 5 deletions pkg/constants/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ const (
// DevWorkspaceStorageTypeAttribute defines the strategy used for provisioning storage for the workspace.
// If empty, the common PVC strategy is used.
// Supported options:
// - "common": Create one PVC per namespace, and store data for all workspaces in that namespace in that PVC
// - "async" : Create one PVC per namespace, and create a remote server that syncs data from workspaces to the PVC.
// All volumeMounts used for devworkspaces are emptyDir
// - "ephemeral": Use emptyDir volumes for all volumes in the DevWorkspace. All data is lost when the workspace is
// stopped.
// - "common": Create one PVC per namespace, and store data for all workspaces in that namespace in that PVC
// - "async" : Create one PVC per namespace, and create a remote server that syncs data from workspaces to the PVC.
// All volumeMounts used for devworkspaces are emptyDir
// - "per-workspace": Create one PVC per workspace, delete that PVC when the workspace is deleted.
// - "ephemeral": Use emptyDir volumes for all volumes in the DevWorkspace. All data is lost when the workspace is
// stopped.
DevWorkspaceStorageTypeAttribute = "controller.devfile.io/storage-type"

// RuntimeClassNameAttribute is an attribute added to a DevWorkspace to specify a runtimeClassName for container
Expand Down
Loading