agentikube/internal/kube/wait.go
2026-02-07 14:39:14 -05:00

74 lines
1.9 KiB
Go

package kube
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
)
// WaitForReady watches a resource until its Ready condition becomes True
// or the context is cancelled/times out.
func (c *Client) WaitForReady(ctx context.Context, namespace string, gvr schema.GroupVersionResource, name string) error {
watcher, err := c.Dynamic().Resource(gvr).Namespace(namespace).Watch(ctx, metav1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
})
if err != nil {
return fmt.Errorf("watching %s %s/%s: %w", gvr.Resource, namespace, name, err)
}
defer watcher.Stop()
for {
select {
case <-ctx.Done():
return fmt.Errorf("timed out waiting for %s %s/%s to become ready", gvr.Resource, namespace, name)
case event, ok := <-watcher.ResultChan():
if !ok {
return fmt.Errorf("watch channel closed for %s %s/%s", gvr.Resource, namespace, name)
}
if event.Type == watch.Error {
return fmt.Errorf("watch error for %s %s/%s", gvr.Resource, namespace, name)
}
obj, ok := event.Object.(*unstructured.Unstructured)
if !ok {
continue
}
if isReady(obj) {
return nil
}
}
}
}
// isReady checks whether an unstructured object has a condition with
// type=Ready and status=True.
func isReady(obj *unstructured.Unstructured) bool {
status, found, err := unstructured.NestedMap(obj.Object, "status")
if err != nil || !found {
return false
}
conditionsRaw, found, err := unstructured.NestedSlice(status, "conditions")
if err != nil || !found {
return false
}
for _, c := range conditionsRaw {
condition, ok := c.(map[string]interface{})
if !ok {
continue
}
condType, _ := condition["type"].(string)
condStatus, _ := condition["status"].(string)
if condType == "Ready" && condStatus == "True" {
return true
}
}
return false
}