~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
170 lines · 5.4 KB Raw History
package contractevent

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"strings"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"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"
	market "github.com/johnandersen777/socialweb-computer-kcp/internal/contract/market"
	ctlruntime "github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime/kube"
)

type handle struct {
	c *Controller

	owner commonv1.ObjectRef

	manager string
}

var _ market.ContractEvents = (*handle)(nil)

func (c *Controller) open(_ context.Context, to commonv1.ObjectRef, _ []string) (market.ContractEvents, error) {
	if strings.TrimSpace(to.Workspace) == "" || strings.TrimSpace(to.Name) == "" || strings.TrimSpace(to.Kind) == "" {
		return nil, fmt.Errorf("contractevent: %s names no component", to.String())
	}
	return &handle{c: c, owner: to, manager: ctlruntime.FieldManagerPrefix + strings.ToLower(strings.TrimSpace(to.Kind))}, nil
}

func (h *handle) SubscribeContractEvents(ctx context.Context, observer market.ContractEventObserver) (func(), error) {
	return h.c.subscribe(ctx, h.owner.Workspace, observer)
}

func (h *handle) client() (client.Client, error) {
	return h.c.clients(h.owner.Workspace)
}

func skeleton(name string) kube.Doc {
	return kube.Doc{"apiVersion": v1alpha1.GroupVersion.String(), "kind": market.KindContractEvent, "metadata": map[string]any{"name": name}}
}

func encode(what string, value any) (map[string]any, error) {
	raw, err := json.Marshal(value)
	if err != nil {
		return nil, fmt.Errorf("contractevent: encoding the %s: %w", what, err)
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	out := map[string]any{}
	if err := dec.Decode(&out); err != nil {
		return nil, fmt.Errorf("contractevent: encoding the %s: %w", what, err)
	}
	return out, nil
}

func (h *handle) apply(ctx context.Context, doc kube.Doc, status bool) error {
	cl, err := h.client()
	if err != nil {
		return err
	}
	return kube.ApplyWith(ctx, cl, doc, kube.ApplyOptions{FieldManager: h.manager, Force: true, Status: status})
}

func (h *handle) CreateContractEvent(ctx context.Context, event v1alpha1.ContractEvent) error {
	name := strings.TrimSpace(event.Name)
	if name == "" {
		return fmt.Errorf("contractevent: the ContractEvent names no object")
	}
	cl, err := h.client()
	if err != nil {
		return err
	}
	switch _, err := kube.GetWith(ctx, cl, v1alpha1.GroupVersion.WithKind(market.KindContractEvent), types.NamespacedName{Name: name}); {
	case err == nil:
		return market.ErrExists
	case !apierrors.IsNotFound(err):
		return err
	}
	return h.ApplyContractEvent(ctx, event)
}

func (h *handle) ApplyContractEvent(ctx context.Context, event v1alpha1.ContractEvent) error {
	name := strings.TrimSpace(event.Name)
	if name == "" {
		return fmt.Errorf("contractevent: the ContractEvent names no object")
	}
	doc := skeleton(name)
	meta := doc["metadata"].(map[string]any)
	if len(event.Labels) > 0 {
		labels := map[string]any{}
		for k, v := range event.Labels {
			labels[k] = v
		}
		meta["labels"] = labels
	}
	if len(event.OwnerReferences) > 0 {
		refs := make([]any, 0, len(event.OwnerReferences))
		for _, owner := range event.OwnerReferences {
			ref, err := encode("owner reference", owner)
			if err != nil {
				return err
			}
			refs = append(refs, ref)
		}
		meta["ownerReferences"] = refs
	}
	spec, err := encode("ContractEvent spec", event.Spec)
	if err != nil {
		return err
	}
	doc["spec"] = spec
	if err := h.apply(ctx, doc, false); err != nil {
		return fmt.Errorf("contractevent: applying %s: %w", name, err)
	}
	return nil
}

func (h *handle) DeleteContractEvent(ctx context.Context, name string) error {
	cl, err := h.client()
	if err != nil {
		return err
	}
	if err := kube.DeleteWith(ctx, cl, v1alpha1.GroupVersion.WithKind(market.KindContractEvent), types.NamespacedName{Name: name},
		client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil && !apierrors.IsNotFound(err) {
		return fmt.Errorf("contractevent: deleting %s: %w", name, err)
	}
	return nil
}

func ownerReference(contract v1alpha1.ComputeContract) metav1.OwnerReference {
	return metav1.OwnerReference{APIVersion: v1alpha1.GroupVersion.String(), Kind: "ComputeContract", Name: contract.Name, UID: contract.UID}
}

func (h *handle) ApplyContractEventOwner(ctx context.Context, name string, contract v1alpha1.ComputeContract) error {
	if contract.Name == "" || contract.UID == "" {
		return fmt.Errorf("contractevent: the owner of %s names no ComputeContract", name)
	}
	ref, err := encode("owner reference", ownerReference(contract))
	if err != nil {
		return err
	}
	doc := skeleton(name)
	doc["metadata"].(map[string]any)["ownerReferences"] = []any{ref}
	if err := h.apply(ctx, doc, false); err != nil {
		return fmt.Errorf("contractevent: the owner of %s: %w", name, err)
	}
	return nil
}

func (h *handle) ApplyContractEventStatus(ctx context.Context, name string, status v1alpha1.ContractEventStatus) error {
	value, err := encode("ContractEvent status", status)
	if err != nil {
		return err
	}
	doc := skeleton(name)
	doc["status"] = value
	if err := h.apply(ctx, doc, true); err != nil {
		return fmt.Errorf("contractevent: applying the status of %s: %w", name, err)
	}
	return nil
}