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

import (
	"cmp"
	"fmt"
	"strconv"
	"strings"

	"k8s.io/apimachinery/pkg/runtime/schema"

	commonv1 "github.com/johnandersen777/socialweb-computer-kcp/api/common/v1alpha1"
	eventsv1alpha1 "github.com/johnandersen777/socialweb-computer-kcp/api/events/v1alpha1"
	exposurev1alpha1 "github.com/johnandersen777/socialweb-computer-kcp/api/exposure/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/api/kcpgvr"
	pdsv1alpha1 "github.com/johnandersen777/socialweb-computer-kcp/api/pds/v1alpha1"
	policyv1alpha1 "github.com/johnandersen777/socialweb-computer-kcp/api/policy/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime/kube"
)

const (
	OwnerLabel       = "apps.socialweb.computer/root"
	RootKindLabel    = "apps.socialweb.computer/root-kind"
	RootClusterLabel = "apps.socialweb.computer/root-cluster"
	ComponentLabel   = "apps.socialweb.computer/component"
	ManagedByLabel   = "app.kubernetes.io/managed-by"
	LinkSetLabel     = "apps.socialweb.computer/link-set"

	ComponentLinkSet = "components"

	ComponentKindBidder            = "Bidder"
	ComponentKindGuestControlPlane = "GuestControlPlane"
	ComponentKindPds               = "Pds"
	ComponentKindRequester         = "Requester"
	ComponentKindGatewayAPI        = "GatewayAPI"
	ComponentKindSshServer         = "SshServer"
	ComponentKindPlc               = "Plc"
	ComponentKindRelay             = "Relay"
	ComponentKindIngressProxy      = "IngressProxy"
	ComponentKindDispatcherTLS     = "DispatcherTLS"
	ComponentKindPolicyEngine      = "PolicyEngine"
	ComponentKindPolicyWorkflow    = "PolicyWorkflow"
	ComponentKindPolicyWorkflowRun = "PolicyWorkflowRun"
	ComponentKindPolicyTrigger     = "PolicyTrigger"
	ComponentKindService           = "Service"
	ComponentKindExposure          = "Exposure"
	ComponentKindComponentLink     = "ComponentLink"

	KindMarketIdentity  = "MarketIdentity"
	KindOpenBaoServer   = "OpenBaoServer"
	KindVault           = "Vault"
	KindAtprotoRepo     = "AtprotoRepo"
	KindLocalRepo       = "LocalRepo"
	KindRemoteRepo      = "RemoteRepo"
	KindRemotePDS       = "RemotePDS"
	KindComputeVM       = "ComputeVM"
	KindBid             = "Bid"
	KindComputeContract = "ComputeContract"
	KindStateSync       = "StateSync"
	KindMarketPresence  = "MarketPresence"
	KindHostProcess     = "HostProcess"

	ExposureLocalSuffix   = "-local"
	ExposureIngressSuffix = "-ingress"

	PdsComponent          = "pds"
	PolicyEngineComponent = "policy-engine"
	PdsVaultClient        = "pds"
	AppIngressName        = "ingress"
	NetworkIngressName    = "dispatcher"
	NetworkRelayName      = "relay"
	NetworkPLCName        = "plc"
	ChildRootName         = "app"
)

var (
	MarketNetworkGVR   = v1alpha1.MarketNetworkGVR
	MarketBidderGVR    = v1alpha1.MarketBidderGVR
	MarketRequesterGVR = v1alpha1.MarketRequesterGVR
	GatewayGVR         = v1alpha1.GatewayGVR
	SshGatewayGVR      = v1alpha1.SshGatewayGVR
	HostProcessGVR     = v1alpha1.HostProcessGVR

	BidderGVR            = v1alpha1.BidderGVR
	GuestControlPlaneGVR = v1alpha1.GuestControlPlaneGVR
	PdsGVR               = v1alpha1.PdsGVR
	RequesterGVR         = v1alpha1.RequesterGVR
	GatewayAPIGVR        = v1alpha1.GatewayAPIGVR
	SshServerGVR         = v1alpha1.SshServerGVR
	PlcGVR               = v1alpha1.PlcGVR
	RelayGVR             = v1alpha1.RelayGVR
	IngressProxyGVR      = v1alpha1.IngressProxyGVR
	DispatcherTLSGVR     = v1alpha1.DispatcherTLSGVR

	PolicyEngineGVR      = policyv1alpha1.PolicyEngineGVR
	PolicyWorkflowGVR    = policyv1alpha1.PolicyWorkflowGVR
	PolicyWorkflowRunGVR = policyv1alpha1.PolicyWorkflowRunGVR
	PolicyTriggerGVR     = policyv1alpha1.PolicyTriggerGVR

	ServiceGVR       = exposurev1alpha1.ServiceGVR
	ExposureGVR      = exposurev1alpha1.ExposureGVR
	ComponentLinkGVR = exposurev1alpha1.ComponentLinkGVR

	MarketIdentityGVR  = v1alpha1.MarketIdentityGVR
	OpenBaoServerGVR   = v1alpha1.OpenBaoServerGVR
	VaultGVR           = v1alpha1.VaultGVR
	ComputeContractGVR = v1alpha1.ComputeContractGVR
	AtprotoRecordGVR   = v1alpha1.AtprotoRecordGVR
	RecordWatchGVR     = v1alpha1.AtprotoRecordWatchGVR
	RemotePDSGVR       = pdsv1alpha1.RemotePDSGVR
	WorkspaceGVR       = kcpgvr.Workspaces
	EventGVR           = kcpgvr.Events
)

