From 05b8a93a879bdcd36e844f4f465f30c642140d0f Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 9 Jun 2022 11:49:07 -0400 Subject: [PATCH 01/11] Merge proxy configs on start when running in OpenShift Make sure we merge both the dwoc proxy config and the cluster proxy config on OpenShift to avoid an issue where we ignore the cluster proxy if the dwoc contains a proxy config on start. Signed-off-by: Angel Misevski (cherry picked from commit 033fa05ab0c8739c12363e3ce51a197e7547da6d) --- pkg/config/sync.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/config/sync.go b/pkg/config/sync.go index ca9c63c38..fcf6a250b 100644 --- a/pkg/config/sync.go +++ b/pkg/config/sync.go @@ -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 From 51b81f50238738bc05e8043a0eea845663ea61c0 Mon Sep 17 00:00:00 2001 From: Andrew Obuchowicz Date: Mon, 30 May 2022 16:05:36 -0400 Subject: [PATCH 02/11] Move deployment status-checking logic to library package Signed-off-by: Andrew Obuchowicz (cherry picked from commit 540f40871d643705fd491b2881028e9e1ee718fd) --- pkg/library/status/check.go | 168 ++++++++++++++++++++++++++ pkg/provision/workspace/deployment.go | 147 +--------------------- 2 files changed, 174 insertions(+), 141 deletions(-) create mode 100644 pkg/library/status/check.go diff --git a/pkg/library/status/check.go b/pkg/library/status/check.go new file mode 100644 index 000000000..1b01aae78 --- /dev/null +++ b/pkg/library/status/check.go @@ -0,0 +1,168 @@ +// +// Copyright (c) 2019-2022 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package check + +import ( + "context" + "fmt" + "strings" + + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/devworkspace-operator/pkg/common" + "github.com/devfile/devworkspace-operator/pkg/config" + "github.com/devfile/devworkspace-operator/pkg/infrastructure" + "github.com/devfile/devworkspace-operator/pkg/provision/sync" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" + runtimeClient "sigs.k8s.io/controller-runtime/pkg/client" +) + +var containerFailureStateReasons = []string{ + "CrashLoopBackOff", + "ImagePullBackOff", + "CreateContainerError", + "RunContainerError", +} + +// unrecoverablePodEventReasons contains Kubernetes events that should fail workspace startup +// if they occur related to a workspace pod. Events are stored as a map with event names as keys +// and values representing the threshold of how many times we can see an event before it is considered +// unrecoverable. +var unrecoverablePodEventReasons = map[string]int32{ + "FailedPostStartHook": 1, + "FailedMount": 3, + "FailedScheduling": 1, + "FailedCreate": 1, + "ReplicaSetCreateError": 1, +} + +var unrecoverableDeploymentConditionReasons = []string{ + "FailedCreate", +} + +func CheckDeploymentStatus(deployment *appsv1.Deployment) (ready bool) { + return deployment.Status.ReadyReplicas > 0 +} + +func CheckDeploymentConditions(deployment *appsv1.Deployment) (healthy bool, errorMsg string) { + conditions := deployment.Status.Conditions + for _, condition := range conditions { + for _, unrecoverableReason := range unrecoverableDeploymentConditionReasons { + if condition.Reason == unrecoverableReason { + return false, fmt.Sprintf("Detected unrecoverable deployment condition: %s %s", condition.Reason, condition.Message) + } + } + } + return true, "" +} + +// checkPodsState checks if workspace-related pods are in an unrecoverable state. A pod is considered to be unrecoverable +// if it has a container with one of the containerFailureStateReasons states, or if an unrecoverable event (with reason +// matching unrecoverablePodEventReasons) has the pod as the involved object. +// Returns optional message with detected unrecoverable state details +// error if any happens during check +func CheckPodsState(workspace *dw.DevWorkspace, namespace string, labelSelector k8sclient.MatchingLabels, + clusterAPI sync.ClusterAPI) (stateMsg string, checkFailure error) { + podList, err := GetPods(namespace, labelSelector, clusterAPI.Client) + if err != nil { + return "", err + } + + for _, pod := range podList.Items { + for _, containerStatus := range pod.Status.ContainerStatuses { + if !CheckContainerStatusForFailure(&containerStatus) { + return fmt.Sprintf("Container %s has state %s", containerStatus.Name, containerStatus.State.Waiting.Reason), nil + } + } + for _, initContainerStatus := range pod.Status.InitContainerStatuses { + if !CheckContainerStatusForFailure(&initContainerStatus) { + return fmt.Sprintf("Init Container %s has state %s", initContainerStatus.Name, initContainerStatus.State.Waiting.Reason), nil + } + } + if msg, err := CheckPodEvents(&pod, workspace.Status.DevWorkspaceId, clusterAPI); err != nil || msg != "" { + return msg, err + } + } + return "", nil +} + +func CheckPodEvents(pod *corev1.Pod, workspaceID string, clusterAPI sync.ClusterAPI) (msg string, err error) { + evs := &corev1.EventList{} + selector, err := fields.ParseSelector(fmt.Sprintf("involvedObject.name=%s", pod.Name)) + if err != nil { + return "", fmt.Errorf("failed to parse field selector: %s", err) + } + if err := clusterAPI.Client.List(clusterAPI.Ctx, evs, k8sclient.InNamespace(pod.Namespace), k8sclient.MatchingFieldsSelector{Selector: selector}); err != nil { + return "", fmt.Errorf("failed to list events in namespace %s: %w", pod.Namespace, err) + } + for _, ev := range evs.Items { + if ev.InvolvedObject.Kind != "Pod" { + continue + } + + // On OpenShift, it's possible see "FailedMount" events when using a routingClass that depends on the service-ca + // operator. To avoid this, we always ignore FailedMount events if the message refers to the DWO-provisioned volume + if infrastructure.IsOpenShift() && + ev.Reason == "FailedMount" && + strings.Contains(ev.Message, common.ServingCertVolumeName(common.ServiceName(workspaceID))) { + continue + } + + if maxCount, isUnrecoverableEvent := unrecoverablePodEventReasons[ev.Reason]; isUnrecoverableEvent { + if !checkIfUnrecoverableEventIgnored(ev.Reason) && ev.Count >= maxCount { + var msg string + if ev.Count > 1 { + msg = fmt.Sprintf("Detected unrecoverable event %s %d times: %s.", ev.Reason, ev.Count, ev.Message) + } else { + msg = fmt.Sprintf("Detected unrecoverable event %s: %s.", ev.Reason, ev.Message) + } + return msg, nil + } + } + } + return "", nil +} + +func CheckContainerStatusForFailure(containerStatus *corev1.ContainerStatus) (ok bool) { + if containerStatus.State.Waiting != nil { + for _, failureReason := range containerFailureStateReasons { + if containerStatus.State.Waiting.Reason == failureReason { + return checkIfUnrecoverableEventIgnored(containerStatus.State.Waiting.Reason) + } + } + } + return true +} + +// TODO: Remove this function? +func GetPods(namespace string, labelSelector k8sclient.MatchingLabels, client runtimeClient.Client) (*corev1.PodList, error) { + pods := &corev1.PodList{} + if err := client.List(context.TODO(), pods, k8sclient.InNamespace(namespace), labelSelector); err != nil { + return nil, err + } + return pods, nil +} + +func checkIfUnrecoverableEventIgnored(reason string) (ignored bool) { + for _, ignoredReason := range config.Workspace.IgnoredUnrecoverableEvents { + if ignoredReason == reason { + return true + } + } + return false +} diff --git a/pkg/provision/workspace/deployment.go b/pkg/provision/workspace/deployment.go index c5779e3ca..99e5fe992 100644 --- a/pkg/provision/workspace/deployment.go +++ b/pkg/provision/workspace/deployment.go @@ -19,11 +19,10 @@ import ( "context" "errors" "fmt" - "strings" + check "github.com/devfile/devworkspace-operator/pkg/library/status" nsconfig "github.com/devfile/devworkspace-operator/pkg/provision/config" "github.com/devfile/devworkspace-operator/pkg/provision/sync" - "k8s.io/apimachinery/pkg/fields" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" @@ -44,29 +43,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -var containerFailureStateReasons = []string{ - "CrashLoopBackOff", - "ImagePullBackOff", - "CreateContainerError", - "RunContainerError", -} - -// unrecoverablePodEventReasons contains Kubernetes events that should fail workspace startup -// if they occur related to a workspace pod. Events are stored as a map with event names as keys -// and values representing the threshold of how many times we can see an event before it is considered -// unrecoverable. -var unrecoverablePodEventReasons = map[string]int32{ - "FailedPostStartHook": 1, - "FailedMount": 3, - "FailedScheduling": 1, - "FailedCreate": 1, - "ReplicaSetCreateError": 1, -} - -var unrecoverableDeploymentConditionReasons = []string{ - "FailedCreate", -} - type DeploymentProvisioningStatus struct { ProvisioningStatus } @@ -121,7 +97,7 @@ func SyncDeploymentToCluster( } clusterDeployment := clusterObj.(*appsv1.Deployment) - deploymentReady := checkDeploymentStatus(clusterDeployment) + deploymentReady := check.CheckDeploymentStatus(clusterDeployment) if deploymentReady { return DeploymentProvisioningStatus{ ProvisioningStatus: ProvisioningStatus{ @@ -130,7 +106,7 @@ func SyncDeploymentToCluster( } } - deploymentHealthy, deploymentErrMsg := checkDeploymentConditions(clusterDeployment) + deploymentHealthy, deploymentErrMsg := check.CheckDeploymentConditions(clusterDeployment) if !deploymentHealthy { return DeploymentProvisioningStatus{ ProvisioningStatus: ProvisioningStatus{ @@ -140,7 +116,9 @@ func SyncDeploymentToCluster( } } - failureMsg, checkErr := checkPodsState(workspace, clusterAPI) + failureMsg, checkErr := check.CheckPodsState(workspace, workspace.Namespace, k8sclient.MatchingLabels{ + constants.DevWorkspaceIDLabel: workspace.Status.DevWorkspaceId, + }, clusterAPI) if checkErr != nil { return DeploymentProvisioningStatus{ ProvisioningStatus: ProvisioningStatus{ @@ -201,22 +179,6 @@ func GetDevWorkspaceSecurityContext() *corev1.PodSecurityContext { return config.Workspace.PodSecurityContext } -func checkDeploymentStatus(deployment *appsv1.Deployment) (ready bool) { - return deployment.Status.ReadyReplicas > 0 -} - -func checkDeploymentConditions(deployment *appsv1.Deployment) (healthy bool, errorMsg string) { - conditions := deployment.Status.Conditions - for _, condition := range conditions { - for _, unrecoverableReason := range unrecoverableDeploymentConditionReasons { - if condition.Reason == unrecoverableReason { - return false, fmt.Sprintf("Detected unrecoverable deployment condition: %s %s", condition.Reason, condition.Message) - } - } - } - return true, "" -} - func getSpecDeployment( workspace *dw.DevWorkspace, podAdditionsList []v1alpha1.PodAdditions, @@ -337,46 +299,6 @@ func getSpecDeployment( return deployment, nil } -func getPods(workspace *dw.DevWorkspace, client runtimeClient.Client) (*corev1.PodList, error) { - pods := &corev1.PodList{} - if err := client.List(context.TODO(), pods, k8sclient.InNamespace(workspace.Namespace), k8sclient.MatchingLabels{ - constants.DevWorkspaceIDLabel: workspace.Status.DevWorkspaceId, - }); err != nil { - return nil, err - } - return pods, nil -} - -// checkPodsState checks if workspace-related pods are in an unrecoverable state. A pod is considered to be unrecoverable -// if it has a container with one of the containerStateFailureReasons states, or if an unrecoverable event (with reason -// matching unrecoverablePodEventReasons) has the pod as the involved object. -// Returns optional message with detected unrecoverable state details -// error if any happens during check -func checkPodsState(workspace *dw.DevWorkspace, - clusterAPI sync.ClusterAPI) (stateMsg string, checkFailure error) { - podList, err := getPods(workspace, clusterAPI.Client) - if err != nil { - return "", err - } - - for _, pod := range podList.Items { - for _, containerStatus := range pod.Status.ContainerStatuses { - if !checkContainerStatusForFailure(&containerStatus) { - return fmt.Sprintf("Container %s has state %s", containerStatus.Name, containerStatus.State.Waiting.Reason), nil - } - } - for _, initContainerStatus := range pod.Status.InitContainerStatuses { - if !checkContainerStatusForFailure(&initContainerStatus) { - return fmt.Sprintf("Init Container %s has state %s", initContainerStatus.Name, initContainerStatus.State.Waiting.Reason), nil - } - } - if msg, err := checkPodEvents(&pod, workspace.Status.DevWorkspaceId, clusterAPI); err != nil || msg != "" { - return msg, err - } - } - return "", nil -} - func mergePodAdditions(toMerge []v1alpha1.PodAdditions) (*v1alpha1.PodAdditions, error) { podAdditions := &v1alpha1.PodAdditions{} @@ -476,60 +398,3 @@ func getAdditionalAnnotations(workspace *dw.DevWorkspace) (map[string]string, er return annotations, nil } - -func checkPodEvents(pod *corev1.Pod, workspaceID string, clusterAPI sync.ClusterAPI) (msg string, err error) { - evs := &corev1.EventList{} - selector, err := fields.ParseSelector(fmt.Sprintf("involvedObject.name=%s", pod.Name)) - if err != nil { - return "", fmt.Errorf("failed to parse field selector: %s", err) - } - if err := clusterAPI.Client.List(clusterAPI.Ctx, evs, k8sclient.InNamespace(pod.Namespace), k8sclient.MatchingFieldsSelector{Selector: selector}); err != nil { - return "", fmt.Errorf("failed to list events in namespace %s: %w", pod.Namespace, err) - } - for _, ev := range evs.Items { - if ev.InvolvedObject.Kind != "Pod" { - continue - } - - // On OpenShift, it's possible see "FailedMount" events when using a routingClass that depends on the service-ca - // operator. To avoid this, we always ignore FailedMount events if the message refers to the DWO-provisioned volume - if infrastructure.IsOpenShift() && - ev.Reason == "FailedMount" && - strings.Contains(ev.Message, common.ServingCertVolumeName(common.ServiceName(workspaceID))) { - continue - } - - if maxCount, isUnrecoverableEvent := unrecoverablePodEventReasons[ev.Reason]; isUnrecoverableEvent { - if !checkIfUnrecoverableEventIgnored(ev.Reason) && ev.Count >= maxCount { - var msg string - if ev.Count > 1 { - msg = fmt.Sprintf("Detected unrecoverable event %s %d times: %s", ev.Reason, ev.Count, ev.Message) - } else { - msg = fmt.Sprintf("Detected unrecoverable event %s: %s", ev.Reason, ev.Message) - } - return msg, nil - } - } - } - return "", nil -} - -func checkContainerStatusForFailure(containerStatus *corev1.ContainerStatus) (ok bool) { - if containerStatus.State.Waiting != nil { - for _, failureReason := range containerFailureStateReasons { - if containerStatus.State.Waiting.Reason == failureReason { - return checkIfUnrecoverableEventIgnored(containerStatus.State.Waiting.Reason) - } - } - } - return true -} - -func checkIfUnrecoverableEventIgnored(reason string) (ignored bool) { - for _, ignoredReason := range config.Workspace.IgnoredUnrecoverableEvents { - if ignoredReason == reason { - return true - } - } - return false -} From 92994a70779547a4d8adde35c49ee5508f8b1b1a Mon Sep 17 00:00:00 2001 From: Andrew Obuchowicz Date: Mon, 30 May 2022 16:06:27 -0400 Subject: [PATCH 03/11] feat: report error when common PVC cleanup job hangs Fix devfile#551 Signed-off-by: Andrew Obuchowicz (cherry picked from commit f3e317a1ea68ff17e32b6b8b9e2d5d5205bf74a5) --- pkg/library/status/check.go | 41 +++++++++++++-------------- pkg/provision/storage/cleanup.go | 24 +++++++++++++++- pkg/provision/workspace/deployment.go | 8 +++--- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/pkg/library/status/check.go b/pkg/library/status/check.go index 1b01aae78..d36dc9d1f 100644 --- a/pkg/library/status/check.go +++ b/pkg/library/status/check.go @@ -13,14 +13,13 @@ // limitations under the License. // -package check +package status import ( "context" "fmt" "strings" - dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/devworkspace-operator/pkg/common" "github.com/devfile/devworkspace-operator/pkg/config" "github.com/devfile/devworkspace-operator/pkg/infrastructure" @@ -29,7 +28,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/fields" k8sclient "sigs.k8s.io/controller-runtime/pkg/client" - runtimeClient "sigs.k8s.io/controller-runtime/pkg/client" ) var containerFailureStateReasons = []string{ @@ -76,25 +74,27 @@ func CheckDeploymentConditions(deployment *appsv1.Deployment) (healthy bool, err // matching unrecoverablePodEventReasons) has the pod as the involved object. // Returns optional message with detected unrecoverable state details // error if any happens during check -func CheckPodsState(workspace *dw.DevWorkspace, namespace string, labelSelector k8sclient.MatchingLabels, +func CheckPodsState(workspaceID string, namespace string, labelSelector k8sclient.MatchingLabels, clusterAPI sync.ClusterAPI) (stateMsg string, checkFailure error) { - podList, err := GetPods(namespace, labelSelector, clusterAPI.Client) - if err != nil { + podList := &corev1.PodList{} + if err := clusterAPI.Client.List(context.TODO(), podList, k8sclient.InNamespace(namespace), labelSelector); err != nil { return "", err } for _, pod := range podList.Items { for _, containerStatus := range pod.Status.ContainerStatuses { - if !CheckContainerStatusForFailure(&containerStatus) { - return fmt.Sprintf("Container %s has state %s", containerStatus.Name, containerStatus.State.Waiting.Reason), nil + ok, reason := CheckContainerStatusForFailure(&containerStatus) + if !ok { + return fmt.Sprintf("Container %s has state %s", containerStatus.Name, reason), nil } } for _, initContainerStatus := range pod.Status.InitContainerStatuses { - if !CheckContainerStatusForFailure(&initContainerStatus) { - return fmt.Sprintf("Init Container %s has state %s", initContainerStatus.Name, initContainerStatus.State.Waiting.Reason), nil + ok, reason := CheckContainerStatusForFailure(&initContainerStatus) + if !ok { + return fmt.Sprintf("Init Container %s has state %s", initContainerStatus.Name, reason), nil } } - if msg, err := CheckPodEvents(&pod, workspace.Status.DevWorkspaceId, clusterAPI); err != nil || msg != "" { + if msg, err := CheckPodEvents(&pod, workspaceID, clusterAPI); err != nil || msg != "" { return msg, err } } @@ -138,24 +138,23 @@ func CheckPodEvents(pod *corev1.Pod, workspaceID string, clusterAPI sync.Cluster return "", nil } -func CheckContainerStatusForFailure(containerStatus *corev1.ContainerStatus) (ok bool) { +func CheckContainerStatusForFailure(containerStatus *corev1.ContainerStatus) (ok bool, reason string) { if containerStatus.State.Waiting != nil { for _, failureReason := range containerFailureStateReasons { if containerStatus.State.Waiting.Reason == failureReason { - return checkIfUnrecoverableEventIgnored(containerStatus.State.Waiting.Reason) + return checkIfUnrecoverableEventIgnored(containerStatus.State.Waiting.Reason), containerStatus.State.Waiting.Reason } } } - return true -} -// TODO: Remove this function? -func GetPods(namespace string, labelSelector k8sclient.MatchingLabels, client runtimeClient.Client) (*corev1.PodList, error) { - pods := &corev1.PodList{} - if err := client.List(context.TODO(), pods, k8sclient.InNamespace(namespace), labelSelector); err != nil { - return nil, err + if containerStatus.State.Terminated != nil { + for _, failureReason := range containerFailureStateReasons { + if containerStatus.State.Terminated.Reason == failureReason { + return checkIfUnrecoverableEventIgnored(containerStatus.State.Terminated.Reason), containerStatus.State.Terminated.Reason + } + } } - return pods, nil + return true, "" } func checkIfUnrecoverableEventIgnored(reason string) (ignored bool) { diff --git a/pkg/provision/storage/cleanup.go b/pkg/provision/storage/cleanup.go index 6aa9f0463..ac81e22be 100644 --- a/pkg/provision/storage/cleanup.go +++ b/pkg/provision/storage/cleanup.go @@ -21,6 +21,7 @@ import ( "time" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/devworkspace-operator/pkg/library/status" nsconfig "github.com/devfile/devworkspace-operator/pkg/provision/config" "github.com/devfile/devworkspace-operator/pkg/provision/sync" batchv1 "k8s.io/api/batch/v1" @@ -29,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "github.com/devfile/devworkspace-operator/internal/images" @@ -91,6 +93,21 @@ func runCommonPVCCleanupJob(workspace *dw.DevWorkspace, clusterAPI sync.ClusterA } } } + + msg, err := status.CheckPodsState(workspace.Status.DevWorkspaceId, clusterJob.Namespace, k8sclient.MatchingLabels{"job-name": common.PVCCleanupJobName(workspace.Status.DevWorkspaceId)}, clusterAPI) + if err != nil { + return &ProvisioningError{ + Err: err, + } + } + + if msg != "" { + errMsg := fmt.Sprintf("DevWorkspace common PVC cleanup job failed: see logs for job %q for details. Additional information: %s", clusterJob.Name, msg) + return &ProvisioningError{ + Message: errMsg, + } + } + // Requeue at least each 10 seconds to check if PVC is not removed by someone else return &NotReadyError{ Message: "Cleanup job is not in completed state", @@ -110,7 +127,9 @@ func getSpecCommonPVCCleanupJob(workspace *dw.DevWorkspace, clusterAPI sync.Clus } jobLabels := map[string]string{ - constants.DevWorkspaceIDLabel: workspaceId, + constants.DevWorkspaceIDLabel: workspaceId, + constants.DevWorkspaceNameLabel: workspace.Name, + constants.DevWorkspaceCreatorLabel: workspace.Labels[constants.DevWorkspaceCreatorLabel], } if restrictedAccess, needsRestrictedAccess := workspace.Annotations[constants.DevWorkspaceRestrictedAccessAnnotation]; needsRestrictedAccess { jobLabels[constants.DevWorkspaceRestrictedAccessAnnotation] = restrictedAccess @@ -126,6 +145,9 @@ func getSpecCommonPVCCleanupJob(workspace *dw.DevWorkspace, clusterAPI sync.Clus Completions: &cleanupJobCompletions, BackoffLimit: &cleanupJobBackoffLimit, Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: jobLabels, + }, Spec: corev1.PodSpec{ RestartPolicy: "Never", SecurityContext: wsprovision.GetDevWorkspaceSecurityContext(), diff --git a/pkg/provision/workspace/deployment.go b/pkg/provision/workspace/deployment.go index 99e5fe992..bb3a35a79 100644 --- a/pkg/provision/workspace/deployment.go +++ b/pkg/provision/workspace/deployment.go @@ -20,7 +20,7 @@ import ( "errors" "fmt" - check "github.com/devfile/devworkspace-operator/pkg/library/status" + "github.com/devfile/devworkspace-operator/pkg/library/status" nsconfig "github.com/devfile/devworkspace-operator/pkg/provision/config" "github.com/devfile/devworkspace-operator/pkg/provision/sync" @@ -97,7 +97,7 @@ func SyncDeploymentToCluster( } clusterDeployment := clusterObj.(*appsv1.Deployment) - deploymentReady := check.CheckDeploymentStatus(clusterDeployment) + deploymentReady := status.CheckDeploymentStatus(clusterDeployment) if deploymentReady { return DeploymentProvisioningStatus{ ProvisioningStatus: ProvisioningStatus{ @@ -106,7 +106,7 @@ func SyncDeploymentToCluster( } } - deploymentHealthy, deploymentErrMsg := check.CheckDeploymentConditions(clusterDeployment) + deploymentHealthy, deploymentErrMsg := status.CheckDeploymentConditions(clusterDeployment) if !deploymentHealthy { return DeploymentProvisioningStatus{ ProvisioningStatus: ProvisioningStatus{ @@ -116,7 +116,7 @@ func SyncDeploymentToCluster( } } - failureMsg, checkErr := check.CheckPodsState(workspace, workspace.Namespace, k8sclient.MatchingLabels{ + failureMsg, checkErr := status.CheckPodsState(workspace.Status.DevWorkspaceId, workspace.Namespace, k8sclient.MatchingLabels{ constants.DevWorkspaceIDLabel: workspace.Status.DevWorkspaceId, }, clusterAPI) if checkErr != nil { From 26b7387a9cc28321a418dca3fcc51965d7864d04 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 30 Jun 2022 14:09:05 -0400 Subject: [PATCH 04/11] Add 'per-workspace' to storage-type attribute documentation Signed-off-by: Angel Misevski (cherry picked from commit 480d686b3f0909418da33a2c04ab9d7da008ed7a) --- pkg/constants/attributes.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/constants/attributes.go b/pkg/constants/attributes.go index 9ce5f65fe..71994662f 100644 --- a/pkg/constants/attributes.go +++ b/pkg/constants/attributes.go @@ -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 From a62b26c8969f04033cb8823388a488667b146ca9 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 27 Jun 2022 20:41:46 -0400 Subject: [PATCH 05/11] Rework DevWorkspace status handling in the finalize section Rework how we update workspace status to avoid multiple calls that update the workspace status. Previously, every time we enter the finalize function, we would set the status to terminating, and then potentially set it to errored. Instead, we use the same deferred-function handling of updating the workspace status from main reconcile function -- defer a function that updates the status and pass around a currentStatus struct reference that needs to be updated. This moves all calls that update the workspace status into one place. Signed-off-by: Angel Misevski (cherry picked from commit eaa8381a1cf4d47229246b824c49c729d9ed2349) --- controllers/workspace/finalize.go | 46 ++++++++++++++++--------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/controllers/workspace/finalize.go b/controllers/workspace/finalize.go index cffc3baf7..9cbe7b23f 100644 --- a/controllers/workspace/finalize.go +++ b/controllers/workspace/finalize.go @@ -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" @@ -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" @@ -46,28 +46,30 @@ 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 := ¤tStatus{phase: devworkspacePhaseTerminating} + finalizeStatus.setConditionTrue(conditions.Started, "Cleaning up resources for deletion") + defer func() (reconcile.Result, error) { + return r.updateWorkspaceStatus(workspace, log, finalizeStatus, finalizeResult, finalizeErr) + }() + if workspace.Status.Phase != dw.DevWorkspaceStatusError { for _, finalizer := range workspace.Finalizers { switch finalizer { case constants.StorageCleanupFinalizer: - return r.finalizeStorage(ctx, log, workspace) + return r.finalizeStorage(ctx, log, workspace, finalizeStatus) case constants.ServiceAccountCleanupFinalizer: - return r.finalizeServiceAccount(ctx, log, workspace) + 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 { @@ -90,9 +92,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, @@ -107,9 +109,9 @@ func (r *DevWorkspaceReconciler) finalizeStorage(ctx context.Context, log logr.L 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) + finalizeStatus.phase = dw.DevWorkspaceStatusError + finalizeStatus.setConditionTrue(dw.DevWorkspaceError, err.Error()) + return reconcile.Result{}, nil default: return reconcile.Result{}, storageErr } @@ -119,13 +121,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 From 712721e0bbc934c04e75286af6afeeca3c5dd7d1 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 27 Jun 2022 20:53:47 -0400 Subject: [PATCH 06/11] Reconcile terminating workspace that have an error status set With the update to terminating workspace status handling (see previous commit), it turns out it's no longer necessary to stop reconciling workspaces once their job encounters an error. The previous bug where DWO would enter a tight loop reconciling failed workspaces turns out to be due to each finalize call updating the workspace to Terminating status, then to Errored status, triggering a new reconcile. Signed-off-by: Angel Misevski (cherry picked from commit 8865f7f266784bd10577d6efa42ae50a3da5ed3d) --- controllers/workspace/finalize.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/controllers/workspace/finalize.go b/controllers/workspace/finalize.go index 9cbe7b23f..98f33ea63 100644 --- a/controllers/workspace/finalize.go +++ b/controllers/workspace/finalize.go @@ -56,14 +56,12 @@ func (r *DevWorkspaceReconciler) finalize(ctx context.Context, log logr.Logger, return r.updateWorkspaceStatus(workspace, log, finalizeStatus, finalizeResult, finalizeErr) }() - if workspace.Status.Phase != dw.DevWorkspaceStatusError { - 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) - } + 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 From 73600ec727c859280f5e596e18c6c7487153e18f Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 27 Jun 2022 21:07:22 -0400 Subject: [PATCH 07/11] Avoid logging error from updating status of a deleted workspace When the last finalizer on a DevWorkspace is cleared, the cluster may garbage-collect that workspace immediately, which can result in the object being deleted before we reach the point of attempting to update the workspace's status. This results in logging a harmless but confusing error, so we don't try to update the status for terminating workspaces with no finalizers. Signed-off-by: Angel Misevski (cherry picked from commit e3805fb8c19d95c5efa5e9ecf07cabe0c5719f29) --- controllers/workspace/finalize.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/controllers/workspace/finalize.go b/controllers/workspace/finalize.go index 98f33ea63..bd9ae22f1 100644 --- a/controllers/workspace/finalize.go +++ b/controllers/workspace/finalize.go @@ -53,6 +53,13 @@ func (r *DevWorkspaceReconciler) finalize(ctx context.Context, log logr.Logger, finalizeStatus := ¤tStatus{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) }() From ef0707bfc03132dbcf9d2796814df649e96a61ac Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 30 Jun 2022 18:21:01 -0600 Subject: [PATCH 08/11] Avoid logging failure message repeatedly in PVC cleanup Since setting a workspace to the errored status will queue another reconcile, we only log the "failed to clean up common PVC" message if the workspace status is not already set to errored. Signed-off-by: Angel Misevski (cherry picked from commit 225fbf1ef10068c68c08d689feda3c6fa9e6e7cd) --- controllers/workspace/finalize.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/controllers/workspace/finalize.go b/controllers/workspace/finalize.go index bd9ae22f1..201c176f1 100644 --- a/controllers/workspace/finalize.go +++ b/controllers/workspace/finalize.go @@ -113,7 +113,10 @@ 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") + 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 From fffb3fbd9eb4213af65c58ddadf6c31467542e63 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 30 Jun 2022 18:46:12 -0600 Subject: [PATCH 09/11] Queue reconcile for all common storage-type workspaces on PVC deletion To make sure workspaces that are in an errored state due to PVC cleanup failing are removed when the common PVC is deleted (e.g. when all DevWorkspaces are deleted), add a watch and enqueue reconciles for all common-storage workspaces when the common PVC is deleted. Signed-off-by: Angel Misevski (cherry picked from commit a2e149f5b147adf70861ba6aecc704545eca21fa) --- .../workspace/devworkspace_controller.go | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 51dad51f2..14f36dd79 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -631,28 +631,49 @@ 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{ - { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: labels[constants.DevWorkspaceNameLabel], + Namespace: obj.GetNamespace(), + }, + }, + } +} + +func (r *DevWorkspaceReconciler) dwPVCHandler(obj client.Object) []reconcile.Request { + 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: labels[constants.DevWorkspaceNameLabel], - Namespace: obj.GetNamespace(), + Name: workspace.GetName(), + Namespace: workspace.GetNamespace(), }, - }, + }) } } - return handler.EnqueueRequestsFromMapFunc(podToDW) + return reconciles } func (r *DevWorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error { @@ -683,7 +704,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). From 95de11b94e4cea3345da89c25857727046796468 Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Thu, 30 Jun 2022 19:00:00 -0600 Subject: [PATCH 10/11] Enqueue reconciles for per-workspace PVC events Update the watch for PVCs to also enqueue reconciles for per-workspace PVC events. Previously, the per-workspace PVC could be modified/deleted without the DevWorkspace that owns that PVC noticing. Signed-off-by: Angel Misevski (cherry picked from commit 2a44ff6da5aa079edd1f21b4032bb9aba10600c2) --- controllers/workspace/devworkspace_controller.go | 16 ++++++++++++++++ pkg/provision/storage/perWorkspaceStorage.go | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 14f36dd79..968ab54ec 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -653,6 +653,22 @@ func dwRelatedPodsHandler(obj client.Object) []reconcile.Request { } 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: ownerref.Name, + Namespace: obj.GetNamespace(), + }, + }, + } + } + + // 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{} diff --git a/pkg/provision/storage/perWorkspaceStorage.go b/pkg/provision/storage/perWorkspaceStorage.go index 4dafe8bdd..db6af9a01 100644 --- a/pkg/provision/storage/perWorkspaceStorage.go +++ b/pkg/provision/storage/perWorkspaceStorage.go @@ -63,6 +63,13 @@ func (p *PerWorkspaceStorageProvisioner) ProvisionStorage(podAdditions *v1alpha1 } pvcName := perWorkspacePVC.Name + // If PVC is being deleted, we need to fail workspace startup as a running pod will block deletion. + if perWorkspacePVC.DeletionTimestamp != nil { + return &ProvisioningError{ + Message: "DevWorkspace PVC is being deleted", + } + } + // Rewrite container volume mounts if err := p.rewriteContainerVolumeMounts(workspace.Status.DevWorkspaceId, pvcName, podAdditions, &workspace.Spec.Template); err != nil { return &ProvisioningError{ From 6d616c2aba547dea1040a4a68485060399c258be Mon Sep 17 00:00:00 2001 From: Angel Misevski Date: Mon, 4 Jul 2022 15:14:27 -0600 Subject: [PATCH 11/11] Update base images to current ubi8-minimal & ubi8/go-toolset Update ubi-minimal base image to tag '8.6-854' and ubi8/go-toolset to tag '1.17.10-4' Signed-off-by: Angel Misevski --- build/Dockerfile | 4 ++-- project-clone/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 50190b932..f9d209bc5 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -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 @@ -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 diff --git a/project-clone/Dockerfile b/project-clone/Dockerfile index dca26adcf..7e12d57b2 100644 --- a/project-clone/Dockerfile +++ b/project-clone/Dockerfile @@ -15,7 +15,7 @@ # Build the manager binary # 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 /project-clone @@ -37,7 +37,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build \ project-clone/main.go # 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 install -y time git git-lfs && microdnf clean all && rm -rf /var/cache/yum && echo "Installed Packages" && rpm -qa | sort -V && echo "End Of Installed Packages" WORKDIR / COPY --from=builder /project-clone/_output/bin/project-clone /usr/local/bin/project-clone