~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
123 lines · 3.9 KB Raw History
package compose

import (
	"context"
	"errors"
	"fmt"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime/kube"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime/manifests"
)

var ErrWorkloadDeleting = errors.New("compose: a set workload is being deleted; the set is applied once it is gone")

type Workload struct {
	Kind string

	Workspace string

	Name string
}

var replaceableLost = map[v1alpha1.HostProcessLostReason]bool{v1alpha1.HostProcessLostUnitGone: true, v1alpha1.HostProcessLostHostRebooted: true,
	v1alpha1.HostProcessLostManagerRestarted: true, v1alpha1.HostProcessLostIdentityMismatch: true}

func (c *Composer) SetWorkloads(setName, profile string, values any, include ...string) ([]Workload, error) {
	set, err := infraSet(setName, profile, include)
	if err != nil {
		return nil, err
	}
	return c.renderWorkloads(set, values)
}

func (c *Composer) PhaseWorkloads(setName string, values any, phases ...string) ([]Workload, error) {
	set, err := phaseSet(setName, phases)
	if err != nil {
		return nil, err
	}
	return c.renderWorkloads(set, values)
}

func (c *Composer) renderWorkloads(set manifests.Set, values any) ([]Workload, error) {
	set.Base = c.Workspace()
	rendered, err := set.Render(values)
	if err != nil {
		return nil, fmt.Errorf("compose: the workloads of set %s: %w", set.Name, err)
	}
	var out []Workload
	for _, phase := range rendered {
		for _, obj := range phase.Objects {
			if u := obj.Unstructured; u != nil && (u.GetKind() == KindHostProcess || u.GetKind() == KindOpenBaoServer) {
				out = append(out, Workload{Kind: u.GetKind(), Workspace: phase.Workspace, Name: u.GetName()})
			}
		}
	}
	return out, nil
}

func (c *Composer) CheckWorkloads(ctx context.Context, r Root) func([]Workload, error) (bool, error) {
	return func(list []Workload, err error) (bool, error) {
		if err != nil {
			return false, err
		}
		intact, why, err := c.workloadsIntact(ctx, r, list)
		if err != nil {
			return false, err
		}
		if !intact {
			c.log.Info("a set workload is missing or Lost; the set is applied again", "root", r.Owner(), "why", why)
			c.Unapply(r)
		}
		return intact, nil
	}
}

func (c *Composer) workloadsIntact(ctx context.Context, r Root, list []Workload) (bool, string, error) {
	for _, w := range list {
		switch w.Kind {
		case KindHostProcess:
			ri, err := c.Client(w.Workspace, HostProcessGVR)
			if err != nil {
				return false, "", err
			}
			hp, err := kube.Get[v1alpha1.HostProcess](ctx, ri, w.Name)
			if apierrors.IsNotFound(err) {
				return false, "HostProcess " + w.Name + " is missing", nil
			}
			if err != nil {
				return false, "", err
			}
			if hp.DeletionTimestamp != nil {
				return false, "", fmt.Errorf("%w: HostProcess %s", ErrWorkloadDeleting, w.Name)
			}
			if hp.Status.Phase == v1alpha1.HostProcessPhaseLost && replaceableLost[hp.Status.LostReason] {
				if err := ri.Delete(ctx, w.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
					return false, "", fmt.Errorf("compose: deleting the Lost HostProcess %s: %w", w.Name, err)
				}
				c.log.Info("a Lost set HostProcess is deleted to be placed again", "root", r.Owner(), "hostprocess", w.Name, "reason", hp.Status.LostReason)
				return false, "HostProcess " + w.Name + " was Lost (" + string(hp.Status.LostReason) + ")", nil
			}
		case KindOpenBaoServer:
			ri, err := c.Client(w.Workspace, OpenBaoServerGVR)
			if err != nil {
				return false, "", err
			}
			if _, err := kube.Get[v1alpha1.OpenBaoServer](ctx, ri, w.Name); apierrors.IsNotFound(err) {
				return false, "OpenBaoServer " + w.Name + " is missing", nil
			} else if err != nil {
				return false, "", err
			}
		}
	}
	return true, "", nil
}

func (c *Composer) Unapply(r Root) {
	c.mu.Lock()
	defer c.mu.Unlock()
	delete(c.applied, string(r.UID))
}