~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
160 lines · 4.1 KB Raw History
package sweepkcp

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"sort"
	"strings"

	kcpv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/client-go/rest"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/contract/clusterhome"
)

var (
	socialwebResource = v1alpha1.GroupVersion
	tenancyResource   = kcpv1alpha1.SchemeGroupVersion
)

const SweepExemptLabel = "socialweb.computer/sweep"

const SweepExemptValue = "exempt"

type Options struct {
	Host string

	RestConfig *rest.Config

	Transport http.RoundTripper
}

type Worklist struct {
	cfg    *rest.Config
	http   *http.Client
	codecs runtime.NegotiatedSerializer
}

func NewWorklist(opts Options) (*Worklist, error) {
	if opts.Host == "" {
		return nil, errors.New("sweepkcp: Host is required")
	}
	cfg := &rest.Config{}
	if opts.RestConfig != nil {
		cfg = rest.CopyConfig(opts.RestConfig)
	}
	cfg.Host = strings.TrimSuffix(opts.Host, "/")
	cfg.ContentType = "application/json"
	cfg.AcceptContentTypes = "application/json"
	if opts.Transport != nil {
		cfg.Transport = opts.Transport
	}
	httpClient, err := rest.HTTPClientFor(cfg)
	if err != nil {
		return nil, fmt.Errorf("sweepkcp: %w", err)
	}
	scheme, err := newScheme()
	if err != nil {
		return nil, err
	}
	return &Worklist{cfg: cfg, http: httpClient, codecs: serializer.NewCodecFactory(scheme).WithoutConversion()}, nil
}

func (w *Worklist) DIDs(ctx context.Context) ([]string, error) {
	names, err := w.accountWorkspaces(ctx)
	if err != nil {
		return nil, err
	}
	seen := make(map[string]bool, len(names))
	out := make([]string, 0, len(names))
	for _, name := range names {
		dids, err := w.accountDIDs(ctx, clusterhome.RootWorkspace+":"+name)
		if apierrors.IsNotFound(err) {
			continue
		}
		if err != nil {
			return nil, err
		}
		for _, did := range dids {
			if did == "" || seen[did] {
				continue
			}
			seen[did] = true
			out = append(out, did)
		}
	}
	sort.Strings(out)
	return out, nil
}

func (w *Worklist) accountWorkspaces(ctx context.Context) ([]string, error) {
	r, err := w.resource(clusterhome.RootWorkspace, tenancyResource)
	if err != nil {
		return nil, err
	}
	var list kcpv1alpha1.WorkspaceList
	if err := r.Get().Resource("workspaces").Do(ctx).Into(&list); err != nil {
		return nil, fmt.Errorf("sweepkcp: list workspaces in %s: %w", clusterhome.RootWorkspace, err)
	}
	names := make([]string, 0, len(list.Items))
	for _, ws := range list.Items {
		if ws.Spec.Mount == nil {
			names = append(names, ws.Name)
		}
	}
	return names, nil
}

func (w *Worklist) accountDIDs(ctx context.Context, logicalCluster string) ([]string, error) {
	r, err := w.resource(logicalCluster, socialwebResource)
	if err != nil {
		return nil, err
	}
	var list v1alpha1.AtprotoIdentityList
	if err := r.Get().Resource("atprotoidentities").Do(ctx).Into(&list); err != nil {
		return nil, fmt.Errorf("sweepkcp: list identities in %s: %w", logicalCluster, err)
	}
	out := make([]string, 0, len(list.Items))
	for _, rec := range list.Items {
		if rec.Spec.Scope != v1alpha1.IdentityScopeAccount {
			continue
		}
		if rec.Labels[SweepExemptLabel] == SweepExemptValue {
			continue
		}
		out = append(out, rec.Spec.DID)
	}
	return out, nil
}

func (w *Worklist) resource(logicalCluster string, gv schema.GroupVersion) (rest.Interface, error) {
	cfg := rest.CopyConfig(w.cfg)
	cfg.GroupVersion = &gv
	cfg.APIPath = "/clusters/" + logicalCluster + "/apis"
	cfg.NegotiatedSerializer = w.codecs
	r, err := rest.RESTClientForConfigAndClient(cfg, w.http)
	if err != nil {
		return nil, fmt.Errorf("sweepkcp: %s: %w", logicalCluster, err)
	}
	return r, nil
}

func newScheme() (*runtime.Scheme, error) {
	s := runtime.NewScheme()
	if err := v1alpha1.AddToScheme(s); err != nil {
		return nil, fmt.Errorf("sweepkcp: %w", err)
	}
	if err := kcpv1alpha1.AddToScheme(s); err != nil {
		return nil, fmt.Errorf("sweepkcp: %w", err)
	}
	metav1.AddToGroupVersion(s, schema.GroupVersion{Version: "v1"})
	return s, nil
}