Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions internal/atc/reconciler_airway.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/yokecd/yoke/internal/k8s"
"github.com/yokecd/yoke/internal/wasi/cache"
"github.com/yokecd/yoke/internal/wasi/host"
"github.com/yokecd/yoke/pkg/apis/v1alpha1"
"github.com/yokecd/yoke/pkg/flight"
"github.com/yokecd/yoke/pkg/k8s/ctrl"
"github.com/yokecd/yoke/pkg/openapi"
Expand All @@ -33,10 +34,11 @@ func (atc atc) Reconcile(ctx context.Context, event ctrl.Event) (result ctrl.Res
var (
client = (*k8s.Client)(ctrl.Client(ctx))
airwayIntf = client.AirwayIntf()
airwayCache = ctrl.CacheFromEvent[v1alpha1.Airway](ctx, event)
webhookIntf = ctrl.Client(ctx).Clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations()
)

airway, err := airwayIntf.Get(ctx, event.Name, metav1.GetOptions{})
airway, err := airwayCache.Get(event.Name)
if err != nil {
if kerrors.IsNotFound(err) {
ctrl.Logger(ctx).Info("airway not found")
Expand Down Expand Up @@ -224,9 +226,9 @@ func (atc atc) Reconcile(ctx context.Context, event ctrl.Event) (result ctrl.Res
}
statusSchema, ok := version.Schema.OpenAPIV3Schema.Properties["status"]
if !ok {
version.Schema.OpenAPIV3Schema.Properties["status"] = *(openapi.SchemaFor[struct {
version.Schema.OpenAPIV3Schema.Properties["status"] = *openapi.SchemaFor[struct {
Conditions flight.Conditions `json:"conditions,omitempty"`
}]())
}]()
} else {
if statusSchema.Type != "object" {
return ctrl.Result{}, fmt.Errorf("invalid airway: status must be an object but got type: %q", statusSchema.Type)
Expand Down
9 changes: 5 additions & 4 deletions internal/atc/reconciler_flight.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,17 @@ func flightReconciler(modules *cache.ModuleCache, clusterScope bool) ctrl.Funcs
type AltFlight v1alpha1.Flight

var (
client = (*k8s.Client)(ctrl.Client(ctx))
commander = yoke.FromK8Client(client)
flightIntf = k8s.TypedInterface[AltFlight](client.Dynamic, gvr).Namespace(evt.Namespace)
client = (*k8s.Client)(ctrl.Client(ctx))
commander = yoke.FromK8Client(client)
flightIntf = k8s.TypedInterface[AltFlight](client.Dynamic, gvr).Namespace(evt.Namespace)
flightCache = ctrl.CacheFromEvent[AltFlight](ctx, evt)
)

if cleanup := cleanups[evt.String()]; cleanup != nil {
cleanup()
}

flight, err := flightIntf.Get(ctx, evt.Name, metav1.GetOptions{})
flight, err := flightCache.Get(evt.Name)
if err != nil {
if kerrors.IsNotFound(err) {
return ctrl.Result{}, nil
Expand Down
17 changes: 9 additions & 8 deletions internal/atc/reconciler_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,15 @@ func (atc atc) InstanceReconciler(params InstanceReconcilerParams) ctrl.Funcs {

reconciler := func(ctx context.Context, event ctrl.Event) (result ctrl.Result, err error) {
client := (*k8s.Client)(ctrl.Client(ctx))
resourceCache := ctrl.CacheFromEvent[unstructured.Unstructured](ctx, event)

mapping, err := client.Mapper.RESTMapping(params.GK, params.Version)
if err != nil {
client.Mapper.Reset()
return ctrl.Result{}, fmt.Errorf("failed to get rest mapping for gk: %w", err)
}

resourceIntf := func() dynamic.ResourceInterface {
if mapping.Scope == meta.RESTScopeNamespace {
return client.Dynamic.Resource(mapping.Resource).Namespace(event.Namespace)
}
return client.Dynamic.Resource(mapping.Resource)
}()

resource, err := resourceIntf.Get(ctx, event.Name, metav1.GetOptions{})
resource, err := resourceCache.Get(event.Name)
if err != nil {
if kerrors.IsNotFound(err) {
ctrl.Logger(ctx).Info("resource not found")
Expand All @@ -67,6 +61,13 @@ func (atc atc) InstanceReconciler(params InstanceReconcilerParams) ctrl.Funcs {
return ctrl.Result{}, fmt.Errorf("failed to get resource: %w", err)
}

resourceIntf := func() dynamic.ResourceInterface {
if mapping.Scope == meta.RESTScopeNamespace {
return client.Dynamic.Resource(mapping.Resource).Namespace(event.Namespace)
}
return client.Dynamic.Resource(mapping.Resource)
}()

if resource.GetNamespace() == "" && mapping.Scope == meta.RESTScopeNamespace {
resource.SetNamespace("default")
if _, err := resourceIntf.Update(ctx, resource, metav1.UpdateOptions{FieldManager: fieldManager}); err != nil {
Expand Down
4 changes: 3 additions & 1 deletion pkg/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ func (chart Chart) Render(release, namespace string, values any, opts ...RenderO
return nil, fmt.Errorf("failed to convert values to map: %w", err)
}

chartutil.ProcessDependencies(chart.Chart, valueMap)
if err := chartutil.ProcessDependencies(chart.Chart, valueMap); err != nil {
return nil, fmt.Errorf("failed to process chart dependencies: %w", err)
}

valueMap, err = chartutil.ToRenderValues(chart.Chart, valueMap, releaseOptions, capabilities)
if err != nil {
Expand Down
60 changes: 59 additions & 1 deletion pkg/k8s/ctrl/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ import (
"github.com/davidmdm/x/xsync"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic/dynamicinformer"
kcache "k8s.io/client-go/tools/cache"

"github.com/yokecd/yoke/internal"
internalk8s "github.com/yokecd/yoke/internal/k8s"
"github.com/yokecd/yoke/pkg/k8s"
)

Expand Down Expand Up @@ -56,6 +59,7 @@ type HandleFunc func(context.Context, Event) (Result, error)

type gkstate struct {
handler HandleFunc
lister kcache.GenericLister
shutdown func()
}

Expand Down Expand Up @@ -111,7 +115,9 @@ func (instance *Instance) register(entry Entry) error {

factory := dynamicinformer.NewDynamicSharedInformerFactory(instance.Client.Dynamic, 0)

informer := factory.ForResource(mapping.Resource).Informer()
genericInformer := factory.ForResource(mapping.Resource)
informer := genericInformer.Informer()
lister := genericInformer.Lister()

var resourceMap xsync.Set[Event]

Expand Down Expand Up @@ -196,6 +202,7 @@ func (instance *Instance) register(entry Entry) error {
teardown()
}
}),
lister: lister,
},
)

Expand Down Expand Up @@ -387,3 +394,54 @@ func safe(handler HandleFunc) HandleFunc {
return handler(ctx, event)
}
}

type ResourceCache[T any] struct {
lister kcache.GenericNamespaceLister
}

func (r ResourceCache[T]) Get(name string) (*T, error) {
obj, err := r.lister.Get(name)
if err != nil {
return nil, err
}
var result T
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.(*unstructured.Unstructured).Object, &result); err != nil {
return nil, err
}
return &result, nil
}

func (r ResourceCache[T]) List(selector labels.Selector) ([]*T, error) {
objects, err := r.lister.List(selector)
if err != nil {
return nil, err
}
result := make([]*T, len(objects))
for i, obj := range objects {
var value T
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.(*unstructured.Unstructured).Object, &value); err != nil {
return nil, err
}
result[i] = &value
}
return result, nil
}

func Cache[T any, obj internalk8s.MetaObject[T]](ctx context.Context, gk schema.GroupKind, ns string) *ResourceCache[T] {
state, loaded := Inst(ctx).gks.Load(gk)
if !loaded {
return nil
}
return &ResourceCache[T]{
lister: func() kcache.GenericNamespaceLister {
if ns != "" {
return state.lister.ByNamespace(ns)
}
return state.lister
}(),
}
}

func CacheFromEvent[T any, obj internalk8s.MetaObject[T]](ctx context.Context, evt Event) *ResourceCache[T] {
return Cache[T, obj](ctx, evt.GroupKind, evt.Namespace)
}
2 changes: 1 addition & 1 deletion pkg/openapi/validation/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func Validate(props *apiextv1.JSONSchemaProps, value any) error {
for i, e := range fieldErrors {
errs[i] = e
}
return xerr.Join(errs...)
return xerr.JoinOrdered(errs...)
}

// ValidateStrict deep copies the schema and modifies the copy such that additionalProperties are not allowed for objects that don't define any.
Expand Down
2 changes: 1 addition & 1 deletion pkg/openapi/validation/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ func TestValidateStrict(t *testing.T) {
[]string{
"errors:",
` - <nil>: Invalid value: "anything": .anything in body is a forbidden property`,
` - subobj: Invalid value: "prop": subobj.prop in body is a forbidden property`,
` - subarr[0]: Invalid value: "prop": subarr[0].prop in body is a forbidden property`,
` - subobj: Invalid value: "prop": subobj.prop in body is a forbidden property`,
},
"\n",
),
Expand Down
Loading