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

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

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/rest"
)

type ClusterResource[T any] struct {
	store *Store

	gvr schema.GroupVersionResource
}

func NewClusterResource[T any](store *Store, gvr schema.GroupVersionResource) *ClusterResource[T] {
	return &ClusterResource[T]{store: store, gvr: gvr}
}

func (r *ClusterResource[T]) client(cluster string) (rest.Interface, error) {
	if r == nil || r.store == nil {
		return nil, fmt.Errorf("kcpstore: the kcp store is not configured")
	}
	return r.store.ClientFor(cluster, r.gvr.GroupVersion())
}

func (r *ClusterResource[T]) Get(ctx context.Context, cluster, name string) (*T, error) {
	c, err := r.client(cluster)
	if err != nil {
		return nil, err
	}
	raw, err := c.Get().Resource(r.gvr.Resource).Name(name).Do(ctx).Raw()
	if err != nil {
		return nil, fmt.Errorf("kcpstore: read %s %s in %s: %w", r.gvr.Resource, name, cluster, err)
	}
	var out T
	if err := json.Unmarshal(raw, &out); err != nil {
		return nil, fmt.Errorf("kcpstore: decode %s: %w", r.gvr.Resource, err)
	}
	return &out, nil
}

func (r *ClusterResource[T]) List(ctx context.Context, cluster string) ([]T, error) {
	c, err := r.client(cluster)
	if err != nil {
		return nil, err
	}
	raw, err := c.Get().Resource(r.gvr.Resource).Do(ctx).Raw()
	if err != nil {
		return nil, fmt.Errorf("kcpstore: list %s in %s: %w", r.gvr.Resource, cluster, err)
	}
	var list struct {
		Items []T `json:"items"`
	}
	if err := json.Unmarshal(raw, &list); err != nil {
		return nil, fmt.Errorf("kcpstore: decode %s list: %w", r.gvr.Resource, err)
	}
	return list.Items, nil
}

func (r *ClusterResource[T]) Create(ctx context.Context, cluster string, obj *T) error {
	c, err := r.client(cluster)
	if err != nil {
		return err
	}
	raw, err := json.Marshal(obj)
	if err != nil {
		return fmt.Errorf("kcpstore: encode %s: %w", r.gvr.Resource, err)
	}
	response, err := c.Post().Resource(r.gvr.Resource).Body(raw).Do(ctx).Raw()
	if err != nil {
		return fmt.Errorf("kcpstore: create %s %s in %s: %w%s", r.gvr.Resource, NameOf(raw), cluster, err, ResponseDetail(response))
	}
	return nil
}

func (r *ClusterResource[T]) Delete(ctx context.Context, cluster, name string) error {
	c, err := r.client(cluster)
	if err != nil {
		return err
	}
	err = c.Delete().Resource(r.gvr.Resource).Name(name).Body([]byte(`{"propagationPolicy":"Background"}`)).Do(ctx).Error()
	if err != nil && !apierrors.IsNotFound(err) {
		return fmt.Errorf("kcpstore: delete %s %s in %s: %w", r.gvr.Resource, name, cluster, err)
	}
	return nil
}

func (r *ClusterResource[T]) ApplyStatus(ctx context.Context, cluster, kind, name, fieldManager string, status map[string]any) error {
	c, err := r.client(cluster)
	if err != nil {
		return err
	}
	body, err := json.Marshal(map[string]any{
		"apiVersion": r.gvr.GroupVersion().String(),
		"kind":       kind,
		"metadata":   map[string]any{"name": name},
		"status":     status,
	})
	if err != nil {
		return fmt.Errorf("kcpstore: encode %s status: %w", r.gvr.Resource, err)
	}
	response, err := c.Patch(types.ApplyPatchType).SubResource("status").Resource(r.gvr.Resource).Name(name).
		Param("fieldManager", fieldManager).Param("force", "true").Body(body).Do(ctx).Raw()
	if err != nil {
		return fmt.Errorf("kcpstore: apply %s status for %s in %s: %w%s", r.gvr.Resource, name, cluster, err, ResponseDetail(response))
	}
	return nil
}

func (r *ClusterResource[T]) ApplySpec(ctx context.Context, cluster, kind, name, fieldManager string, spec map[string]any) error {
	c, err := r.client(cluster)
	if err != nil {
		return err
	}
	body, err := json.Marshal(map[string]any{
		"apiVersion": r.gvr.GroupVersion().String(),
		"kind":       kind,
		"metadata":   map[string]any{"name": name},
		"spec":       spec,
	})
	if err != nil {
		return fmt.Errorf("kcpstore: encode %s spec: %w", r.gvr.Resource, err)
	}
	response, err := c.Patch(types.ApplyPatchType).Resource(r.gvr.Resource).Name(name).
		Param("fieldManager", fieldManager).Param("force", "true").Body(body).Do(ctx).Raw()
	if err != nil {
		return fmt.Errorf("kcpstore: apply %s spec for %s in %s: %w%s", r.gvr.Resource, name, cluster, err, ResponseDetail(response))
	}
	return nil
}