~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
168 lines · 5.9 KB Raw History
package infra

import (
	"context"
	"fmt"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	commonv1 "github.com/johnandersen777/socialweb-computer-kcp/api/common/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	ctlruntime "github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/infra"
)

func (c *Controller) owned(ctx context.Context, req ctlruntime.Request, kind infra.Kind) ([]v1alpha1.ComputeContract, error) {
	var list v1alpha1.ComputeContractList
	if err := req.List(ctx, &list, client.MatchingLabels{v1alpha1.InfraKindLabel: string(kind), v1alpha1.InfraNameLabel: req.Name}); err != nil {
		return nil, fmt.Errorf("infra: listing the ComputeContracts of %s %s: %w", kind, req.Name, err)
	}
	return list.Items, nil
}

func ownerRef(v view) metav1.OwnerReference {
	return metav1.OwnerReference{APIVersion: v1alpha1.GroupVersion.String(), Kind: string(v.kind), Name: v.object.GetName(),
		UID: v.object.GetUID(), Controller: ptr.To(true)}
}

func childLabels(v view, extra ...string) map[string]string {
	out := map[string]string{v1alpha1.InfraKindLabel: string(v.kind), v1alpha1.InfraNameLabel: v.object.GetName()}
	for i := 0; i+1 < len(extra); i += 2 {
		out[extra[i]] = extra[i+1]
	}
	return out
}

func (r *reconciler[T]) contracts(ctx context.Context, req ctlruntime.Request, v view, existing []v1alpha1.ComputeContract,
	ending bool) ([]v1alpha1.InfraContractStatus, int, error) {
	byName := map[string]*v1alpha1.ComputeContract{}
	for i := range existing {
		byName[existing[i].Labels[v1alpha1.InfraContractLabel]] = &existing[i]
	}
	previous := map[string]v1alpha1.InfraContractPhase{}
	for _, s := range v.status.Contracts {
		previous[s.Name] = s.Phase
	}
	secrets := map[string]*commonv1.SecretRef{}
	for i := range v.spec.Secrets {
		secrets[v.spec.Secrets[i].Contract] = &v.spec.Secrets[i].SecretsRef
	}
	nodes := map[string]*v1alpha1.InfraContractNode{}
	for i := range v.spec.Nodes {
		nodes[v.spec.Nodes[i].Contract] = &v.spec.Nodes[i]
	}
	declared := map[string]bool{}
	left := 0
	var out []v1alpha1.InfraContractStatus
	for _, c := range v.declaration.Contracts {
		declared[c.Name] = true
		cc := byName[c.Name]
		state := c.State
		if ending {
			state = v1alpha1.InfraContractReleased
		}
		step := infra.Plan(state, c.ReleaseRequestedAt, cc, previous[c.Name])
		if step.Create {
			created, err := r.create(ctx, req, v, c, secrets[c.Name], nodes[c.Name])
			if err != nil {
				return nil, 0, err
			}
			cc = created
		} else if err := r.apply(ctx, req, v, cc, step); err != nil {
			return nil, 0, err
		}
		if cc != nil && !infra.Finished(cc) {
			left++
		}
		out = append(out, contractStatus(c.Name, cc, step.Phase))
	}
	for name, cc := range byName {
		if declared[name] {
			continue
		}
		if err := r.apply(ctx, req, v, cc, infra.Orphan(cc)); err != nil {
			return nil, 0, err
		}
		left++
	}
	return out, left, nil
}

func (r *reconciler[T]) releaseDeclared(ctx context.Context, req ctlruntime.Request, v view, existing []v1alpha1.ComputeContract) error {
	byName := map[string]*v1alpha1.ComputeContract{}
	for i := range existing {
		byName[existing[i].Labels[v1alpha1.InfraContractLabel]] = &existing[i]
	}
	for _, c := range v.declaration.Contracts {
		cc := byName[c.Name]
		if cc == nil {
			continue
		}
		if step := infra.Plan(c.State, c.ReleaseRequestedAt, cc, ""); step.Release {
			if err := r.apply(ctx, req, v, cc, step); err != nil {
				return err
			}
		}
	}
	return nil
}

func contractStatus(name string, cc *v1alpha1.ComputeContract, phase v1alpha1.InfraContractPhase) v1alpha1.InfraContractStatus {
	out := v1alpha1.InfraContractStatus{Name: name, Phase: phase}
	if cc == nil {
		return out
	}
	out.ComputeContract = cc.Name
	out.Message = cc.Status.Message
	if state := cc.Status.Contract; state != nil {
		out.ReceiptURI = state.ReceiptURI
		if state.Winner != nil {
			out.Winner = state.Winner.DID
		}
	}
	return out
}

func (r *reconciler[T]) create(ctx context.Context, req ctlruntime.Request, v view, c v1alpha1.InfraContract,
	secrets *commonv1.SecretRef, node *v1alpha1.InfraContractNode) (*v1alpha1.ComputeContract, error) {
	name := infra.ChildName(v.kind, v.object.GetName(), c.Name)
	cc := &v1alpha1.ComputeContract{ObjectMeta: metav1.ObjectMeta{Name: name, Labels: childLabels(v, v1alpha1.InfraContractLabel, c.Name),
		OwnerReferences: []metav1.OwnerReference{ownerRef(v)}}, Spec: infra.ContractSpec(c, secrets, node)}
	if err := req.Apply(ctx, cc); err != nil {
		return nil, fmt.Errorf("infra: applying the ComputeContract %s: %w", name, err)
	}
	r.c.log.Info("infra_contract_requested", "kind", v.kind, "workspace", req.Workspace, "name", req.Name, "contract", c.Name,
		"computeContract", name)
	return cc, nil
}

func (r *reconciler[T]) apply(ctx context.Context, req ctlruntime.Request, v view, cc *v1alpha1.ComputeContract, step infra.Step) error {
	switch {
	case cc == nil:
		return nil
	case step.Release:
		next := &v1alpha1.ComputeContract{ObjectMeta: metav1.ObjectMeta{Name: cc.Name, Labels: cc.Labels, OwnerReferences: cc.OwnerReferences}}
		cc.Spec.DeepCopyInto(&next.Spec)
		next.Spec.Release = true
		if step.RequestedAt != "" {
			next.Spec.ReleaseRequestedAt = step.RequestedAt
		}
		if err := req.Apply(ctx, next); err != nil {
			return fmt.Errorf("infra: releasing the ComputeContract %s: %w", cc.Name, err)
		}
		r.c.log.Info("infra_contract_release_requested", "kind", v.kind, "workspace", req.Workspace, "name", req.Name, "computeContract", cc.Name,
			"phase", cc.Status.Phase, "releaseRequestedAt", next.Spec.ReleaseRequestedAt)
	case step.Delete:
		if !cc.DeletionTimestamp.IsZero() {
			return nil
		}
		if err := req.Delete(ctx, cc); err != nil {
			return fmt.Errorf("infra: deleting the ComputeContract %s: %w", cc.Name, err)
		}
		r.c.log.Info("infra_contract_deleted", "kind", v.kind, "workspace", req.Workspace, "name", req.Name, "computeContract", cc.Name,
			"phase", cc.Status.Phase)
	}
	return nil
}