~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
128 lines · 4.2 KB Raw History
package gatewayssh

import (
	"context"
	"errors"
	"fmt"
	"slices"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/rest"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/app/compose"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/app/sshgateway"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime/kube"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/kcpserver"
)

const (
	RootName     = "app"
	FieldManager = "run-gateway-ssh"
	Poll         = time.Second
	DefaultName  = "ssh-gateway"

	SshPublicListener = "ssh-public"
)

type SpecInput struct {
	Profile           string
	Network           *v1alpha1.MarketNetworkValue
	BidderDids        []string
	Options           []v1alpha1.ApplicationOption
	Comms             *v1alpha1.MarketComms
	RequesterIngress  *v1alpha1.IngressConfig
	OwnerSessionsFile string

	Ssh *v1alpha1.SshGatewaySsh
}

func Spec(in SpecInput) (v1alpha1.SshGatewaySpec, error) {
	if !slices.Contains(sshgateway.SshGatewayProfiles, in.Profile) {
		return v1alpha1.SshGatewaySpec{}, fmt.Errorf("gatewayssh: --profile %q: want one of %s", in.Profile, strings.Join(sshgateway.SshGatewayProfiles, ", "))
	}
	if in.Profile == sshgateway.SshGatewayProfileNetwork && in.Network == nil {
		return v1alpha1.SshGatewaySpec{}, fmt.Errorf("gatewayssh: the %s profile takes its network value from a market network (--network)", in.Profile)
	}
	return v1alpha1.SshGatewaySpec{Profile: in.Profile, Network: in.Network, BidderDids: in.BidderDids, Comms: in.Comms, Options: in.Options,
		RequesterIngress: in.RequesterIngress, OwnerSessionsFile: in.OwnerSessionsFile, Ssh: in.Ssh}, nil
}

func InstanceID(name string) string {
	return "ssh-gateway-" + name
}

type Kcp struct {
	dyn *kube.Client
}

func OpenKcp(kubeconfig string) (*Kcp, error) {
	cfg, err := kcpserver.FrontRestConfig(kubeconfig)
	if err != nil {
		return nil, err
	}
	root := rest.CopyConfig(cfg)
	root.Host = strings.TrimSuffix(cfg.Host, "/") + "/clusters/" + compose.DefaultWorkspace
	dc, err := kube.New(root)
	if err != nil {
		return nil, err
	}
	return &Kcp{dyn: dc}, nil
}

func (k *Kcp) ApplyRoot(ctx context.Context, spec v1alpha1.SshGatewaySpec) error {
	if err := compose.AwaitBound(ctx, k.dyn.Resource(compose.APIBindingGVR), compose.RootBindTimeout, compose.RootExports...); err != nil {
		return err
	}
	obj := &v1alpha1.SshGateway{TypeMeta: metav1.TypeMeta{APIVersion: v1alpha1.GroupVersion.String(), Kind: string(v1alpha1.ApplicationKindSshGateway)},
		ObjectMeta: metav1.ObjectMeta{Name: RootName}, Spec: spec}
	_, err := kube.Apply[kube.Doc](ctx, k.dyn.Resource(compose.SshGatewayGVR), obj, kube.ApplyOptions{FieldManager: FieldManager, Force: true})
	return err
}

func (k *Kcp) Root(ctx context.Context) (*v1alpha1.SshGateway, error) {
	return kube.Get[v1alpha1.SshGateway](ctx, k.dyn.Resource(compose.SshGatewayGVR), RootName)
}

func (k *Kcp) WaitReady(ctx context.Context, timeout time.Duration, progress func(v1alpha1.ApplicationStatus)) (*v1alpha1.SshGateway, error) {
	last := ""
	o, err := kube.Await(ctx, k.dyn.Resource(compose.SshGatewayGVR), RootName, timeout, func(o *v1alpha1.SshGateway) (bool, error) {
		if o == nil {
			return false, nil
		}
		if key := string(o.Status.Phase) + o.Status.Message; key != last && progress != nil {
			progress(o.Status)
			last = key
		}
		return o.Status.Phase == v1alpha1.ApplicationPhaseReady || o.Status.Phase == v1alpha1.ApplicationPhaseFailed, nil
	})
	switch {
	case errors.Is(err, kube.ErrAwaitTimeout):
		return o, fmt.Errorf("gatewayssh: the SshGateway was not Ready within %s", timeout)
	case err != nil:
		return o, err
	case o.Status.Phase == v1alpha1.ApplicationPhaseFailed:
		return o, fmt.Errorf("gatewayssh: the SshGateway failed: %s", o.Status.Message)
	}
	return o, nil
}

func SshListener(ls []v1alpha1.ApplicationListener) string {
	for _, l := range ls {
		if l.Name == SshPublicListener && l.Address != "" {
			return l.Address
		}
	}
	for _, l := range ls {
		if !strings.HasSuffix(l.Component, "-ssh-local") || l.Address == "" {
			continue
		}
		if strings.Contains(l.Address, "://") {
			return l.Address
		}
		return "ssh://" + l.Address
	}
	return ""
}