type ComponentType struct {
	Kind string
	GVR  schema.GroupVersionResource
}

func (t ComponentType) APIVersion() string {
	return t.GVR.Group + "/" + t.GVR.Version
}

var ComponentTypes = []ComponentType{
	{ComponentKindBidder, BidderGVR},
	{ComponentKindGuestControlPlane, GuestControlPlaneGVR},
	{ComponentKindPds, PdsGVR},
	{ComponentKindRequester, RequesterGVR},
	{ComponentKindGatewayAPI, GatewayAPIGVR},
	{ComponentKindSshServer, SshServerGVR},
	{ComponentKindPlc, PlcGVR},
	{ComponentKindRelay, RelayGVR},
	{ComponentKindIngressProxy, IngressProxyGVR},
	{ComponentKindDispatcherTLS, DispatcherTLSGVR},
	{ComponentKindPolicyEngine, PolicyEngineGVR},
	{ComponentKindService, ServiceGVR},
	{ComponentKindExposure, ExposureGVR},
}

var PolicyTypes = []ComponentType{
	{ComponentKindPolicyEngine, PolicyEngineGVR},
	{ComponentKindPolicyWorkflow, PolicyWorkflowGVR},
	{ComponentKindPolicyWorkflowRun, PolicyWorkflowRunGVR},
	{ComponentKindPolicyTrigger, PolicyTriggerGVR},
}

var ComponentGVRs = map[string]schema.GroupVersionResource{}

func init() {
	for _, t := range ComponentTypes {
		ComponentGVRs[t.Kind] = t.GVR
	}
}

func ComponentTypeFor(kind string) (ComponentType, bool) {
	gvr, ok := ComponentGVRs[kind]
	return ComponentType{Kind: kind, GVR: gvr}, ok
}

func componentTypeFor(kind string) (ComponentType, error) {
	ct, ok := ComponentTypeFor(kind)
	if !ok {
		return ComponentType{}, failf("%q is not a component kind", kind)
	}
	return ct, nil
}

func ComponentKindsFor(rootKind string) []string {
	switch v1alpha1.ApplicationKind(rootKind) {
	case v1alpha1.ApplicationKindMarketBidder:
		return []string{ComponentKindBidder, ComponentKindGuestControlPlane, ComponentKindPds, ComponentKindPolicyEngine}
	case v1alpha1.ApplicationKindMarketRequester:
		return []string{ComponentKindRequester, ComponentKindPds, ComponentKindPolicyEngine}
	case v1alpha1.ApplicationKindGateway:
		return []string{ComponentKindGatewayAPI, ComponentKindPds, ComponentKindRequester, ComponentKindPolicyEngine}
	case v1alpha1.ApplicationKindSshGateway:
		return []string{ComponentKindSshServer, ComponentKindRequester, ComponentKindPolicyEngine}
	case "MarketNetwork":
		return []string{ComponentKindPlc, ComponentKindRelay, ComponentKindIngressProxy, ComponentKindDispatcherTLS,
			ComponentKindPds, ComponentKindPolicyEngine}
	}
	return nil
}

func GVRFor(k v1alpha1.ApplicationKind) (schema.GroupVersionResource, error) {
	switch k {
	case v1alpha1.ApplicationKindMarketBidder:
		return MarketBidderGVR, nil
	case v1alpha1.ApplicationKindMarketRequester:
		return MarketRequesterGVR, nil
	case v1alpha1.ApplicationKindGateway:
		return GatewayGVR, nil
	case v1alpha1.ApplicationKindSshGateway:
		return SshGatewayGVR, nil
	}
	return schema.GroupVersionResource{}, fmt.Errorf("compose: %q is no application kind (MarketBidder, MarketRequester, Gateway, SshGateway)", k)
}

