diff --git a/apis/templates/v1/resourcesrefs.go b/apis/templates/v1/resourcesrefs.go index 4261c3c..f58f445 100644 --- a/apis/templates/v1/resourcesrefs.go +++ b/apis/templates/v1/resourcesrefs.go @@ -1,5 +1,12 @@ package v1 +type Slice struct { + Continue bool + Offset int + Page int + PerPage int +} + // ResourceRef defines a template for an action. type ResourceRef struct { // ID for the action. @@ -14,6 +21,8 @@ type ResourceRef struct { APIVersion string `json:"apiVersion,omitempty"` // Verb is the HTTP request verb. Verb string `json:"verb,omitempty"` + // Slice is used for pagination + Slice *Slice `json:"slice,omitempty"` } // ResourceRefResult defines the action result after evaluating a template. diff --git a/apis/templates/v1/zz_generated.deepcopy.go b/apis/templates/v1/zz_generated.deepcopy.go index 095abf8..73d15d1 100644 --- a/apis/templates/v1/zz_generated.deepcopy.go +++ b/apis/templates/v1/zz_generated.deepcopy.go @@ -252,6 +252,11 @@ func (in *Reference) DeepCopy() *Reference { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceRef) DeepCopyInto(out *ResourceRef) { *out = *in + if in.Slice != nil { + in, out := &in.Slice, &out.Slice + *out = new(Slice) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRef. @@ -312,7 +317,7 @@ func (in *ResourceRefTemplate) DeepCopyInto(out *ResourceRefTemplate) { *out = new(string) **out = **in } - out.Template = in.Template + in.Template.DeepCopyInto(&out.Template) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRefTemplate. @@ -325,6 +330,21 @@ func (in *ResourceRefTemplate) DeepCopy() *ResourceRefTemplate { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Slice) DeepCopyInto(out *Slice) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Slice. +func (in *Slice) DeepCopy() *Slice { + if in == nil { + return nil + } + out := new(Slice) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WidgetDataTemplate) DeepCopyInto(out *WidgetDataTemplate) { *out = *in diff --git a/internal/handlers/dispatchers/helpers.go b/internal/handlers/dispatchers/helpers.go index 8aa8e48..7848724 100644 --- a/internal/handlers/dispatchers/helpers.go +++ b/internal/handlers/dispatchers/helpers.go @@ -41,7 +41,7 @@ func fetchObject(req *http.Request) (got objects.Result) { func paginationInfo(log *slog.Logger, req *http.Request) (perPage, page int) { perPage, page = -1, -1 - if val := req.URL.Query().Get("per_page"); val != "" { + if val := req.URL.Query().Get("perpage"); val != "" { var err error perPage, err = strconv.Atoi(val) if err != nil { diff --git a/internal/resolvers/restactions/api/handler.go b/internal/resolvers/restactions/api/handler.go index dd3d706..a62ece5 100644 --- a/internal/resolvers/restactions/api/handler.go +++ b/internal/resolvers/restactions/api/handler.go @@ -35,8 +35,8 @@ func jsonHandler(ctx context.Context, opts jsonHandlerOptions) func(io.ReadClose pig := map[string]any{ opts.key: tmp, } - if si, ok := opts.out["_slice_"]; ok { - pig["_slice_"] = si + if si, ok := opts.out["slice"]; ok { + pig["slice"] = si } if opts.filter != nil { diff --git a/internal/resolvers/restactions/api/helpers.go b/internal/resolvers/restactions/api/helpers.go deleted file mode 100644 index 00355a0..0000000 --- a/internal/resolvers/restactions/api/helpers.go +++ /dev/null @@ -1,71 +0,0 @@ -package api - -import ( - "fmt" - "strings" -) - -func nestedSliceNoCopy(obj map[string]any, fields ...string) ([]any, bool, error) { - val, found, err := NestedFieldNoCopy(obj, fields...) - if !found || err != nil { - return nil, found, err - } - - items, ok := val.([]any) - if !ok { - return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected []any", - jsonPath(fields), val, val) - } - - return items, true, nil -} - -// nestedMapNoCopy returns a map[string]interface{} value of a nested field. -// Returns false if value is not found and an error if not a map[string]interface{}. -func nestedMapNoCopy(obj map[string]any, fields ...string) (map[string]any, bool, error) { - val, found, err := NestedFieldNoCopy(obj, fields...) - if !found || err != nil { - return nil, found, err - } - - m, ok := val.(map[string]any) - if !ok { - return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected map[string]any", - jsonPath(fields), val, val) - } - - return m, true, nil -} - -// NestedFieldNoCopy returns a reference to a nested field. -// Returns false if value is not found and an error if unable -// to traverse obj. -// -// Note: fields passed to this function are treated as keys within the passed -// object; no array/slice syntax is supported. -func NestedFieldNoCopy(obj map[string]any, fields ...string) (any, bool, error) { - var val interface{} = obj - - for i, field := range fields { - if val == nil { - return nil, false, nil - } - - if m, ok := val.(map[string]any); ok { - val, ok = m[field] - if !ok { - return nil, false, nil - } - } else { - return nil, false, - fmt.Errorf("%v accessor error: %v is of the type %T, expected map[string]any", - jsonPath(fields[:i+1]), val, val) - } - } - - return val, true, nil -} - -func jsonPath(fields []string) string { - return "." + strings.Join(fields, ".") -} diff --git a/internal/resolvers/restactions/api/helpers_test.go b/internal/resolvers/restactions/api/helpers_test.go deleted file mode 100644 index 8a01718..0000000 --- a/internal/resolvers/restactions/api/helpers_test.go +++ /dev/null @@ -1,192 +0,0 @@ -//go:build unit -// +build unit - -package api - -import ( - "testing" -) - -func TestNestedSliceNoCopy(t *testing.T) { - tests := []struct { - name string - obj map[string]any - fields []string - expect []any - found bool - err bool - }{ - { - name: "valid nested slice", - obj: map[string]any{ - "data": map[string]any{ - "items": []any{1, 2, 3}, - }, - }, - fields: []string{"data", "items"}, - expect: []any{1, 2, 3}, - found: true, - err: false, - }, - { - name: "field not found", - obj: map[string]any{ - "data": map[string]any{}, - }, - fields: []string{"data", "items"}, - expect: nil, - found: false, - err: false, - }, - { - name: "not a slice", - obj: map[string]any{ - "data": map[string]any{ - "items": "not a slice", - }, - }, - fields: []string{"data", "items"}, - expect: nil, - found: false, - err: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - result, found, err := nestedSliceNoCopy(tc.obj, tc.fields...) - - if found != tc.found { - t.Errorf("expected found %v, got %v", tc.found, found) - } - if (err != nil) != tc.err { - t.Errorf("expected error %v, got %v", tc.err, err) - } - if !deepEqual(result, tc.expect) { - t.Errorf("expected %v, got %v", tc.expect, result) - } - }) - } -} - -func TestNestedMapNoCopy(t *testing.T) { - tests := []struct { - name string - obj map[string]any - fields []string - expect map[string]any - found bool - err bool - }{ - { - name: "valid nested map", - obj: map[string]any{ - "data": map[string]any{ - "config": map[string]any{"key": "value"}, - }, - }, - fields: []string{"data", "config"}, - expect: map[string]any{"key": "value"}, - found: true, - err: false, - }, - { - name: "field not found", - obj: map[string]any{ - "data": map[string]any{}, - }, - fields: []string{"data", "config"}, - expect: nil, - found: false, - err: false, - }, - { - name: "not a map", - obj: map[string]any{ - "data": map[string]any{ - "config": "not a map", - }, - }, - fields: []string{"data", "config"}, - expect: nil, - found: false, - err: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - result, found, err := nestedMapNoCopy(tc.obj, tc.fields...) - - if found != tc.found { - t.Errorf("expected found %v, got %v", tc.found, found) - } - if (err != nil) != tc.err { - t.Errorf("expected error %v, got %v", tc.err, err) - } - if !deepEqual(result, tc.expect) { - t.Errorf("expected %v, got %v", tc.expect, result) - } - }) - } -} - -func TestNestedFieldNoCopy(t *testing.T) { - tests := []struct { - name string - obj map[string]any - fields []string - expect any - found bool - err bool - }{ - { - name: "valid nested field", - obj: map[string]any{ - "data": map[string]any{ - "config": "value", - }, - }, - fields: []string{"data", "config"}, - expect: "value", - found: true, - err: false, - }, - { - name: "field not found", - obj: map[string]any{ - "data": map[string]any{}, - }, - fields: []string{"data", "config"}, - expect: nil, - found: false, - err: false, - }, - { - name: "not a map", - obj: map[string]any{ - "data": "not a map", - }, - fields: []string{"data", "config"}, - expect: nil, - found: false, - err: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - result, found, err := NestedFieldNoCopy(tc.obj, tc.fields...) - - if found != tc.found { - t.Errorf("expected found %v, got %v", tc.found, found) - } - if (err != nil) != tc.err { - t.Errorf("expected error %v, got %v", tc.err, err) - } - if !deepEqual(result, tc.expect) { - t.Errorf("expected %v, got %v", tc.expect, result) - } - }) - } -} diff --git a/internal/resolvers/restactions/api/resolve.go b/internal/resolvers/restactions/api/resolve.go index 11df512..193a29f 100644 --- a/internal/resolvers/restactions/api/resolve.go +++ b/internal/resolvers/restactions/api/resolve.go @@ -43,6 +43,7 @@ func Resolve(ctx context.Context, opts ResolveOptions) map[string]any { } log := xcontext.Logger(ctx) + log.Info("pagination options", slog.Int("page", opts.Page), slog.Int("perPage", opts.PerPage)) user, err := xcontext.UserInfo(ctx) if err != nil { @@ -82,13 +83,15 @@ func Resolve(ctx context.Context, opts ResolveOptions) map[string]any { } if opts.PerPage > 0 && opts.Page > 0 { - dict["_slice_"] = map[string]any{ + dict["slice"] = map[string]any{ "page": opts.Page, "perPage": opts.PerPage, "offset": (opts.Page - 1) * opts.PerPage, } } + log.Info("base dict for api resolver", slog.Any("dict", dict)) + for _, id := range names { // Get the api with this identifier apiCall, ok := apiMap[id] @@ -158,10 +161,35 @@ func Resolve(ctx context.Context, opts ResolveOptions) map[string]any { return dict } } + + log.Info("api successfully resolved", + slog.String("name", id), + slog.String("host", call.Endpoint.ServerURL), slog.String("path", call.Path), + slog.Any("depth", mapDepth(dict)), + ) } } - //delete(dict, "_slice_") + removeManagedFields(dict) + //delete(dict, "slice") return dict } + +func removeManagedFields(data any) { + switch v := data.(type) { + case map[string]any: + delete(v, "managedFields") + // scansiona tutte le altre chiavi + for _, val := range v { + removeManagedFields(val) + } + case []any: + for _, elem := range v { + removeManagedFields(elem) + } + // other types (string, int, ecc.) -> do nothing + default: + return + } +} diff --git a/internal/resolvers/restactions/api/support.go b/internal/resolvers/restactions/api/support.go new file mode 100644 index 0000000..31c0e0c --- /dev/null +++ b/internal/resolvers/restactions/api/support.go @@ -0,0 +1,26 @@ +package api + +func mapDepth(data any) int { + switch v := data.(type) { + case map[string]any: + maxDepth := 1 + for _, val := range v { + d := mapDepth(val) + 1 + if d > maxDepth { + maxDepth = d + } + } + return maxDepth + case []any: + maxDepth := 0 + for _, elem := range v { + d := mapDepth(elem) + if d > maxDepth { + maxDepth = d + } + } + return maxDepth + default: + return 0 + } +} diff --git a/internal/resolvers/restactions/restactions.go b/internal/resolvers/restactions/restactions.go index 6a1dc18..404ea7b 100644 --- a/internal/resolvers/restactions/restactions.go +++ b/internal/resolvers/restactions/restactions.go @@ -4,7 +4,9 @@ import ( "context" "encoding/json" "fmt" + "log/slog" + xcontext "github.com/krateoplatformops/plumbing/context" "github.com/krateoplatformops/plumbing/jqutil" "github.com/krateoplatformops/plumbing/ptr" templates "github.com/krateoplatformops/snowplow/apis/templates/v1" @@ -44,6 +46,9 @@ func Resolve(ctx context.Context, opts ResolveOptions) (*templates.RESTAction, e dict = map[string]any{} } + log := xcontext.Logger(ctx) + log.Debug("resolved api", slog.Any("dict", dict)) + var raw []byte if opts.In.Spec.Filter != nil { q := ptr.Deref(opts.In.Spec.Filter, "") diff --git a/internal/resolvers/widgets/resolve.go b/internal/resolvers/widgets/resolve.go index 529131c..bb008fb 100644 --- a/internal/resolvers/widgets/resolve.go +++ b/internal/resolvers/widgets/resolve.go @@ -79,7 +79,7 @@ func Resolve(ctx context.Context, opts ResolveOptions) (*Widget, error) { if hasNext { page = page + 1 } - pig["_slice_"] = map[string]any{ + pig["slice"] = map[string]any{ "perPage": opts.PerPage, "page": page, "continue": hasNext, diff --git a/internal/resolvers/widgets/resourcesrefs/resolve.go b/internal/resolvers/widgets/resourcesrefs/resolve.go index 52e2a2a..1aba450 100644 --- a/internal/resolvers/widgets/resourcesrefs/resolve.go +++ b/internal/resolvers/widgets/resourcesrefs/resolve.go @@ -3,9 +3,10 @@ package resourcesrefs import ( "context" "errors" - "fmt" "log/slog" "net/http" + "net/url" + "strconv" xcontext "github.com/krateoplatformops/plumbing/context" "github.com/krateoplatformops/plumbing/kubeconfig" @@ -47,6 +48,8 @@ func resolveOne(ctx context.Context, rc *rest.Config, in *templatesv1.ResourceRe return all, nil } + log := xcontext.Logger(ctx) + gv, err := schema.ParseGroupVersion(in.APIVersion) if err != nil { return all, err @@ -58,6 +61,13 @@ func resolveOne(ctx context.Context, rc *rest.Config, in *templatesv1.ResourceRe return all, err } + log.Info("resolving resource ref", + slog.String("id", in.ID), + slog.String("group", gvr.Group), + slog.String("name", in.Name), + slog.String("namespace", in.Namespace), + ) + verbs := mapVerbs(in.Verb) for _, verb := range verbs { el := templatesv1.ResourceRefResult{ @@ -71,20 +81,15 @@ func resolveOne(ctx context.Context, rc *rest.Config, in *templatesv1.ResourceRe Namespace: in.Namespace, }) if !el.Allowed { - xcontext.Logger(ctx).Warn("action not allowed", + log.Warn("resource ref action not allowed", + slog.String("id", in.ID), slog.String("verb", verb), slog.String("group", gvr.Group), slog.String("resource", gvr.Resource), slog.String("namespace", in.Namespace)) } - if in.Name == "" { - el.Path = fmt.Sprintf("/call?resource=%s&apiVersion=%s&namespace=%s", - gvr.Resource, gvr.GroupVersion().String(), in.Namespace) - } else { - el.Path = fmt.Sprintf("/call?resource=%s&apiVersion=%s&name=%s&namespace=%s", - gvr.Resource, gvr.GroupVersion().String(), in.Name, in.Namespace) - } + el.Path = buildPath(gvr, in) if el.Verb == http.MethodPost || el.Verb == http.MethodPut || el.Verb == http.MethodPatch { el.Payload = &templatesv1.ResourceRefPayload{ @@ -98,7 +103,40 @@ func resolveOne(ctx context.Context, rc *rest.Config, in *templatesv1.ResourceRe } all = append(all, el) + + log.Info("resource ref successfully resolved", + slog.String("id", in.ID), + slog.String("group", gvr.Group), + slog.String("name", in.Name), + slog.String("namespace", in.Namespace), + slog.String("verb", verb), + slog.String("path", el.Path), + slog.Bool("allowed", el.Allowed), + ) } return all, nil } + +func buildPath(gvr schema.GroupVersionResource, in *templatesv1.ResourceRef) string { + u := url.URL{ + Path: "/call", + } + + q := url.Values{} + q.Set("resource", gvr.Resource) + q.Set("apiVersion", gvr.GroupVersion().String()) + q.Set("namespace", in.Namespace) + + if in.Name != "" { + q.Set("name", in.Name) + } + + if slice := in.Slice; slice != nil { + q.Set("page", strconv.Itoa(slice.Page)) + q.Set("perpage", strconv.Itoa(slice.PerPage)) + } + + u.RawQuery = q.Encode() + return u.String() +} diff --git a/manifests/deploy.snowplow.yaml b/manifests/deploy.snowplow.yaml index fa8d49d..961684d 100644 --- a/manifests/deploy.snowplow.yaml +++ b/manifests/deploy.snowplow.yaml @@ -55,7 +55,7 @@ spec: image: kind.local/snowplow:latest imagePullPolicy: Never args: - - --debug=true + - --debug=false - --blizzard=false - --port=8081 - --authn-namespace=demo-system diff --git a/testdata/curl-samples.txt b/testdata/curl-samples.txt index 4a37f10..471378a 100644 --- a/testdata/curl-samples.txt +++ b/testdata/curl-samples.txt @@ -34,33 +34,6 @@ curl -v -G \ -d "name=button-sample" \ "http://127.0.0.1:30081/call" - curl -v -G GET \ - -H "Authorization: Bearer ${KRATEO_TOKEN}" \ - -d 'apiVersion=widgets.templates.krateo.io/v1beta1' \ - -d 'resource=buttons' \ - -d 'namespace=demo-system' \ - -d 'name=button-with-actions' \ - -d 'per_page=3' -d 'page=1' \ - "http://127.0.0.1:30081/call" - - curl -v -G GET \ - -H "Authorization: Bearer ${KRATEO_TOKEN}" \ - -d 'apiVersion=widgets.templates.krateo.io/v1beta1' \ - -d 'resource=buttons' \ - -d 'namespace=demo-system' \ - -d 'name=pagination-demo' \ - -d 'per_page=3' -d 'page=1' \ - "http://127.0.0.1:30081/call" - - -curl -v -G GET \ - -H "Authorization: Bearer ${KRATEO_TOKEN}" \ - -d 'apiVersion=templates.krateo.io/v1' \ - -d 'resource=restactions' \ - -d 'namespace=demo-system' \ - -d 'name=list-pods' \ - -d 'per_page=3' \ - "http://127.0.0.1:30081/call" ### With Extras diff --git a/testdata/missing-additional-props/table.crd.yaml b/testdata/missing-additional-props/table.crd.yaml index b62a62c..324e54a 100644 --- a/testdata/missing-additional-props/table.crd.yaml +++ b/testdata/missing-additional-props/table.crd.yaml @@ -57,7 +57,7 @@ spec: type: object resourcesRefs: properties: - _slice_: + slice: properties: continue: type: boolean diff --git a/testdata/missing-additional-props/table.schema.json b/testdata/missing-additional-props/table.schema.json index fbf30ba..9679f57 100644 --- a/testdata/missing-additional-props/table.schema.json +++ b/testdata/missing-additional-props/table.schema.json @@ -76,7 +76,7 @@ "resourcesRefs": { "type": "object", "properties": { - "_slice_": { + "slice": { "type": "object", "properties": { "offset": { diff --git a/testdata/pagination/curl.txt b/testdata/pagination/curl.txt new file mode 100644 index 0000000..494555c --- /dev/null +++ b/testdata/pagination/curl.txt @@ -0,0 +1,27 @@ +curl -v -G \ + -H "Authorization: Bearer ${KRATEO_TOKEN}" \ + -d "apiVersion=widgets.templates.krateo.io/v1beta1" \ + -d "resource=pages" \ + -d "namespace=demo-system" \ + -d "name=simple-page" \ + "http://127.0.0.1:30081/call" + + +curl -v -G GET \ + -H "Authorization: Bearer ${KRATEO_TOKEN}" \ + -d "apiVersion=widgets.templates.krateo.io/v1beta1" \ + -d "resource=buttons" \ + -d "namespace=demo-system" \ + -d "name=pods-buttons" \ + -d "perpage=3" -d "page=1" \ + "http://127.0.0.1:30081/call" + + +curl -v -G GET \ + -H "Authorization: Bearer ${KRATEO_TOKEN}" \ + -d "apiVersion=templates.krateo.io/v1" \ + -d "resource=restactions" \ + -d "namespace=demo-system" \ + -d "name=list-pods" \ + -d "perpage=3" -d "page=1" \ + "http://127.0.0.1:30081/call" \ No newline at end of file diff --git a/testdata/pagination/restaction-list-pods.yml b/testdata/pagination/restaction-list-pods.yaml similarity index 87% rename from testdata/pagination/restaction-list-pods.yml rename to testdata/pagination/restaction-list-pods.yaml index 050fc66..78de4f4 100644 --- a/testdata/pagination/restaction-list-pods.yml +++ b/testdata/pagination/restaction-list-pods.yaml @@ -16,8 +16,8 @@ spec: { "pods": ( .pods.items as $items - | ._slice_.offset as $offset - | ._slice_.perPage as $perPage + | .slice.offset as $offset + | .slice.perPage as $perPage | [ $items | length as $len diff --git a/testdata/pagination/setup.sh b/testdata/pagination/setup.sh new file mode 100755 index 0000000..6f012f7 --- /dev/null +++ b/testdata/pagination/setup.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Nome del namespace +NAMESPACE="demo-system" + +# Crea il namespace se non esiste +kubectl get namespace $NAMESPACE >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo "Creating namespace: $NAMESPACE" + kubectl create namespace $NAMESPACE +else + echo "Namespace $NAMESPACE already exists" +fi + +kubectl apply -f crds/templates.krateo.io_restactions.yaml +kubectl apply -f testdata/pagination/widget.page.schema.crd.yaml +kubectl apply -f testdata/widgets/widgets.templates.krateo.io_buttons.yaml + + +kubectl apply -f testdata/pagination/restaction-list-pods.yaml +kubectl apply -f testdata/pagination/widget.page.sample.yaml +kubectl apply -f testdata/pagination/widget.yaml + +kubectl apply -f testdata/rbac.pods.yaml +kubectl apply -f testdata/rbac.restactions.yaml +kubectl apply -f testdata/rbac.widgets.yaml diff --git a/testdata/pagination/widget.page.sample.yaml b/testdata/pagination/widget.page.sample.yaml new file mode 100644 index 0000000..84f0260 --- /dev/null +++ b/testdata/pagination/widget.page.sample.yaml @@ -0,0 +1,20 @@ +kind: Page +apiVersion: widgets.templates.krateo.io/v1beta1 +metadata: + name: simple-page + namespace: demo-system +spec: + widgetData: + items: + - resourceRefId: simple-page + resourcesRefs: + items: + - id: pods-buttons-page + apiVersion: widgets.templates.krateo.io/v1beta1 + name: pods-buttons + namespace: demo-system + resource: buttons + verb: GET + slice: + page: 1 + perPage: 3 \ No newline at end of file diff --git a/testdata/pagination/widget.page.schema.crd.yaml b/testdata/pagination/widget.page.schema.crd.yaml new file mode 100644 index 0000000..1f199c9 --- /dev/null +++ b/testdata/pagination/widget.page.schema.crd.yaml @@ -0,0 +1,175 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: pages.widgets.templates.krateo.io +spec: + group: widgets.templates.krateo.io + names: + categories: + - widgets + - krateo + kind: Page + listKind: PageList + plural: pages + singular: page + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + spec: + properties: + apiRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + resourcesRefs: + properties: + slice: + properties: + continue: + type: boolean + offset: + type: integer + page: + type: integer + perPage: + type: integer + required: + - page + - perPage + type: object + items: + items: + properties: + apiVersion: + type: string + id: + type: string + name: + type: string + namespace: + type: string + payload: + type: object + resource: + type: string + slice: + properties: + continue: + type: boolean + offset: + type: integer + page: + type: integer + perPage: + type: integer + required: + - page + - perPage + type: object + verb: + enum: + - POST + - PUT + - PATCH + - DELETE + - GET + type: string + required: + - id + type: object + type: array + required: + - items + type: object + resourcesRefsTemplate: + items: + properties: + iterator: + type: string + template: + properties: + apiVersion: + type: string + id: + type: string + name: + type: string + namespace: + type: string + payload: + type: object + x-kubernetes-preserve-unknown-fields: true + resource: + type: string + verb: + enum: + - POST + - PUT + - PATCH + - DELETE + - GET + type: string + type: object + type: object + type: array + widgetData: + properties: + items: + items: + properties: + resourceRefId: + type: string + required: + - resourceRefId + type: object + type: array + type: object + widgetDataTemplate: + items: + properties: + expression: + type: string + forPath: + type: string + type: object + type: array + type: object + status: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} diff --git a/testdata/pagination/widget.yaml b/testdata/pagination/widget.yaml index d524056..aba4b56 100644 --- a/testdata/pagination/widget.yaml +++ b/testdata/pagination/widget.yaml @@ -2,7 +2,7 @@ apiVersion: widgets.templates.krateo.io/v1beta1 kind: Button metadata: namespace: demo-system - name: pagination-demo + name: pods-buttons spec: widgetData: actions: {} diff --git a/testdata/rbac.widgets.yaml b/testdata/rbac.widgets.yaml index 5cd08c3..9dc2d81 100644 --- a/testdata/rbac.widgets.yaml +++ b/testdata/rbac.widgets.yaml @@ -9,6 +9,7 @@ rules: resources: - buttons - tables + - pages verbs: - get - list diff --git a/testdata/widgets/widgets.templates.krateo.io_buttons.yaml b/testdata/widgets/widgets.templates.krateo.io_buttons.yaml index 7eb5b2b..76ec154 100644 --- a/testdata/widgets/widgets.templates.krateo.io_buttons.yaml +++ b/testdata/widgets/widgets.templates.krateo.io_buttons.yaml @@ -21,9 +21,6 @@ spec: - jsonPath: .metadata.creationTimestamp name: AGE type: date - - jsonPath: .status.conditions[?(@.type=='Ready')].status - name: READY - type: string name: v1beta1 schema: openAPIV3Schema: @@ -57,7 +54,7 @@ spec: type: object resourcesRefs: properties: - _slice_: + slice: properties: continue: type: boolean @@ -85,7 +82,25 @@ spec: payload: type: object resource: + enum: + - AAA + - BBB + - CCC type: string + slice: + properties: + continue: + type: boolean + offset: + type: integer + page: + type: integer + perPage: + type: integer + required: + - page + - perPage + type: object verb: enum: - POST @@ -351,6 +366,12 @@ spec: type: object type: array type: object + allowedResources: + enum: + - AAA + - BBB + - CCC + type: string clickActionId: description: the id of the action to be executed when the button is clicked