diff --git a/go.mod b/go.mod index bd78db21..8b2a1417 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/rancher/lasso v0.2.3 github.com/rancher/norman v0.7.0 github.com/rancher/remotedialer v0.4.5-rc.3 - github.com/rancher/wrangler/v3 v3.2.2 + github.com/rancher/wrangler/v3 v3.2.3-rc.2 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v2 v2.27.7 diff --git a/go.sum b/go.sum index 73e365b8..a4dd7d22 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,8 @@ github.com/rancher/norman v0.7.0 h1:duBZxekBj13k/2RTyWKZgV/ntXkIXm0sRKqwFO8ui+I= github.com/rancher/norman v0.7.0/go.mod h1:IOQn3CNCms6UK72QHujesLKedqZh4+SP8/FDEFc+7Ns= github.com/rancher/remotedialer v0.4.5-rc.3 h1:Bfik0ZF89Bpm13ft1GPVg3/0xzCO+c0N0yD9jmlavXc= github.com/rancher/remotedialer v0.4.5-rc.3/go.mod h1:N96a/IQXoP9JLc9cbeJEly3fLi8lnpXFeFOJofgJBbA= -github.com/rancher/wrangler/v3 v3.2.2 h1:IK1/v8n8gaZSB4izmJhGFXJt38Z8gkbwzl3Lo/e2jQc= -github.com/rancher/wrangler/v3 v3.2.2/go.mod h1:TA1QuuQxrtn/kmJbBLW/l24IcfHBmSXBa9an3IRlqQQ= +github.com/rancher/wrangler/v3 v3.2.3-rc.2 h1:CnbO8lT8ZwQF4PfrptfCwmYLfQBQR9BRxGvOtEiZZKU= +github.com/rancher/wrangler/v3 v3.2.3-rc.2/go.mod h1:TA1QuuQxrtn/kmJbBLW/l24IcfHBmSXBa9an3IRlqQQ= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= diff --git a/pkg/clustercache/controller.go b/pkg/clustercache/controller.go index 34ffdf1f..86a63793 100644 --- a/pkg/clustercache/controller.go +++ b/pkg/clustercache/controller.go @@ -52,7 +52,7 @@ type clusterCache struct { sync.RWMutex ctx context.Context - summaryClient client.Interface + summaryClient client.ExtendedInterface watchers map[schema2.GroupVersionKind]*watcher workqueue workqueue.DelayingInterface @@ -64,7 +64,7 @@ type clusterCache struct { func NewClusterCache(ctx context.Context, dynamicClient dynamic.Interface) ClusterCache { c := &clusterCache{ ctx: ctx, - summaryClient: client.NewForDynamicClient(dynamicClient), + summaryClient: client.NewForExtendedDynamicClient(dynamicClient), watchers: map[schema2.GroupVersionKind]*watcher{}, workqueue: workqueue.NewNamedDelayingQueue("cluster-cache"), } @@ -147,7 +147,10 @@ func (h *clusterCache) OnSchemas(schemas *schema.Collection) error { continue } - summaryInformer := informer.NewFilteredSummaryInformer(h.summaryClient, gvr, metav1.NamespaceAll, 2*time.Hour, + opts := &client.Options{ + Schema: schema.Schema, + } + summaryInformer := informer.NewFilteredSummaryInformerWithOptions(h.summaryClient, gvr, opts, metav1.NamespaceAll, 2*time.Hour, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, nil) ctx, cancel := context.WithCancel(h.ctx) w := &watcher{ diff --git a/pkg/resources/cluster/cluster.go b/pkg/resources/cluster/cluster.go index 3e80e2e2..bd992a48 100644 --- a/pkg/resources/cluster/cluster.go +++ b/pkg/resources/cluster/cluster.go @@ -29,7 +29,7 @@ func Register(ctx context.Context, apiSchemas *types.APISchemas, cg proxy.Client schema.ResourceMethods = []string{http.MethodGet} schema.Attributes["access"] = accesscontrol.AccessListByVerb{ "watch": accesscontrol.AccessList{ - { + accesscontrol.Access{ Namespace: "*", ResourceName: "*", }, diff --git a/pkg/resources/counts/counts.go b/pkg/resources/counts/counts.go index 78f50e9b..bbfc5a18 100644 --- a/pkg/resources/counts/counts.go +++ b/pkg/resources/counts/counts.go @@ -10,6 +10,7 @@ import ( "github.com/rancher/steve/pkg/accesscontrol" "github.com/rancher/steve/pkg/attributes" "github.com/rancher/steve/pkg/clustercache" + "github.com/rancher/wrangler/v3/pkg/schemas" "github.com/rancher/wrangler/v3/pkg/summary" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" @@ -31,7 +32,7 @@ func Register(schemas *types.APISchemas, ccache clustercache.ClusterCache) { schema.ResourceMethods = []string{http.MethodGet} schema.Attributes["access"] = accesscontrol.AccessListByVerb{ "watch": accesscontrol.AccessList{ - { + accesscontrol.Access{ Namespace: "*", ResourceName: "*", }, @@ -151,7 +152,7 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types. return nil } - _, namespace, revision, summary, ok := getInfo(obj) + _, namespace, revision, summary, ok := getInfo(obj, schema) if !ok { return nil } @@ -162,7 +163,7 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types. } if oldObj != nil { - if _, _, _, oldSummary, ok := getInfo(oldObj); ok { + if _, _, _, oldSummary, ok := getInfo(oldObj, schema); ok { if oldSummary.Transitioning == summary.Transitioning && oldSummary.Error == summary.Error && simpleState(oldSummary) == simpleState(summary) { @@ -230,7 +231,7 @@ func (s *Store) schemasToWatch(apiOp *types.APIRequest) (result []*types.APISche return } -func getInfo(obj interface{}) (name string, namespace string, revision int, summaryResult summary.Summary, ok bool) { +func getInfo(obj interface{}, schema *types.APISchema) (name string, namespace string, revision int, summaryResult summary.Summary, ok bool) { r, ok := obj.(runtime.Object) if !ok { return "", "", 0, summaryResult, false @@ -246,7 +247,12 @@ func getInfo(obj interface{}) (name string, namespace string, revision int, summ return "", "", 0, summaryResult, false } - summaryResult = summary.Summarize(r) + opts := &summary.SummarizeOptions{HasObservedGeneration: false} + if schema != nil && schema.Attributes != nil { + opts.HasObservedGeneration = schemas.HasObservedGeneration(schema.Schema) + } + + summaryResult = summary.SummarizeWithOptions(r, opts) return meta.GetName(), meta.GetNamespace(), revision, summaryResult, true } @@ -324,7 +330,7 @@ func (s *Store) getCount(apiOp *types.APIRequest) Count { all := access.Grants("list", "*", "*") for _, obj := range s.ccache.List(gvk) { - name, ns, revision, summary, ok := getInfo(obj) + name, ns, revision, summary, ok := getInfo(obj, schema) if !ok { continue } diff --git a/pkg/schema/converter/crd.go b/pkg/schema/converter/crd.go index 17bfa4de..fe0eff51 100644 --- a/pkg/schema/converter/crd.go +++ b/pkg/schema/converter/crd.go @@ -77,5 +77,27 @@ func forVersion(group, kind string, version v1.CustomResourceDefinitionVersion, } if version.Schema != nil && version.Schema.OpenAPIV3Schema != nil { schema.Description = version.Schema.OpenAPIV3Schema.Description + + if hasObservedGeneration(version.Schema.OpenAPIV3Schema) { + schemas.SetHasObservedGeneration(schema.Schema, true) + } } } + +func hasObservedGeneration(schema *v1.JSONSchemaProps) bool { + if schema == nil { + return false + } + if schema.Properties == nil { + return false + } + status, ok := schema.Properties["status"] + if !ok { + return false + } + if status.Properties == nil { + return false + } + _, found := status.Properties["observedGeneration"] + return found +} diff --git a/pkg/schema/converter/crd_test.go b/pkg/schema/converter/crd_test.go index 07b7ebb7..528b6ebc 100644 --- a/pkg/schema/converter/crd_test.go +++ b/pkg/schema/converter/crd_test.go @@ -306,3 +306,73 @@ func TestAddCustomResources(t *testing.T) { }) } } +func TestHasObservedGeneration(t *testing.T) { + tests := []struct { + name string + schema *v1.JSONSchemaProps + expected bool + }{ + { + name: "nil schema", + schema: nil, + expected: false, + }, + { + name: "nil properties", + schema: &v1.JSONSchemaProps{}, + expected: false, + }, + { + name: "no status property", + schema: &v1.JSONSchemaProps{ + Properties: map[string]v1.JSONSchemaProps{ + "foo": {}, + }, + }, + expected: false, + }, + { + name: "status property without properties", + schema: &v1.JSONSchemaProps{ + Properties: map[string]v1.JSONSchemaProps{ + "status": {}, + }, + }, + expected: false, + }, + { + name: "status property with properties but no observedGeneration", + schema: &v1.JSONSchemaProps{ + Properties: map[string]v1.JSONSchemaProps{ + "status": { + Properties: map[string]v1.JSONSchemaProps{ + "foo": {}, + }, + }, + }, + }, + expected: false, + }, + { + name: "status property with observedGeneration", + schema: &v1.JSONSchemaProps{ + Properties: map[string]v1.JSONSchemaProps{ + "status": { + Properties: map[string]v1.JSONSchemaProps{ + "observedGeneration": {}, + }, + }, + }, + }, + expected: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + result := hasObservedGeneration(tt.schema) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/pkg/server/config.go b/pkg/server/config.go index 4d6a36a2..b6fc84d6 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -4,9 +4,9 @@ import ( "context" "time" - "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiextensions.k8s.io" + apiextensions "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiextensions.k8s.io" apiextensionsv1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiextensions.k8s.io/v1" - "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiregistration.k8s.io" + apiregistration "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiregistration.k8s.io" apiregistrationv1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiregistration.k8s.io/v1" "github.com/rancher/wrangler/v3/pkg/generated/controllers/core" corev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" diff --git a/pkg/summarycache/summarycache.go b/pkg/summarycache/summarycache.go index 90a1055e..d0600535 100644 --- a/pkg/summarycache/summarycache.go +++ b/pkg/summarycache/summarycache.go @@ -10,6 +10,7 @@ import ( "github.com/rancher/steve/pkg/clustercache" "github.com/rancher/steve/pkg/schema" "github.com/rancher/steve/pkg/schema/converter" + wranglerSchemas "github.com/rancher/wrangler/v3/pkg/schemas" "github.com/rancher/wrangler/v3/pkg/slice" "github.com/rancher/wrangler/v3/pkg/summary" "k8s.io/apimachinery/pkg/api/meta" @@ -106,7 +107,7 @@ func (s *SummaryCache) SummaryAndRelationship(obj runtime.Object) (*summary.Summ defer s.RUnlock() key := toKey(obj) - summarized := summary.Summarized(obj) + summarized := summary.SummarizedWithOptions(obj, getSummarizeOptions(obj, s.schemas)) relObjs, err := s.cache.ByIndex(relationshipIndex, key) if err != nil { @@ -183,7 +184,7 @@ func (s *SummaryCache) toRel(ns string, rel *summary.Relationship) Relationship FromID: id, FromType: converter.GVKToSchemaID(runtimeschema.FromAPIVersionAndKind(rel.APIVersion, rel.Kind)), Rel: rel.Type, - }, obj) + }, obj, s.schemas) } toNS := "" @@ -197,10 +198,10 @@ func (s *SummaryCache) toRel(ns string, rel *summary.Relationship) Relationship Rel: rel.Type, ToNamespace: toNS, Selector: toSelector(rel.Selector), - }, obj) + }, obj, s.schemas) } -func addObject(rel Relationship, obj interface{}) Relationship { +func addObject(rel Relationship, obj interface{}, schemas *schema.Collection) Relationship { if obj == nil { return rel } @@ -210,7 +211,8 @@ func addObject(rel Relationship, obj interface{}) Relationship { return rel } - summarized := summary.Summarized(ro) + summarized := summary.SummarizedWithOptions(ro, getSummarizeOptions(ro, schemas)) + rel.State = summarized.State rel.Error = summarized.Error rel.Message = strings.Join(summarized.Message, "; ") @@ -267,7 +269,7 @@ func (s *SummaryCache) Change(newObj, oldObj runtime.Object) { func (s *SummaryCache) process(obj runtime.Object) (*summary.SummarizedObject, []*summary.Relationship) { var ( rels []*summary.Relationship - summary = summary.Summarized(obj) + summary = summary.SummarizedWithOptions(obj, getSummarizeOptions(obj, s.schemas)) ) for _, rel := range summary.Relationships { @@ -339,6 +341,19 @@ func (s *SummaryCache) OnChange(_ runtimeschema.GroupVersionKind, key string, ob return nil } +func getSummarizeOptions(obj runtime.Object, schemas *schema.Collection) *summary.SummarizeOptions { + gvk := obj.GetObjectKind().GroupVersionKind() + schemaID := converter.GVKToSchemaID(gvk) + schema := schemas.Schema(schemaID) + + opts := &summary.SummarizeOptions{HasObservedGeneration: false} + if schema != nil && schema.Attributes != nil { + opts.HasObservedGeneration = wranglerSchemas.HasObservedGeneration(schema.Schema) + } + + return opts +} + func toKeyFrom(namespace, name string, gvk runtimeschema.GroupVersionKind, other ...string) string { parts := []string{ gvk.Group,