func ComponentRefFor(kind, name string) *commonv1.ObjectRef {
	return &commonv1.ObjectRef{Kind: kind, Name: name}
}

func ComponentKey(kind, name string) string {
	return kind + "/" + name
}

func NewComponentSpec(stateDir string, network *v1alpha1.MarketNetworkValue) v1alpha1.ComponentSpec {
	return v1alpha1.ComponentSpec{RestartPolicy: v1alpha1.ComponentRestartAlways, StateDir: stateDir, Network: network}
}

func IsExposureKind(kind string) bool {
	return kind == ComponentKindService || kind == ComponentKindExposure
}

type ComponentView struct {
	Kind       string
	Name       string
	Workspace  string
	Generation int64
	Status     v1alpha1.ComponentStatus
}

func (v ComponentView) ApplicationComponent() v1alpha1.ApplicationComponent {
	return v1alpha1.ApplicationComponent{Kind: v.Kind, Name: v.Name, Workspace: v.Workspace, Phase: string(v.Status.Phase),
		Ready: v.Status.Ready, Suspended: v.Status.Phase == v1alpha1.ComponentPhaseSuspended,
		ObservedGeneration: v.Status.ObservedGeneration, Restarts: v.Status.Restarts, Message: v.Status.Message}
}

func (v ComponentView) Listeners() []v1alpha1.ApplicationListener {
	out := make([]v1alpha1.ApplicationListener, 0, len(v.Status.Endpoints))
	for _, e := range v.Status.Endpoints {
		out = append(out, v1alpha1.ApplicationListener{Component: v.Name, Name: e.Name, Kind: e.Protocol, Address: cmp.Or(e.Address, e.URL)})
	}
	return out
}

func exposureViewOf(v ComponentView, kind string, m kube.Doc) (ComponentView, error) {
	switch kind {
	case ComponentKindService:
		st, err := kube.Decode[exposurev1alpha1.ServiceStatus](m)
		if err != nil {
			return v, err
		}
		v.Status.Phase = v1alpha1.ComponentPhase(st.Phase)
		v.Status.Ready = st.Ready
		for i, endpoint := range st.Endpoints {
			v.Status.Endpoints = append(v.Status.Endpoints, v1alpha1.Endpoint{Name: "endpoint-" + strconv.Itoa(i), URL: endpoint})
		}
	case ComponentKindExposure:
		st, err := kube.Decode[exposurev1alpha1.ExposureStatus](m)
		if err != nil {
			return v, err
		}
		v.Status.Phase = v1alpha1.ComponentPhase(st.Phase)
		v.Status.Ready = st.Ready
		v.Status.Conditions = st.Conditions
		if st.Address != "" {
			v.Status.Endpoint = st.Address
			v.Status.Endpoints = []v1alpha1.Endpoint{{Name: "url", URL: st.Address, Address: st.Endpoint}}
		}
	}
	return v, nil
}

func ComponentViewOf(ws, kind string, o *Generic) (ComponentView, error) {
	v := ComponentView{Kind: kind, Name: o.Name, Workspace: ws, Generation: o.Generation}
	m := o.Status
	if len(m) == 0 {
		return v, nil
	}
	if IsExposureKind(kind) {
		return exposureViewOf(v, kind, m)
	}
	st, err := kube.Decode[v1alpha1.ComponentStatus](m)
	if err != nil {
		return v, err
	}
	v.Status = *st
	return v, nil
}

func ComponentEndpoint(v ComponentView, name string) (v1alpha1.Endpoint, bool) {
	for _, e := range v.Status.Endpoints {
		if e.Name == name {
			return e, true
		}
	}
	if len(v.Status.Endpoints) > 0 {
		return v.Status.Endpoints[0], true
	}
	return v1alpha1.Endpoint{}, false
}

func EndpointURL(e v1alpha1.Endpoint) string {
	if e.URL != "" {
		return e.URL
	}
	if e.Address != "" {
		return "http://" + e.Address
	}
	return ""
}

func EventSourceRef(name string) commonv1.ObjectRef {
	return commonv1.ObjectRef{Group: eventsv1alpha1.GroupName, Kind: eventsv1alpha1.EventSourceKind, Name: name}
}

func lowerFirst(s string) string {
	if s == "" {
		return s
	}
	return strings.ToLower(s[:1]) + s[1:]
}