Path: blob/main/components/ws-daemon/pkg/controller/snapshot_controller.go
2500 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License-AGPL.txt in the project root for license information.34package controller56import (7"context"8"fmt"910corev1 "k8s.io/api/core/v1"11"k8s.io/client-go/tools/record"12"k8s.io/client-go/util/retry"13ctrl "sigs.k8s.io/controller-runtime"14"sigs.k8s.io/controller-runtime/pkg/client"15"sigs.k8s.io/controller-runtime/pkg/controller"16"sigs.k8s.io/controller-runtime/pkg/event"17"sigs.k8s.io/controller-runtime/pkg/log"18"sigs.k8s.io/controller-runtime/pkg/predicate"1920workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"21)2223// SnapshotReconciler reconciles a Snapshot object24type SnapshotReconciler struct {25client.Client26maxConcurrentReconciles int27nodeName string28operations WorkspaceOperations29recorder record.EventRecorder30}3132func NewSnapshotController(c client.Client, recorder record.EventRecorder, nodeName string, maxConcurrentReconciles int, wso WorkspaceOperations) *SnapshotReconciler {33return &SnapshotReconciler{34Client: c,35maxConcurrentReconciles: maxConcurrentReconciles,36nodeName: nodeName,37operations: wso,38recorder: recorder,39}40}4142// SetupWithManager sets up the controller with the Manager.43func (r *SnapshotReconciler) SetupWithManager(mgr ctrl.Manager) error {44return ctrl.NewControllerManagedBy(mgr).45Named("snapshot").46WithOptions(controller.Options{47MaxConcurrentReconciles: r.maxConcurrentReconciles,48}).49For(&workspacev1.Snapshot{}).50WithEventFilter(snapshotEventFilter(r.nodeName)).51Complete(r)52}5354func snapshotEventFilter(nodeName string) predicate.Predicate {55return predicate.Funcs{56CreateFunc: func(e event.CreateEvent) bool {57if ss, ok := e.Object.(*workspacev1.Snapshot); ok {58return ss.Spec.NodeName == nodeName59}60return false61},62UpdateFunc: func(ue event.UpdateEvent) bool {63return false64},65DeleteFunc: func(de event.DeleteEvent) bool {66return false67},68}69}7071//+kubebuilder:rbac:groups=workspace.gitpod.io,resources=snapshots,verbs=get;list;watch;create;update;patch;delete72//+kubebuilder:rbac:groups=workspace.gitpod.io,resources=snapshots/status,verbs=get;update;patch73//+kubebuilder:rbac:groups=workspace.gitpod.io,resources=snapshots/finalizers,verbs=update7475// Reconcile is part of the main kubernetes reconciliation loop which aims to76// move the current state of the cluster closer to the desired state.77// For more details, check Reconcile and its Result here:78// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile79func (ssc *SnapshotReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {80log := log.FromContext(ctx)8182var snapshot workspacev1.Snapshot83if err := ssc.Client.Get(ctx, req.NamespacedName, &snapshot); err != nil {84return ctrl.Result{}, client.IgnoreNotFound(err)85}8687if snapshot.Status.Completed {88return ctrl.Result{}, nil89}9091snapshotURL, snapshotName, snapshotErr := ssc.operations.SnapshotIDs(ctx, snapshot.Spec.WorkspaceID)92if snapshotErr != nil {93return ctrl.Result{}, fmt.Errorf("failed to get snapshot name and URL: %w", snapshotErr)94}9596err := retry.RetryOnConflict(retryParams, func() error {97err := ssc.Client.Get(ctx, req.NamespacedName, &snapshot)98if err != nil {99return err100}101102snapshot.Status.URL = snapshotURL103return ssc.Client.Status().Update(ctx, &snapshot)104})105106if err != nil {107log.Error(err, "could not set snapshot url", "workspace", snapshot.Spec.WorkspaceID)108return ctrl.Result{}, fmt.Errorf("could not set snapshot url: %w", err)109}110111snapshotErr = ssc.operations.Snapshot(ctx, snapshot.Spec.WorkspaceID, snapshotName)112if snapshotErr != nil {113log.Error(snapshotErr, "could not take snapshot", "workspace", snapshot.Spec.WorkspaceID)114}115116err = retry.RetryOnConflict(retryParams, func() error {117err := ssc.Client.Get(ctx, req.NamespacedName, &snapshot)118if err != nil {119return err120}121122snapshot.Status.Completed = true123if snapshotErr != nil {124snapshot.Status.Error = fmt.Errorf("could not take snapshot: %w", snapshotErr).Error()125}126127return ssc.Status().Update(ctx, &snapshot)128})129130if err != nil {131log.Error(err, "could not set completion status for snapshot", "workspace", snapshot.Spec.WorkspaceID)132err = fmt.Errorf("could not set completion status for snapshot: %w", err)133}134135ssc.emitEvent(&snapshot, snapshotErr)136return ctrl.Result{}, err137}138139func (ssc *SnapshotReconciler) emitEvent(s *workspacev1.Snapshot, failure error) {140eventType := corev1.EventTypeNormal141reason := "Succeeded"142message := ""143144if failure != nil {145eventType = corev1.EventTypeWarning146reason = "Failed"147message = failure.Error()148}149150ssc.recorder.Event(s, eventType, reason, message)151}152153154